How should the ViewModel close the form?
Asked Answered
N

25

268

I'm trying to learn WPF and the MVVM problem, but have hit a snag. This question is similar but not quite the same as this one (handling-dialogs-in-wpf-with-mvvm)...

I have a "Login" form written using the MVVM pattern.

This form has a ViewModel which holds the Username and Password, which are bound to the view in the XAML using normal data bindings. It also has a "Login" command which is bound to the "Login" button on the form, agan using normal databinding.

When the "Login" command fires, it invokes a function in the ViewModel which goes off and sends data over the network to log in. When this function completes, there are 2 actions:

  1. The login was invalid - we just show a MessageBox and all is fine

  2. The login was valid, we need to close the Login form and have it return true as its DialogResult...

The problem is, the ViewModel knows nothing about the actual view, so how can it close the view and tell it to return a particular DialogResult?? I could stick some code in the CodeBehind, and/or pass the View through to the ViewModel, but that seems like it would defeat the whole point of MVVM entirely...


Update

In the end I just violated the "purity" of the MVVM pattern and had the View publish a Closed event, and expose a Close method. The ViewModel would then just call view.Close. The view is only known via an interface and wired up via an IOC container, so no testability or maintainability is lost.

It seems rather silly that the accepted answer is at -5 votes! While I'm well aware of the good feelings that one gets by solving a problem while being "pure", Surely I'm not the only one that thinks that 200 lines of events, commands and behaviors just to avoid a one line method in the name of "patterns" and "purity" is a bit ridiculous....

Nolasco answered 2/2, 2009 at 0:17 Comment(1)
I used attached behaviours to close the window. Bind a "signal" property on your ViewModel to the attached behaviour (I actually use a trigger) When it's set to true, the behaviour closes the window. http://adammills.wordpress.com/2009/07/01/window-close-from-xaml/Coccus
V
342

I was inspired by Thejuan's answer to write a simpler attached property. No styles, no triggers; instead, you can just do this:

<Window ...
        xmlns:xc="clr-namespace:ExCastle.Wpf"
        xc:DialogCloser.DialogResult="{Binding DialogResult}">

This is almost as clean as if the WPF team had gotten it right and made DialogResult a dependency property in the first place. Just put a bool? DialogResult property on your ViewModel and implement INotifyPropertyChanged, and voilà, your ViewModel can close the Window (and set its DialogResult) just by setting a property. MVVM as it should be.

Here's the code for DialogCloser:

using System.Windows;
 
namespace ExCastle.Wpf
{
    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 = e.NewValue as bool?;
        }
        public static void SetDialogResult(Window target, bool? value)
        {
            target.SetValue(DialogResultProperty, value);
        }
    }
}

I've also posted this on my blog.

Valladolid answered 25/7, 2010 at 14:6 Comment(1)
Hello, is the blog post down forever?Gromme
P
71

From my perspective the question is pretty good as the same approach would be used not only for the "Login" window, but for any kind of window. I've reviewed a lot of suggestions and none are OK for me. Please review my suggestion that was taken from the MVVM design pattern article.

Each ViewModel class should inherit from WorkspaceViewModel that has the RequestClose event and CloseCommand property of the ICommand type. The default implementation of the CloseCommand property will raise the RequestClose event.

In order to get the window closed, the OnLoaded method of your window should be overridden:

void CustomerWindow_Loaded(object sender, RoutedEventArgs e)
{
    CustomerViewModel customer = CustomerViewModel.GetYourCustomer();
    DataContext = customer;
    customer.RequestClose += () => { Close(); };
}

or OnStartup method of you app:

    protected override void OnStartup(StartupEventArgs e)
    {
        base.OnStartup(e);

        MainWindow window = new MainWindow();
        var viewModel = new MainWindowViewModel();
        viewModel.RequestClose += window.Close;
        window.DataContext = viewModel;

        window.Show();
    }

I guess that RequestClose event and CloseCommand property implementation in the WorkspaceViewModel are pretty clear, but I will show them to be consistent:

