0

Recently I answered a question How to Bind to window's close button the X-button with what I thought was an MVVM solution. Please don't focus on actual question there, because that's not what's bothering me. I wouldn't even use my solution there in that particular case. I would most certainly use @ChrisW's solution.

Then response from @SpikeX appeared and now I am confused. But I have to thank him for that. I can't stop thinking about that, because untill now I was probably thinking about MVVM in wrong way.

So I started research:

Close Window from ViewModel

Basic concepts of MVVM— what should a ViewModel do?

and so on...

As you can see I am not the only person in the universe who closed window from ViewModel. But can I really do that? Or is it really true that I should not use window in ViewModel. Is MVVM really so strict about this? Is really my solution breaking an MVVM pattern?

Community
  • 1
  • 1
Kapitán Mlíko
  • 4,498
  • 3
  • 43
  • 60

2 Answers2

1

Well, in my opinion the viewmodel should be completely isolated from the client technology in which it is being used.

Since you call the close method on the actual Window instance, you need a reference to a client specific assembly (in this case WPF), which pretty much makes it impossible to reuse that viewmodel for anything else.

If you wanted to make both WPF, Silverlight, Windows Phone, Windows Store App, etc. clients, you would have no chance of using the same viewmodel for them all, since Windows Phone probably doesn't know what a WPF window is.

Also, unit-testing viewmodels becomes more cumbersome when you have references to actual view elements in there.

So instead of referencing the window directly, you could abstract it away in some kind of view adapter.

If the viewmodel only knows about an interface like this:

public interface IView
{
    void Show();
    void Close();
}

...each of your clients can create their own implementation of it, and inject it into the viewmodel so it does the right thing on any given client.

The point is that the viewmodel has no knowledge of the actual view. Everything is hidden in the implementation of the interface.

Peter Hansen
  • 8,807
  • 1
  • 36
  • 44
1

Well, yes your solution is breaking the pattern. Biggest disadvantage is, that you cannot completely test the VM and the logic, which is interfering with the closing of the window. But as always you have to consider, if it's worth the effort to implement a workaround.

So if you really want to stick to MVVM, you could use the solution I posted in the first linked SO post. I copied the essential part of my post in here.

Quote

<Window x:Class="AC.Frontend.Controls.DialogControl.Dialog"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:DialogControl="clr-namespace:AC.Frontend.Controls.DialogControl" 
        xmlns:hlp="clr-namespace:AC.Frontend.Helper"
        MinHeight="150" MinWidth="300" ResizeMode="NoResize" SizeToContent="WidthAndHeight"
        WindowStartupLocation="CenterScreen" Title="{Binding Title}"
        hlp:AttachedProperties.DialogResult="{Binding DialogResult}" WindowStyle="ToolWindow" ShowInTaskbar="True"
        Language="{Binding UiCulture, Source={StaticResource Strings}}">
        <!-- A lot more stuff here -->
</Window>

As you can see, I'm declaring the namespace xmlns:hlp="clr-namespace:AC.Frontend.Helper" first and afterwards the binding hlp:AttachedProperties.DialogResult="{Binding DialogResult}".

[...]

public class AttachedProperties
{
    #region DialogResult

    public static readonly DependencyProperty DialogResultProperty =
        DependencyProperty.RegisterAttached("DialogResult", typeof (bool?), typeof (AttachedProperties), new PropertyMetadata(default(bool?), OnDialogResultChanged));

    private static void OnDialogResultChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var wnd = d as Window;
        if (wnd == null)
            return;

        wnd.DialogResult = (bool?) e.NewValue;
    }

    public static bool? GetDialogResult(DependencyObject dp)
    {
        if (dp == null) throw new ArgumentNullException("dp");

        return (bool?)dp.GetValue(DialogResultProperty);
    }

    public static void SetDialogResult(DependencyObject dp, object value)
    {
        if (dp == null) throw new ArgumentNullException("dp");

        dp.SetValue(DialogResultProperty, value);
    }

    #endregion
}

/Quote

The only thing you need is a VM like this to make my solution work.

public class WindowVm : ViewModelBase // base class implementing INotifyPropertyChanged
{
    private bool? _dialogResult;
    public bool? DialogResult
    {
        get { return _dialogResult; }
        set 
        {
             _dialogResult = value;
             RaisePropertyChanged(() => DialogResult);
        }
    }

    //... many other properties
}
Community
  • 1
  • 1
DHN
  • 4,807
  • 3
  • 31
  • 45