44

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?

Community
  • 1
  • 1
mcalex
  • 6,628
  • 5
  • 50
  • 80
  • 1
    Several options have already been discussed [http://stackoverflow.com/questions/4376475/wpf-mvvm-how-to-close-a-window][here] Generally the approach I would use is the CommandParameter with a relative source back to the calling Window. (As demonstrated by Simone) – Steve Py Aug 14 '12 at 05:27
  • does this solution require Expression Blend? I'm challenged on that front – mcalex Aug 14 '12 at 05:44
  • See how to use attached property to solve this [here](http://blog.excastle.com/2010/07/25/mvvm-and-dialogresult-with-no-code-behind/) – dvvrd Aug 14 '12 at 06:04

13 Answers13

68

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();
}
Alex P.
  • 3,697
  • 9
  • 45
  • 110
ken2k
  • 48,145
  • 10
  • 116
  • 176
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

Rohit Vats
  • 79,502
  • 12
  • 161
  • 185
  • 4
    I like this, but is coupling between the viewmodel and the application allowed/approved? – mcalex Aug 14 '12 at 09:24
  • 3
    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. – Rohit Vats Aug 14 '12 at 09:42
  • 15
    @Rohit: You couple your viewmodel to WPF this way. (Application class) – g.pickardou Jul 08 '14 at 11:12
  • @g.pickardou - 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). – Rohit Vats Jul 08 '14 at 12:05
  • 8
    @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 interfaces – Massimiliano Kraus Oct 03 '16 at 15:09
  • This solution threw an exception for me. – abdou_dev Jun 02 '21 at 16:59
  • Good luck running unit tests on this! – Etienne Charland Aug 29 '22 at 13:39
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" />
Alex P.
  • 3,697
  • 9
  • 45
  • 110
PRASAD CP
  • 315
  • 3
  • 6
13

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;
Alex P.
  • 3,697
  • 9
  • 45
  • 110
HB MAAM
  • 3,913
  • 4
  • 29
  • 39
  • Thanks, will give this a try. Where does the NotifyPropertyChanged sit and what does it look like? – mcalex Aug 14 '12 at 08:13
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;
    }
}
Krish
  • 616
  • 9
  • 19
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();
            }
        }
    }
}
Alex P.
  • 3,697
  • 9
  • 45
  • 110
eoldre
  • 1,075
  • 18
  • 28
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();
});
Eric Bole-Feysot
  • 13,949
  • 7
  • 47
  • 53
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());
    }
}
Matt Winward
  • 1,255
  • 2
  • 15
  • 43
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();
    }
});
David Fawzy
  • 1,056
  • 15
  • 17
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();
}
Chris Amelinckx
  • 4,334
  • 2
  • 23
  • 21
0

Given a way, Please check

https://stackoverflow.com/a/30546407/3659387

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
Community
  • 1
  • 1
Anil8753
  • 2,663
  • 4
  • 29
  • 40
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;          
     }      
}
dnxit
  • 7,118
  • 2
  • 30
  • 34
0

Most MVVM-compliant solution using HanumanInstitute.MvvmDialogs

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

No code in your view whatsoever.

Etienne Charland
  • 3,424
  • 5
  • 28
  • 58