public abstract class WorkspaceViewModel : ViewModelBase
// There's nothing interesting in ViewModelBase as it only implements the INotifyPropertyChanged interface
{
    RelayCommand _closeCommand;
    public ICommand CloseCommand
    {
        get
        {
            if (_closeCommand == null)
            {
                _closeCommand = new RelayCommand(
                   param => Close(),
                   param => CanClose()
                   );
            }
            return _closeCommand;
        }
    }

    public event Action RequestClose;

    public virtual void Close()
    {
        if ( RequestClose != null )
        {
            RequestClose();
        }
    }

    public virtual bool CanClose()
    {
        return true;
    }
}

And the source code of the RelayCommand:

public class RelayCommand : ICommand
{
    #region Constructors

    public RelayCommand(Action<object> execute, Predicate<object> canExecute)
    {
        if (execute == null)
            throw new ArgumentNullException("execute");

        _execute = execute;
        _canExecute = canExecute;
    }
    #endregion // Constructors

    #region ICommand Members

    [DebuggerStepThrough]
    public bool CanExecute(object parameter)
    {
        return _canExecute == null ? true : _canExecute(parameter);
    }

    public event EventHandler CanExecuteChanged
    {
        add { CommandManager.RequerySuggested += value; }
        remove { CommandManager.RequerySuggested -= value; }
    }

    public void Execute(object parameter)
    {
        _execute(parameter);
    }

    #endregion // ICommand Members

    #region Fields

    readonly Action<object> _execute;
    readonly Predicate<object> _canExecute;

    #endregion // Fields
}

P.S. Don't treat me badly for those sources! If I had them yesterday that would have saved me a few hours...

P.P.S. Any comments or suggestions are welcome.

Polysynthetic answered 20/1, 2010 at 11:5 Comment(0)
F
15

There are a lot of comments arguing the pros and cons of MVVM here. For me, I agree with Nir; it's a matter of using the pattern appropriately and MVVM doesn't always fit. People seems to have become willing to sacrifice all of the most important principles of software design JUST to get it to fit MVVM.

That said,..i think your case could be a good fit with a bit of refactoring.

In most cases I've come across, WPF enables you to get by WITHOUT multiple Windows. Maybe you could try using Frames and Pages instead of Windows with DialogResults.

In your case my suggestion would be have LoginFormViewModel handle the LoginCommand and if the login is invalid, set a property on LoginFormViewModel to an appropriate value (false or some enum value like UserAuthenticationStates.FailedAuthentication). You'd do the same for a successful login (true or some other enum value). You'd then use a DataTrigger which responds to the various user authentication states and could use a simple Setter to change the Source property of the Frame.

Having your login Window return a DialogResult i think is where you're getting confused; that DialogResult is really a property of your ViewModel. In my, admittedly limited experience with WPF, when something doesn't feel right it usually because I'm thinking in terms of how i would've done the same thing in WinForms.

Hope that helps.

Fates answered 24/3, 2009 at 12:54 Comment(0)
C
10

Assuming your login dialog is the first window that gets created, try this inside your LoginViewModel class:

    void OnLoginResponse(bool loginSucceded)
    {
        if (loginSucceded)
        {
            Window1 window = new Window1() { DataContext = new MainWindowViewModel() };
            window.Show();

            App.Current.MainWindow.Close();
            App.Current.MainWindow = window;
        }
        else
        {
            LoginError = true;
        }
    }
Crochet answered 28/12, 2009 at 20:9 Comment(0)
V
9

This is a simple and clean solution - You add an event to the ViewModel and instruct the Window to close itself when that event is fired.

For more details see my blog post, Close window from ViewModel.

XAML:

<Window
  x:Name="this"
  xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"  
  xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions">
  <i:Interaction.Triggers>
    <i:EventTrigger SourceObject="{Binding}" EventName="Closed">
      <ei:CallMethodAction
        TargetObject="{Binding ElementName=this}"
        MethodName="Close"/>
    </i:EventTrigger>
  </i:Interaction.Triggers>
<Window>

ViewModel:

private ICommand _SaveAndCloseCommand;
public ICommand SaveAndCloseCommand
{
  get
  {
    return _SaveAndCloseCommand ??
      (_SaveAndCloseCommand = new DelegateCommand(SaveAndClose));
  }
}
private void SaveAndClose()
{
  Save();
  Close();
}

