WPF MVVM: How to close a window
Asked Answered
N

23

86

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?

Naylor answered 7/12, 2010 at 12:18 Comment(2)
Note- Setting IsCancel = "True" on an OK button is a bad idea. That property is for Cancel buttons.Untinged
To clarify, the code behind is the closeWindow Click event handler, not SaveCommand. This appears to be a typo.Sjambok
A
75

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.

Aftersensation answered 30/6, 2013 at 15:56 Comment(10)
This doesn't work for me. When I call the CloseAction(), it says that CloseAction is null, despite of the code in the ViewMayence
@DannyTan, does the code in the View's constructor define the ViewModel's CloseAction action? Or is the action not null at the time of the View's constructor execution?Aftersensation
Excuse my ignorance but how doesn't this violate the principal of decoupling the View and the ViewModel? If you are instantiating your ViewModel in your View, you may as well not use MVVM. I think best practice is to instantiate your View and ViewModel individually and set the DataContext to the View outside the view itself.Gellman
Worked when I had the view model as the datacontext for the window. Did not work when: I had a window called MainWindow, had a user control called LogIn, it's data context is called LoginViewModel. In main window I also set the datacontext to loginViewModel. When I run the command, I get a null reference exception in the CloseAction ThanksLysine
Solved it by making the Action a static property. Daaaah!Lysine
I realize this is getting old, but I would argue that this method doesn't break MVVM unless there is a strict definition I am not aware. Ultimately, MVVM requires that the VM is unaware of the view but the view must be aware of the VM. If one was to replace the view, it wouldn't break the VM in any kind of way. There would be an uninstantiated Action, but I don't think that's a declaration of MVVM rules being broken. Searching for "WPF DataContext Instantiation" will bring up this very method in many articles.Poteen
You can do constructor injection instead of property injection to get rid of the null check: programmers.stackexchange.com/questions/177649/… this.DataContext = new ViewModel(this.Close); and then in ViewModel's constructor you assign close to CloseAction. This also has the advantage of making CloseAction get-only.Feign
Works like a charm and is super easy to implement. It is identical to "the View subscribes to an event of the ViewModel". When people talk about decoupling things, they usually use Dependency injection or Events. This is using action as an (crippled) event. So View and ViewModel is still decoupled in my opinion. you can easily unit test the viewModel without having the viewShontashoo
Terrible code behind, you can't do this in ViewModel First architecture.Soutor
The last line can be simplified as vm.CloseAction = Close;Russ
I
33

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.Xaml.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.

Individual answered 21/1, 2016 at 15:22 Comment(5)
This is the cleanest approach I've seen so far in terms of no code behind and no coupling the ViewModel to the View. It works with commands too. You will need to deploy a couple of extra DLLs, and would need extra work if you wanted to be able to cancel the close from within your command. It's not a lot different to having a Click event in the code behind and just calling Close(), the code behind event handler would make it easier to handle the scenario of the close command cancelling the close event (e.g. if there was an error saving data). Thanks MassimilianoPlaster
Rajnikant's code longer works in VS 2019 because MS made WPF behaviors open-source and moved to the Microsoft.Xaml.Behaviors.Wpf NuGet package. Source of info is comments to issue: developercommunity.visualstudio.com/content/problem/198075/… . Detailed steps to refactor your code are at: devblogs.microsoft.com/dotnet/…Prelate
It is cleaner, but it's something that I believe should be controlled by the view model, not the view, since it belongs to a command, not to the standard close button.Jimmie
This only works if you always want to close the window on button click. If you want to decide if the button press really should close the window, you cant.Shontashoo
Brilliant. This is definitely the way to go. As already mentioned by others, be sure to use the latest version of the Interactivity namespace via the aforementioned NuGet package.Pronty
M
20

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

}
Medlar answered 25/1, 2012 at 14:9 Comment(8)
You never, repeat with me, never couple the Window with the ViewModel. Now write the sentence 100 instances :)Mellissamellitz
+1 IMHO this is the best solution: it does the thing, it's the shortest, requires no complicated infrastructure, solves the problem in MVVM ways. @SoMoS - there's no coupling here. At all. VM isn't aware of existence of View; command gets Window as a parameter as it needs to know what to close.Backdrop
+1 @SoMoS I agree with Ilia this is exactly the decoupled solution, I wouldn't combine save and close window logic together but this is another matterKishakishinev
@Barahovski: Window is a WPF object. The viewmodel should not depend on a WPF or any heavy framework. How can a unit test (without UI) get a Window instance to test this?Saransarangi
@IgnacioSolerGarcia +1 to your comment. maybe an attached behaviour approach is a better solution? I have updated my answer with it.Medlar
@Saransarangi +1 to your comment, maybe an attached behaviour approach is a better solution? I have updated my answer with it.Medlar
Excellent 2nd solution! You don't actually need to set the Command property on the button at all though, so you don't need the code for it in the ViewModel.Roan
"Behaviour Class Something similar to this:" That looks like the cleanest approach of all… but is it guaranteed that both the command and the Click handler fire? The docs don't seem to explicitly say this.Cerenkov
A
13

