Implementing "close window" command with MVVM
Asked Answered
C

13

45

So my first attempt did everything out of the code behind, and now I'm trying to refactor my code to use the MVVM pattern, following the guidance of the MVVM in the box information.

I've created a viewmodel class to match my view class, and I'm moving the code out of the code behind into the viewmodel starting with the commands.

My first snag is trying to implement a 'Close' button that closes the window if the data has not been modified. I've rigged up a CloseCommand to replace the 'onClick' method and all is good except for where the code tries to run this.Close(). Obviously, since the code has been moved from a window to a normal class, 'this' isn't a window and therefore isn't closeable. However, according to MVVM, the viewmodel doesn't know about the view, so i can't call view.Close().

Can someone suggest how I can close the window from the viewmodel command?

Charlie answered 14/8, 2012 at 4:31 Comment(3)
Several options have already been discussed [#4376975 Generally the approach I would use is the CommandParameter with a relative source back to the calling Window. (As demonstrated by Simone)Infrequent
does this solution require Expression Blend? I'm challenged on that frontCharlie
See how to use attached property to solve this hereWillyt
D
29

You don't need to pass the View instance to your ViewModel layer. You can access the main window like this -

Application.Current.MainWindow.Close()

I see no issue in accessing your main window in ViewModel class as stated above. As per MVVM principle there should not be tight coupling between your View and ViewModel i.e. they should work be oblivious of others operation. Here, we are not passing anything to ViewModel from View. If you want to look for other options this might help you - Close window using MVVM

Dissident answered 14/8, 2012 at 8:9 Comment(7)
I like this, but is coupling between the viewmodel and the application allowed/approved?Charlie
Coupling is when you pass data across layer using instance variables but here you are accessing the static property of application to get the window. For me its not a violation of any rule of MVVM.Dissident
@Rohit: You couple your viewmodel to WPF this way. (Application class)Anesthesiologist
@Anesthesiologist - Like I said in the above comment (if you read it) that it's completely upto the person. I see no violation of MVVM here since View and ViewModel and oblivious to each other. That's why I also give OP with a link in case this doesn't fit his needs, he can create wrapper service to achieve that OR also can achieve this using attached behaviour (which already mentioned in other great answers here).Dissident
@RohitVats this IS a violation of MVVM. Splitting Views and ViewModels brings to portability: I can realize a Console Application, a background process, a SilverLight web site, a mobile App, leaving all the ViewModels the same, changing only the Views. So, calling something like "MainWindow" on a ViewModel is totally wrong. In a background process what does it mean "close the main window"? Or in a test-case suite? And if the window to close is not the main? The best way to handle these problems without violating MVVM is to inject View instances in the ViewModels' properties through interfacesLeralerch
This solution threw an exception for me.Stoops
Good luck running unit tests on this!Daguerre
G
70

I personally use a very simple approach: for every ViewModel that is related to a closeable View, I created a base ViewModel like this following example:

public abstract class CloseableViewModel
{
    public event EventHandler ClosingRequest;

    protected void OnClosingRequest()
    {
        if (this.ClosingRequest != null)
        {
            this.ClosingRequest(this, EventArgs.Empty);
        }
    }
}

Then in your ViewModel that inherits from CloseableViewModel, simply call this.OnClosingRequest(); for the Close command.

In the view:

public class YourView
{
    ...
    var vm = new ClosableViewModel();
    this.Datacontext = vm;
    vm.ClosingRequest += (sender, e) => this.Close();
}
Gyrostat answered 14/8, 2012 at 8:31 Comment(2)
I think this is a very nice way of doing this!Ocreate
Nice solution, very handful.Ermelindaermengarde
D
29

You don't need to pass the View instance to your ViewModel layer. You can access the main window like this -

Application.Current.MainWindow.Close()

I see no issue in accessing your main window in ViewModel class as stated above. As per MVVM principle there should not be tight coupling between your View and ViewModel i.e. they should work be oblivious of others operation. Here, we are not passing anything to ViewModel from View. If you want to look for other options this might help you - Close window using MVVM

Dissident answered 14/8, 2012 at 8:9 Comment(7)
I like this, but is coupling between the viewmodel and the application allowed/approved?Charlie
Coupling is when you pass data across layer using instance variables but here you are accessing the static property of application to get the window. For me its not a violation of any rule of MVVM.Dissident
@Rohit: You couple your viewmodel to WPF this way. (Application class)Anesthesiologist
@Anesthesiologist - Like I said in the above comment (if you read it) that it's completely upto the person. I see no violation of MVVM here since View and ViewModel and oblivious to each other. That's why I also give OP with a link in case this doesn't fit his needs, he can create wrapper service to achieve that OR also can achieve this using attached behaviour (which already mentioned in other great answers here).Dissident
@RohitVats this IS a violation of MVVM. Splitting Views and ViewModels brings to portability: I can realize a Console Application, a background process, a SilverLight web site, a mobile App, leaving all the ViewModels the same, changing only the Views. So, calling something like "MainWindow" on a ViewModel is totally wrong. In a background process what does it mean "close the main window"? Or in a test-case suite? And if the window to close is not the main? The best way to handle these problems without violating MVVM is to inject View instances in the ViewModels' properties through interfacesLeralerch
This solution threw an exception for me.Stoops
Good luck running unit tests on this!Daguerre
K
26

My solution to close a window from view model while clicking a button is as follows:

In view model

public RelayCommand CloseWindow;
Constructor()
{
    CloseWindow = new RelayCommand(CloseWin);
}

public void CloseWin(object obj)
{
    Window win = obj as Window;
    win.Close();
}

In View, set as follows

<Button Command="{Binding CloseWindowCommand}" CommandParameter="{Binding ElementName=WindowNameTobeClose}" Content="Cancel" />
Keenan answered 29/8, 2013 at 6:33 Comment(4)
agreed, saves on code in the view (that might get forgotten about!) and doesn't force you to do the DataContext assignment in the view either.Tonatonal
Aren't you making view-model aware of the view (Window)?Aspect
I always get a Null-Reference-Exception, as obj is null in my case? But i do CommandParameter="{Binding ElementName=myWindow}"in XAML, what can be wrong?Horsehair
You are not giving the window a name.Jalopy
G
14

I do it by creating a attached property called DialogResult:

public static class DialogCloser
{
    public static readonly DependencyProperty DialogResultProperty =
        DependencyProperty.RegisterAttached(
            "DialogResult",
            typeof(bool?),
            typeof(DialogCloser),
            new PropertyMetadata(DialogResultChanged));

    private static void DialogResultChanged(
        DependencyObject d,
        DependencyPropertyChangedEventArgs e)
    {
        var window = d as Window;
        if (window != null && (bool?)e.NewValue == true) 
                window.Close();
    }

    public static void SetDialogResult(Window target, bool? value)
    {
        target.SetValue(DialogResultProperty, value);
    }
}

then write this to you XAML, in the window tag

WindowActions:DialogCloser.DialogResult="{Binding Close}"

finally in the ViewModel

    private bool _close;
    public bool Close
    {
        get { return _close; }
        set
        {
            if (_close == value)
                return;
            _close = value;
            NotifyPropertyChanged("Close");
        }
    }

if you change the Close to true, the window will be closed

Close = True;
Gladiator answered 14/8, 2012 at 7:16 Comment(1)
Thanks, will give this a try. Where does the NotifyPropertyChanged sit and what does it look like?Charlie
H
7

Here is the simplest and pure MVVM solution

ViewModel Code

public class ViewModel
{
    public Action CloseAction { get; set; }

    private void CloseCommandFunction()
    {
        CloseAction();
    }
}

Here is XAML View Code

public partial class DialogWindow : Window
{
    public DialogWindow()
    {
        ViewModel vm = new ViewModel();
        this.DataContext = vm;

        vm.CloseAction = Close;
    }
}
Highams answered 7/9, 2017 at 12:56 Comment(3)
simple and effective :)Sandra
Very nice approach!Ailssa
The last line can be simplified as vm.CloseAction = Close;Vick
S
4