public event EventHandler Closed;
private void Close()
{
  if (Closed != null) Closed(this, EventArgs.Empty);
}

Note: The example uses Prism's DelegateCommand (see Prism: Commanding), but any ICommand implementation can be used for that matter.

You can use behaviors from this official package.

Visitor answered 6/10, 2011 at 8:14 Comment(2)
can you please edit this and have a detailed explanation for this answer? I am new to WPF and I want to understand this fully. I can't seem to get it, but my code works now thanks to this logic. It's okay if you just add in some comments on the "MethodName="Close"", EventHandler Closed, and the method Close. (Regarding the blogpost that you pointed out, its just the same as the answer here)Neurasthenic
Please update your blog link, current link is broken.Polypody
M
6

The way I would handle it is to add an event handler in my ViewModel. When the user was successfully logged in I would fire the event. In my View I would attach to this event and when it fired I would close the window.

Metaphase answered 28/4, 2009 at 21:8 Comment(0)
N
4

Here's what I initially did, which does work, however it seems rather long-winded and ugly (global static anything is never good)

1: App.xaml.cs

public partial class App : Application
{
    // create a new global custom WPF Command
    public static readonly RoutedUICommand LoggedIn = new RoutedUICommand();
}

2: LoginForm.xaml

// bind the global command to a local eventhandler
<CommandBinding Command="client:App.LoggedIn" Executed="OnLoggedIn" />

3: LoginForm.xaml.cs

// implement the local eventhandler in codebehind
private void OnLoggedIn( object sender, ExecutedRoutedEventArgs e )
{
    DialogResult = true;
    Close();
}

4: LoginFormViewModel.cs

// fire the global command from the viewmodel
private void OnRemoteServerReturnedSuccess()
{
    App.LoggedIn.Execute(this, null);
}

I later on then removed all this code, and just had the LoginFormViewModel call the Close method on it's view. It ended up being much nicer and easier to follow. IMHO the point of patterns is to give people an easier way to understand what your app is doing, and in this case, MVVM was making it far harder to understand than if I hadn't used it, and was now an anti-pattern.

Nolasco answered 2/2, 2009 at 0:31 Comment(0)
C
4

Ok, so this question is nearly 6 years old and I still can't find in here what I think it's the proper answer, so allow me to share my "2 cents"...

I actually have 2 ways of doing it, first one is the simple one...the second on the right one, so if you are looking for the right one, just skip #1 and jump to #2:

1. Quick and Easy (but not complete)

If I have just a small project I sometimes just create a CloseWindowAction in the ViewModel:

        public Action CloseWindow { get; set; } // In MyViewModel.cs

And whoever crates the View, or in the View's code behind I just set the Method the Action will call:

(remember MVVM is about separation of the View and the ViewModel...the View's code behins is still the View and as long as there is proper separation you are not violating the pattern)

If some ViewModel creates a new window:

private void CreateNewView()
{
    MyView window = new MyView();
    window.DataContext = new MyViewModel
                             {
                                 CloseWindow = window.Close,
                             }; 
    window.ShowDialog();
}

Or if you want it in your Main Window, just place it under your View's constructor:

public MyView()
{
    InitializeComponent();           
    this.DataContext = new MainViewModel
                           {
                                CloseWindow = this.Close
                           };
}

when you want to close the window, just call the Action on your ViewModel.


2. The right way

Now the proper way of doing it is using Prism (IMHO), and all about it can be found here.

You can make an Interaction Request, populate it with whatever data you will need in your new Window, launch it, close it and even receive data back. All of this encapsulated and MVVM approved. You even get a status of how the Window was closed, like if the User Canceled or Accepted (OK button) the Window and data back if you need it. It's a bit more complicated and Answer #1, but it's a lot more complete, and a Recommended Pattern by Microsoft.

The link I gave have all the code snippets and examples, so I won't bother to place any code in here, just read the article of download the Prism Quick Start and run it, it's really simple to understad just a little more verbose to make it work, but the benefits are bigger than just closing a window.

Convert answered 13/6, 2015 at 23:17 Comment(0)
M
4
public partial class MyWindow: Window
{
    public ApplicationSelection()
    {
      InitializeComponent();

      MyViewModel viewModel = new MyViewModel();

      DataContext = viewModel;

      viewModel.RequestClose += () => { Close(); };

    }
}

public class MyViewModel
{

  //...Your code...

  public event Action RequestClose;

  public virtual void Close()
  {
    if (RequestClose != null)
    {
      RequestClose();
    }
  }

  public void SomeFunction()
  {
     //...Do something...
     Close();
  }
}
Muslim answered 30/8, 2016 at 7:14 Comment(0)
P
3

FYI, I ran into this same problem and I think I figured out a work around that doesn't require globals or statics, although it may not be the best answer. I let the you guys decide that for yourself.

In my case, the ViewModel that instantiates the Window to be displayed (lets call it ViewModelMain) also knows about the LoginFormViewModel (using the situation above as an example).

So what I did was to create a property on the LoginFormViewModel that was of type ICommand (Lets call it CloseWindowCommand). Then, before I call .ShowDialog() on the Window, I set the CloseWindowCommand property on the LoginFormViewModel to the window.Close() method of the Window I instantiated. Then inside the LoginFormViewModel all I have to do is call CloseWindowCommand.Execute() to close the window.

It is a bit of a workaround/hack I suppose, but it works well without really breaking the MVVM pattern.

Feel free to critique this process as much as you like, I can take it! :)