I'd personally use a behaviour to do this sort of thing:

public class WindowCloseBehaviour : Behavior<Window>
{
    public static readonly DependencyProperty CommandProperty =
      DependencyProperty.Register(
        "Command",
        typeof(ICommand),
        typeof(WindowCloseBehaviour));

    public static readonly DependencyProperty CommandParameterProperty =
      DependencyProperty.Register(
        "CommandParameter",
        typeof(object),
        typeof(WindowCloseBehaviour));

    public static readonly DependencyProperty CloseButtonProperty =
      DependencyProperty.Register(
        "CloseButton",
        typeof(Button),
        typeof(WindowCloseBehaviour),
        new FrameworkPropertyMetadata(null, OnButtonChanged));

    public ICommand Command
    {
        get { return (ICommand)GetValue(CommandProperty); }
        set { SetValue(CommandProperty, value); }
    }

    public object CommandParameter
    {
        get { return GetValue(CommandParameterProperty); }
        set { SetValue(CommandParameterProperty, value); }
    }

    public Button CloseButton
    {
        get { return (Button)GetValue(CloseButtonProperty); }
        set { SetValue(CloseButtonProperty, value); }
    }

    private static void OnButtonChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var window = (Window)((WindowCloseBehaviour)d).AssociatedObject;
        ((Button) e.NewValue).Click +=
            (s, e1) =>
            {
                var command = ((WindowCloseBehaviour)d).Command;
                var commandParameter = ((WindowCloseBehaviour)d).CommandParameter;
                if (command != null)
                {
                    command.Execute(commandParameter);                                                      
                }
                window.Close();
            };
        }
    }

You can then attach this to your Window and Button to do the work:

<Window x:Class="WpfApplication6.Window1"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
        xmlns:local="clr-namespace:WpfApplication6"
        Title="Window1" Height="300" Width="300">
    <i:Interaction.Behaviors>
        <local:WindowCloseBehaviour CloseButton="{Binding ElementName=closeButton}"/>
    </i:Interaction.Behaviors>
    <Grid>
        <Button Name="closeButton">Close</Button>
    </Grid>
</Window>

I've added Command and CommandParameter here so you can run a command before the Window closes.

Aquiline answered 25/1, 2012 at 17:0 Comment(2)
I'm a bit late to this party, but this can be simplified further by putting the behavior directly on the Button. You can define a handler for the Click event that calls Window.GetWindow(AssociatedObject)?.Close() (with appropriate null checks, of course) that gets attached/detached in overrides for the OnAttached and OnDetaching hooks. Three trivial functions, zero properties, and can be attached to an arbitrary number of buttons in the same (or different) windows.Awoke
Hm, wouldn't this be a better design if, instead of attaching the behavior to the window and passing a button, you could attach the behavior to the button and pass the window?Cerenkov
S
9

