Correct approach to UserControl creation when using MVVM - c#

This is more of a conceptual question rather than a practical one. I'm just starting to learn the MVVM concept for developing UI , and I've come across a dillema I'm not sure the answer to:
Say I have a main window and a little pop-up window (meaning it's a small window with some UI elements in it). The structure of the program will look something like this:
MainWindow
model <-- MainWindowViewModel.cs <-- MainWindowView.xaml (containing no code-behind)
PopUpWindow (A UserControl)
model <-- PopUpWindowViewModel.cs <-- PopUpWindowView.xaml (containing no code-behind)
*the model is just a bunch of BL classes that are irrelevant for this question.
Now , lets say I want to create a new PopUp window from inside the MainWindowViewModel (or even save an instance of it in a private data-member). What is the correct way of doing so?
The way I see it I can't do something like this :
PopUpWindow pop = new PopUpWindow()
Because it kind of defeats the purpose of abstracting the view from the view model(What if a year from now i'll want to create a better version of the PopUpWindow using the same PopUpWindowViewModel?).
On the other hand , I can't initialize a new instnace of the PopUpWindow using just it's view model (The viewModel as I understand is not supposed to know anything about the view that will use it).
Hope it all makes sense... so what would you do in that situation?
*Just to clarify it further , let's say for argument's sake that the situation I'm describing is a button on the MainWindowView that upon clicking will open a PopUpWindowView.
Thanks in advnace.

I had somewhat a similar dilemma and I'll explain how I solved it.
Let's say you have MainWindow and a SettingsWindow, which you want to display when the SettingsButton is clicked.
You have two respective view models, MainWindowViewModel and SettingsViewModel, which you will be passing as their Window.DataContext properties.
Your MainWindowViewModel should expose an ICommand property named SettingsButtonCommand (or similar). Bind this command to the SettingsButton.Command.
Now your command should invoke something like this:
void OnSettingsButtonClicked()
{
var viewModel = new SettingsViewModel();
var window = new SettingsWindow();
window.DataContext = viewModel;
window.Show();
}
There is a slight issue when you want to use Window.ShowDialog(), because you need to resume execution.
For these cases I have an asynchronous variant of the DelegateCommand:
public sealed class AsyncDelegateCommand : ICommand
{
readonly Func<object, Task> onExecute;
readonly Predicate<object> onCanExecute;
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public AsyncDelegateCommand(Func<object, Task> onExecute)
: this(onExecute, null) { }
public AsyncDelegateCommand(Func<object, Task> onExecute, Predicate<object> onCanExecute)
{
if (onExecute == null)
throw new ArgumentNullException("onExecute");
this.onExecute = onExecute;
this.onCanExecute = onCanExecute;
}
#region ICommand Methods
public async void Execute(object parameter)
{
await onExecute(parameter);
}
public bool CanExecute(object parameter)
{
return onCanExecute != null ? onCanExecute(parameter) : true;
}
#endregion
}

You've specifically said that the popup is a UserControl so you can use basic data templating. First create view models for your main window and popup control:
public class MainViewModel : ViewModelBase
{
private PopUpViewModel _PopUp;
public PopUpViewModel PopUp
{
get { return _PopUp; }
set { _PopUp = value; RaisePropertyChanged(() => this.PopUp); }
}
}
public class PopUpViewModel : ViewModelBase
{
private string _Message;
public string Message
{
get { return _Message; }
set { _Message = value; RaisePropertyChanged(() => this.Message); }
}
}
The MainViewModel's PopUp member is initially null, we'll set it to an instance of PopUpViewModel when we want the popup to appear. To do that we create a content control on the main window and set it's content to that member. We also use a data template to specify the type of child control to create when the popup view model has been set:
<Window.Resources>
<DataTemplate DataType="{x:Type local:PopUpViewModel}">
<local:PopUpWindow />
</DataTemplate>
</Window.Resources>
<StackPanel>
<Button Content="Show PopUp" Click="Button_Click_1" HorizontalAlignment="Left"/>
<ContentControl Content="{Binding PopUp}" />
</StackPanel>
I'm doing a big no-no here by creating the view model in the code-behind along with a click handler, but it's just for illustrative purposes:
public partial class MainWindow : Window
{
MainViewModel VM = new MainViewModel();
public MainWindow()
{
InitializeComponent();
this.DataContext = this.VM;
}
private void Button_Click_1(object sender, RoutedEventArgs e)
{
this.VM.PopUp = new PopUpViewModel { Message = "Hello World!" };
}
}
That's it! Click the button, popup window appears underneath it showing the content. Now it may not always be this simple, sometimes you may want to create multiple children on a parent control...in that case you'd set up an ItemsControl, set its panel to a Grid (say) and modify the data templates to set the Margin etc on each element to position them. Or you may not always know what type of view model is going to be created, in which case you need to add multiple data templates for each type you're expecting. Either way you still have good separation of concerns because it is the views that are deciding how to display the content in the view models. The view models themselves still don't know anything about the views and they can be unit-tested etc independently.

Related

c# WPF MVVM TabControl with Multiple ViewModels and changing tabs

So I currently have a Window with a TabControl. The MainWindow has its own ViewModel and all the TabItems have their own ViewModels also.
I can easily change tabs from the MainWindow ViewModel through a bound SelectedIndex property. What I would like to do is change to another tab from code that runs within ANOTHER tab viewmodel. Since the Tabs are not part of the MainWindowViewModel, I am looking for a clean way to change the Tab without resorting to code behind to do it.
There are also cases, where I might need to change the tab from something such as a message prompt. I thinking my only way is to create and event and subscribe to that from MainWindowViewModel.
So I solved this with an EventAggregator.
public static class IntAggregator
{
public static void Transmit(int data)
{
if (OnDataTransmitted != null)
{
OnDataTransmitted(data);
}
}
public static Action<int> OnDataTransmitted;
}
First ViewModel sends data.
public class ModifyUsersViewModel : INotifyPropertyChanged
{
private void change_tab(int data)
{
IntAggregator.Transmit(data);
}
}
Second ViewModel receives data and then does something with it.
public class MainWindowViewModel : INotifyPropertyChanged
{
private int _Tab_SelectedIndex = 0;
public int Tab_SelectedIndex
{
get
{
return _Tab_SelectedIndex;
}
set
{
_Tab_SelectedIndex = value;
OnPropertyChanged(new PropertyChangedEventArgs("Tab_SelectedIndex"));
}
}
public MainWindowViewModel()
{
IntAggregator.OnDataTransmitted += OnDataReceived;
}
private void OnDataReceived(int data)
{
Tab_SelectedIndex = data;
}
}
Rather than trying to bind to SelectedIndex, if the TabItems have their own view models, then you can create a property for each of those view models: IsSelected and then bind the TabItem.IsSelected property to that:
<TabItem IsSelected="{Binding IsSelected}">
This prevents the view models from needing to know the index of their corresponding TabItem, something I would argue is a detail that should be specific to the view and something the view model should not concern itself with. What if you add another TabItem or want to change the order? Now you've got changes to make in the view models for something that could be just simple change to the view.

How to change UserControl in background?

I am stuck with a problem where I want to change user control on different events in background. I am new in MVVM but I am bound to use MVVM only to achive this task. Code structure is little complex to me but still I figured that New Employee form is getting shown on button click but in new window but I want that form to be opened in current window's content. Code is given here which I need to modify to open usercontrol.
public Task<bool?> InitModification(CoreViewModel vm)
{
var tcs = new TaskCompletionSource<bool?>();
_dispatcherService.CurrentDispatcher.BeginInvoke(new Action(() =>
{
bool? result = null;
Window activeWindow = null;
for (var i = 0; i < Application.Current.Windows.Count; i++)
{
var win = Application.Current.Windows[i];
if ((win != null) && (win.IsActive))
{
activeWindow = win;
break;
}
}
if (activeWindow != null)
{
var win = new NewEmp(vm) { Owner = activeWindow };
result = win.ShowDialog();
}
tcs.SetResult(result);
}));
return tcs.Task;
}
My answer will not use your code since I don't find it useful to what you want to achieve. My answer will suffice with a suggestion on how you can achieve changing content control with MVVM
The way I go about it is with every MVVM-project I have a "application-shell" that acts as a wrapper for all my other content and through that you can easily change content. This application is a View and a ViewModel like such.
ShellView
<xmlns:ViewModel="clr-namespace:WhereOurViewModelIs.ViewModel">
<!--More XAML code-->
<Window.Resources>
<DataTemplate DataType="{x:Type ViewModel:MyViewModel}">
<Views:MyView/>
</DataTemplate>
<DataTemplate DataType="{x:Type ViewModel:AnotherViewModel}">
<Views:AnotherView/>
</DataTemplate>
</Window.Resources>
<ContentControl Content="{Binding CurrentPage}"/>
ShellViewModel
public class ShellViewModel
{
private BaseViewModel _currentPage{get;set;}
Public BaseViewModel CurrentPage{
get{return _currentPage;}
set{_currentPage = value; OnPropertyChanged();}
}
public ShellViewModel
{
CurrentPage = new MyViewModel();
}
}
Since we don't know how many different pages there is (in theory) we will tell them that need to be a of a type (inherited or an object) of BaseViewModel. This way we don't need to check for every single page and remove redundant code.
Then you set the Datacontext = new ShellViewModel(); in behind code of ShellView.xaml
BaseViewModel
public class BaseViewModel
{
/*This class can contains whatever you want your other ViewModels to be able to do*/
}
Now you need to set up 2 ViewModels with 2 Views just like we did with our shell.
MyViewModel
public class MyViewModel:BaseViewModel
{
/*Contains Properties,methods,private fields. What you want to show on view*/
}
AnotherViewModel
public class AnotherViewModel:BaseViewModel
{
/*Contains Properties,methods,private fields. What you want to show on view*/
}
Now you can set event to your ShellViewModel to change content whenever something happens. Hopefully this can atleast give you some idea how to work with MVVM. Of course you need to set up our ViewModels with properties changed event and other to get everything working, but this is a start for you.
If you find this answer helpful please chose it as an answer since it take some time to make an example like this.

View does not update with changing viewModel

I have a userControl named SensorControl which I want to bind its dataContext to a viewModel class named SensorModel.
The binding is successful and the initial values of viewModel is visible in the userControl, but the problem is that when I change the viewModel's properties, the userControl does not update, and I have to manually update that (by assigning null to its dataContext and assigning the viewModel again).
Here is a simplified example of my code.
SensorControl.xml:
<UserControl ...[other attributes]... DataContext={Binding Model}>
.
.
.
<Label Content="{Binding Title}"/>
.
.
.
</UserControl>
SensorControl.xml.cs (code-behind):
public partial class SensorControl : UserControl
{
public SensorModel model;
public SensorModel Model
{
get { return model; }
set { model = value; }
}
public SensorControl(SensorModel sm)
{
Model = sm;
}
}
MainWindow.xml.cs:
public partial class MainWindow : Window
{
public SensorModel sensorModel_1;
public SensorControl sensorControl_1;
public MainWindow()
{
InitializeComponent();
sensorModel_1 = new SensorModel(...[some arguments]...);
sensorControl_1 = new SensorControl(sensorModel_1);
mainGrid.Children.Add(sensorControl_1);
}
private void Button_Click(object sender, RoutedEventArgs e)
{
sensorModel_1.Title = "myTitle";
//The UserControl does not update
}
0) I implemented INotifyPropertyChanged in SensorModel
1) The reason I need this, is that there is only one single concept of 'Sensor' in my project (it is a real electronic sensor) and therefore I have a single model for it (that deal with the real sensor, the database, etc), but in the UI I have multiple userControls for presenting different aspects of Sensor. So I have to create one instance of model (SensorModel) for each real sensor, and multiple userControls must bind to that (each one uses different parts of model).
2) I'm not that new to WPF, but I'm kind of new to MVVM and it's possible that I misunderstand something essential, so I would appreciate if someone could clearly explain the correct approach.
Thanks in advance
In your UserControl, remove the DataContext attribute and add an x:Name attribute. Then in your Label, bind like this:
<UserControl x:Name="uc">
<Label Content="{Binding ElementName=uc,Path=Model.Title}" />
</UserControl>
I believe the issue is the DataContext can't be set to Model because binding works off the parent's DataContext which will be based on mainGrid when it gets added as a child to that. Since the property "Model" doesn't exist in maiGrid's DataContext no binding will occur so your update won't reflect. Getting the DataContext of a UserControl properly can be tricky. I use the ElementName quite a bit or create DependencyProperties on the UserControl and then set them from the parent who will be using the control.
You need to set the DataContext to your ViewModel class in your View, and if you're applying the MVVM pattern, you should use ICommand for actions. Maybe it would be better If you'd implement a MainView class that does the logic in the background instead in the MainWindow class.
public MainWindow()
{
InitializeComponent();
DataContext = new MainView();
// sensorModel_1 = new SensorModel(...[some arguments]...);
// sensorControl_1 = new SensorControl(sensorModel_1);
// mainGrid.Children.Add(sensorControl_1);
}
Your MainView class :
public class MainView {
public SensorControl Control {get; internal set;}
...
}
And in your xaml change the binding :
<Label Content="{Binding Control.Model.Title}"/>
Thanks to all of you guys, I took your advices and finally I found a way.
1) I implemented an event in the SensorModel that fires every time any of properties changes (name ModelChanged)
2) Then as Merve & manOvision both suggested, I declared a dependency property in the SensorControl (of type SensorModel) and bind ui elements to that (Binding Path=Model.Title)
3) Then I used the ModelChanged event of this dependency property in the SensorControl and raise an event (of type INotifyPropertyChanged) so the bindings update their value
It works fine.