Propaganda answered 29/4, 2009 at 18:4 Comment(0)
L
3

This is probably very late, but I came across the same problem and I found a solution that works for me.

I can't figure out how to create an app without dialogs(maybe it's just a mind block). So I was at an impasse with MVVM and showing a dialog. So I came across this CodeProject article:

http://www.codeproject.com/KB/WPF/XAMLDialog.aspx

Which is a UserControl that basically allows a window to be within the visual tree of another window(not allowed in xaml). It also exposes a boolean DependencyProperty called IsShowing.

You can set a style like,typically in a resourcedictionary, that basically displays the dialog whenever the Content property of the control != null via triggers:

<Style TargetType="{x:Type d:Dialog}">
    <Style.Triggers>
        <Trigger Property="HasContent"  Value="True">
            <Setter Property="Showing" Value="True" />
        </Trigger>
    </Style.Triggers>
</Style>

In the view where you want to display the dialog simply have this:

<d:Dialog Content="{Binding Path=DialogViewModel}"/>

And in your ViewModel all you have to do is set the property to a value(Note: the ViewModel class must support INotifyPropertyChanged for the view to know something happened ).

like so:

DialogViewModel = new DisplayViewModel();

To match the ViewModel with the View you should have something like this in a resourcedictionary:

<DataTemplate DataType="{x:Type vm:DisplayViewModel}">
    <vw:DisplayView/>
</DataTemplate>

With all of that you get a one-liner code to show dialog. The problem you get is you can't really close the dialog with just the above code. So that's why you have to put in an event in a ViewModel base class which DisplayViewModel inherits from and instead of the code above, write this

        var vm = new DisplayViewModel();
        vm.RequestClose += new RequestCloseHandler(DisplayViewModel_RequestClose);
        DialogViewModel = vm;

Then you can handle the result of the dialog via the callback.

This may seem a little complex, but once the groundwork is laid, it's pretty straightforward. Again this is my implementation, I'm sure there are others :)

Hope this helps, it saved me.

Leasehold answered 28/5, 2009 at 18:8 Comment(0)
T
3

You could have the ViewModel expose an event that the View registers to. Then, when the ViewModel decides its time to close the view, it fires that event which causes the view to close. If you want a specific result value passed back, then you would have a property in the ViewModel for that.

Taishataisho answered 23/2, 2011 at 22:0 Comment(0)
W
1

Why not just pass the window as a command parameter?

C#:

 private void Cancel( Window window )
  {
     window.Close();
  }

  private ICommand _cancelCommand;
  public ICommand CancelCommand
  {
     get
     {
        return _cancelCommand ?? ( _cancelCommand = new Command.RelayCommand<Window>(
                                                      ( window ) => Cancel( window ),
                                                      ( window ) => ( true ) ) );
     }
  }

XAML:

<Window x:Class="WPFRunApp.MainWindow"
        x:Name="_runWindow"
