Introduction
In WPF applications following the MVVM pattern, it’s not easy to control the View
from a ViewModel
class. Theoretically, it should not be possible at all. But as you know, in practice this doesn’t always work. So similar to the Command pattern letting a View
control its ViewModel
, I’m proposing the ViewCommand
pattern that works in the opposite direction, while maintaining the loose coupling of View
and ViewModel
.
This article has been copied to my website. A German translation is planned.
Background
The Model-View-ViewModel (MVVM) design pattern is a great way to separate concerns of UI and business logic in WPF applications. It is based on the general principle that the View
layer defines and handles anything UI-related while the ViewModel
layer implements the business logic and prepares model data to be used in a view. This makes the view replaceable, for example by a dummy when running test cases on the application to verify the business logic implementation.
For this to work, the ViewModel
must not know anything about the View
it is presented in. There could even be multiple views presenting the same ViewModel
or parts of it at the same time. Conversely that means that the ViewModel
cannot control the view in any way. It merely provides the data for displaying, notifies on changes, and accepts any changed data for validation and processing.
Now sometimes when data is modified from a ViewModel
class, it is advisable to somehow influence its presentation beyond the capabilities of style data binding. A colour highlighting can be provided through a property that a style can bind to. The visibility of some UI parts can also be controlled through such a property. But if a new item is added to a list, it should be made visible by scrolling to its position. If a new text input becomes visible, it should be focused so that the user can continue typing right away. All of these actions are not states that could be represented by a state variable, but rather like events that are raised in the view. One-time actions, not time-span states. While some suggest using a state variable for such tasks and reacting on changing their value, this solution has the issue that nobody will reset the state when it no longer matches reality; and when coming back to the View, it will still be set and perform that action again.
This is where the ViewCommand
pattern comes in. Much like the Command mechanism used to perform actions in the ViewModel
initiated by the View
, it can be used to perform actions in the View
initiated by the ViewModel
.
Using the Code
For this connection between the View
and ViewModel
to be set up, each view must welcome any new DataContext
that comes in. This DataContext
is the ViewModel
that may want to send commands to the view that is currently displaying it. There’s a shortcut method that does the DependencyProperty
metadata overriding for you with just a single memorisable call (marked with the arrow):
public partial class TextItemView : UserControl
{
static TextItemView()
{
ViewCommandManager.SetupMetadata<TextItemView>(); }
public TextItemView()
{
InitializeComponent();
}
}
In this example, TextItemView
is the view that displays an item of something with a text input control.
Next is the definition of the commands that a view wants to offer. If there are controls in the view that may need to be focused, then an appropriate implementation could look like this:
public partial class TextItemView : UserControl
{
[ViewCommand]
public void FocusText()
{
MyTextBox.Focus();
}
}
Following is the XAML code of our view, really nothing special here:
<UserControl
x:Class="MyNamespace.View.TextItemView"
(The usual XML namespace declarations)>
<Grid>
<TextBox
Name="MyTextBox"
Text="{Binding SomeText}"/>
</Grid>
</UserControl>
To enable a ViewModel
class as a source of ViewCommand
invocations, and to allow a view to register with it, the ViewModel
class must implement the IViewCommandSource
interface. This just adds a ViewCommandManager
property that can be used by views to register and by the ViewModel
itself to invoke commands on the view(s). This is what a basic ViewModel
class could look like:
class TextItemViewModel : ViewModelBase, IViewCommandSource
{
private ViewCommandManager viewCommandManager = new ViewCommandManager();
public ViewCommandManager ViewCommandManager
{
get { return viewCommandManager; }
}
private string someText;
public string SomeText
{
get { return someText; }
set
{
if (value != someText)
{
someText = value;
OnPropertyChanged("SomeText");
Somewhere.Else.FileModified = true;
}
}
}
}
Now everything is set up to use the whole ViewCommand
thing.
Finally, calling a command on a view can be done through the ViewCommandManager.Invoke
method. Just pass it the name of the command as first argument and it will try its best to call the command method on every registered view instance. Just like the Command pattern uses data binding to fetch the command object from the data context, which in turn uses reflection to look up the property, this class also uses reflection to find the command method in the view class. So there’s no additional connection setup required. Just add the methods in the view and call them through the ViewCommandManager
class. Here’s an example (spot the bug!), this time from a parent ViewModel
that is managing a collection of TextItemViewModel
s.
private void OnAddText()
{
TextItemViewModel newVM = new TextItemViewModel(this);
TextItemVMs.Add(newVM);
Somewhere.Else.FileModified = true;
newVM.ViewCommandManager.Invoke("FocusText");
}
But... this still contains a major issue. Did you find it?
The OnAddText
method just created a new instance of the ViewModel
and added it to an ObservableCollection<TextItemViewModel>
for the parent view to pick it up and display it in some ItemsControl
. A template directs it to use our TextItemView
control for each instance in the list. So as soon as the new item is added to the collection, a new view instance will be created and assigned the respective ViewModel
instance. But this is also done on the UI thread and only after the thread is free from the OnAddText
method. This means that at the time the new ViewModel
is added to the collection, the view may not yet exist and thus newVM
isn’t assigned as its DataContext
yet and consequently the view isn’t registered with the ViewModel
yet. Invoking a command now would not do anything because there is no view registered.
To overcome this issue, the command invocation needs to be delayed until this association is completed. The current Dispatcher
can be used to invoke methods asynchronously at a given priority. Our priority is Loaded
which comes after DataBind
and Render
, when the view is created, with data context, and registered in the ViewModel
. For our focus action, it also makes sure that the control to be focused is already on the screen. To make this common scenario easier to use, there’s the InvokeLoaded
method that does that for you:
private void OnAddText()
{
TextItemViewModel newVM = new TextItemViewModel(this);
TextItemVMs.Add(newVM);
newVM.ViewCommandManager.InvokeLoaded("FocusText"); }
In other situations where the addressed view already exists, using the normal (synchronous) Invoke
method is perfectly fine though.
Also note that if you use the same ViewModel
instance in multiple Views, doing something like setting the UI focus won’t work correctly because the second view will steal the focus from the first one. Other actions like scrolling a list or starting an animation will work on every registered view.
Implementation
The implementation of the ViewCommand
pattern is actually pretty simple. It’s contained in the file ViewCommand.cs in the sample application and consists of three types. First, and most important, is the ViewCommandManager
class. It provides static
methods for the DependencyProperty
metadata overriding. This allows to monitor changes to the DataContext
property which means that another ViewModel
instance is now presented by the view. This uses the next group of members in the ViewCommandManager
class: RegisterView
and DeregisterView
. Obviously a ViewModel
should only be able to control the views that are currently displaying them. This, by the way, makes a notable difference to the MVVM Light Messenger
class that was also suggested as a solution to the above described focusing problem. A ViewModel
can simply issue a command, and the ViewCommandManager
will automatically find the currently connected views from the list it keeps. Last is the Invoke
methods, that actually send the command to the views. You can also pass any number of arguments to the Invoke
methods, but you must ensure that number and types match the ViewCommand
method in the View
class.
Also in this file is the interface IViewCommandSource
that makes ViewModel
classes recognisable by the automatic registration (only ViewModel
classes implementing this interface can be registered and thus send ViewCommands
anywhere), and the ViewCommandAttribute
class that is used to mark ViewCommand
methods in the View
class. This attribute is used to ensure that only those methods in a view can be called through this mechanism that are explicitly declared as such.
Worth mentioning is that the ViewCommandManager
class keeps weak references to the registered views. This means that no hard reference to the views is held, which could lead to memory leaks if views are unloaded from the UI and not used anymore, but would still be kept referenced from a ViewModel
as connected View
. Those views can be garbage-collected and free their memory without the need to explicitly deregister from their ViewModel
on unloading. Views that no longer exist will simply be skipped when invoking a command in views.
Since the ViewCommandManager
property in a ViewModel
is public
, it can be accessed from other ViewModels
in the hierarchy like regular data properties. Just like in the example above, the parent ViewModel
that creates a new sub-ViewModel
can invoke a ViewCommand
on that child instance.
Room for Improvements
Just like normal Data Binding will generate a Trace message when it cannot find a property, the Invoke
method could also do this to help the developer finding problems with the used commands. Two types of errors could be detected: Either when the requested command method is not found in the View
class, or when no views are currently registered at all. The latter may be the case when trying to invoke a ViewCommand
on a view that wasn’t created yet.
Notice
Because of the use of reflection and no type-binding to the View
class, you won’t get any IntelliSense support when selecting a ViewCommand
. Code refactoring will also ignore the method names where you call them. But none of these points make a difference to the DataBinding
that is already used in WPF and XAML.
If you apply code obfuscation to protect your code in closed-source applications, you must exclude the ViewCommand
methods from renaming or they cannot be found at runtime. (The ViewCommandAttribute
may help you in specifying the affected methods.) Alternatively, you can specify a name for the View
class method with the ViewCommandAttribute
which helps the ViewCommandManager
finding the requested method if it was renamed. You could also use this mechanism to assign arbitrary ViewCommand
names and keep them as string
constants in a separate class in your application.
The
ViewCommandManager
instance methods are not threadsafe. They will also not perform any cross-thread marshalling of commands to the UI thread. Should this be necessary (does anybody use
ViewModels
in separate threads?), appropriate locking and dispatching would need to be implemented.
History
- 2013-02-28 First version
- 2014-02-01
ViewCommand
name parameter support. Smaller language updates