Click here to Skip to main content
15,867,308 members
Articles / Web Development / HTML

Binding XAML controls directly to the ViewModel methods

Rate me:
Please Sign up or sign in to vote.
4.73/5 (5 votes)
22 Jun 2014CPOL5 min read 29.5K   236   20   3
In this article I want to show how binding controls directly to the View Model methods can simplify development of XAML UI, and completely eliminate manual work with Commands.

Typical problem

Developing client-side using XAML (WPF, Silverlight) MVVM pattern is commonly used. It means, that ViewModel business methods are bound to XAML View not by the events directly (as it was in WinForms and other technologies), but to the Commands, which should be published by ViewModel.

To invoke a ViewModel method, you need to publish a Command that is bound to control on one hand, and to the ViewModel method on the other hand. Thus, clicking the control, the appropriate method is called directly from the Command.

This approach ensures correct code working. However, using it in this mostly common implementation is uncomfortable and "wordy". It is quite correct to use the method, since the method contains the required business logic, but the Command is used solely to achieve compliance with MVVM pattern. Therefore, for each method, you have to create the Command, which will invoke it, but the code of this Command does not contain any additional logic. Though this code is absolutely trivial, it still takes place and the complexity of the code grows. Then, you also have to bind to this Command two ViewModel methods - one method, that executes the command, and the second method, that checks whether the command can be executed.

This approach is acceptable, if your ViewModel is simple, when there is not so much code. However, when you write large UI, you would definitely want to save your time and effort, and to automate this process.

Proposed solution

How can we remove the redundant code and simplify things? We need to ensure that the controls are bound not to the Commands, but to the methods directly. At this time, we must also retain two most important advantages of binding to the Commands:

  • automatic disabling of the controls, when the Command is not available
  • binding many controls to one Command.

Image 1

Image 2

 

Now I want to show, how we can achieve this.

First, let me remind you briefly the principles of binding, which is responsible for linking controls and Commands of ViewModel.


One of the view properties is DataContext. When we assign DataContext to the ViewModel instance, infrastructure explores ViewModel metadata using reflection and gets a list of public properties. Then these public properties are assigned to controls in XAML according to binding expressions. Binding would be successful, only if public property has the same name as the name specified in the binding expression, and if the type of public property is ICommand.

 

Image 3

Since we want to save our time and get rid of manual work, we need to teach ViewModel how to cheat Binding so that the ViewModel returned dynamically generated properties (Command type), and these properties were automatically bound to the XAML controls on the one hand, and to the necessary methods on the other hand.

Using Binding, infrastructure receives metadata ViewModel using a TypeDescriptor class. We can use this to deceive infrastructure, because we can change the behavior of TypeDescriptor to our advantage. This is achieved by interface ICustomTypeDescriptor implementation.

 

Image 4

This solution is quite easy, when you use the naming convention: i.e., the method to which a control is bound must have a name that is identical to the binding expression in XAML.

Implementation sample

Solution Structure is shown on the screenshot below:

Image 5

I started coding with writing acceptance test:

When click button “Message”,
Then show “Hello, World!” in text box.

Image 6

Here is the acceptance test code:

C#
[CodedUITest]
public class CommandToMethodBindingTests
{
    private UIMap map;
    private ApplicationUnderTest application;

    [TestInitialize]
    public void SetupTest()
    {
        var autName = ConfigurationManager.AppSettings["ApplicationUnderTest"];
        application = ApplicationUnderTest.Launch(autName);
    }

    [TestMethod]
    public void When_button_is_pressed_Then_messagebox_contains_a_HelloWorld_message()
    {
        UIMap.ClickButton();
        UIMap.AssertMessageIsChanged();
    }

    [TestCleanup]
    public void CleanupTest()
    {
        application.Close();
    }

    public TestContext TestContext { get; set; }

    public UIMap UIMap
    {
        get
        {
            if ((this.map == null))
            {
                this.map = new UIMap();
            }

            return this.map;
        }
    }
}

Here is the View:

C#
public partial class DialogView : Window
{
    public DialogView()
    {
        InitializeComponent();

        var viewModelFactory = new AutobindViewModelFactory();
        var viewModel = viewModelFactory.Create(this);

        DataContext = viewModel;
    }
}

Here is the ViewModel:

C#
public class DialogViewModel : AutobindViewModel
{
    public DialogViewModel(IBindableMethodFinder methodFinder, IMethodToCommandConverter methodToCommandConverter, IPropertyDescriptorMapper propertyDescriptorMapper)

        : base(methodFinder, methodToCommandConverter, propertyDescriptorMapper)
    {
    }

    public string Message { get; set; }

    public void ShowMessage()
    {
        Message = "Hello, World!";

        OnPropertyChanged("Message");
    }
}

Let's see how this infrastructure works beginning with unittests.

Here is one test method that checks that the infrastructure behaves as expected, i.e. returns a collection of properties of type ICommand, containing only one property - ShowMessage.

This test creates an instance of ViewModel, and then requests the TypeDescriptor class for the collection of ViewModel properties.

 

C#
[TestMethod]
public void Если_запросить_у_вьюмодели_список_свойств_То_в_нем_будет_свойство_ShowMessage_типа_ICommand()
    {
        // arrange
        var expectedPropertyMetadata = new { Name = "ShowMessage", Type = typeof(ICommand) };

        // act
       var viewModelProperties = TypeDescriptor.GetProperties(viewModel);
        var actualProperty = viewModelProperties[expectedPropertyMetadata.Name];

        // assert
       Assert.AreEqual(expectedPropertyMetadata.Name, actualProperty.Name);
        Assert.AreEqual(expectedPropertyMetadata.Type, actualProperty.PropertyType);
    }

Now let us turn to the class DialogViewModel. As you can see, this is a very simple class. It contains the method ShowMessage, and it inherits from AutobindViewModel. Look at the code in detail:

    public abstract class AutobindViewModel : ViewModel, ICustomTypeDescriptor
    {
        private readonly IBindableMethodFinder methodFinder;
        private readonly IMethodToCommandConverter methodToCommandConverter;
        private readonly IPropertyDescriptorMapper propertyDescriptorMapper;

        protected AutobindViewModel(IBindableMethodFinder methodFinder, IMethodToCommandConverter methodToCommandConverter, IPropertyDescriptorMapper propertyDescriptorMapper)
        {
            this.methodFinder = methodFinder;
            this.methodToCommandConverter = methodToCommandConverter;
            this.propertyDescriptorMapper = propertyDescriptorMapper;
        }

        #region Implementation of ICustomTypeDescriptor


        PropertyDescriptorCollection ICustomTypeDescriptor.GetProperties()
        {
            var propertyDescriptors = GetPropertyDescriptors();
            return new PropertyDescriptorCollection(propertyDescriptors);
        }
        
        #endregion Implementation of ICustomTypeDescriptor

        private PropertyDescriptor[] GetPropertyDescriptors()
        {            
            var methodsThatCanBindToCommands = methodFinder.FindMethodsFrom(this);
            var commandsToBind = ConvertMethodsToCommands(methodsThatCanBindToCommands);

            return propertyDescriptorMapper.MapToCollection(commandsToBind, this);
        }

        private IEnumerable<commandinfo> ConvertMethodsToCommands(IEnumerable<methodinfo> methodsToBind)
        {
            return from method in methodsToBind
                   let command = methodToCommandConverter.Convert(method, this)
                   select new CommandInfo(method.Name, command);
        }
    }

</methodinfo></commandinfo>

So, the class implements an interface AutobindViewModel ICustomTypeDescriptor. To make the infrastructure work the way we want, it is sufficient to implement only one of the methods of this interface, namely, the method GetProperties, which generates and returns a collection of dynamically generated properties.

Inside GetProperties method, we invoke method GetPropertiesDescriptor. It retrieves a collection of ViewModel methodsThatCanBindToCommands. The collection is converted into commands using the method ConvertMethodsToCommands.

Class AutobindViewModel uses three services.

The first service implements the interface IBindableMethodFinder.

It has implementation DefaultMethodFinder, which enumerates all ViewModel methods using reflection and finds among them those that can bind to command. As you see, everything is very simple.

    public class DefaultMethodFinder : IBindableMethodFinder
    {
        private readonly Func<methodinfo, bool=""> bindableMethodCriteria = method =>
            method.IsPublic && !method.IsAbstract && !method.IsConstructor && !method.IsStatic && method.GetParameters().Length <= 1 &&
            method.ReturnType == typeof(void) && !method.Attributes.HasFlag(MethodAttributes.SpecialName);

        #region Implementation of IBindableMethodFinder

        public IEnumerable<methodinfo> FindMethodsFrom(AutobindViewModel autobindViewModel)
        {
            return autobindViewModel
                .GetType()
                .GetMethods()
                .Where(bindableMethodCriteria);
        }

        #endregion Implementation of IBindableMethodFinder
    }

</methodinfo></methodinfo,>

Second service implements the interface IMethodToCommandConverter. It contains only one method Convert, which gets MethodInfo and ViewModel, and returns an instance of the Command.

public class DefaultMethodToCommandConverter : IMethodToCommandConverter
{
    #region Implementation of IMethodToCommandConverter

    public ICommand Convert(MethodInfo method, AutobindViewModel viewModel)
    {
        var arguments = new object[0];//TODO: потом понадобится передача параметров.
        Action<object> executeAction = o => method.Invoke(viewModel, arguments);
        return new DelegateCommand(executeAction);
    }

    #endregion Implementation of IMethodToCommandConverter
}</object>

And finally, the third service implements the interface IPropertyDescriptorMapper. It contains only one method MapToCollection, it generates an array of PropertyDescriptors, which we need to work with the interface ICustomTypeDescriptor.

    public class DefaultPropertyDescriptorMapper : IPropertyDescriptorMapper
    {
        #region Implementation of IPropertyDescriptorMapper

        public PropertyDescriptor[] MapToCollection(IEnumerable<commandinfo> commandsToBind, AutobindViewModel viewModel)
        {
            return commandsToBind
                .Select(info => new AutobindPropertyDescriptor(info.Name, info.Command))
                .ToArray();
        }

        #endregion Implementation of IPropertyDescriptorMapper
    }

</commandinfo>

That's all the code we needed to implement this idea. As you can see, it's really a very simple solution that eliminates a lot of unnecessary code, as well as mistakes because of inattention.

The figure below shows all of the major classes of infrastructure that support automatic binding.

 

Image 7

 

Conclusions and perspectives

You could see in the article a really simple solution of how binding controls directly to the View Model methods can simplify development of XAML UI, and completely eliminate manual work with Commands.

The example I took for this article is rather simple, so I want to say briefly what improvements you would need to use this approach in a larger and more complicated project:

  1. 1. It is clear, that sometimes using the naming convention is not enough (when the method name for some reasons cannot match the binding expression). Then you can add a special attribute to mark the methods.
  2. 2.  In real project you might also need to support method CanExecute, which determines whether the command can be executed.
  3. 3. Sometimes you may need to ensure sending arguments to the Command. 

 

License

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


Written By
Architect Smart Complexity
Russian Federation Russian Federation
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
QuestionWhy is it not compatible with Visual Studio 2010? Pin
kempofighter24-Jun-14 7:03
kempofighter24-Jun-14 7:03 
I could not find anything within the article that indicates the required versions of software. However, the project does not load into Visual Studio 2010 professional. I'm not sure why.
QuestionHave you looked at Caliburn Micro Pin
Bill Seddon24-Jun-14 1:00
Bill Seddon24-Jun-14 1:00 
QuestionRegarding Access to get full project in English Version Pin
kbk_1022-Jun-14 23:12
kbk_1022-Jun-14 23:12 

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

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