...
   <Button Content="Cancel"
           Command="{Binding Path=CancelCommand}"
           CommandParameter="{Binding ElementName=_runWindow}" />
Wadley answered 11/10, 2010 at 18:49 Comment(0)
S
1

Just to add to the massive number of answers, I want to add the following. Assuming that you have a ICommand on your ViewModel, and you want that command to close its window (or any other action for that matter), you can use something like the following.

var windows = Application.Current.Windows;
for (var i=0;i< windows.Count;i++ )
    if (windows[i].DataContext == this)
        windows[i].Close();

It's not perfect, and might be difficult to test (as it is hard to mock/stub a static) but it is cleaner (IMHO) than the other solutions.

Erick

Strychnic answered 5/3, 2011 at 2:11 Comment(0)
W
1

I implemented Joe White's solution, but ran into problems with occasional "DialogResult can be set only after Window is created and shown as dialog" errors.

I was keeping the ViewModel around after the View was closed and occasionally I later opened a new View using the same VM. It appears that closing the new View before the old View had been garbage collected resulted in DialogResultChanged trying to set the DialogResult property on the closed window, thus provoking the error.

My solution was to change DialogResultChanged to check the window's IsLoaded property:

private static void DialogResultChanged(
    DependencyObject d,
    DependencyPropertyChangedEventArgs e)
{
    var window = d as Window;
    if (window != null && window.IsLoaded)
        window.DialogResult = e.NewValue as bool?;
}

After making this change any attachments to closed dialogs are ignored.

Walloon answered 15/7, 2012 at 19:56 Comment(0)
C
1

I ended up blending Joe White's answer and some code from Adam Mills's answer, since I needed to show a user control in a programmatically created window. So the DialogCloser need not be on the window, it can be on the user control itself

<UserControl ...
    xmlns:xw="clr-namespace:Wpf"
    xw:DialogCloser.DialogResult="{Binding DialogResult}">

And the DialogCloser will find the window of the user control if it was not attached to the window itself.

namespace Wpf
{
  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.GetWindow();
      if (window != null)
        window.DialogResult = e.NewValue as bool?;
    }

    public static void SetDialogResult(DependencyObject target, bool? value)
    {
      target.SetValue(DialogResultProperty, value);
    }
  }

  public static class Extensions
  {
    public static Window GetWindow(this DependencyObject sender_)
    {
      Window window = sender_ as Window;        
      return window ?? Window.GetWindow( sender_ );
    }
  }
}
Colwin answered 27/3, 2014 at 16:13 Comment(0)
L
1

Behavior is the most convenient way here.

  • From one hand, it can be binded to the given viewmodel (that can signal "close the form!")

  • From another hand, it has access to the form itself so can subscribe to necessary form-specific events, or show confirmation dialog, or anything else.

Writing necessary behavior can be seen boring very first time. However, from now on, you can reuse it on every single form you need by exact one-liner XAML snippet. And if necessary, you can extract it as a separate assembly so it can be included into any next project you want.

Liss answered 24/5, 2018 at 13:9 Comment(0)
L
0

Another solution is to create property with INotifyPropertyChanged in View Model like DialogResult, and then in Code Behind write this:

public class SomeWindow: ChildWindow
{
    private SomeViewModel _someViewModel;

    public SomeWindow()
    {
        InitializeComponent();

        this.Loaded += SomeWindow_Loaded;
        this.Closed += SomeWindow_Closed;
    }

    void SomeWindow_Loaded(object sender, RoutedEventArgs e)
    {
        _someViewModel = this.DataContext as SomeViewModel;
        _someViewModel.PropertyChanged += _someViewModel_PropertyChanged;
    }

    void SomeWindow_Closed(object sender, System.EventArgs e)
    {
        _someViewModel.PropertyChanged -= _someViewModel_PropertyChanged;
        this.Loaded -= SomeWindow_Loaded;
        this.Closed -= SomeWindow_Closed;
    }

    void _someViewModel_PropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        if (e.PropertyName == SomeViewModel.DialogResultPropertyName)
        {
            this.DialogResult = _someViewModel.DialogResult;
        }
    }
}

