Problem
I’m attempting to learn WPF and how to solve the MVVM problem, but I’m having trouble. This question is similar but not identical to the previous one (handling-dialogs-in-wpf-with-mvvm)…
I’ve created a “Login” form that follows the MVVM design.
This form has a ViewModel that contains the Username and Password, which are coupled to the view using regular data bindings in the XAML. It also provides a “Login” command that is tied to the form’s “Login” button using standard databinding.
When the “Login” command is executed, a function in the ViewModel is called, and data is sent over the network in order to log in. There are two things that happen after this function is finished:
The issue is that the ViewModel has no knowledge of the real view, so how can it close it and tell it to return a specific DialogResult? I could put some code in the CodeBehind and/or pass the ViewModel through to the ViewModel, but it seems to negate the entire purpose of MVVM…
Finally, I broke the MVVM pattern’s “purity” by having the View publish a Closed event and exposing a Close function. After that, the ViewModel would just call view. Close. No testability or maintainability is lost because the view is only known through an interface and is wired up through an IOC container.
The fact that the acceptable response has a score of -5 votes appears to be very absurd! While I understand the satisfaction that comes from addressing an issue in a “pure” manner, I’m sure I’m not the only one who believes that writing 200 lines of events, commands, and actions only to avoid a one-line method in the name of “patterns” and “purity” is a bit excessive….
Asked by Orion Edwards
Solution #1
Thejuan’s response prompted me to create a simpler linked property. You won’t need any styles or triggers if you just do this:
<Window ...
xmlns:xc="clr-namespace:ExCastle.Wpf"
xc:DialogCloser.DialogResult="{Binding DialogResult}">
This is nearly as clean as if the WPF guys had gotten it right the first time and made DialogResult a dependent property. Simply add a bool? DialogResult property to your ViewModel and implement INotifyPropertyChanged, and your ViewModel may shut the Window (and set its DialogResult) with a single property change. As it should be, MVVM.
The DialogCloser code is as follows:
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);
}
}
}
This is also something I’ve written about on my blog.
Answered by Joe White
Solution #2
The question, in my opinion, is a valid one because the same strategy might be applied not only for the “Login” window, but for every window. I’ve looked over a lot of ideas and none of them are suitable for me. Please review my suggestion that was taken from the MVVM design pattern article.
WorkspaceViewModel, which has the RequestClose event and the CloseCommand property of the ICommand type, should be inherited by all ViewModel classes. The RequestClose event is raised by the default implementation of the CloseCommand property.
The OnLoaded method of your window should be overridden in order to close the window:
void CustomerWindow_Loaded(object sender, RoutedEventArgs e)
{
CustomerViewModel customer = CustomerViewModel.GetYourCustomer();
DataContext = customer;
customer.RequestClose += () => { Close(); };
}
or your app’s OnStartup method:
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();
}
The RequestClose event and CloseCommand property implementation in the WorkspaceViewModel are probably self-evident, but I’ll demonstrate that they’re 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 here’s the RelayCommand’s source code:
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. Please don’t make fun of me because of those sources! I could have saved a few hours if I had them yesterday…
P.P.S. I welcome any feedback or recommendations.
Answered by Budda
Solution #3
There are a lot of comments here about the benefits and drawbacks of MVVM. I agree with Nir; it’s an issue of applying the pattern correctly, and MVVM isn’t always applicable. People seems to have become willing to sacrifice all of the most important principles of software design JUST to get it to fit MVVM.
With a little reworking, I believe your situation may be an excellent fit.
WPF allows you to get by without using multiple windows in the vast majority of scenarios I’ve encountered. Instead of utilizing Windows, you may try using Frames and Pages with DialogResults.
In your instance, I recommend having LoginFormViewModel handle the LoginCommand and setting a property on LoginFormViewModel to an appropriate value if the login is invalid (false or some enum value like UserAuthenticationStates.FailedAuthentication). For a successful login, you’d do the same thing (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.
I believe you’re getting confused because your login window returns a DialogResult; the DialogResult is actually a ViewModel property. When something doesn’t feel right in my admittedly limited experience with WPF, it’s usually because I’m thinking about how I would have done it in WinForms.
Hope that helps.
Answered by EightyOne Unite
Solution #4
Try this inside your LoginViewModel class, assuming your login dialog is the first window that is created:
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;
}
}
Answered by Jim Wallace
Solution #5
This is a straightforward solution: you add an event to the ViewModel and direct the Window to dismiss when the event occurs.
See my blog post, Close window from ViewModel, for additional information.
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 that while the example utilizes Prism’s DelegateCommand (see Prism: Commanding), any ICommand implementation might be used.
This official package contains behaviors that you can utilize.
Answered by Shimmy Weitzhandler
Post is based on https://stackoverflow.com/questions/501886/how-should-the-viewmodel-close-the-form