This solution is quick and easy. Downside is that there is some coupling between the layers.

In your viewmodel:

public class MyWindowViewModel: ViewModelBase
{


    public Command.StandardCommand CloseCommand
    {
        get
        {
            return new Command.StandardCommand(Close);
        }
    }
    public void Close()
    {
        foreach (System.Windows.Window window in System.Windows.Application.Current.Windows)
        {
            if (window.DataContext == this)
            {
                window.Close();
            }
        }
    }
}
Shayneshays answered 2/10, 2013 at 13:48 Comment(1)
like this approach too, but a bit worried about scalability.Tonatonal
N
4

MVVM-light with a custom message notification to avoid the window to process every notificationmessage

In the viewmodel:

public class CloseDialogMessage : NotificationMessage
{
    public CloseDialogMessage(object sender) : base(sender, "") { }
}

private void OnClose()
{
    Messenger.Default.Send(new CloseDialogMessage(this));
}

Register the message in the window constructor:

Messenger.Default.Register<CloseDialogMessage>(this, nm =>
{
    Close();
});
Northwestward answered 21/1, 2017 at 7:33 Comment(0)
A
2

This is very similar to eoldre's answer. It's functionally the same in that it looks through the same Windows collection for a window that has the view model as its datacontext; but I've used a RelayCommand and some LINQ to achieve the same result.

