0
votes

I am trying to close a window from its ViewModel. I am using the MVVM pattern. I have tired to get the window using;

Window parentWindow = Window.GetWindow(this);

But I cannot do this, how do I get the window of the ViewModel so I am able to close the window. I want to be able to do this in code.

Can you find the parent window in code?

5
I'd like to notice that invoking window's Close() method from ViewModel is not a good practice and is contrary to the MVVM idea itself. Use triggers for such purposes.Anatolii Gabuza

5 Answers

3
votes

ViewModels should not be referencing the View in any way, including closing windows, in MVVM.

Instead, communication between the View and ViewModel is typically done through some kind of Event or Messaging System, such as Microsoft Prism's EventAggregator, or MVVM Light's Messenger

For example, the View should subscribe to listen for event messages of type CloseWindow, and when it receives one of those message it should close itself. Then the ViewModel simply has to broadcast a CloseWindow message anytime it wants to tell the View to close.

There's a brief overview of event systems in MVVM, and some examples, on my blog post about Communication between ViewModels if you're interested

1
votes

yes referencing view in viewmodel isn't best practice. WHY? because when you unit test your viewmodel it is require you to instantiate view, for small view will not difficult to do that, but for a complex view with complex tree of dependency? that wont be good.

for me, the easiest way to do communication with view is by passing IInputElement on viewmodel constructor. the bennefit of IInputElement is Routed Event backbone, it has RaiseEvent and AddHandler method required for routed event. thus you can bubble/tunnel/direct event to any view or viewmodel on your application freely without any additional library.

here is my the simplified code on viewmodel but remember this technique only work for view first approach

public class MyViewModel : INotifyPropertyChanged
{
    public static readonly RoutedEvent RequestCloseEvent = EventManager.RegisterRoutedEvent("RequestClose",
        RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(MyViewModel));

    private IInputElement dispatcher;

    public MyViewModel(IInputElement dispatcher)
    {
        this.dispatcher = dispatcher;
    }

    public void CloseApplication()
    {
        dispatcher.RaiseEvent(new RoutedEventArgs(RequestCloseEvent));
    }
}

on your View simply

DataContext = new MyViewModel(this)
//notice "this" on the constructor

and the root view (Window) of your application simply

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        AddHandler(MyViewModel.RequestCloseEvent, new RoutedEventHandler(onRequestClose));
    }

    private void onRequestClose(object sender, RoutedEventArgs e)
    {
        if (MessageBox.Show("Are you sure you want to quit?", "Confirmation", MessageBoxButton.YesNo) == MessageBoxResult.Yes)
        {
            Close();
        }
    }
}

and because IInputElement is interface rather than class, you easily create a mock class for your unit test

var target = new MyViewModel(new DispatcherMock)

or you can use mock library like RhinoMocks

for further reading, you can learn more about how to use Routed Event

0
votes

Let the ViewModel do this, if really in need.

The Models says for example, that there are no longer valid data

pass that information to the ViewModel

the ViewModel recognizes, that it can no longer display anything

and then closes the window.

An empty view is the normal way of expressing that there are no more data

0
votes

You can define an action in your ViewModel

public Action CloseAction { get; set; }

then, in your window (for example in the DataContextChanged) you can set this action :

((IClosable)viewModel.Content).CloseAction = () => System.Windows.Application.Current.Dispatcher.Invoke(Close());

Well, all this is part of a bigger dependency injection pattern, but basic principle is here... Next, you juste need to call the action from the VM.

0
votes

There is a useful behavior for this task which doesn't break MVVM, a Behavior, introduced with Expression Blend 3, to allow the View to hook into commands defined completely within the ViewModel.

This behavior demonstrates a simple technique for allowing the ViewModel to manage the closing events of the View in a Model-View-ViewModel application.

This allows you to hook up a behavior in your View (UserControl) which will provide control over the control's Window, allowing the ViewModel to control whether the window can be closed via standard ICommands.

Using Behaviors to Allow the ViewModel to Manage View Lifetime in M-V-VM

http://gallery.expression.microsoft.com/WindowCloseBehavior/