WPF – Dialog Service as Injectable Service or Static Class

cdesign-patternswpf

The background

In MVVM, we tend "not" want to couple windows to view models for various reasons. Though from the very second screen on your application you start hitting this conceptual grey zone.

The problem

You see viewmodals tend to want to open windows/Views (its just a fact of life). Windows/dialog APIs tend to like an Owner, because thats just the nature of the Windows Operating system. As can be seen here Window.Owner

When a child window is opened by a parent window by calling
ShowDialog, an implicit relationship is established between both
parent and child window. This relationship enforces certain behaviors,
including with respect to minimizing, maximizing, and restoring.

This all creates a bit of a dilemma where Viewmodals are natively disconnected from their view/window hence you have to use some sort of decoupled messaging event aggregation, or some other logic to couple the dialog relationship.

At this point people use frameworks or other regimes which basically consist of weird and contrived world of DI/IOC, view constructors, locators, messages or event aggregation to do all the coupling, spinning up of viewmodals and so forth and keeping the MVVM police happy.

The current solve

In the current projects im working on, the principle developer has solved this problem with using typed decoupled messages (pub/sub), which gets picked up in a view constructor with an application wide view mapping regime that maps these messages to view to windows, converts and passes objects to the newly created view models through more messages in what can only be called more spurious plumbing, and at the end of the dialog lifecyles passes back out parameters back into the original typed message which can then be probed bythe original caller.

This is all very messy (IMO) overly complicated overly engineered and inadequacy designed.

Dialog Service

I guess this where the notion of a dialog service comes in. A simple Fluent library that can use viewmaps or implicitly coupled commands to call, spinup windows.

// Modal Dialog no results
_dialogService.Dialog<Window1Vm>(this)
              .OnInit(vm => vm.SomeProperty = false)
              .ShowDialog();

// Dialog with some results  
var someResult = _dialogService.Dialog<Window1Vm>(this)
                               .OnInit(vm => vm.SomeProperty = false)
                               .ShowDialog();

// Dialog with some input params
var someResult = _dialogService.Dialog<Window1Vm>(this)
                               .WithParams(new SomeParamsToPassIn())
                               .ShowDialog();

// And the one the "decoupled police" will be wanting to arrest me for
// Dialog with explicit notation to map a view to a viewmdal
// (I know i should be shot and fed to the wolves)
var dialog1 = _dialogService.Dialog<Window1Vm, Window1>(this)
                            .OnInit(vm => vm.SomeProperty = false)
                            .ShowDialog();

My question is now, should this be an injectable service, or a static class

The advantages of an injectable service is that, ViewModals and code who need access to this, can just ask for it. Its easy to see who is implimenting this functionatlity via the constructor

The disadvantages is if you have lists of lists of vms and something in the child list needs to open a window you have to inject it into all the child viewmodals or once again send a message back.

The advantages of being a static class, is you can call it everywhere, kind of like a true UI cross cutting concern.


Personally i think the DI service is the way to go, it has no state logic i.e it can be a singleton. However i have had several comments about this approach (from mainly the principle developer) that messages are just better and it can be called from anywhere, also that static is bad mmkay.

However i just want to get opinions about if logic, like does this sit better as an injectable service, a static library, or in other peoples experience that might sugest having a totally decouple system like we have is actually a better approach in large systems and the long term

Best Answer

Your current approach sounds like the Mediator Pattern. Which is a recognised solution to this kind of problem and several other cross ViewModel communication issues.

However, I do agree that it can get messy. especially with something as simple as wanting to open a new window and you have them everywhere.

Indeed in the thing I am currently working on we use a (professional looking) third party MessageBox which uses a static method. I really would not recommend this though as its a nightmare to test.

Here is an alternate approach, which allows you to keep the binding in XAML and the View Model Creation and event firing in the ViewModel

So we have a Behaviour with an Action DependencyProperty that can be bound to:

public class OpenWindowBehaviour : Behavior<Control>
{
    public Type TypeOfWindow { get; set; }

    public Action<object> OpenWindowWithVM
    {
        get { return (Action<object>)this.GetValue(OpenWindowWithVMProperty); }
        set { this.SetValue(OpenWindowWithVMProperty, value); }
    }

    public static readonly DependencyProperty OpenWindowWithVMProperty = 
        DependencyProperty.Register(
            "OpenWindowWithVM", 
            typeof(Action<object>), 
            typeof(OpenWindowBehaviour), 
            new PropertyMetadata(null)
            );

    protected override void OnAttached()
    {
        base.OnAttached();
        this.AssociatedObject.Loaded += AssociatedObject_Loaded;
    }

    private void AssociatedObject_Loaded(object sender, RoutedEventArgs e)
    {
        this.OpenWindowWithVM = OpenWindow;
    }

    private void OpenWindow(object ViewModel)
    {
        var constructor = TypeOfWindow.GetConstructors()[0];
        var w = constructor.Invoke(new object[] { }) as Window;
        w.DataContext = ViewModel;
        w.Show();
    }
}

We have to wait for the Loaded event, which comes after binding so we can set the bound Action to be our Open New Window code.

Now in our ViewModel we have an Action 'event' and a ICommand which we are going to bind to a button. When the button is pressed we are going to 'Move to the next stage' raising our StageChanged pseudo event to which we pass the View Model for stage 2

public class ViewModel : INotifyPropertyChanged
{
    public Action<object> StageChanged {
        get;
        set;
    }

    public ICommand MoveToNextStage { get; set; }

    public ViewModel()
    {
        MoveToNextStage = new DelegateCommand((o) =>
        {
            if (this.StageChanged != null)
            {
                StageChanged( new Stage2VM());
            }
        });
    }

    public event PropertyChangedEventHandler PropertyChanged;
}

our xaml binds the two together, so that pressing the button fires the behaviours event.

<Window
namespaces.....
>
    <i:Interaction.Behaviors>
        <local:OpenWindowBehaviour 
            TypeOfWindow="{x:Type local:NewWindow}" 
            OpenWindowWithVM="{Binding StageChanged, Mode=TwoWay}"/>
    </i:Interaction.Behaviors>
    <Grid>
        <Button Content="Click Me" Command="{Binding MoveToNextStage}"/>
    </Grid>
</Window>

(I'm using Fody PropertyChanged for the boilerplate and Expression.Blend.Sdk for the Windows.Interactivity Behaviours)