For small apps, I use my own Application Controller for showing, closing and disposing windows and DataContexts. It's a central point in UI of an application.

It's something like this:

//It is singleton, I will just post 2 methods and their invocations
public void ShowNewWindow(Window window, object dataContext = null, bool dialog = true)
{
    window.DataContext = dataContext;
    addToWindowRegistry(dataContext, window);

    if (dialog)
        window.ShowDialog();
    else
        window.Show();

}

public void CloseWindow(object dataContextSender)
{
    var correspondingWindows = windowRegistry.Where(c => c.DataContext.Equals(dataContextSender)).ToList();
    foreach (var pair in correspondingWindows)
    {
        pair.Window.Close();              
    }
}

and their invocations from ViewModels:

// Show new Window with DataContext
ApplicationController.Instance.ShowNewWindow(
                new ClientCardsWindow(),
                new ClientCardsVM(),
                false);

// Close Current Window from viewModel
ApplicationController.Instance.CloseWindow(this);

Of course you can find some restrictions in my solution. Again: I use it for small projects, and it's enough. If you're interested, I can post full code here or somewhere else/

Soriano answered 7/12, 2010 at 14:26 Comment(0)
G
7

I've tried to resolve this issue in some generic, MVVM way, but I always find that I end up unnecessary complex logic. To achieve close behavior I have made an exception from the rule of no code behind and resorted to simply using good ol' events in code behind:

XAML:

<Button Content="Close" Click="OnCloseClicked" />

Code behind:

private void OnCloseClicked(object sender, EventArgs e)
{
    Visibility = Visibility.Collapsed;
}

Although I wish this would be better supported using commands/MVVM, I simply think that there is no simpler and more clear solution than using events.

Grizzle answered 2/5, 2013 at 12:2 Comment(0)
K
6

I use the Publish Subscribe pattern for complicated class-dependencies:

ViewModel:

    public class ViewModel : ViewModelBase
    {
        public ViewModel()
        {
            CloseComand = new DelegateCommand((obj) =>
                {
                    MessageBus.Instance.Publish(Messages.REQUEST_DEPLOYMENT_SETTINGS_CLOSED, null);
                });
        }
}

Window:

public partial class SomeWindow : Window
{
    Subscription _subscription = new Subscription();

    public SomeWindow()
    {
        InitializeComponent();

        _subscription.Subscribe(Messages.REQUEST_DEPLOYMENT_SETTINGS_CLOSED, obj =>
            {
                this.Close();
            });
    }
}

You can leverage Bizmonger.Patterns to get the MessageBus.

MessageBus

public class MessageBus
{
    #region Singleton
    static MessageBus _messageBus = null;
    private MessageBus() { }

    public static MessageBus Instance
    {
        get
        {
            if (_messageBus == null)
            {
                _messageBus = new MessageBus();
            }

            return _messageBus;
        }
    }
    #endregion

    #region Members
    List<Observer> _observers = new List<Observer>();
    List<Observer> _oneTimeObservers = new List<Observer>();
    List<Observer> _waitingSubscribers = new List<Observer>();
    List<Observer> _waitingUnsubscribers = new List<Observer>();

    int _publishingCount = 0;
    #endregion

    public void Subscribe(string message, Action<object> response)
    {
        Subscribe(message, response, _observers);
    }

    public void SubscribeFirstPublication(string message, Action<object> response)
    {
        Subscribe(message, response, _oneTimeObservers);
    }

    public int Unsubscribe(string message, Action<object> response)
    {
        var observers = new List<Observer>(_observers.Where(o => o.Respond == response).ToList());
        observers.AddRange(_waitingSubscribers.Where(o => o.Respond == response));
        observers.AddRange(_oneTimeObservers.Where(o => o.Respond == response));

        if (_publishingCount == 0)
        {
            observers.ForEach(o => _observers.Remove(o));
        }

        else
        {
            _waitingUnsubscribers.AddRange(observers);
        }

        return observers.Count;
    }

    public int Unsubscribe(string subscription)
    {
        var observers = new List<Observer>(_observers.Where(o => o.Subscription == subscription).ToList());
        observers.AddRange(_waitingSubscribers.Where(o => o.Subscription == subscription));
        observers.AddRange(_oneTimeObservers.Where(o => o.Subscription == subscription));

        if (_publishingCount == 0)
        {
            observers.ForEach(o => _observers.Remove(o));
        }

        else
        {
            _waitingUnsubscribers.AddRange(observers);
        }

        return observers.Count;
    }

    public void Publish(string message, object payload)
    {
        _publishingCount++;

        Publish(_observers, message, payload);
        Publish(_oneTimeObservers, message, payload);
        Publish(_waitingSubscribers, message, payload);

        _oneTimeObservers.RemoveAll(o => o.Subscription == message);
        _waitingUnsubscribers.Clear();

        _publishingCount--;
    }

    private void Publish(List<Observer> observers, string message, object payload)
    {
        Debug.Assert(_publishingCount >= 0);

        var subscribers = observers.Where(o => o.Subscription.ToLower() == message.ToLower());

        foreach (var subscriber in subscribers)
        {
            subscriber.Respond(payload);
        }
    }

    public IEnumerable<Observer> GetObservers(string subscription)
    {
        var observers = new List<Observer>(_observers.Where(o => o.Subscription == subscription));
        return observers;
    }

    public void Clear()
    {
        _observers.Clear();
        _oneTimeObservers.Clear();
    }

    #region Helpers
    private void Subscribe(string message, Action<object> response, List<Observer> observers)
    {
        Debug.Assert(_publishingCount >= 0);

        var observer = new Observer() { Subscription = message, Respond = response };

        if (_publishingCount == 0)
        {
            observers.Add(observer);
        }
        else
        {
            _waitingSubscribers.Add(observer);
        }
    }
    #endregion
}

}

Subscription

public class Subscription
{
    #region Members
    List<Observer> _observerList = new List<Observer>();
    #endregion

    public void Unsubscribe(string subscription)
    {
        var observers = _observerList.Where(o => o.Subscription == subscription);

        foreach (var observer in observers)
        {
            MessageBus.Instance.Unsubscribe(observer.Subscription, observer.Respond);
        }

        _observerList.Where(o => o.Subscription == subscription).ToList().ForEach(o => _observerList.Remove(o));
    }

    public void Subscribe(string subscription, Action<object> response)
    {
        MessageBus.Instance.Subscribe(subscription, response);
        _observerList.Add(new Observer() { Subscription = subscription, Respond = response });
    }

    public void SubscribeFirstPublication(string subscription, Action<object> response)
    {
        MessageBus.Instance.SubscribeFirstPublication(subscription, response);
    }
}
Kentledge answered 20/2, 2015 at 16:10 Comment(0)
C
4

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/

Above link has been archived to http://code.msdn.microsoft.com/Window-Close-Attached-fef26a66#content

Cardiganshire answered 26/6, 2012 at 10:44 Comment(0)
C
4

I struggled with this topic for some time, and eventually went with the simplest approach that is still consistent with MVVM: Have the button execute the Command that does all the heavy lifting and have the button's Click handler close the window.

XAML

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

XAML.cs

public void closeWindow() 
{
    this.DialogResult = true;
}

SaveCommand.cs

 // I'm in my own file, not the code-behind!

True, there is still code-behind, but there isn't anything inherently bad about that. And it makes the most sense to me, from an OO perspective, to just tell the window to close itself.

Curling answered 19/10, 2015 at 21:18 Comment(0)
C
4

We have the name property in the .xaml definition:

x:Name="WindowsForm"

Then we have the button:

<Button Command="{Binding CloseCommand}" 
CommandParameter="{Binding ElementName=WindowsForm}" />

Then in the ViewModel:

public DelegateCommand <Object>  CloseCommand { get; private set; }

Constructor for that view model:
this.CloseCommand = new DelegateCommand<object>(this.CloseAction);

Then at last, the action method:

private void CloseAction (object obj)
{
  Window Win = obj as Window;
  Win.Close();

}

I used this code to close a pop-up window from an application..

Chaconne answered 21/3, 2016 at 17:33 Comment(0)
T
3

I found myself having to do this on a WPF application based on .Net Core 3.0, where unfortunately behaviour support was not yet officially available in the Microsoft.Xaml.Behaviors.Wpf NuGet package.

Instead, I went with a solution that made use of the Façade design pattern.

Interface:

public interface IWindowFacade
{
    void Close();
}

Window:

public partial class MainWindow : Window, IWindowFacade
…

Standard command property on the view model:

public ICommand ExitCommand
…

Control binding:

<MenuItem Header="E_xit" Command="{Binding ExitCommand}" CommandParameter="{Binding RelativeSource={RelativeSource AncestorType=Window}}"/>

Command:

public class ExitCommand : ICommand
{
    …
    public void Execute(object parameter)
    {
        var windowFacade = parameter as IWindowFacade;
        windowFacade?.Close();
    }
    …
}

Because the Close() method is already implemented by the Window class, applying the façade interface to the window is the only required code behind in the UI layer (for this simple example). The command in the presentation layer avoids any dependencies on the view/UI layer as it has no idea what it is talking to when it calls the Close method on the façade.

Tenstrike answered 8/9, 2019 at 12:10 Comment(0)
B
2

In your current window xaml.cs file, call the below code:

var curWnd = Window.GetWindow(this); // passing current window context
curWnd?.Close();

This should do the thing.
It worked for me, hope will do the same for you )

Browse answered 13/3, 2020 at 10:42 Comment(0)
R
1

I have following solution in Silverlight. Would also be in WPF.

ChildWindowExt.cs:

namespace System.Windows.Controls
{
    public class ChildWindowExt : ChildWindow
    {
        public static readonly DependencyProperty IsOpenedProperty =
          DependencyProperty.Register(
          "IsOpened",
          typeof(bool),
          typeof(ChildWindowExt),
          new PropertyMetadata(false, IsOpenedChanged));

        private static void IsOpenedChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            if ((bool)e.NewValue == false)
            {
                ChildWindowExt window = d as ChildWindowExt;
                window.Close();
            }
            else if ((bool)e.NewValue == true)
            {
                ChildWindowExt window = d as ChildWindowExt;
                window.Show();
            }
        }

        public bool IsOpened
        {
            get { return (bool)GetValue(IsOpenedProperty); }
            set { SetValue(IsOpenedProperty, value); }
        }

        protected override void OnClosing(ComponentModel.CancelEventArgs e)
        {
            this.IsOpened = false;
            base.OnClosing(e);
        }

        protected override void OnOpened()
        {
            this.IsOpened = true;
            base.OnOpened();
        }
    }
}

ItemWindow.xaml:

<extControls:ChildWindowExt  
    x:Class="MyProject.ItemWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    xmlns:extControls="clr-namespace:System.Windows.Controls"
    Title="{Binding Title}" IsOpened="{Binding IsOpened, Mode=TwoWay}" Width="640" Height="480">

    <Grid x:Name="LayoutRoot">
        <Button Command="{Binding UpdateCommand}" Content="OK" Width="70" HorizontalAlignment="Center" VerticalAlignment="Center"/>
    </Grid>

</extControls:ChildWindowExt>

ItemViewModel.cs:

private bool _IsOpened;
public bool IsOpened
{
    get
    {
        return _IsOpened;
    }
    set
    {
        if (!Equals(_IsOpened, value))
        {
            _IsOpened = value;
            RaisePropertyChanged("IsOpened");
        }
    }
}

private RelayCommand _UpdateCommand;
/// <summary>
/// Insert / Update data entity
/// </summary>
public RelayCommand UpdateCommand
{
    get
    {
        if (_UpdateCommand == null)
        {
            _UpdateCommand = new RelayCommand(
                () =>
                {
                    // Insert / Update data entity
                    ...

                    IsOpened = false;
                },
                () =>
                {
                    return true;
                });
        }
        return _UpdateCommand;
    }
}

ItemsViewModel.cs:

    private RelayCommand _InsertItemCommand;
    /// <summary>
    /// 
    /// </summary>
    public RelayCommand InsertItemCommand
    {
        get
        {
            if (_InsertItemCommand == null)
            {
                _InsertItemCommand = new RelayCommand(
                    () =>
                    {
                        ItemWindow itemWin = new ItemWindow();
                        itemWin.DataContext = new ItemViewModel();
                        itemWin.Show();

                        // OR

                        // ItemWindow itemWin = new ItemWindow();
                        // ItemViewModel newItem = new ItemViewModel();
                        // itemWin.DataContext = newItem;
                        // newItem.IsOpened = true;

                    },
                    () =>
                    {
                        return true;
                    });
            }
            return _InsertItemCommand;
        }
    }

MainPage.xaml:

<Grid x:Name="LayoutRoot">
    <Button Command="{Binding InsertItemCommand}" Content="Add New" Width="70" HorizontalAlignment="Left" VerticalAlignment="Center" />
</Grid>

I wish you all good ideas and projects ;-)

Resort answered 6/12, 2013 at 12:36 Comment(0)
H
1

This might helps you, closing a wpf window using mvvm with minimal code behind: http://jkshay.com/closing-a-wpf-window-using-mvvm-and-minimal-code-behind/

Heraclitean answered 28/1, 2015 at 20:38 Comment(1)
You're right, but Jonathan Shay just answered here (https://mcmap.net/q/112066/-wpf-mvvm-how-to-close-a-window)Acceptance
M
1

I think the most simple way has not been included already (almost). Instead of using Behaviours which adds new dependencies just use attached properties:

    using System;
    using System.Windows;
    using System.Windows.Controls;

    public class DialogButtonManager
    {
        public static readonly DependencyProperty IsAcceptButtonProperty = DependencyProperty.RegisterAttached("IsAcceptButton", typeof(bool), typeof(DialogButtonManager), new FrameworkPropertyMetadata(OnIsAcceptButtonPropertyChanged));
        public static readonly DependencyProperty IsCancelButtonProperty = DependencyProperty.RegisterAttached("IsCancelButton", typeof(bool), typeof(DialogButtonManager), new FrameworkPropertyMetadata(OnIsCancelButtonPropertyChanged));

        public static void SetIsAcceptButton(UIElement element, bool value)
        {
            element.SetValue(IsAcceptButtonProperty, value);
        }

        public static bool GetIsAcceptButton(UIElement element)
        {
            return (bool)element.GetValue(IsAcceptButtonProperty);
        }

        public static void SetIsCancelButton(UIElement element, bool value)
        {
            element.SetValue(IsCancelButtonProperty, value);
        }

        public static bool GetIsCancelButton(UIElement element)
        {
            return (bool)element.GetValue(IsCancelButtonProperty);
        }

        private static void OnIsAcceptButtonPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
        {
            Button button = sender as Button;

            if (button != null)
            {
                if ((bool)e.NewValue)
                {
                    SetAcceptButton(button);
                }
                else
                {
                    ResetAcceptButton(button);
                }
            }
        }

        private static void OnIsCancelButtonPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
        {
            Button button = sender as Button;

            if (button != null)
            {
                if ((bool)e.NewValue)
                {
                    SetCancelButton(button);
                }
                else
                {
                    ResetCancelButton(button);
                }
            }
        }

        private static void SetAcceptButton(Button button)
        {
            Window window = Window.GetWindow(button);
            button.Command = new RelayCommand(new Action<object>(ExecuteAccept));
            button.CommandParameter = window;
        }

        private static void ResetAcceptButton(Button button)
        {
            button.Command = null;
            button.CommandParameter = null;
        }

        private static void ExecuteAccept(object buttonWindow)
        {
            Window window = (Window)buttonWindow;

            window.DialogResult = true;
        }

        private static void SetCancelButton(Button button)
        {
            Window window = Window.GetWindow(button);
            button.Command = new RelayCommand(new Action<object>(ExecuteCancel));
            button.CommandParameter = window;
        }

        private static void ResetCancelButton(Button button)
        {
            button.Command = null;
            button.CommandParameter = null;
        }

        private static void ExecuteCancel(object buttonWindow)
        {
            Window window = (Window)buttonWindow;

            window.DialogResult = false;
        }
    }

