The other day I started moving all my event handlers in my application from the View’s code-behind file to the ViewModel. I accomplished this using command binding. This was all easy enough – until I came to my Close method.
You see, in the MVVM (Model – View – View Model) design pattern, the ViewModel – a class that typically contains the values you want to display in your View – shouldn’t know anything about the View. It’s OK (and quite necessary!) for the View to be aware of the ViewModel, however.
So let’s take a look at the Close button’s event handler as it originally existed in code-behind:
private void Close_Click(object sender, RoutedEventArgs e)
{
this.Close();
}
As you can see, it’s quite simple – only one line of code that I had to write.
So to move it to my ViewModel using command binding, I would have done something like this:
In the XAML of my button:
<Button Content="Close" Command="{Binding CloseCommand}"/>
In the ViewModel:
...
public bool CanClose { get; set; }
private RelayCommand closeCommand;
public ICommand CloseCommand
{
get
{
if(closeCommand == null)
(
closeCommand = new RelayCommand(param => Close(), param => CanClose);
)
}
}
public void Close()
{
this.Close();
}
...
All we’ve done here is move the close button event handler from the code-behind file to the ViewModel. The problem we run into, however, is with line #19. As you may be aware, the this keyword refers to the class in which it exists. This was fine in the code-behind of the Window, as this then referred to the Window, and this.Close() would successfully call the window’s Close() method. But now that we’ve moved the method into the ViewModel, this no longer refers to the Window (View), but rather the ViewModel – which doesn’t have a Close method.
So the problem, then, is how to get a method exposed by the ViewModel that references the View. Remember, the ViewModel isn’t supposed to know anything about the View in the MVVM design pattern.
The solution is quite simple: the Action object.
We’ll add an Action property to the ViewModel, but define it from the View’s code-behind file. This will let us dynamically define a reference on the ViewModel that points to the View.
On the ViewModel, we’ll simply add:
public Action CloseAction { get; set; }
And on the View, we’ll define it as such:
public View()
{
InitializeComponent() // this draws the View
ViewModel vm = new ViewModel(); // this creates an instance of the ViewModel
this.DataContext = vm; // this sets the newly created ViewModel as the DataContext for the View
if ( vm.CloseAction == null )
vm.CloseAction = new Action(() => this.Close());
}
At this point, we’ve assigned a delegate to the CloseAction Action property that we can simply invoke whenever we want to close the Window.
Say, for instance, we have a “Save” button on our window that we want to use to save settings from a ViewModel property, then close the window. First, we’d bind the Command property of the View’s Save button to a SaveCommand RelayCommand on the ViewModel:
...
<Button Content = "Save" Command = "{Binding SaveCommand}" />
...
And our ViewModel would contain the necessary code for the SaveCommand:
...
public bool CanSave { get; set; }
private RelayCommand saveCommand;
public ICommand SaveCommand
{
get
{
if ( saveCommand == null )
{
saveCommand = new RelayCommand(param => Save(), param => CanSave);
}
}
}
public void Save()
{
Properties.Settings.Default.ConnectionString = ConnectionString; // ConnectionString is a property on the ViewModel
Properties.Settings.Default.Save();
CloseAction(); // Invoke the Action previously defined by the View
}
...
If you search the web for ways to close a window in MVVM, you’ll find various methods using several lines of code, creating more classes. Using the method described here, we can define the Close method with a single line on the ViewModel, two lines of code on the View, and invoke the Close method with a single line making a call to CloseAction().
Many thanks to my good friend Robb Allen for the tip on implementing the Action object.
Leave a Reply