Click here to Skip to main content
Click here to Skip to main content

MVVM Dynamic Commands

, 24 Apr 2009
Rate this:
Please Sign up or sign in to vote.
Implementing dynamic WPF ICommands in Model-View-ViewModel architecture

Introduction

The Model-View-ViewModel (MVVM) pattern has become so popular in the WPF community that, to many developers, it is the way WPF is done. It has the benefits of simplicity and directness and, if you already know Model-View-Controller, it is easy to learn.

Commands are straightforward and elegant in MVVM. A command is encapsulated in an ICommand object, which is exposed as a property in the view model. Binding to such a command is as simple as this:

<Button Command=”MyCommand”>Click Me</Button>

That approach works very well if the button and its command binding can be defined in XAML at design-time. But what if the button, and the specification of which command it will be bound to, are loaded dynamically at run time?

This article explains the problems involved in implementing dynamic commands and offers a solution to the problem. I don't offer the solution as necessarily being a best practice. In fact, one reason for publishing this article is to solicit comments on other ways to solve the problem. If a better solution emerges from comments to the article, I will update the article (with credit, of course) to implement that solution.

Background

Let’s say you are creating an application that financial planners will use to manage their clients’ accounts. The main window contains a ListBox that displays alerts for an account. Each alert contains a hyperlink that allows the planner to open a window so that she/he can take whatever action is required. For example, if a client’s account has a negative balance, the planner may need to open a window to review the account, to see what happened.

The hyperlinks are generated at run time by the application, and each hyperlink will need to invoke a different ICommand from those available in the application. What that means is that we can't bind our hyperlinks at design-time.

The issue that we will address is “How do we implement the hyperlink feature, since we can't do design-time binding of their commands?”

The Demo App

The demo application models the ListBox that is described in the usage scenario and shown at the top of this article. The main window for the demo app, MainWindow.xaml, is located in the View folder.

To keep things simple, the demo assumes that hyperlinks can invoke one of two commands; CompleteTransaction, or ReviewAccount. The ICommand classes for these commands are in the ViewModel/Commands folder.

The domain model for the demo app (in the Domain folder) consists of a single object—an Alert. An alert object has three string properties:

  • AlertText: The text of the alert
  • LinkText: The text of the hyperlink
  • LinkCommand: The name of the command the hyperlink should invoke

An Alerts list is exposed by the view model as an ObservableCollection.

The ListBox is implemented with a DataTemplate that allows us to include a hyperlink:

<ListBox Grid.Row="1" Margin="10,0,10,10" ItemsSource="{Binding Alerts}">
    <ListBox.Resources>
        <vm:PaddedStringConverter x:Key="StringConverter" />
    </ListBox.Resources>
    <ListBox.ItemTemplate>
        <DataTemplate>
            <TextBlock Text="{Binding Path=AlertText, 
			Converter={StaticResource StringConverter}}">
                <Hyperlink Command="{Binding Path=LinkCommand, 
			Converter={StaticResource CommandConverter}}">
                    <TextBlock Text="{Binding Path=LinkText}"/>
                </Hyperlink>
            </TextBlock>
        </DataTemplate>
    </ListBox.ItemTemplate>
</ListBox>

The implementation is fairly straightforward. Note that the TextBlock uses an IValueConverter called StringConverter. This converter simply adds a space after the alert text, in order to separate it from the hyperlink. Value converters used by the demo app are in the ViewModel/ValueConverters folder.

Walkthrough

The application is initialized in App.xaml.cs. An override to the OnStartup() handler creates the view model and adds three alerts to it. The first two alerts invoke different ICommands; the third alert does not invoke a command.

At that point, we instantiate an IValueConverter called CommandConverter:

/* We instantiate our CommandConverter here, so that we can
* pass the view model to it. Then we add it to the application
* resource dictionary, where it is available to XAML. */

// Create and initialize main window
Resources.Add("CommandConverter", new CommandConverter(viewModel));
MainWindow mainWindow = new MainWindow();
mainWindow.DataContext = viewModel;
mainWindow.Show();

We will use this converter to get our dynamic commands later in the application. We instantiate this converter in code, rather than markup, because it needs a reference to the application's view model. I will explain why later. Once the main window is shown, WPF takes over and displays the ListBox. The XAML is self-explanatory.