The most important fragment is _someViewModel_PropertyChanged. DialogResultPropertyName can be some public const string in SomeViewModel.

I use this kind of trick to make some changes in View Controls in case when this is hard to do in ViewModel. OnPropertyChanged in ViewModel you can do anything you want in View. ViewModel is still 'unit testable' and some small lines of code in code behind makes no difference.

Ledaledah answered 21/3, 2012 at 12:54 Comment(0)
A
0

I would go this way:

using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.Command;    
using GalaSoft.MvvmLight.Messaging; 

// View

public partial class TestCloseWindow : Window
{
    public TestCloseWindow() {
        InitializeComponent();
        Messenger.Default.Register<CloseWindowMsg>(this, (msg) => Close());
    }
}

// View Model

public class MainViewModel: ViewModelBase
{
    ICommand _closeChildWindowCommand;

    public ICommand CloseChildWindowCommand {
        get {
            return _closeChildWindowCommand?? (_closeChildWindowCommand = new RelayCommand(() => {
                Messenger.Default.Send(new CloseWindowMsg());
        }));
        }
    }
}

public class CloseWindowMsg
{
}
Asyndeton answered 8/8, 2014 at 21:34 Comment(0)
R
0

I've read all the answers but I must say, most of them are just not good enough or even worse.

You could handle this beatifully with DialogService class which responsibility is to show dialog window and return dialog result. I have create sample project demonstrating it's implementation and usage.

here are most important parts:

//we will call this interface in our viewmodels
public interface IDialogService
{
    bool? ShowDialog(object dialogViewModel, string caption);
}

//we need to display logindialog from mainwindow
public class MainWindowViewModel : ViewModelBase
{
    public string Message {get; set;}
    public void ShowLoginCommandExecute()
    {
        var loginViewModel = new LoginViewModel();
        var dialogResult = this.DialogService.ShowDialog(loginViewModel, "Please, log in");

        //after dialog is closed, do someting
        if (dialogResult == true && loginViewModel.IsLoginSuccessful)
        {
            this.Message = string.Format("Hello, {0}!", loginViewModel.Username);
        }
    }
}


public class DialogService : IDialogService
{
    public bool? ShowDialog(object dialogViewModel, string caption)
    {
        var contentView = ViewLocator.GetView(dialogViewModel);
        var dlg = new DialogWindow
        {
            Title = caption
        };
        dlg.PART_ContentControl.Content = contentView;

        return dlg.ShowDialog();
    }
}

Isn't this just simpler? more straitforward, more readable and last but not least easier to debug than EventAggregator or other similar solutions?

as you can see, In my view models I'm have used ViewModel first approach described in my post here: Best practice for calling View from ViewModel in WPF

Of course, in real world, the DialogService.ShowDialog must have more option to configure the dialog, e.g. buttons and commands they should execute. There are different way of doing so, but its out of scope :)

Radiant answered 14/3, 2015 at 18:12 Comment(0)
T
0

While this doesn't answer the question of how to do this via the viewmodel, this does show how to do it using only XAML + the blend SDK.

I chose to download and use two files from the Blend SDK, both of which you can as a package from Microsoft through NuGet. The files are:

System.Windows.Interactivity.dll and Microsoft.Expression.Interactions.dll

Microsoft.Expression.Interactions.dll gives you nice capabilities such as the ability to set property or invoke a method on your viewmodel or other target and has other widgets inside as well.

Some XAML:

<Window x:Class="Blah.Blah.MyWindow"
    ...
    xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
    xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
  ...>
 <StackPanel>
    <Button x:Name="OKButton" Content="OK">
       <i:Interaction.Triggers>
          <i:EventTrigger EventName="Click">
             <ei:ChangePropertyAction
                      TargetObject="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}"
                      PropertyName="DialogResult"
                      Value="True"
                      IsEnabled="{Binding SomeBoolOnTheVM}" />                                
          </i:EventTrigger>
    </Button>
    <Button x:Name="CancelButton" Content="Cancel">
       <i:Interaction.Triggers>
          <i:EventTrigger EventName="Click">
             <ei:ChangePropertyAction
                      TargetObject="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}"
                      PropertyName="DialogResult"
                      Value="False" />                                
          </i:EventTrigger>
    </Button>

    <Button x:Name="CloseButton" Content="Close">
       <i:Interaction.Triggers>
                <i:EventTrigger EventName="Click">
                    <!-- method being invoked should be void w/ no args -->
                    <ei:CallMethodAction
                        TargetObject="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}"
                        MethodName="Close" />
                </i:EventTrigger>
            </i:Interaction.Triggers>
    </Button>
 <StackPanel>
