(X) Hide this
    • Login
    • Join
      • Say No Bots Generate New Image
        By clicking 'Register' you accept the terms of use .
        Login with Facebook

A Designer-friendly Approach to MVVM

(6 votes)
András Velvárt
>
András Velvárt
Joined Feb 11, 2010
Articles:   3
Comments:   4
More Articles
9 comments   /   posted on Mar 12, 2010

This article is compatible with the latest version of Silverlight.


Introduction

If you work with Silverlight or WPF, you have probably met the phrase “MVVM”. Almost everyone who is anyone in the WPF / Silverlight scene has their own MVVM framework, and their own way of explaining and teaching MVVM. Scary terms like IoC, Dependency Injection, Commanding Frameworks, Event Aggregators, Unit Testing, etc just roll off the tongue of the MVVM experts. This is one of the reasons why MVVM is intimidating for a lot of people. Still, you can create perfectly valid MVVM applications without even knowing what those terms mean. This post will show you my approach to MVVM, which is hopefully simple enough to get you started.

So, what is MVVM? There are tons of blog posts on the topic on the net - for example, take a look at John Papa’s 5 Minute Overview of MVVM in Silverlight. I won’t go into details, but here is my very short summary:

  • MVVM allows for real separation between logic and presentation (or developer and designer).
  • Model is the data that is displayed in the application. Model is created by a developer.
  • View is the User Interface, that is defined in XAML (mostly). View is the responsibility of a designer.
  • ViewModel is the logic behind the UI, it handles data retrieval and manipulation, follows the state of the application (such as whether the user has logged on or not). The ViewModel is owned by the developer, and is present in the View as the DataContext.

From our perspective, the interesting part is how the View and the ViewModel are communicating. There are two components to this: databinding (synchronizing data between the View and the ViewModel) and commanding – which is essentially a mechanism that allows the View to notify the ViewModel when user interaction has happened.

Databinding in Silverlight and WPF is pretty powerful. In my mind, the Zen of WPF and Silverlight development is:

“Get the data to be displayed to the client, and trust databinding and XAML – the designer - to do the rest”.

Similarly, the Zen of WPF and Silverlight design is:

“No matter how you want to display the data for the user, XAML and databinding can probably handle it. Only ask a developer to help you after you have tried really hard.”

On the other hand, I always had a bad taste in the mouth when it comes to the commanding facilities of Silverlight. Commanding has two key features:

  • Notify the ViewModel if something (usually a user interaction) happens in the View
  • Disable UI elements when a command is not available (for example, the save button, icons, etc should be disabled if the user does not have the right to modify an item).

Here is why I don’t like commanding:

  • For the designer to be able select a command in Blend, the developer has to create a lot of code – and usually that code simply invokes a method. There is RelayCommand and DelegateCommand to help with this though.
  • The biggest issue I have with commanding is that it really ties the hand of the designer. What if I don’t just want to disable the Save button, I want to change its text to “Login first to Save”? What if I am a crazy designer and want to have all the buttons fly in from outside the screen when the user has logged in and is authorized to press them?
  • Also, the CanExecute approach can be tedious for the developer. For example, after logging in, you have to re-evaluate pretty much all commands, and call CanExecuteChanged for all of them. For complicated tasks, when there are tons of commands, and their executability (is there such a word) is driven by database settings, the built-in approach may be the right one. But for simpler software, this can be overkill.

The key goals of my MVVM approach

For the project itself:

  • Total separation of the designer and developer workflows
  • Shallow learning curve for both the designer and the developer
  • Allowing both the designer and the developer to remain in their comfort zone, and only use the tools they are familiar with
  • Approachable solution for projects with low or medium complexity

For the designer:

  • As much Blend support as possible (this is not totally achieved yet due to limitations in Blend 3)
  • Use of Visual States to reflect different states of the application
  • High level of flexibility
  • Almost everything can be done in Blend and XAML
  • Shallow learning curve: there is only an Action and a Behavior to learn about
  • Create modern UI, including animations, transitions, custom skins, etc.

For the developer:

  • Focus only on coding
  • Display logic (ViewModel) code is unit testable, including user actions
  • Application states can be expressed naturally, via enum values
  • Ability to include techniques from other MVVM approaches, such as the MVVM Light Toolkit

So, what do I do instead of commanding?

I use an Action and a Behavior. The Action is for calling methods in the DataContext (that is, the ViewModel). It works similar to the CallDataMethod Action in the Expression Blend Samples project.

The CallDataContextMethodAction

In the attached sample project, there is a TextBox and a Button to illustrate the basic functionality of the CallDataContextMethodAction. The TextBlock is bound to the Message property of the ViewModel, and the Button has the CallDataContextMethodAction applied to it, with the method name set to ShowMsgBox.

image

Here is the relevant part of the ViewModel (which is set to be the DataContext of the entire form):

 public string Message { get; set; }
  
 public void ShowMsgBox()
 {
       if (Message == null)
             MessageBox.Show("Enter something in the textbox");
       else
             MessageBox.Show(Message);
 }