Property in ViewModel not displayed in View

I have a ViewModel called MainWindowViewModel. In this I have a property that shows a modal window when an error occurs. This works fine as long as an error occurs during start-up.
When an error occurs after start-up, in SubViewModel, I invoke the parametrized constructor in MainWindowViewModel.
MainWindowViewModel.cs
public MainWindowViewModel()
{
if (!isServerRunning)
{
this.ModalWindow = new LogViewModel("Server is down.");
}
else
{
this.ModalWindow = new LogViewModel();
}
}
public MainWindowViewModel(string logMessage)
{
this.ModalWindow = new LogViewModel(logMessage);
}
public LogViewModel ModalWindow
{
get
{
return _modalWindow;
}
set
{
_modalWindow = value;
OnPropertyChanged("ModalWindow");
}
}
MainWindow.xaml
....
<Grid>
<vw:LogView Content="{Binding Path=ModalWindow}"/>
</Grid>
MainWindowViewModel is bound to MainWindow.xaml
SubViewModel is bound to SubView.xaml
MainWindow.xaml loads multiple views, one of them is SubView.
In App.xaml I have created an instance of the ViewModel and bound it to MainWindow.
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
MainWindow mainWindow = new MainWindow();
MainWindowViewModel viewModel = new MainWindowViewModel();
mainWindow.DataContext = viewModel;
mainWindow.Show();
}
What I realized was that the modal window shows up when an error occurs after start-up if I create the modal window property in SubViewModel and bind it to SubView. But this is not ok since SubView is only a DockPanel covering 1/4 of the MainWindow. I.e. 1/4 is only covered with a modal window instead of the whole MainWindow.
I am not sure why modal window does not appear in MainWindow when I call the parametrized constructor from SubViewModel. When I debug I see that the part _modalWindow = value; has correct values but in any case the modal window does not show up.
I am binding the ViewModel to the Datacontext of the MainWindow. That's is probably why I see the Modal window when error occurs on start-up. For errors after start-up: Must I (from SubViewModel where I invoke the parametrized constructor in MainWindowViewModel) do some kind of binding to the datacontext of the Mainwindow again? How is the best way of doing this without having to create a new instance of MainWindow? Because MainWindow should only be created once.
Any help is much appreciated.
It looks like you are recreateing the ViewModel every time an error occours. If so you would need to reset the binding in the view as well, which would defeat the purpose of MVVM.
Rather have one instance of the ViewModel and propagate the errors to the View using OnPropertyChanged().
There are of course many ways of doing this, but I usually keep a reference to the ViewModel in my View, and then a reference to the Model in the ViewModel. This way the Model is completely decoupled from the View/ViewModel and likewise the ViewModel is decoupled from the View.
You should not create MainWindowViewModel again and again. It should be created once and set to the datacontext of the mainwindow.
The problem is with the approach that you are taking to show dialog boxes. This is making things complicated.
The best solution to show dialog boxes is to use mvvmlight toolkit's messenger. Check this for some hints on it's usage.
This is how you could use mvvmlight toolkit's messenger to show dialogboxes:
View:
Messenger.Default.Register<DialogMessage>(
view,
msg =>
{
var result = MessageBox.Show(
msg.Content,
msg.Caption,
msg.Button,
msg.Icon);
}
);
ViewModel:
private void ShowMessageBox(string msgStr, string capStr, MessageBoxButton btn, MessageBoxImage img)
{
var message = new DialogMessage(msgStr, null)
{
Button = btn,
Caption = capStr,
Icon = img
};
Messenger.Default.Send(message);
}
Just call the above method (ShowMessageBox) from any viewmodel to show a dialogbox.

How should the ViewModel close the form?

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:
The login was invalid - we just show a MessageBox and all is fine
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....
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.
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.
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.
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;
}
}
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.
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.
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.
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, lunch 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.
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();
}
}
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! :)
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.
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.
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}" />
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
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.
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_ );
}
}
}
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.
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.
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
{
}
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 :)
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.
Here is the simple bug free solution (with source code), It is working for me.
Derive your ViewModel from INotifyPropertyChanged
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));
}
}
}
Attach a Handler in View for this property change
_loginDialogViewModel = new LoginDialogViewModel();
loginPanel.DataContext = _loginDialogViewModel;
_loginDialogViewModel.PropertyChanged += OnPropertyChanged;
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;
}
}
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. :)
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;
}
}
Application.Current.MainWindow.Close()
Thats enough!

Categories

Resources