Closing a WPF Window using MVVM and minimal code-behind

Standard
Share

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.

27 thoughts on “Closing a WPF Window using MVVM and minimal code-behind

  1. LearnWPF-MVVM

    My LoginViewModel:
    public Action HideFormAction{get; set;}

    My View:

    LoginViewModel vm = new LoginViewModel();
    this.DataContext = vm;
    if (vm.HideFormAction == null)
    vm.HideFormAction = new Action( () => this.Close() );

    and again in my LoginViewModel :

    private ICommand _loginCommand;
    public ICommand LoginCommand
    {
    get
    {
    if (_loginCommand == null)
    {
    _loginCommand = new RelayCommand(param => this.checkAccount(), null);
    }
    return _loginCommand;
    }
    }

    private void checkAccount()
    {
    MainWindow mainWindow = new MainWindow();
    mainWindow.ShowDialog();
    HideFormAction();
    }

    but form Login isn’t close, help me

    • What is the behavior you’re seeing? I’m guessing you’ve got a LogInView that is displayed, and from which you should be able to spawn a MainWindow via the checkAccount method… Is any of this working as expected?

    • I just realized you are placing a call to a modal instance of mainWindow. Your call to HideFormAction() won’t be executed until mainWindow is dismissed. Try moving the call to HideFormAction() before the call to mainWindow.ShowDialog().

  2. David

    Thanks a bunch. I’ve been having difficulties in general getting relay command to work nicely in addition to figuring out a clean way of closing a current popup window.

    Well done mate. Well done.

  3. Antonio Nakic Alfirevic

    I read somewhere that “WPF makes difficult things incredibly easy, and easy things incredibly difficult.”
    This bending over backwards for such a simple thing illustrates just how true the quote is. I love WPF, but gosh, some things should not require this much effort.

    I’d actually keeping both the event handler and the command. Use the command to save, and the event handler to close the view directly. Not much benefit to passing the delegate into the VM (it would only make sense if there was any VM-related logic in deciding when to close the view).

    My 2 cents…

    • The very reason I was trying to implement such functionality was due to an automatic timeout feature I needed to implement. Fifteen minutes of inactivity required an automatic logoff, and modal dialogs were presenting a problem.

  4. Amol Aher

    Done Same Thing with Very Simple Way

    static Login obj;
    
    static App()
    {
        OpenCLoseForm("open");
    }
    
    public static void OpenCLoseForm(string action)
    {
        if(action.Equals("open"))
        {
            obj=new Login();
            obj.DataContext = new Login_ViewModel(new SipmleLogin_Repostory());
            obj.Show();
        }
        else
            obj.Hide();
    }
    

    call this method from ur button Action

  5. Chris

    This is exactly what I’ve been looking for. But I have one issue. I have a case where I want the user to be able to go back and forth between a set of windows. This method works great to close a window the first time. But if the window is reopened; this action fails to close it.
    Is this expected? Or am I pushing a limit? Either way thanks for the post!

  6. Eido

    I would like to know how can we achieve the above method (View-Model closes View) while using MVVM View-Model First approach?

  7. pobox

    I got the following error message
    “object reference not set to an instance of an object”
    And “this.Close” in View XAML also error with red line below “Close”.
    Please help me how to solve it.

Leave a Reply

Your email address will not be published. Required fields are marked *