Here is our Action in Blend:

image

If the Button is pressed, a MessageBox is shown, with the text entered into the TextBox:

image

Note: You shouldn’t show a MessageBox from a ViewModel because the designer has no way of modifying what it looks like and how it behaves. However, this is just a test application, and the message boxes are only here to illustrate that the ViewModel actually received the call

So far, it is pretty trivial. What is new in the CallDataContextMethodAction is that it can handle collections pretty well, too. As Glenn Block said in his excellent article The spirit of MVVM (ViewModel), it’s not a code counting exercise, you usually do not need to have parameters for commands. But there are some circumstances when you cannot avoid parameters, Consider the “Greetings to” list in the above picture. We want to display a greeting message for the person the user has clicked on. If the names are displayed in a ListBox, you can bind the SelectedItem property of the ListBox to a ViewModel property, and call the GreetPerson method by capturing the SelectionChanged event (there is a pretty handy EventTrigger built into Blend to help you with this). But then clicking on the same name again won’t fire the SelectionChanged event one more time. Also, what if the designer uses an ItemsControl instead of a ListBox? What if we want to greet the person not by clicking, but by doing some other, non-selective gesture? In this case, we have to have the GreetPerson method either on the person’s ViewModel, or some way to let the main ViewModel know which person we need to greet.

Here is the relevant code in the ViewModel (note that there is no SelectedItem as the names are displayed in a simple ItemsControl):

 public MyViewModel()
 {
     Names = new ObservableCollection<string> {"John", "Smith", "Toby", "Emily"};
     SG1 = PanelState.On;
 }
  
 public void GreetPerson(string name)
 {
     MessageBox.Show("Hello, " + name);
 }

The Action is now attached to the ItemTemplate:

image

But the items displayed in the ItemsControl are strings, and therefore the DataContext of the TextBlock is also a string. And a string does not have a GreetPerson method, so how does the Action work in this case? When the Action cannot find a method in the DataContext of the AssociatedObject (the element the Action is attached to, in our case, the TextBlock displaying the name), it tries to find the same method in the parent of the AssociatedObject, and its parent and so on. If such a method is found in the parent, it passes the DataContext (ViewModel) of the AssociatedObject to the parent method. By traveling up the VisualTree, we get to the MainPage, where the DataContext is the MyViewModel class shown above. This class has a GreetPerson method, so that method gets called. The GreetPerson method also has a parameter of string – this will be the DataContext of the TextBlock, thus the name of the person to be greeted. Note, that with CallDataContextMethodAction, we could use a strongly typed parameter for the method, unlike in the case of Commands where the parameter has to be an object.

The big drawback of the CallDataContectMethodAction is that the name of the Method has to be typed in by the designer, thus it introduces the possibility of human error. Hopefully, we will be able to extend the design time experience later on, so that the developer can simply select the Method from a list instead of typing its name.

The VSMChangerBehavior

The other piece of the puzzle is the VSMChangerBehavior. It ties enum values in the ViewModel to Visual States in the View. As VisualStateGroups are orthogonal (meaning each group can be in any state regardless of what states the others are in), this is a very powerful means of visually reflecting the internal states the ViewModel is in. Here is an example:

Suppose we have two panels that we want to show depending on various ViewModel calculations. We have an enum type of PanelState:

public enum PanelState { Off, On }

And here are the two enums:

 [VisualStateGroup]
 public PanelState Panel1
 {
     get { return _panel1; }
     set
     {
         _panel1 = value;
         FirePropertyChanged("Panel1");
     }
 }
   
 [VisualStateGroup]
 public PanelState Panel2
 {
     get { return _panel2; }
     set
     {
         _panel2 = value;
         FirePropertyChanged("Panel2");
     }
 }

For both panels (Panel1 and Panel2) we have their own properties of type PanelState, that fire the PropertyChanged event when set. However, we are using the [VisualStateGroup] attribute to mark these enums as ones that have (or should have) a VisualStateGroup counterpart. Here is what the visual states look in Blend:

image

As you can see, we have a VisualStateGroup for each PanelState property, The states themselves are called Panel1_On, Panel1_Off, Panel2_On, Panel2_Off. These state names are calculated by concatenating the enum property’s name (which has the VisualStateGroup attribute), an underscore and the name of the enum value:

<PropertyName>_<ValueName>.

After this has been set up, all we have to do is to add the VSMChanger behavior to the Control that has the states we would like to change from the ViewModel:

image

From this point on, the behavior automatically changes the visual state of the UserControl as the enum values change within the ViewModel. This even includes state changes where the change is animated, as in the case in this simple demo. However, when the state transition is not instantaneous, the developer of the ViewModel may want to be notified when the transition has finished. For this, VSMChangerBehavior includes a callback mechanism. Simply name your callback method <PropertyName>TransitionComplete, and the method gets called when the transition has finished. For example, to show a MessageBox whenever Panel2 has been completely shown or hidden, have this in the ViewModel:

 public void Panel2TransitionComplete()
 {
     MessageBox.Show("Panel2 Transition complete");
 }

