I have been doing tons of research using MVVM (Model View ViewModel) with WPF. I am developing a desktop application. This application consists of a main window. This main window has some buttons which do something. Also, there is a button that opens a OpenFileDialog box.
Currently, this is my ViewModel to which the main window binds to:
MainWindowPresenter Class
namespace BMSVM_Simulator.ViewModel
{
class MainWindowPresenter : ObservableObject
{
private bool logLoaded; // true if a log is currently loaded, false otherwise
public MainWindowPresenter()
{
logLoaded = true;
}
public ICommand load_data_button_pressed
{
get { return new DelegateCommand(doLoadData); }
}
private void doLoadData()
{
// DO LOAD DATA COMMANDS
}
public ICommand exit_button_pressed
{
get { return new DelegateCommand(doExit); }
}
private void doExit()
{
// DO EXIT COMMANDS
}
}
}
QUESTION 1: I am concerned that this is the "wrong" implementation. Is it correct (per MVVM) for each button to have a property of type ICommand and then a corresponding method implementing the functionality? A main window with a lot of buttons would have a very large ViewModel class, no?
QUESTION 2: If one of the buttons was a File->Open File button. So, in that case it would open up a new OpenFileDialog window. Would this be done in the same way I previously done it above (i.e. have a public ICommand open_file_dialog_button_pressed property and a corresponding public void doOpenFileDialog() method? This seems like I am mixing the "view" of the open file dialog into the ViewModel, although the view is already defined by the built in wpf OpenFileDialog class.
QUESTION 3: Is it true that each "view" of our application should have only a single "presenter" class (which is part of the ViewModel) to which that view binds to? In the example above, my main window view binds to only the MainWindowPresenter class. If I were to make another view (say a graph generated with Microsoft's Dynamic Data Display library in it's own popout window), I would need an additional "presenter" class in my ViewModel, correct?
Thank you very much!
Rich
For reference, I've included these classes, but they may not be useful:
DelegateCommand Class
namespace BMSVM_Simulator.ViewModel
{
public class DelegateCommand : ICommand
{
private readonly Action _action;
public DelegateCommand(Action action)
{
_action = action;
}
public void Execute(object parameter)
{
_action();
}
public bool CanExecute(object parameter)
{
return true;
}
#pragma warning disable 67
public event EventHandler CanExecuteChanged;
#pragma warning restore 67
}
}
ObservableObject Class
namespace BMSVM_Simulator.ViewModel
{
class ObservableObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
//basic ViewModelBase
internal void RaisePropertyChanged(string prop)
{
if (PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs(prop)); }
}
}
}
1) Yes that's correct. You need to create a command property for each command. But thanks to your relay command you don't need to implement it directly. To prevent your ViewModel from busting i would recommend to move all commands into a separate CommandsViewModel serving as command source. Your View then binds to it.
2) Opening the Dialog can be achieved in XAML via routed commands using the CommandBinding property. So the tasks remains in the view. You basically try to avoid the dependency on any view related object. .NET provides some ready to use commands for common purposes (MSDN - ApplicationCommands)
3) You can share ViewModels among Views of course. That's one reason you structure your implementation into Model View ViewModel to be independent from changes and for reusability. It can become critical when more than one view is updating the same source simultaneously.
Related
I have simple 2 buttons. Should I create 1 ICommand or is it ok now?
internal class MainPageViewModel : INotifyPropertyChanged
{
public ICommand RegisterBtnClickedCommand {get; }
public ICommand LoginBtnClickedCommand {get; }
public MainPageViewModel()
{
RegisterBtnClickedCommand = new Command(RegisterButtonPressed);
LoginBtnClickedCommand = new Command(LoginButtonPressed);
}
private void LoginButtonPressed()
{
Application.Current.MainPage.DisplayAlert("Login", "Login", "ok");
}
private void RegisterButtonPressed()
{
Application.Current.MainPage.DisplayAlert("Reg", "Reg", "ok");
}
public event PropertyChangedEventHandler PropertyChanged;
}
And for example, if I had 10 buttons, then I should have 10 commands?
And for example, if I had 10 buttons, then I should have 10 commands?
Yes.
Note that a single command might be invoked in different ways (button, menu entry, keyboard shortcut, unit test code), and that each command might have a different CanExecute logic. Thus, commands provide a convenient separation of concerns between the command itself (= your ICommand) and the way it is invoked (bindings in XAML).
If you are worried about too much boilerplate code, you can shorten your code slightly by initializing the property right at the point of declaration (rather than inside the constructor):
public ICommand RegisterBtnClickedCommand { get; } = new Command(RegisterButtonPressed);
...
Keep two separate commands for this. "Login" and "Register" are distinct enough actions. You can reuse commands for something like deleting an item from a list and pass in parameters to identify the item, but this is not the case here.
Though I wouldn't include Btn, Button, and Pressed in their names as they should be separated from any UI ideas (imagine a link being used later on instead of a button). I'd go with LoginCommand and LoginExecute for example.
I have a Window, which contains a Button AddParameter.
This Button has an Event called Button_Click.
Staying within the MVVM pattern, is it allowed to open a new window with a simple Button_Click? As far as I understood it, the code-behind of the View still counts as View:
private void Button_Click(object sender, RoutedEventArgs e) {
AddParameterWindow addParamWindow = new AddParameterWindow();
addParamWindow.Show();
}
Doing that with ICommands seems rather unnecessary, so I wanted to know if this would still count as a clean MVVM solution.
I don't think there is anything at all wrong with opening a window from another window in MVVM. The MVVM pattern is about separation of concerns in terms of ViewModels (and underlying models) being represented in any way necessary without it knowing anything about the View (see here for a good intro).
However, I think you have to ask yourself if making a new Window is really a good feature. Have you seen applications spawn another Window, and do you like that behavior? Have you given popups a thought which can look like Windows and can bind to the same ViewModel as the Window or UserControl it is logically under? Personally I avoid instantiating new Windows because I can centralize things that I want to appear in every View, like Styles, timeout Timers, etc.
You can ofcourse use the event Button_Click to open a new window, but that is now out of MVVM.
This maybe not right or good practice with MVVM, but this is how I do it:
assuming you have a ViewModelBase.cs that is something like this:
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
I have a DelegateCommand.cs that extends ICommand:
public class DelegateCommand : ICommand
{
private readonly Action _action;
public DelegateCommand(Action action)
{
_action = action;
}
public void Execute(object parameter)
{
_action();
}
public bool CanExecute(object parameter)
{
return true;
}
#pragma warning disable 67
public event EventHandler CanExecuteChanged { add { } remove { } }
#pragma warning restore 67
}
Now in your SampleViewModel.cs:
public class SampleViewModel : ViewModelBase
{
public SampleViewModel()
{
}
public ICommand OpenWindowCommand
{
get { return new DelegateCommand(OpenSampleWindow); }
}
private void OpenSampleWindow()
{
var sampleWindow = new SampleWindow();
sampleWindow.Show();
}
}
Now in your View you can now bind your command to your button:
<Button Command="{Binding OpenWindowCommand}"/>
I have a static WindowService class which helps me to create new windows and modal dialogs.
So far, what I have is this:
/// <summary>
/// Opens a new window of type <paramref name="newWindowType"/> and closes the <paramref name="oldWindow"/>
/// </summary>
/// <param name="oldWindow">The window which should be closed (Usually the current open window)</param>
/// <param name="newWindowType">The type of the new window to open</param>
public static void ShowNewWindow(Window oldWindow, Type newWindowType)
{
((Window)Activator.CreateInstance(newWindowType)).Show();
oldWindow.Close();
}
My viewmodel raises an event and the view is subscribed to it. In the event handler in the view, it calls WindowService.ShowNewWindow(this,The type here). This works fine.
My modal dialog creating method will also work in a similar way. The only difference is that the information will be returned to the view (At the event handler) so the view will have to pass that information to the view model in code explicitly. This violates mvvm pattern and I don't know how to make the viewmodel wait for the view to return the value after the event is raised.
Is there a better way of doing this?
Ah, this ol' chestnut.
There are many different variations on how to achieve this, however here's my two cents.
The main ideas here are to ensure that your View and View Model do not know about each other, therefore your View should not subscribe to an event in your View Model, and your View Model should not directly call your service and provide a view Type.
Don't use events, use Commands instead
My recommendation would be to use ICommand implementations instead of relying on a static service class, for the reason that your class will always have a dependency to this service, and also as soon as you send the view Type to this service, then the MVVM pattern is lost.
So, firstly, we need some kind of command which will open a window of a given Type, here's what I have come up with:
public class OpenWindowCommand : ICommand
{
public bool CanExecute(object parameter)
{
TypeInfo p = (TypeInfo)parameter;
return p.BaseType == typeof(Window);
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public void Execute(object parameter)
{
if (parameter == null)
throw new ArgumentNullException("TargetWindowType");
//Get the type.
TypeInfo p = (TypeInfo)parameter;
Type t = p.BaseType;
if (p.BaseType != typeof(Window))
throw new InvalidOperationException("parameter is not a Window type");
//Create the window.
Window wnd = Activator.CreateInstance(t) as Window;
OpenWindow(wnd);
}
protected virtual void OpenWindow(Window wnd)
{
wnd.Show();
}
}
The class inherits from ICommand and specifies the implementation which accepts a Type, which represents the desired View that we want to open. Notice I have marked a method as virtual, I'll explain that part in a moment.
Here's how we can make use of this command in our View Model:
public class MainWindowViewModel
{
public OpenWindowCommand OpenWindowCommand { get; private set; }
public MainWindowViewModel()
{
OpenWindowCommand = new OpenWindowCommand();
}
...
}
Now we've created the command, we simply need to bind a Button to it:
<Button Content="Open Window"
Command="{Binding OpenWindowCommand}"
CommandParameter="{x:Type local:MyWindow}"/>
One thing to note here is that I am using x:Type as the CommandParameter, this is the Window that will be created when this command gets executed.
But what about a dialog?
What we achieved above is only half of the requirement, we now need something that will display a dialog and output the result to our View Model, this isn't so tricky as we have most of what we need already in our existing OpenWindowCommand.
First, we need to create the command:
public class ShowDialogCommand : OpenWindowCommand
{
private Action _PreOpenDialogAction;
private Action<bool?> _PostOpenDialogAction;
public ShowDialogCommand(Action<bool?> postDialogAction)
{
if (postDialogAction == null)
throw new ArgumentNullException("postDialogAction");
_PostOpenDialogAction = postDialogAction;
}
public ShowDialogCommand(Action<bool?> postDialogAction, Action preDialogAction)
: this(postDialogAction)
{
if (preDialogAction == null)
throw new ArgumentNullException("preDialogAction");
_PreOpenDialogAction = preDialogAction;
}
protected override void OpenWindow(System.Windows.Window wnd)
{
//If there is a pre dialog action then invoke that.
if (_PreOpenDialogAction != null)
_PreOpenDialogAction();
//Show the dialog
bool? result = wnd.ShowDialog();
//Invoke the post open dialog action.
_PostOpenDialogAction(result);
}
}
We're making use of our OpenWindowCommand by inheriting from it and using it's implementation instead of having to copy all of it into our new class. The command takes an Action which is a reference to a method in your View Model, you have the option of defining an action before or after (or both) a dialog is displayed.
The next step is to change our View Model so it creates this new command:
public class MainWindowViewModel
{
public OpenWindowCommand OpenWindowCommand { get; private set; }
public ShowDialogCommand ShowDialogCommand { get; private set; }
public MainWindowViewModel()
{
OpenWindowCommand = new OpenWindowCommand();
ShowDialogCommand = new ShowDialogCommand(PostOpenDialog);
}
public void PreOpenDialog()
{
throw new NotImplementedException();
}
public void PostOpenDialog(bool? dialogResult)
{
throw new NotImplementedException();
}
}
The usage of this command is practically the same as before, but it just references a different command:
<Button Content="Open Window"
Command="{Binding ShowDialogCommand}"
CommandParameter="{x:Type local:MyWindow}"/>
And there you have it, everything is loosely coupled, the only real dependencies here are that your View Model depends on your ICommand classes.
Some final words
The ICommand classes that I have created act as a controller between the View and the View Model to ensure that they do not know about each other, and keeps the MVVM pattern enforced.
Like I said at the beginning of this answer, there are many ways of which this can be achieved, however I hope you are now a little more enlightened.
I have a closecommand defined inside my viewmodel for my dialog window. I have another command defined inside that viewmodel. Now I have that command binded to a control in my view. After performing certain command actions, I want it to call closecommand to close the window. Is that possible?
Yes. You can use a CompositeCommand that wraps both (or any number) of your other commands. I believe this is in Prism, but if you don't have access to that in your project, it isn't terribly difficult to implement similar functionality on your own, especially if you're not using parameters - all you do is implement ICommand with a class and then have a private List of ICommands inside the class.
Here's more on the CompositeCommand class from Prism:
http://msdn.microsoft.com/en-us/library/microsoft.practices.composite.presentation.commands.compositecommand_members.aspx
My own admittedly short and possibly non-canonical implementation follows. To use it, all you need to do is have this be referenced on your VM, and then bind to it instead. You can call .AddCommand for all the other commands that you want to run. Probably the Prism one is implemented differently, but I believe this will work:
public class CompositeCommand : ICommand {
private List<ICommand> subCommands;
public CompositeCommand()
{
subCommands = new List<ICommand>();
}
public bool CanExecute(object parameter)
{
foreach (ICommand command in subCommands)
{
if (!command.CanExecute(parameter))
{
return false;
}
}
return true;
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
foreach (ICommand command in subCommands)
{
command.Execute(parameter);
}
}
public void AddCommand(ICommand command)
{
if (command == null)
throw new ArgumentNullException("Yadayada, command is null. Don't pass null commands.");
subCommands.Add(command);
}
}
I have a listbox that is bound to a List<T> -- this is working great.
I'd like to let my users double click a listbox item and open a new window that will display the "detail" view for that record. I'd like this new window to be databound to the same collection as the listbox on the original window. Because, that window has a timer, which polls a webserivce for updated data, I'd like the child (detail window) to also update when the main list updates.
Is this easily done? An example would be great, but any help is appreciated!
You could share the data directly (ie pass the SelectedItem reference to the child window), but that doesn't help you manage behavior and state across multiple windows. If it is a read-only view that's less of a problem, but if data is being changed it gets very problematic very quickly.
This is a good example of the benefits of using a Model-View-? pattern. MVVM is usually the preferred pattern for WPF because WPF is designed for complete separation-of-presentation. However, in a case like this, you may want something closer to MVC (Model-View-Controller), because you really want to coordinate behavior and state between distinct UI elements.
I would recommend a hybrid approach, let's call it "MVVMC" just to make the acronym even longer and more awkward. Implement a ViewModel that is completely UI-agnostic, and just exposes data and data-related state/behavior--probably mostly CRUD type stuff. Then implement a Controller specific to your UI design that consumes and exposes (either by delegation or composition) the ViewModel, but encapsulates. all of the the multi-window display behavior -- things enforcing one window per item, propagating close requests, etc.
public class MyViewModel : INotifyPropertyChanged, INotifyCollectionChanged
{
public MyViewModel(DataModel dataModel) { ... }
}
public class MyController
{
public MyController(MainWindow mainWindow, ViewModel viewModel) { ... }
public ViewModel { get { return _viewModel; } }
public ICommand DisplayChild { ... }
}
So what you're really doing is taking an MVVM, then inverting control so the controller can manage the multi-window UI. So the controller here would inject the ViewModel into windows (including main) as the DataContext for easy binding. It would also bind to events on the main window, launch child windows, and probably bind to child window events in order to manage them properly (eg one window per child record, close children when main closes, etc).
I would go one step further here, implementing the controller against an interface instead of Window. This gives you some flexibility in refactoring, but more importantly allows you to unit test your controller against mocks.
public interface IControllerChild
{
public void Show();
public bool Activate();
public void Close();
// add other behaviors here
}
public class DetailWindow : Window, IControllerChild
{
// implement other behaviors here
}
public class MockControllerChild : IControllerChild
{
public void Show() { IsShowing = true; ActionLog.Add(MockControllerAction.Show); }
public void Activate() { IsShowing = false; ActionLog.Add(MockControllerAction.Activate); }
public void Close() { IsShowing = false; ActionLog.Add(MockControllerAction.Close); }
public bool IsShowing { get; private set; }
public IList<MockControllerAction> ActionLog { get; private set; }
// mock and record other behaviors here
}
public enum MockControllerAction
{
Show,
Activate,
Close,
// Add other behaviors here
};