.NET & Funky Fresh

Syndication

News

  • <script type="text/javascript" src="http://ws.amazon.com/widgets/q?ServiceVersion=20070822&amp;MarketPlace=US&amp;ID=V20070822/US/bluspiconinc-20/8001/8b68bf4b-6724-40e7-99a5-a6decf6d8648"> </script>
MVVM Study Part 5: Convention over Configuration

In the last part I showed a very basic view model. I needed something in order to give those new to the concept of MVVM a concrete example. However, there are several aspects of that example that left me feeling uncomfortable; not to mention the myriad questions that seemed to arise in my mind about how something like this would work in a real-world situation. In this installment, I want to look at something simple we can do to improve the developer experience.

One of the things that bugs me about all the MVVM implementations I see (including the one I showed in part 4) is the need for command properties on the view model. Generally speaking, I think it’s good to remove as much infrastructure code as possible from view models, so that the intent of the view model is made clearer. It’s actually quite easy to do this if we establish a few simple conventions as follows:

  1. All View Model methods with the same name as a View’s ICommandSource should be wired as Commands.
  2. All properties that follow the name convention Can{MethodName} should be used as the “CanExecute” implementation of the Command that has been wired to the corresponding method.

Before we can make this work, we need to replace our old DelegateCommand with a new ReflectiveCommand:

public class ReflectiveCommand : ICommand
{
    private readonly object _model;
    private readonly PropertyInfo _canExecute;
    private readonly MethodInfo _execute;

    public ReflectiveCommand(object model, MethodInfo execute, PropertyInfo canExecute)
    {
        _model = model;
        _execute = execute;
        _canExecute = canExecute;

        var notifier = _model as INotifyPropertyChanged;
        if (notifier != null && _canExecute != null)
        {
            notifier.PropertyChanged += (s, e) =>{
                if (e.PropertyName == _canExecute.Name)
                    CanExecuteChanged(this, EventArgs.Empty);
            };
        }
    }

    public void Execute(object parameter)
    {
        _execute.Invoke(_model, null);
    }

    public bool CanExecute(object parameter)
    {
        if (_canExecute != null)
            return (bool)_canExecute.GetValue(_model, null);
        return true;
    }

    public event EventHandler CanExecuteChanged = delegate { };
}

ReflectiveCommand allows us to easily enable a command to execute an arbitrary method and to raise its CanExecuteChanged event whenever a particular property changes.  By combining this with our conventions, we can easily bridge the gap between our view and view model. Let’s take a look at a simple binder service that applies conventions to a View/View Model pair:

public static class Binder
{
    public static void Bind(object viewModel, FrameworkElement view)
    {
        var methods = viewModel.GetType().GetMethods();
        var properties = viewModel.GetType().GetProperties();

        foreach(var method in methods)
        {
            var foundControl = view.FindName(method.Name);
            if(foundControl == null) continue;

            var foundProperty = properties
                .FirstOrDefault(x => x.Name == "Can" + method.Name);

            var command = new ReflectiveCommand(viewModel, method, foundProperty);
            TrySetCommand(foundControl, command);
        }

        view.DataContext = viewModel;
    }

    private static void TrySetCommand(object control, ICommand command)
    {
        if (!TrySetCommandBinding<ButtonBase>(control, ButtonBase.CommandProperty, command))
            if (!TrySetCommandBinding<MenuItem>(control, MenuItem.CommandProperty, command))
                TrySetCommandBinding<Hyperlink>(control, Hyperlink.CommandProperty, command);
    }

    private static bool TrySetCommandBinding<T>(object control, DependencyProperty property, ICommand command)
        where T : DependencyObject, ICommandSource
    {
        var commandSource = control as T;
        if(commandSource == null) return false;
         
        BindingOperations.SetBinding(commandSource, property, new Binding { Source = command });
        return true;
    }
}

The binder uses reflection to look for conventions between the provided View and View Model. If found, it creates instances of ReflectiveCommand and binds them appropriately. Finally, it sets the View Model to the View’s DataContext.

Taking the same example as in Part 4, we can now create our View Model as follows:

public class CreateEmployeeViewModel : PropertyChangedBase
{
    private string _firstName;
    private string _lastName;

    public string FirstName
    {
        get { return _firstName; }
        set
        {
            _firstName = value; 
            NotifyOfPropertyChange("FirstName");
            NotifyOfPropertyChange("CanSave");
        }
    }

    public string LastName
    {
        get { return _lastName; }
        set
        {
            _lastName = value; 
            NotifyOfPropertyChange("LastName");
            NotifyOfPropertyChange("CanSave");
        }
    }

    public bool CanSave
    {
        get { return !string.IsNullOrEmpty(FirstName) && !string.IsNullOrEmpty(LastName); }
    }

    public void Save()
    {
        MessageBox.Show("Saved.");
    }
}

Notice the absence of commands.  I also factored out the following base class:

public abstract class PropertyChangedBase : INotifyPropertyChanged
{
    public virtual event PropertyChangedEventHandler PropertyChanged = delegate { };

    public void NotifyOfPropertyChange(string propertyName)
    {
        PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }
}

We’ll use the same view as last time, but without the command bindings:

<Window x:Class="MVVMStudy.PartFive.CreateEmployeeView"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        SizeToContent="Height"
        Width="400">
    <Grid Margin="4">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto" />
            <ColumnDefinition Width="*" />
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>
        
        <Label>First Name:</Label>
        <TextBox Text="{Binding FirstName}" 
                 Grid.Column="1"/>
        
        <Label Grid.Row="1">Last Name:</Label>
        <TextBox Text="{Binding LastName}" 
                 Grid.Row="1"
                 Grid.Column="1"/>
        
        <Button Content="Save"
                x:Name="Save"
                HorizontalAlignment="Right" 
                Margin="0 4 0 0"
                Grid.Row="2"
                Grid.ColumnSpan="2"/>
    </Grid>
</Window>

And the code behind becomes as simple as this:

public partial class CreateEmployeeView : Window
{
    public CreateEmployeeView()
    {
        InitializeComponent();
        Binder.Bind(new CreateEmployeeViewModel(), this);
    }
}

If you are willing to establish a few conventions for your application, it’s pretty amazing how much simpler your life can become…


Posted 12-14-2009 5:33 PM by Rob Eisenberg

[Advertisement]

Comments

Cameron MacFarland wrote re: MVVM Study Part 5: Convention over Configuration
on 12-14-2009 11:09 PM

Cool idea!

What about commands that take parameters? How does the CanExecute property handle them?

Bjoern Rochel wrote re: MVVM Study Part 5: Convention over Configuration
on 12-15-2009 3:05 AM

Very cool!

It seems to me that you could call your series also "Implementing the POCO ViewModel". A lot reminds me of the discussion we had with ORMs. Is that also something you wanted to achieve in the first place?

And besides that is this kind of convention also baked into Caliburn?

Regards

Bjoern

Alexander Sedov wrote re: MVVM Study Part 5: Convention over Configuration
on 12-15-2009 7:24 AM

Hello!

What about UserControls that can be used in the view? Is there a way to use binding conventions in that case?

p.s.: Sorry for my English.

Alexander Sedov wrote re: MVVM Study Part 5: Convention over Configuration
on 12-15-2009 7:26 AM

Hello!

What about UserControls that can be used in the view? Is there a way to use binding conventions in that case?

p.s.: Sorry for my English.

JES wrote re: MVVM Study Part 5: Convention over Configuration
on 12-15-2009 8:14 AM

Consider making a separate view model for the UserControl.

Rob Eisenberg wrote re: MVVM Study Part 5: Convention over Configuration
on 12-15-2009 11:11 AM

@Cameron

The solution I show doesn't handle parameters at all. But consider how you might extend the solution with another convention around this. Because, at the time of determining the convention binding, you have access to the element tree, you could, for example, pass the element into the ReflectiveCommand, hold a reference and then pass it's DataContext to the Command.Execute or it's Tag. You could even create a special attached property for defining the parameter. Over the years, I have found the need for parameters to be less than I initially thought. But, Caliburn's actions were designed to solve this problem well.

caliburn.codeplex.com/wikipage

@Bjoern

I'm not going the full POCO route with this series (or not initially). It's definitely harder to get rid of INPC than it is to get rid of the commands. You can do it with DynamicProxy, for example, but I'm not sure if I want to go there just yet. Caliburn, does support the exact convention I demonstrate here. It will also conventionally bind parameters based on your method declaration too. It does a whole lot of other fun stuff :) See the link above. One of the things I am doing with this series, is showing simple versions of some of the things Caliburn does, even if people don't want to commit to Caliburn, I think this gets the ideas out more and then people can build their own implementations if they decide it works for their scenario.

@Alexander and JES

I completely agree with you JES. This is a topic I am going to discuss in a future post on View Model composition and Hierarchical View Models. Alexander, generally if you feel a need to break out separate UserControls, you probably have separate behavior or at lease a more complex UI and you would benefit from breaking out separate view models as well.

brad wrote re: MVVM Study Part 5: Convention over Configuration
on 12-15-2009 3:09 PM

well that is certainly a bad idea.  Lets get MORE STRINGS INVOLVED!

FAIL.

Rob Eisenberg wrote re: MVVM Study Part 5: Convention over Configuration
on 12-15-2009 4:58 PM

@brad

If you are referring to the INPC goop, you can thank the WPF and Silverlight teams for that. If you don't like it, there are plenty of examples on the web that show how to do this with lambda expressions. As I mentioned in the comments above, you can also get rid of it by using DynamicProxy.

I would love to hear how you are doing databinding and property change notifications otherwise.

Ward Bell wrote re: MVVM Study Part 5: Convention over Configuration
on 12-16-2009 3:03 AM

Insightful, Rob!

I personally am not as bugged by command properties but I think where you're going ... encouraging people to consider auto-wiring with conventions ... is an important avenue to explore.

It comes at a cost. The auto-wiring road can lead to code that is not obvious and a pain to debug. It's hard to know where to stop.

For example, it is tempting to next factor away your Binder.Bind, delegating that job to an IoC "post-build" step. Then the simple act of 'asking" the IoC to build a View leads to automatic VM instantiation and autowiring of V and VM. Very cool ... and very opaque.

The more you embrace auto-wiring, the more you need great diagnostics built in ... so you don't tear your hair out trying to find what did (or did not) happen.

This is a caveat, not an objection.

As for the worry about "magic" strings; I think we shouldn't get hung up on the specifics of your sample code. You are illustrating your point(s); that is best done w/o the clutter of obscuring perfectionism.

This is good stuff!

Brian Genisio wrote re: MVVM Study Part 5: Convention over Configuration
on 12-16-2009 10:00 AM

I really like approaches like this.  The wiring of delegate commands, including knowing when to fire the CanSave event really bugs me.  It feels unnecessary.

In a recent post of mine, I outlined a similar approach... I use DynamicObject as a base class, and use the convention of Execute* and CanExecute* methods to generate a dynamic property that generates the command under the hood.  I also use a [DependsUpon] attribute to determine when to fire the CanExecute* method.

The biggest problem with my approach, is that DynamicObject binding is only supported in WPF... not even Silverlight 4 :(

More information is here: houseofbilz.com/.../adventures-in-mvvm-ndash-a-rails-inspired-viewmodel.aspx

Miguel Madero wrote re: MVVM Study Part 5: Convention over Configuration
on 01-03-2010 5:41 AM

It's a shame that we can't bind to dynamic objects in Silverlight. I hope this change in a future release.

Rob, your captachas are really cool, but can't be used from iPhone :(

Miguel Madero wrote re: MVVM Study Part 5: Convention over Configuration
on 01-03-2010 7:42 PM

I'm using this in Silverlight and works perfectly. Nice approach.

I was thinking on integrating it with an IoC, but usually my views are constructed in XAML, so the IoC is only constructing the ViewModels but don't know about the views to bind both together.

This is one of the reasons I'll deviate from this approach.

I'm thinking on using DynamicProxy to create a normal DelegatingCommand Property and the binding manually. The change isn't that different, the VM developer will still just create the CanExecute property and the Execute method, but the View instead of having a name that matches the command it will use Binding to set the Command Property.

This will also allow us to use other CommandSources.

Alexander Sedov wrote re: MVVM Study Part 5: Convention over Configuration
on 02-18-2010 4:06 PM

Hello Rob,

I'm still waiting for your post about View Model composition and Hierarchical View Models :) It's a realy interesting thing.

Rob Eisenberg wrote re: MVVM Study Part 5: Convention over Configuration
on 02-20-2010 12:49 PM

I will get around to the hierarchical models eventually :) Right now I'm preparing for Mix10.  My talk at Mix will encompass content I've blogged about so far in this series as well as additional content, which will eventually make it into this blog.  Though I may not explicitly discuss hierarchical VMs in the presentation, the sample will demonstrate some practical applications of this.

About The CodeBetter.Com Blog Network
CodeBetter.Com FAQ

Our Mission

Advertisers should contact Brendan

Subscribe
Google Reader or Homepage

del.icio.us CodeBetter.com Latest Items
Add to My Yahoo!
Subscribe with Bloglines
Subscribe in NewsGator Online
Subscribe with myFeedster
Add to My AOL
Furl CodeBetter.com Latest Items
Subscribe in Rojo

Member Projects
DimeCasts.Net - Derik Whittaker

Friends of Devlicio.us
Red-Gate Tools For SQL and .NET

NDepend

SlickEdit
 
SmartInspect .NET Logging
NGEDIT: ViEmu and Codekana
LiteAccounting.Com
DevExpress
Fixx
NHibernate Profiler
Unfuddle
Balsamiq Mockups
Scrumy
JetBrains - ReSharper
Umbraco
NServiceBus
RavenDb
Web Sequence Diagrams
Ducksboard<-- NEW Friend!

 



Site Copyright © 2007 CodeBetter.Com
Content Copyright Individual Bloggers

 

Community Server (Commercial Edition)