</Window>

Note that if you're just going for simple OK/Cancel behavior, you can get away w/ using the IsDefault and IsCancel properties as long as the window is shown w/ Window.ShowDialog().
I personally had problems w/ a button that had the IsDefault property set to true, but it was hidden when the page is loaded. It didn't seem to want to play nicely after it was shown, so I just am setting the Window.DialogResult property as shown above instead and it works for me.

Taxicab answered 2/4, 2015 at 0:5 Comment(0)
J
0

Here is the simple bug free solution (with source code), It is working for me.

  1. Derive your ViewModel from INotifyPropertyChanged

  2. Create a observable property CloseDialog in ViewModel

    public void Execute()
    {
        // Do your task here
    
        // if task successful, assign true to CloseDialog
        CloseDialog = true;
    }
    
    private bool _closeDialog;
    public bool CloseDialog
    {
        get { return _closeDialog; }
        set { _closeDialog = value; OnPropertyChanged(); }
    }
    
    public event PropertyChangedEventHandler PropertyChanged;
    
    private void OnPropertyChanged([CallerMemberName]string property = "")
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(property));
        }
    }
    

    }

  3. Attach a Handler in View for this property change

        _loginDialogViewModel = new LoginDialogViewModel();
        loginPanel.DataContext = _loginDialogViewModel;
        _loginDialogViewModel.PropertyChanged += OnPropertyChanged;
    
  4. Now you are almost done. In the event handler make DialogResult = true

    protected void OnPropertyChanged(object sender, PropertyChangedEventArgs args)
    {
        if (args.PropertyName == "CloseDialog")
        {
            DialogResult = true;
        }
    }
    
Jellify answered 30/5, 2015 at 12:50 Comment(0)
P
0

Create a Dependency Property in your View/any UserControl(or Window you want to close). Like below:

 public bool CloseTrigger
        {
            get { return (bool)GetValue(CloseTriggerProperty); }
            set { SetValue(CloseTriggerProperty, value); }
        }

        public static readonly DependencyProperty CloseTriggerProperty =
            DependencyProperty.Register("CloseTrigger", typeof(bool), typeof(ControlEventBase), new PropertyMetadata(new PropertyChangedCallback(OnCloseTriggerChanged)));

        private static void OnCloseTriggerChanged(DependencyObject dp, DependencyPropertyChangedEventArgs e)
        {
            //write Window Exit Code
        }

And bind it from your ViewModel's property:

<Window x:Class="WpfStackOverflowTempProject.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow"  Width="525"
        CloseTrigger="{Binding Path=CloseWindow,Mode=TwoWay}"

Property In VeiwModel:

private bool closeWindow;

    public bool CloseWindow
    {
        get { return closeWindow; }
        set 
        { 
            closeWindow = value;
            RaiseChane("CloseWindow");
        }
    }

Now trigger the close operation by changing the CloseWindow value in ViewModel. :)

Photophore answered 8/2, 2016 at 10:39 Comment(0)
A
-4

Where you need to close the window, simply put this in the viewmodel:

ta-da

  foreach (Window window in Application.Current.Windows)
        {
            if (window.DataContext == this)
            {
                window.Close();
                return;
            }
        }
Arand answered 31/1, 2014 at 8:2 Comment(3)
A ViewModel mustn't contain an UIElement in any way, because this can create bug'sMukul
What if DataContext is being inherited be several windows ?Photophore
ta-da, this is totally not MVVM.Walley
C
-10
Application.Current.MainWindow.Close() 

Thats enough!

Cinderella answered 21/7, 2011 at 9:52 Comment(1)
-1 Only true if the window that you want to close is the main window... Very unlikely assumption for Login dialog...Handsome

© 2022 - 2024 — McMap. All rights reserved.