Then just set it on your dialog buttons:

<UniformGrid Grid.Row="2" Grid.Column="1" Rows="1" Columns="2" Margin="3" >
    <Button Content="Accept" IsDefault="True" Padding="3" Margin="3,0,3,0" DialogButtonManager.IsAcceptButton="True" />
    <Button Content="Cancel" IsCancel="True" Padding="3" Margin="3,0,3,0" DialogButtonManager.IsCancelButton="True" />
</UniformGrid>
Mellissamellitz answered 23/2, 2015 at 12:46 Comment(0)
K
1

I also had to deal with this problem, so here my solution. It works great for me.

1. Create class DelegateCommand

    public class DelegateCommand<T> : ICommand
{
    private Predicate<T> _canExecuteMethod;
    private readonly Action<T> _executeMethod;
    public event EventHandler CanExecuteChanged;

    public DelegateCommand(Action<T> executeMethod) : this(executeMethod, null)
    {
    }
    public DelegateCommand(Action<T> executeMethod, Predicate<T> canExecuteMethod)
    {
        this._canExecuteMethod = canExecuteMethod;
        this._executeMethod = executeMethod ?? throw new ArgumentNullException(nameof(executeMethod), "Command is not specified."); 
    }


    public void RaiseCanExecuteChanged()
    {
        if (this.CanExecuteChanged != null)
            CanExecuteChanged(this, null);
    }
    public bool CanExecute(object parameter)
    {
        return _canExecuteMethod == null || _canExecuteMethod((T)parameter) == true;
    }

    public void Execute(object parameter)
    {
        _executeMethod((T)parameter);
    }
}

2. Define your command

        public DelegateCommand<Window> CloseWindowCommand { get; private set; }


    public MyViewModel()//ctor of your viewmodel
    {
        //do something

        CloseWindowCommand = new DelegateCommand<Window>(CloseWindow);


    }
        public void CloseWindow(Window win) // this method is also in your viewmodel
    {
        //do something
        win?.Close();
    }

3. Bind your command in the view

public MyView(Window win) //ctor of your view, window as parameter
    {
        InitializeComponent();
        MyButton.CommandParameter = win;
        MyButton.Command = ((MyViewModel)this.DataContext).CloseWindowCommand;
    }

4. And now the window

  Window win = new Window()
        {
            Title = "My Window",
            Height = 800,
            Width = 800,
            WindowStartupLocation = WindowStartupLocation.CenterScreen,

        };
        win.Content = new MyView(win);
        win.ShowDialog();

so thats it, you can also bind the command in the xaml file and find the window with FindAncestor and bind it to the command parameter.

Kore answered 22/9, 2017 at 22:43 Comment(0)
S
0

I've been searching for a solution to the same problem and found that doing following works fine. The solution is similar to what OP has mentioned in his question with some differences:

  1. No need of IsCancel property.

  2. Code behind should not close window. Just set DialogResult

In my case it first executes code behind and then view model command bound to the button.

XAML

<Button x:Name="buttonOk" Click="Save_Click" Command="{Binding SaveCommand}">OK</Button>

Code Behind

private void Apply_OnClick(object sender, RoutedEventArgs e)
{
    this.DialogResult = true;
}

View Model

private void Save()
{
 // Save data.
}

