Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

View Control from a ViewModel with ViewCommands

0.00/5 (No votes)
12 Feb 2014 2  
Describes a ViewCommand pattern analog to the Command pattern, only in the opposite direction to control Views from a ViewModel.

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
{
    // Add this static constructor to each of your View classes:
    static TextItemView()
    {
        // The ViewCommandManager provides a static method that updates
        // the metadata of the DataContext dependency property so that
        // changes to that property can be handled to register the view
        // instance to each new ViewModel that is currently displayed.
        ViewCommandManager.SetupMetadata<TextItemView>();   // <--
    }

    // This is already there. Nothing to change here.
    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
{
    // (Constructors see above...)

    // This method implements the FocusText command in the view. It puts
    // the focus on the TextBox control named MyTextBox. This is defined
    // in the XAML part of the view (see below).
    // ViewCommand methods must be public and tagged with the ViewCommand
    // attribute to prevent arbitrary methods to be called in the view.
    // They also cannot return a value because there may be multiple views
    // registered with a ViewModel and we couldn't decide on one of the
    // return values we’d get.
    [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:

// Implement the IViewCommandSource interface.
class TextItemViewModel : ViewModelBase, IViewCommandSource
{
    // This is the IViewCommandSource implementation. It's a
    // ViewCommandManager instance that handles everything in this
    // ViewModel instance and a public property that makes it available.
    private ViewCommandManager viewCommandManager = new ViewCommandManager();
    public ViewCommandManager ViewCommandManager
    {
        get { return viewCommandManager; }
    }

    // The data property used in the XAML binding above.
    private string someText;
    public string SomeText
    {
        get { return someText; }
        set
        {
            if (value != someText)
            {
                someText = value;
                OnPropertyChanged("SomeText");
                // As a small extra, it will mark the loaded file "modified"
                // when the text has changed. This could enable a Save
                // button in the application’s toolbar.
                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 TextItemViewModels.

// This is a command handler from the parent ViewModel class:
private void OnAddText()
{
    // Create a new ViewModel instance
    TextItemViewModel newVM = new TextItemViewModel(this);
    TextItemVMs.Add(newVM);
    // Again, this marks the loaded file changed (little gimmick here)
    Somewhere.Else.FileModified = true;
    // Now the TextBox in the new view should be focused:
    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()
{
    // Create a new ViewModel instance
    TextItemViewModel newVM = new TextItemViewModel(this);
    TextItemVMs.Add(newVM);
    // ...
    // Notice the different Invoke method now:
    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

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here