Reputation: 3796
I have a static WindowService
class which helps me to create new windows and modal dialogs.
So far, what I have is this:
/// <summary>
/// Opens a new window of type <paramref name="newWindowType"/> and closes the <paramref name="oldWindow"/>
/// </summary>
/// <param name="oldWindow">The window which should be closed (Usually the current open window)</param>
/// <param name="newWindowType">The type of the new window to open</param>
public static void ShowNewWindow(Window oldWindow, Type newWindowType)
{
((Window)Activator.CreateInstance(newWindowType)).Show();
oldWindow.Close();
}
My viewmodel raises an event and the view is subscribed to it. In the event handler in the view, it calls WindowService.ShowNewWindow(this,The type here)
. This works fine.
My modal dialog creating method will also work in a similar way. The only difference is that the information will be returned to the view (At the event handler) so the view will have to pass that information to the view model in code explicitly. This violates mvvm pattern and I don't know how to make the viewmodel wait for the view to return the value after the event is raised.
Is there a better way of doing this?
Upvotes: 3
Views: 1547
Reputation: 9723
Ah, this ol' chestnut.
There are many different variations on how to achieve this, however here's my two cents.
The main ideas here are to ensure that your View
and View Model
do not know about each other, therefore your View
should not subscribe to an event in your View Model
, and your View Model
should not directly call your service and provide a view Type
.
My recommendation would be to use ICommand
implementations instead of relying on a static service class, for the reason that your class will always have a dependency to this service, and also as soon as you send the view Type
to this service, then the MVVM pattern is lost.
So, firstly, we need some kind of command which will open a window of a given Type
, here's what I have come up with:
public class OpenWindowCommand : ICommand
{
public bool CanExecute(object parameter)
{
TypeInfo p = (TypeInfo)parameter;
return p.BaseType == typeof(Window);
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public void Execute(object parameter)
{
if (parameter == null)
throw new ArgumentNullException("TargetWindowType");
//Get the type.
TypeInfo p = (TypeInfo)parameter;
Type t = p.BaseType;
if (p.BaseType != typeof(Window))
throw new InvalidOperationException("parameter is not a Window type");
//Create the window.
Window wnd = Activator.CreateInstance(t) as Window;
OpenWindow(wnd);
}
protected virtual void OpenWindow(Window wnd)
{
wnd.Show();
}
}
The class inherits from ICommand
and specifies the implementation which accepts a Type
, which represents the desired View
that we want to open. Notice I have marked a method as virtual
, I'll explain that part in a moment.
Here's how we can make use of this command in our View Model
:
public class MainWindowViewModel
{
public OpenWindowCommand OpenWindowCommand { get; private set; }
public MainWindowViewModel()
{
OpenWindowCommand = new OpenWindowCommand();
}
...
}
Now we've created the command, we simply need to bind a Button
to it:
<Button Content="Open Window"
Command="{Binding OpenWindowCommand}"
CommandParameter="{x:Type local:MyWindow}"/>
One thing to note here is that I am using x:Type
as the CommandParameter
, this is the Window
that will be created when this command gets executed.
What we achieved above is only half of the requirement, we now need something that will display a dialog and output the result to our View Model
, this isn't so tricky as we have most of what we need already in our existing OpenWindowCommand
.
First, we need to create the command:
public class ShowDialogCommand : OpenWindowCommand
{
private Action _PreOpenDialogAction;
private Action<bool?> _PostOpenDialogAction;
public ShowDialogCommand(Action<bool?> postDialogAction)
{
if (postDialogAction == null)
throw new ArgumentNullException("postDialogAction");
_PostOpenDialogAction = postDialogAction;
}
public ShowDialogCommand(Action<bool?> postDialogAction, Action preDialogAction)
: this(postDialogAction)
{
if (preDialogAction == null)
throw new ArgumentNullException("preDialogAction");
_PreOpenDialogAction = preDialogAction;
}
protected override void OpenWindow(System.Windows.Window wnd)
{
//If there is a pre dialog action then invoke that.
if (_PreOpenDialogAction != null)
_PreOpenDialogAction();
//Show the dialog
bool? result = wnd.ShowDialog();
//Invoke the post open dialog action.
_PostOpenDialogAction(result);
}
}
We're making use of our OpenWindowCommand
by inheriting from it and using it's implementation instead of having to copy all of it into our new class. The command takes an Action
which is a reference to a method in your View Model
, you have the option of defining an action before or after (or both) a dialog is displayed.
The next step is to change our View Model
so it creates this new command:
public class MainWindowViewModel
{
public OpenWindowCommand OpenWindowCommand { get; private set; }
public ShowDialogCommand ShowDialogCommand { get; private set; }
public MainWindowViewModel()
{
OpenWindowCommand = new OpenWindowCommand();
ShowDialogCommand = new ShowDialogCommand(PostOpenDialog);
}
public void PreOpenDialog()
{
throw new NotImplementedException();
}
public void PostOpenDialog(bool? dialogResult)
{
throw new NotImplementedException();
}
}
The usage of this command is practically the same as before, but it just references a different command:
<Button Content="Open Window"
Command="{Binding ShowDialogCommand}"
CommandParameter="{x:Type local:MyWindow}"/>
And there you have it, everything is loosely coupled, the only real dependencies here are that your View Model
depends on your ICommand
classes.
The ICommand
classes that I have created act as a controller between the View
and the View Model
to ensure that they do not know about each other, and keeps the MVVM pattern enforced.
Like I said at the beginning of this answer, there are many ways of which this can be achieved, however I hope you are now a little more enlightened.
Upvotes: 3