public RelayCommand CloseCommand
{
    get
    {
        return new RelayCommand(() => Application.Current.Windows
            .Cast<Window>()
            .Single(w => w.DataContext == this)
            .Close());
    }
}
Acervate answered 3/6, 2014 at 21:6 Comment(0)
N
2

using MVVM-light toolkit:

In the ViewModel:

 public void notifyWindowToClose()
{
    Messenger.Default.Send<NotificationMessage>(
        new NotificationMessage(this, "CloseWindowsBoundToMe")
    );
}

And in the View:

 Messenger.Default.Register<NotificationMessage>(this, (nm) =>
{
    if (nm.Notification == "CloseWindowsBoundToMe")
    {
        if (nm.Sender == this.DataContext)
            this.Close();
    }
});
Nasal answered 19/7, 2016 at 10:15 Comment(0)
B
0

This is taken from ken2k answer (thanks!), just adding the CloseCommand also to the base CloseableViewModel.

public class CloseableViewModel
{
    public CloseableViewModel()
    {
        CloseCommand = new RelayCommand(this.OnClosingRequest);
    }

    public event EventHandler ClosingRequest;

    protected void OnClosingRequest()
    {
        if (this.ClosingRequest != null)
        {
            this.ClosingRequest(this, EventArgs.Empty);
        }
    }

    public RelayCommand CloseCommand
    {
        get;
        private set;
    }
}

Your view model, inherits it

public class MyViewModel : CloseableViewModel

Then on you view

public MyView()
{
    var viewModel = new StudyDataStructureViewModel(studyId);
    this.DataContext = viewModel;

    //InitializeComponent(); ...

    viewModel.ClosingRequest += (sender, e) => this.Close();
}
Botzow answered 27/8, 2014 at 20:32 Comment(0)
B
0

Given a way, Please check

https://mcmap.net/q/108689/-how-should-the-viewmodel-close-the-form

Short Description

  1. Derive your ViewModel from INotifyPropertyChanged
  2. Create a observable property CloseDialog in ViewModel, Change CloseDialog property whenever you want to close the dialog.
  3. Attach a Handler in View for this property change
  4. Now you are almost done. In the event handler make DialogResult = true
Bloodstream answered 30/5, 2015 at 13:10 Comment(0)
V
0

first of all give your window a name like

x:Name="AboutViewWindow"

on my close button I've defined Command and Command Parameter like

CommandParameter="{Binding ElementName=AboutViewWindow}"
Command="{Binding CancelCommand}"

then in my view model

private ICommand _cancelCommand;        
public ICommand CancelCommand       
{
   get          
     {
        if (_cancelCommand == null)
           {
              _cancelCommand = new DelegateCommand<Window>(
                    x =>
                    {
                        x?.Close();
                    });
            }

            return _cancelCommand;          
     }      
}
Virulence answered 12/10, 2017 at 20:28 Comment(0)
D
0

Most MVVM-compliant solution using HanumanInstitute.MvvmDialogs

Implement ICloseable interface in your ViewModel and that's it!

No code in your view whatsoever.

Daguerre answered 29/8, 2022 at 13:41 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.