There is one big problem with this behavior, though. Just like in the case of the CallDataContextmethodAction, the designer has to type in the names of the visual states groups and states. As we are wiring up things based on convention, If he/she makes a mistake, the state change will not happen. To help ease this pain, I have added a “VerboseInitialization” property to the behavior.

If it is on, the behavior will write detailed information to the output stream (unfortunately this stream can only be viewed from Visual Studio):

image

This should help debugging typos and missing states / groups until I figure out how to bend Blend to my will add add a proper designer experience for this behavior.

Summary

So, there you have it. A designer-friendly way to wire up the View and the ViewModel. User gestures can be turned into commands via the CallDataContextMethodAction, and internal states and state changes of the ViewModel can be easily reflected via the VSMChangerBehavior. The later can also be used to disable / enable / hide UI elements in a declarative way that gives much more freedom to the designer than the CanExecute approach. Please let me know what you think in the comments!

You can download the solution with code here. You are free to do whatever you want with it, except say it is yours :)


Subscribe

Comments

  • -_-

    RE: A Designer-friendly Approach to MVVM


    posted by Michael Washington on Mar 12, 2010 18:36
    Thank you so much for sharing this. This is really great. I intend to use this moving forward.
  • andrasvelvart

    RE: A Designer-friendly Approach to MVVM


    posted by andrasvelvart on Mar 13, 2010 18:56
    You are welcome, Michael!
  • -_-

    RE: A Designer-friendly Approach to MVVM


    posted by Mike Greenway on Mar 19, 2010 20:08

    Blend 4 directly supports viewmodel and binding a button to a viewmodel method is drag and drop.

    Right?

  • andrasvelvart

    RE: A Designer-friendly Approach to MVVM


    posted by andrasvelvart on Mar 19, 2010 20:34
    Mike: Yes, but the article was written before Blend 4 was made public, I had to be very careful to keep my NDA :). But it definitely helps with the method name typing issue, and I love Blend's solution. On the other hand, Blend's solution cannot do the automatic method invocation on parent element's datacontext.
  • -_-

    RE: A Designer-friendly Approach to MVVM


    posted by Andrew on Sep 14, 2010 16:39

    I'm using Blend 4, Silverlight 4, and VS 2010.  I've tried your VSMChangerBehavior solution and it seems I have it hooked up correctly, but the state is not changing.  When I examine all the states in Blend, they appear to be setup correctly.  Below is my VS Output.  Any suggestions?

    Initializing VSMChangeBehavior for 'MessageBox' named ''
    Found VSM Group 'ButtonType'
    Callback method 'ButtonTypeTransitionComplete' found.
    Initializing VSMChangeBehavior for 'MessageBox' named ''
    Found VSM Group 'ButtonType'
    Callback method 'ButtonTypeTransitionComplete' found.
    State 'ButtonType_OK' found.
    State 'ButtonType_OKCancel' found.
    State 'ButtonType_YesNo' found.
    State 'ButtonType_YesNoCancel' found.
    Transitioning to state 'ButtonType_YesNo' 

  • andrasvelvart

    RE: A Designer-friendly Approach to MVVM


    posted by andrasvelvart on Sep 14, 2010 18:29

    Hello Andrew,

    I am sorry that you have problems with the behavior, let me see if I can help.Did you attach the behavior to the root item in your control (which is the one that has the states defined)? The visual states in Blend are defined at the usercontrol level, so you need to attach the behavior accordingly.

    Thanks,
    András

  • -_-

    RE: A Designer-friendly Approach to MVVM


    posted by Andrew on Sep 14, 2010 22:23

    Hi András,

     My view is actually a ChildWindow and yes, I did attache the behavior to the root item of the ChildWindow.  Does having a ChildWindow make a difference?  I think my problem is outside the scope of your behavior.  I tried to manually change the state of the ChildWindow via the codebehind, but it has no effect. 

    VisualStateManager.GoToState(this, "ButtonType_YesNo", false);

    When I'm in Blend, I can click on the different states and I see the Design view change correctly.  Is there somewhere I need to go to enable states in the ChildWindow?

  • andrasvelvart

    RE: A Designer-friendly Approach to MVVM


    posted by andrasvelvart on Sep 14, 2010 23:18

    Hi Andrew,

    It seems that others have had trouble with using Visual States in a ChildWindow. The recommended approach is to put the content of the ChildWindow in a separate UserControl, and VSM changes should work there. Read more here: http://stackoverflow.com/questions/2118814/how-can-i-use-visualstates-in-a-childwindow

    András

  • -_-

    RE: A Designer-friendly Approach to MVVM


    posted by Andrew on Sep 14, 2010 23:19

    Hi András,

    I found the answer to my problem.  Thanks.

    http://stackoverflow.com/questions/2118814/how-can-i-use-visualstates-in-a-childwindow

Add Comment

Login to comment:
  *      *       
Login with Facebook