WPF MVVM: How to close a window

I have a Button that closes my window when it's clicked:

<Button x:Name="buttonOk"  IsCancel="True">Ok</Button>

That's fine until I add a Command to the Button i.e.

<Button x:Name="buttonOk" 
        Command="{Binding SaveCommand}" 
        IsCancel="True">Ok</Button>

Now it doesn't close presumably because I am handling the Command. I can fix this by putting an EventHandler in and calling this.Close() i.e.

<Button x:Name="buttonOk" 
        Click="closeWindow" 
        Command="{Binding SaveCommand}" 
        IsCancel="True">Ok</Button>

but now I have code in my code behind i.e. the method SaveCommand. I am using the MVVM pattern and SaveCommand is the only code in my code behind.

How can I do this differently so as not to use code behind?


I just completed a blog post on this very topic. In a nutshell, add an Action property to your ViewModel with get and set accessors. Then define the Action from your View constructor. Finally, invoke your action in the bound command that should close the window.

In the ViewModel:

public Action CloseAction  { get; set;}

and in the View constructor:

private View()
{
    InitializeComponent();
    ViewModel vm = new ViewModel();
    this.DataContext = vm;
    if ( vm.CloseAction == null )
        vm.CloseAction = new Action(this.Close);
}

Finally, in whatever bound command that should close the window, we can simply invoke

CloseAction(); // Calls Close() method of the View

This worked for me, seemed like a fairly elegant solution, and saved me a bunch of coding.


Very clean and MVVM way is to use InteractionTrigger and CallMethodAction defined in Microsoft.Interactivity.Core

You will need to add a new namespace as below

xmlns:i="http://schemas.microsoft.com/xaml/behaviors"

You will need the Microsoft.Xmal.Behaviours.Wpf assembly and then the below xaml code will work.

<Button Content="Save" Command="{Binding SaveCommand}">
  <i:Interaction.Triggers>
    <i:EventTrigger EventName="Click">
      <i:CallMethodAction MethodName="Close"
                           TargetObject="{Binding RelativeSource={RelativeSource
                                                  Mode=FindAncestor,
                                                  AncestorType=Window}}" />
    </i:EventTrigger>
  </i:Interaction.Triggers>
</Button>

You don't need any code behind or anything else and can also call any other method of Window.


As someone commented, the code I have posted is not MVVM friendly, how about the second solution?

1st, not MVVM solution (I will not delete this as a reference)

XAML:

<Button Name="okButton" Command="{Binding OkCommand}" CommandParameter="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}">OK</Button>

ViewModel:

public ICommand OkCommand
{
    get
    {
        if (_okCommand == null)
        {
            _okCommand = new ActionCommand<Window>(DoOk, CanDoOk);
        }
        return _okCommand ;
    }
}

void DoOk(Window win)
{
    // Your Code
    win.DialogResult = true;
    win.Close();
}

bool CanDoOk(Window win) { return true; }

2nd, probably better solution: Using attached behaviours

XAML

<Button Content="Ok and Close" Command="{Binding OkCommand}" b:CloseOnClickBehaviour.IsEnabled="True" />

View Model

public ICommand OkCommand
{
    get { return _okCommand; }
}

Behaviour Class Something similar to this:

public static class CloseOnClickBehaviour
{
    public static readonly DependencyProperty IsEnabledProperty =
        DependencyProperty.RegisterAttached(
            "IsEnabled",
            typeof(bool),
            typeof(CloseOnClickBehaviour),
            new PropertyMetadata(false, OnIsEnabledPropertyChanged)
        );

    public static bool GetIsEnabled(DependencyObject obj)
    {
        var val = obj.GetValue(IsEnabledProperty);
        return (bool)val;
    }

    public static void SetIsEnabled(DependencyObject obj, bool value)
    {
        obj.SetValue(IsEnabledProperty, value);
    }

    static void OnIsEnabledPropertyChanged(DependencyObject dpo, DependencyPropertyChangedEventArgs args)
    {
        var button = dpo as Button;
        if (button == null)
            return;

        var oldValue = (bool)args.OldValue;
        var newValue = (bool)args.NewValue;

        if (!oldValue && newValue)
        {
            button.Click += OnClick;
        }
        else if (oldValue && !newValue)
        {
            button.PreviewMouseLeftButtonDown -= OnClick;
        }
    }

    static void OnClick(object sender, RoutedEventArgs e)
    {
        var button = sender as Button;
        if (button == null)
            return;

        var win = Window.GetWindow(button);
        if (win == null)
            return;

        win.Close();
    }

}