Dynamic Commands

And that brings us to the core issue: How do we dynamically bind a hyperlink to its command? Normally, when we lay out a window, we know in advance which command a control will invoke. That’s what makes MVVM simple to implement—we simply bind the control’s Command property to the view model property that exposes the command we want.

But in this case, we don't know at design-time which command a hyperlink will invoke. All we know is that the name of the appropriate ICommand will be contained in the Alert.LinkCommand property. So, we need some way to get from the name of the ICommand to the actual ICommand itself.

The CommandConverter

The demo implements an IValueConverter, called CommandConverter, to solve the problem. The CommandConverter accepts a string that represents the name of an ICommand, and it returns the ICommand with that name:

// Create command 
switch (commandName)
{
    case "CompleteTransaction":
        command = new CompleteTransaction(m_ViewModel);
        break;
    case "ReviewAccount":
        command = new ReviewAccout(m_ViewModel);
        break;
    default:
        throw new ArgumentException("Invalid ICommand name passed in.");
}

The heart of the CommandConverter is a simple switch statement that instantiates and returns the appropriate ICommand object.

You might notice that the CommandConverter holds a view model reference (m_ViewModel) as a member variable, and that it injects this reference into the ICommands. The ICommands hold the reference as a member variable, but they don't do anything with it.

The view model reference is designed to facilitate communication between the ICommands and the view model. The ICommands don't call the view model in the demo app, but they do so frequently enough in my production apps that I routinely inject a view model reference into all new ICommands.

The feature is included in the demo to show how the view model reference gets injected into the CommandConverter, and from there how it is injected into the ICommand objects. The need for the view model injection also explains why the CommandConverter is instantiated in code, rather than simply being declared as a XAML resource in MainWindow.xaml.
The result is as follows:

<Hyperlink Command="{Binding Path=LinkCommand, 
	Converter={StaticResource CommandConverter}}">

WPF reads the name of the ICommand from the LinkCommand property and passes it to the CommandConverter. The CommandConverter creates the specified ICommand object and passes it back to the hyperlink’s Command property. When the hyperlink is clicked, the command is invoked.

Call for Comments

This solution works, but quite frankly, it feels like a bit of a hack. I am not entirely comfortable pushing IValueConverter as far as this solution does. But is there a simpler, more elegant solution?

Here are some arguments I see in favor of the solution described in this article:

  • The creation of ICommands is centralized in one place.
  • The solution is very similar to the way WPF loads images.

Here are some arguments I see against it:

  • The solution may use IValueConverters in a way not intended by WPF’s designers.
  • You have to know to look in value converters for ICommand instantiation.

The last item concerns me the most. In conventional MVVM, I can find all of my commands in my command properties. This solution requires me to hunt around for them, and value converters are not the first place I would expect to find ICommand instantiation.
So, is the solution in the article the best way to implement dynamic commands, or can it be done with a very clever line or two of XAML? I look forward to your comments!

History

  • April 24, 2009: Initial posting

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

David Veeneman
Software Developer (Senior) Foresight Systems
United States United States
David Veeneman is a financial planner and software developer. He is the author of "The Fortune in Your Future" (McGraw-Hill 1998). His company, Foresight Systems, develops planning and financial software.

Comments and Discussions

 
GeneralThere *Is* a Better Way! PinmemberDavid Veeneman2-May-09 10:31 
Karl Shifflett has shown me a better way to wire these commands. As soon as I have a chance to work through it, I'll update the article with a complete explanation and demo.
 
David Veeneman
www.veeneman.com

GeneralRe: There *Is* a Better Way! Pinmembercrystalmac19-Jun-09 5:17 
GeneralRe: There *Is* a Better Way! Pinmemberyannduran22-Jan-11 21:38 
QuestionRe: There *Is* a Better Way! PinmemberWiiMaxx29-Jan-14 2:05 
AnswerRe: There *Is* a Better Way! Pinmembersbarnes2-Apr-14 10:34 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web02 | 2.8.140721.1 | Last Updated 24 Apr 2009
Article Copyright 2009 by David Veeneman
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid