Implementing a non-bindable view correctly using MVVM

by Geert 21. July 2011 20:51

When consulting lots of other people about MVVM, I noticed that most people understand the basic concepts fairly easily. However, as soon as it becomes a little bit more complex, users immediately break the pattern and start making strange “solutions” to their problems.

The wrong “solution”

On of these problems is keeping the MVVM pattern in tact when a view does not support bindings. People don’t know how to handle the stuff, and basically come up with the following “solution” (it’s a real-world solution I’ve seen, but I’ve simplified it a bit and took the part where UI elements are used inside the view-model):

   1: public class MainPageViewModel : ViewModelBase
   2: {
   3:     public MainPageViewModel()
   4:     {
   5:         Navigate = new Command(OnNavigateCommand);
   6:     }
   7:  
   8:     public ICommand Navigate { get; private set; }
   9:  
  10:     private void OnNavigateCommand(string request)
  11:     {
  12:         FrameworkElement screenControl = GetScreenControl(request);
  13:     
  14:         if (screenControl == null)
  15:         {
  16:             screenControl = CreateScreenControl(request);
  17:     
  18:             DeskTopContent.Children.Add(screenControl);
  19:     
  20:             ObservableCollection<TaskBarItem> taskBarItemsList =
  21:                 (ObservableCollection<TaskBarItem>)TaskBarItemsSource.Source;
  22:     
  23:             TaskBarItem item = new TaskBarItem(screenControl, request);
  24:             taskBarItemsList.Add(item);
  25:             TaskBarItems.MoveCurrentTo(item);
  26:     
  27:             ViewedControl = screenControl;
  28:         }
  29:     }
  30:  
  31:     /* other content left out for the sake of simplicity */
  32: }

If you think the solution above is good, please keep reading (you actually need it).

When I see such code, I always ask two simple questions:

  1. What is the best argument for MVVM? (they probably answer: loosely coupled design and testability)
  2. How are you going to unit test the view model above? (they probably answer: not)

Exactly, the code above cannot be unit tested because it is actually just a code-behind instead of a view model (thus the developer decided to break the MVVM pattern for whatever reason he thought was acceptable). If you think this is valid MVVM, then stop calling it a view model and call it by its name: code-behind file.

The right solution

Sometimes, when using controls that do not support MVVM bindings, you want to implement something specific, but don’t break the pattern. An excellent example of this is to use the Image control as a button. There are other solutions such as the EventToCommand behavior to solve this specific case, but this example should be easy to understand for everyone. A different example (which is a bit more complex) is for example a GridView that returns IEnumerable<GridRow> instead of a model in the ItemsSource property of the grid. However, using this as an example would make it all a bit more complex than needed.

The Image class unfortunately has no Command property where we can bind a view model command to. So, how are we going to solve this problem without breaking the MVVM pattern? Let’s see how easy it is.

1) Fortunately, the Image does have MouseLeftButtonDown event. So, in our code-behind, we are going to subscribe to that event (or in xaml, whatever you prefer):

Xaml code (notice the OnLogoClicked for the Image):

   1: <Grid>
   2:     <Grid.RowDefinitions>
   3:         <RowDefinition Height="Auto" />
   4:         <RowDefinition Height="Auto" />
   5:     </Grid.RowDefinitions>
   6:     
   7:     <Grid.ColumnDefinitions>
   8:         <ColumnDefinition Width="300" />
   9:     </Grid.ColumnDefinitions>
  10:  
  11:     <Label Grid.Row="0">
  12:         <TextBlock TextWrapping="Wrap">
  13:             This example shows how to apply the MVVM pattern in a valid way, even with 
  14:             a non-bindable view such as using the Image control as a button (binding to a command).
  15:         </TextBlock>
  16:     </Label>
  17:  
  18:     <Image Grid.Row="1" Width="48" Height="48" HorizontalAlignment="Center" 
  19:            Source="/RightMvvm;component/Resources/Images/Catel.png"
  20:            Cursor="Hand" MouseLeftButtonDown="OnLogoClicked" />
  21: </Grid>

2) As soon as the event occurs, retrieve the view model from the DataContext and use that to invoke the command manually.

