43
votes

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?

12
Several options have already been discussed [stackoverflow.com/questions/4376475/… Generally the approach I would use is the CommandParameter with a relative source back to the calling Window. (As demonstrated by Simone)Steve Py
does this solution require Expression Blend? I'm challenged on that frontmcalex
See how to use attached property to solve this heredvvrd

12 Answers

29
votes

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

66
votes

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();
}
26
votes

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" />
13
votes

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;
6
votes

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 = new Action(() => this.Close());
    }
}
5
votes

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();
            }
        }
    }
}
3
votes

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();
});
2
votes

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());
    }
}
2
votes

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();
    }
});
0
votes

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();
}
0
votes

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
0
votes

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;          
     }      
}