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:
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
our xaml binds the two together, so that pressing the button fires the behaviours event.
(I'm using Fody PropertyChanged for the boilerplate and Expression.Blend.Sdk for the Windows.Interactivity Behaviours)