Code-behind:

   1: private void OnLogoClicked(object sender, EventArgs e)
   2: {
   3:     var viewModel = DataContext as MainWindowViewModel;
   4:     if (viewModel != null)
   5:     {
   6:         viewModel.ShowMessage.Execute();
   7:     }
   8: }

In the code-behind, we retrieve the view model from the DataContext, and immediately invoke the command.

Be careful if you are not using Catel, there is probably a security leak in your implementation of the Command (DelegateCommand or RelayCommand). First check if CanExecute returns true before invoking Execute.

3) In our view model, we don’t rely on any UI aspect at all

   1: public class MainWindowViewModel : ViewModelBase
   2: {
   3:     #region Constructor & destructor
   4:     /// <summary>
   5:     /// Initializes a new instance of the <see cref="MainWindowViewModel"/> class.
   6:     /// </summary>
   7:     public MainWindowViewModel()
   8:     {
   9:         ShowMessage = new Command(OnShowMessageExecute);
  10:     }
  11:     #endregion
  12:  
  13:     #region Properties
  14:     /// <summary>
  15:     /// Gets the title of the view model.
  16:     /// </summary>
  17:     /// <value>The title.</value>
  18:     public override string Title { get { return "Non-bindable views in MVVM the right way!"; } }
  19:     #endregion
  20:  
  21:     #region Commands
  22:     /// <summary>
  23:     /// Gets the ShowMessage command.
  24:     /// </summary>
  25:     public Command ShowMessage { get; private set; }
  26:  
  27:     /// <summary>
  28:     /// Method to invoke when the ShowMessage command is executed.
  29:     /// </summary>
  30:     private void OnShowMessageExecute()
  31:     {
  32:         var messageService = GetService<IMessageService>();
  33:         messageService.Show("You just did it the right way!");
  34:     }
  35:     #endregion
  36: }   

Can you see how loosely coupled the view model still is? We can unit test it any way we want. We can mock the IMessageService, and test whether the command can be executed without relying on any view.

RightMvvm.zip (1.01 mb) [Downloads: 118]

Tags: , ,

C# | Catel | MVVM | WPF

Comments (5) -

Paweł Stroiński
Paweł Stroiński Poland
7/23/2011 10:20:28 AM #

Generally agree. But why are you using command instead of simple metod here? You could do e.g. viewModel.ShowMessage(); Just curious.

Geert
Geert United Kingdom
7/25/2011 8:45:35 AM #

A command can be bindable as a command, and can also have a CanExecute. In theory, this *should* be the order in which software is developed:

1) Model
2) ViewModel
3) View

The view might be created by a designer or other software developer (who is, for example, more familiar with xaml). The logic exposes the functionality via a command. The view-developer can then decide how to use that command.

If it was a method, I would force the view-developer to manually call it from the code-behind.

Andrew Khmylov
Andrew Khmylov Belarus
7/25/2011 9:15:12 AM #

Thanks for the post!
It also makes sense to use Interactivity SDK behaviors. You can combine an EventTrigger with an InvokeCommandAction. And it also has a full Blend support, makes them flexible and easy to use.

Andrew

Geert
Geert Netherlands
7/25/2011 9:21:36 AM #

Thanks for your comment. That's what I normally do, but this is stated in the article:

"There are other solutions such as the EventToCommand behavior to solve this specific case, but this example should be easy to understand for everyone"

I agree with you, but sometimes you have a non-bindable views (some grids for example don't have a SelectedItem).

Paweł Stroiński
Paweł Stroiński Poland
7/25/2011 9:30:38 AM #

Nice explanation, thanks.

As I understand, in this context designer may choose control that do support MVVM bindings instead of Image. So binding make sense here.

Pingbacks and trackbacks (1)+

Comments are closed

About the Author

Geert van Horrik is a independent freelance software developer since January 1st, 2007. Since then he was been working on several projects from C++ to C# (WPF, ASP.NET, etc). Currently he loves to write his software using WPF (or Silverlight if WPF isn't an option).

Lately, Geert is spending a lot of time on Catel, a free open-source MVVM Framework for WPF and Silverlight. Actually, it's more than "just" an MVVM Framework, it's a complete application library!