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

Designer-friendly MVVM for XAML Windows Store applications

(1 votes)
András Velvárt
>
András Velvárt
Joined Feb 11, 2010
Articles:   4
Comments:   5
More Articles
4 comments   /   posted on Feb 07, 2013
Tweet

Introduction

It was almost three years ago that I published my company’s preferred approach to MVVM with Silverlight. Time sure flies, and now I can say that the basic ideas and general way of thinking outlined in that article stood the test of time – we created dozens of applications that used that approach, many of them ended being in the top 5 apps in the Windows Phone Store in their own categories.

And then, WinRT happened. WinRT (or Windows 8 Store application) development does not support Behaviors, Actions or Triggers, which were the foundation of the two key pieces of our MVVM puzzle: CallDataContextMethodAction and VSMChangerBehavior. We found that we had to write a lot of repetitive code in the codebehind, and often could not avoid littering it with some business logic that belonged in the ViewModel instead.

Note: I am not an MVVM purist – I think that using code behind is fine when it comes to manipulating the UI itself, and not the data that drives the UI. However, a lot of this functionality could be abstracted and reused with Behaviors – e.g. you could write a behavior that selected the content of an entire TextBox when it got focus, making it easier to replace the text within. Sadly, this is not really an option with WinRT without proper tool support.

After gaining enough experience with WinRT development, I set out to port over our proven approach to WinRT. A lot had to be changed since we could not take advantage of Behaviors, but the basics remained the same. This article introduces the WinRT version of the Designer-friendly MVVM Helpers library.

A Word about MVVM

Discussing what MVVM is and why it’s useful is waaaay beyond the scope of this article. I am assuming that You already know what it is, at least on a conceptual level. If not, please read the original Silverlight version of this article, which discusses the most important things about MVVM, and also gives you pointers for further reading.

Goals of the Designer-friendly MVVM Helpers

The goals are mostly the same as they were in the Silverlight / Windows Phone version.

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 and lack of Behaviors)
  • Use of Visual States to reflect different states of the application
  • High level of flexibility
  • Almost everything can be done in XAML
  • Create a 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

Using the Designer-friendly MVVM Helpers

When discussing the relationship between a ViewModel and a View, XAML technologies use two key concepts:

  • Data binding – to get data from the VM to the View and vice versa
  • Commanding – to notify the VM of the user’s actions, such as the pressing of a button

Our library extends data binding by creating a bi-directional link between a View’s Visual States and an Enum in the ViewModel, and also provides a way for the View to call ViewModel methods upon user interactions. This covers 90% of the use cases for a typical, well architected MVVM app. For the rest, we are still relying on code behind, and desperately wait for the arrival of Behaviors in an upcoming WinRT / Blend release.

First, let’s see how the VM can be notified of user interactions.

The CallVMMethod Attached Property

For the Silverlight / Windows Phone version, this used to be the CallDataContextMethodAction. With Triggers, you had a very flexible way of specifying when the VM method should be called. While a similar approach could be used for WinRT using third party Behavior libraries, due to the lack of Blend support, these result in a ton of XAML code you had to write, making the solution designer-unfriendly. I looked at our usage of this pattern in our projects, and found that 98% of the time, we are just using a simple EventTrigger to trigger the CallDataContextMethodAction. So, for Windows 8, I focused on this use case, and I went for the most fluid experience I could achieve (leaving the other 2% to codebehind solutions).

Basic Example

Our first example simply shows the content of a TextBox in a dialog. Showing a MessageDialog from a ViewModel is not a good practice (beats testability and also separation of concerns), but I am using it as an example here to illustrate that the VM did receive a message.

The entire Page has a DataContext (MainVM), defined as follows:

   1:  <Page.DataContext>   
   2:         <df:MainVM></df:MainVM>
   3:  </Page.DataContext>

Here is the relevant code snippet from the ViewModel:

 
   1:  public string Message { get; set; }
   2:   
   3:  public void ShowMessage()
   4:  {
   5:      string msg = Message ?? "Enter something in the textbox";
   6:      MessageDialog dialog = new MessageDialog(msg, "Dialog from ViewModel");
   7:      dialog.ShowAsync();
   8:  }

The UI for this sample looks like this:

image

And the XAML is:

   1:  <TextBox  Text="{Binding Message, Mode=TwoWay}"/>
   2:  <Button Content="Message!" df:CallVMMethod.Click="ShowMessage"/>

The ViewModel learns about the content of the TextBox with a simple binding –0 no surprises here. The more interesting part is the second line, where the new attached property is used to call the ShowMessage method of the ViewModel (or technically, the DataContext of the Button), when the Button’s Click event is fired.

When you are typing the above line, Intellisense helps you as much as possible. Here is what it looks like:

image

As you can see, the most used event handlers are available – and if the one you are looking for is not, you can easily add a new one.

Using from an ItemTemplate

This is all nice, but often you want to call a VM method from a list. E.g. the VM should know when you selected an item, or clicked the “Delete” button next to an item.

Suppose we have a list of strings, and want the user to choose one of those. Here is the VM (again, simplified to focus on the main point of the article):

   1:  public MainVM()
   2:  {
   3:      SomeStrings = new ObservableCollection<string>();
   4:      for (int i = 0; i < 10; i++)
   5:          SomeStrings.Add(Guid.NewGuid().ToString());
   6:  }
   7:   
   8:  public ObservableCollection<String> SomeStrings { get; private set; }

We have a ListView in the View to display these strings. Pretty straightforward so far:

<ListView ItemsSource="{Binding SomeStrings}" ItemTemplate="{StaticResource DataTemplate1}"/>

You could bind the SelectedItem to a property in the VM and handle selection in the property setter, but the setter does not get called if you click on the item already selected. Instead, you could do something like this in the ItemTemplate:

   1:  <DataTemplate x:Key="DataTemplate1">   
   2:      <TextBlock TextWrapping="Wrap" Text="{Binding}" df:CallVMMethod.Tapped="StringSelected"/>
   3:  </DataTemplate>

This would call the StringSelected method of the DataContext, except… the DataContext of the DataTemplate in this case is just a String, which does not have such a method. What happens in this case, is that the CallVMMethod starts to traverse the Visual Tree. If the DataContext of the TextBlock does not have a StringSelected method, maybe its parent does. Or maybe the parent of that… and so on, and so on, until the method with the right signature is found (see below), or the top of the Visual Tree is reached (in which case you get an ArgumentException).

This way, as we go up, we reach the parent ListBox, the DataContext of which does have the StringSelected method defined in the MainVM class:

   1:  public void StringSelected(string dataContext)
   2:  {
   3:      MessageDialog msgd = new MessageDialog("Selected: " + dataContext);
   4:      msgd.ShowAsync();
   5:  }

This StringSelected method excepts a single parameter, with the type of String. This is important – since we have traversed up the Visual Tree until the point where the original DataContext of the tapped TextBlock is no longer visible, we have to have a way for the StringSelected method to know which string was tapped on. This technique can also be used to pass a business object, such as a Person or Company to the VM.

Method signatures

As you saw above, there are several ways you can define the methods in the ViewModel to be called by CallVMMethod. Here is a reference table of all the supported signatures:

MethodName()
No parameters, no data passed to the method

MethodName(T dataContext)
If the DataContext of the source FrameworkElement mathes the type T (or can be assigned to a variable of type T), this method is a match. The value passed is the DataContext of the source FrameworkElement

MethodName(T dataContext, Targs args)
It is rare, but sometimes you may want to pass the original event’s arguments to the ViewModel. If so, this signature is understood by CallVMMethod and invoked.

The CallVMMethod will look for all these methods in the DataContext of the source FrameworkElement and its parents’ DataContext until it finds a suitable method or reaches the top of the tree.

Adding more events

You may find that the events implemented by CallVMMethod does not include the event you want to use. Luckily, it is easy to expand the list of events: open the CallMVVMMethodHandledEvents.tt file (which is a T4 template), and add your event to the others by listing the event name, event handler type and event argument type. For example, this is what the first few events look like in the template:

   1:  string[] handledEvents = new string[] {
   2:   
   3:  "Click,RoutedEventHandler,RoutedEventArgs",
   4:  "DoubleTapped,DoubleTappedEventHandler,DoubleTappedRoutedEventArgs",
   5:  "DragEnter,DragEventHandler,DragEventArgs",

The T4 code generator will take care of the rest.

The EnumToVisualState Control

The other piece of the designer-friendly MVVM puzzle was the VSMChangerBehavior, which mapped ViewModel enums to Visual States in the View. Again, due to the lack of Behavior support in WinRT, this had to be rewritten, but the basic idea remained the same.

The sample I am going to show for the EnumToVisualState control is a simple one: the ViewModel controls whether a panel is to be open or closed via a simple Toggle method.

Configuration

Let’s define a simple enum for the panel’s two states: On and Off:

public enum PanelState { Off, On }

…and use it in the ViewModel:

   1:  private PanelState _panel = PanelState.Off;
   2:  public PanelState Panel
   3:  {
   4:      get { return _panel; }
   5:      set
   6:      {
   7:          _panel = value;
   8:          if (PropertyChanged != null)
   9:              PropertyChanged(this, new PropertyChangedEventArgs("Panel"));
  10:      }
  11:  }
  12:   
  13:  public void TogglePanel(object dataContext, TappedRoutedEventArgs args)
  14:  {
  15:      Panel = Panel == PanelState.Off ? PanelState.On : PanelState.Off;
  16:  }

You can use a more sophisticated method for raising the PropertyChanged event, but it is important that you do perform this call.

Next, you need to define the Visual States that correspond to the enum values and property names above. For this example, we need to define a Visual State Group called “Panel”, and two states within it: “Panel_On” and “Panel_Off”. Here is an example for a panel that slides in from the left:

   1:  <VisualStateManager.VisualStateGroups>
   2:      <VisualStateGroup x:Name="Panel">
   3:          <VisualStateGroup.Transitions>
   4:              <VisualTransition GeneratedDuration="0:0:0.5">
   5:                  <VisualTransition.GeneratedEasingFunction>
   6:                      <CircleEase EasingMode="EaseInOut"/>
   7:                  </VisualTransition.GeneratedEasingFunction>
   8:              </VisualTransition>
   9:          </VisualStateGroup.Transitions>
  10:          <VisualState x:Name="Panel_On"/>
  11:          <VisualState x:Name="Panel_Off">
  12:              <Storyboard>
  13:                  <DoubleAnimation Duration="0" To="-340" Storyboard.TargetProperty="(UIElement.RenderTransform).(CompositeTransform.TranslateX)" Storyboard.TargetName="grid1" d:IsOptimized="True"/>
  14:              </Storyboard>
  15:          </VisualState>
  16:      </VisualStateGroup>
  17:  </VisualStateManager.VisualStateGroups>

The last step is to add an EnumToVisualState control to your Page:

<df:EnumToVisualState Opacity="0" VerboseInitialization="True" ElementWithVisualStates="{Binding ElementName=grid}" />

The control is invisible. It has three important properties:

DataContext
The ViewModel where the enums are defined. Since DataContext is inherited in the Visual Tree, most of the time you can leave it as is – however, if you need to, you can bind it to something else, using the full binding arsenal of WinRT

ElementWithVisualStates
This is the FrameworkElement the Visual States of which we want to control.

VerboseInitialization
Set this to true to have EnumToVisualState write detailed initialization information to the Debug output window. This will show you what kind of properties and methods the control is looking for in the ViewModel, and help hunt down typos.

Finally, we can add a Button to the UI to toggle the panel by calling the TogglePanel VM method using the CallVMMethod technique:

<Button Content="Toggle Panel" df:CallVMMethod.Tapped="TogglePanel" />

Tapping the button now invokes the TogglePanel VM method, which in turn changes the PanelState enum, which causes the Visual State to change and the animation to be played.

Conventions

EnumToVisualState uses a convention-based approach. This means that instead of using a lot of binding, you only have to make sure that the enums in the ViewModel and the state names in the View match to work. Here are the naming conventions you have to follow:

  • To map a Visual State Group to an enum, all you have to do is make sure they both have the same name.
  • To map a Visual State to a value of an enum, you have to name the Visual State correctly: <Enum variable name>_<enum value name>.
  • To get notified in the ViewModel when a visual state transition is finished, you need to have a method called <VisualStateGroup name>TransitionComplete() with no parameters.

So, if you have an enum called PanelState, which has the possible values of “Off” and “On”, and you have a ViewModel property called “Panel”, you need to call your Visual State Group “Panel”, and the states withing it “Panel_Off” and “Panel_On”.

Callbacks

Since Visual States can have transition animations which can take a while to finish, it is sometimes useful to know when the user actually sees the final scene. EnumToVisualState provides you with this information: if you have a method called “PanelTransitionComplete”, this method will be called every time the “Panel” Visual State Group finishes a transition animation. Here is an example:

   1:  public void PanelTransitionComplete()
   2:  {
   3:      MessageDialog msgd = new MessageDialog("Panel transition to " + Panel.ToString() + " complete!");
   4:      msgd.ShowAsync();
   5:  }

The new state is not passed to this method, it can be determined from the value of the corresponding enum.

Initial State

You can also set an initial state for the enums in the VM, and the Visual States will be initialized in that state. For example, if you set the Panel to PanelState.Off, the Panel will not be visible when the Page first shows up. While this is achieved without a visible transition animation, the TransitionComplete method is still invoked.

Download

Finally, you can download the entire demo project here. You will need to copy four files to your project: CallVMMethod.cs, CallVMMethodHandledEvents.tt, EnumToVisualState.cs, and also . I will also prepare a Nuget package, and will upload it soon after this article is published.

Summary

Porting our tried and proven Behaviors to WinRT was not a simple task. However, I believe that in the end, the core values of the original solution are preserved despite the different architecture. Please let me know if you find this library useful, or if you have any feedback!


Subscribe

Comments

  • silverlightfan

    Re: Designer-friendly MVVM for XAML Windows Store applications


    posted by silverlightfan on Feb 09, 2013 09:05
    Great article...nice that you have shared this helpers...I want just to ask if it is possible to see the relevant implementation in Silverlight - I am aware that there we have the triggers to help us in the first case, but what abot the hidden control...what its the equivalent?
  • andrasvelvart

    Re: Designer-friendly MVVM for XAML Windows Store applications


    posted by andrasvelvart on Feb 09, 2013 13:01
    You can find the VSMChangerBehavior in the original article (referenced in the intro of this one), which server the same purpose.
  • FilipSkakun

    Re: Designer-friendly MVVM for XAML Windows Store applications


    posted by FilipSkakun on Feb 20, 2013 19:29

    Looks good.

    CallVMMethod is a bit like EventToCommand only without the need for using commands and with support for passing UI event args to a view model (which might not be the best idea if you want to reuse the view model on another platform). It feels like just handling events and calling view model methods from code behind might have better tool support and while it is a little bit more code in code behind - it is a bit more flexible without adding dependency on this helper library. It seems like making it depend on reflection might make the code less maintainable by removing the compile-time checks.

    EnumToVisualState is nice since if you use visual states extensively - the visual state often depends on the view model state property. Since names of visual states are just strings - you can't really ensure compile-time checks there and it is nice to have a simple way to bind a visual state to the view model. I am only thinking it would be better if it were an attached property instead of a control.

  • MikeGoatly

    Re: Designer-friendly MVVM for XAML Windows Store applications


    posted by MikeGoatly on Feb 21, 2013 10:40

    Interesting approach - because you've focused on making it as easy as possible for a designer to use it has ended up as quite a clean design, although perhaps not as flexible as other libraries.

    I quite like that you traverse the visual tree to find the method rather than force the designer to bind up the correct part of the VM for the action - simplicity at the expense of a marginal perf hit.

Add Comment

Login to comment:
  *      *