Hope this helps.

Shaft answered 29/5, 2013 at 6:17 Comment(0)
C
0

You could rephrase the question, and by doing so - coming up with another solution. How can I enable communication between views, viewmodels and whatnot in an MVVM environment? You could use the Mediator pattern. It's basically a notification system. For the actual Mediator implementation, google for it or ask me and I can email it.

Make a Command whose purpose is to close the view.

public void Execute( object parameter )
{
    this.viewModel.DisposeMyStuff();
    Mediator.NotifyColleagues(Mediator.Token.ConfigWindowShouldClose);
}

The Mediator will raise a notification (a token)

Listen to this notification (token) like this in the View codebehind constructor:

public ClientConfigView()
{
    InitializeComponent();
    Mediator.ListenOn(Mediator.Token.ConfigWindowShouldClose, callback => this.Close() );
}
Cupreous answered 29/10, 2013 at 15:0 Comment(0)
S
0

The solution to close a window in wpf that that worked for me is not answered here so i thought i would add my solution too.

        private static Window GetWindow(DependencyObject sender)
        {
            Window window = null;
            if (sender is Window)
                window = (Window)sender;
            if (window == null)
                window = Window.GetWindow(sender);
            return window;
        }
        private void CloseWindow(object sender, RoutedEventArgs e)
        {
            var button = (Button)sender as DependencyObject;

            Window window = GetWindow(button);
                if (window != null)
                    window.Close();
                   // window.Visibility = Visibility.Hidden; 
           // choose between window.close or set window.visibility to close or hide the window.

            //            }
        }

Add CloseWindow event to the button in you window as following.

<Button Content="Cancel" Click="CloseWindow" >
Stealth answered 1/4, 2019 at 14:1 Comment(1)
Don't want to be rude, but when you have the Click event handled in the code behind of the window itself, you can close the window directly by calling Close(). You don't have to search for the Window parent of the Button to be able to close it. Your answer has nothing to do with MVVM and your code can be resumed to: private void CloseWindow(object sender, RoutedEventArgs e) { Close(); }Acord
M
0

Simple approach is close window on saveComand Implementation. Use below code to close window.

Application.Current.Windows[1].Close();

It will close the child window.

Maya answered 24/6, 2020 at 19:46 Comment(0)
S
0

Without any dependencies.

<Window ...>
  ...
  <Button Command="{x:Static SystemCommands.CloseWindowCommand}" />
</Window>
Soutor answered 14/5, 2021 at 10:43 Comment(0)
F
0
  1. Add in window definition:
        xmlns:cv="clr-namespace:xxxCore.WPF.Helpers;assembly=xxxCore.WPF"
        cv:DialogCloser.DialogResult="{Binding DialogResult}"

where DialogResult is bool property in your view model

  1. Dialog close helper class:
using System.Windows;

namespace xxxCore.WPF.Helpers
{
    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 && window.DialogResult == null && e.NewValue != null)
            {
                window.DialogResult = e.NewValue as bool?;
            }
        }

        public static void SetDialogResult(DependencyObject target, bool? value)
        {
            target.SetValue(DialogResultProperty, value);
        }
    }
}
  1. Set value of DialogResult to true or false in your commands
Fernando answered 22/3 at 10:32 Comment(0)
B
-2

You can do it without code behind. Create command, in Execute method call "Save" method on viewmodel and after that call close method on edit window, which you can pass to the command by parameter:

public void Execute(object parameter)
{
    _mainViewModel.SaveSomething();
    var editWindow = parameter as MyEditWindow;
    editWindow?.Close();
}

Save&Close button XAML:

<Button Content"Save&Close" Command="{Binding SaveCmd}" CommandParameter="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=Window}}"  IsDefault="True" />
Bharat answered 10/11, 2015 at 12:53 Comment(1)
Code behind would be better than having the view's instance in the view model like this. The question is about following MVVM pattern.Acord

© 2022 - 2024 — McMap. All rights reserved.