An another interesting aspect about Rob Eisenberg MVVM sample application is how it handles binding solely through conventions. That is no binding expressions in the XAML and no event hookups in the code behind. This is accomplished through a class named ViewModelBinder. This class takes a view model instance and a view instance. For each public property on the view model it will try to find a corresponding control with that name in the view, if found it will define the binding expression in code.
A simplified version of the method that handles the property bindings:
private static void BindProperties(FrameworkElement view, IEnumerable<PropertyInfo> properties) { foreach (var property in properties) { var foundControl = view.FindName(property.Name) as DependencyObject; if(foundControl == null) continue; DependencyProperty boundProperty; if(!_boundProperties.TryGetValue(foundControl.GetType(), out boundProperty)) continue; if(((FrameworkElement)foundControl).GetBindingExpression(boundProperty) != null) continue; var binding = new Binding(property.Name) { Mode = property.CanWrite ? BindingMode.TwoWay : BindingMode.OneWay, }; BindingOperations.SetBinding(foundControl, boundProperty, binding); } }When a control matching a property is found it will lookup in a dictionary the DependencyProperty that the view model property should be bound to. For example for a TextBox control it will bind to the TextBox.TextProperty. In the complete version of this method (please check the sample app) it handles hookup of bool properties to a Border control's visibility property, automatically configuring a BoolToVisibility converter on the binding. An important part of the method is where it checks if any manual (in the XAML) binding exists, if it already exists it skips the property/control. This allows you to override the conventions with manual bindings in edge cases.
There is a similar method that handles binding of public methods to commands:
private static void BindCommands(object viewModel, FrameworkElement view, IEnumerable<MethodInfo> methods, IEnumerable<PropertyInfo> properties) { 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); } }If a method with the same name as control is found it will create a new ReflectiveCommand and define the command binding. For example a method named ExecuteSearch matches the name of a button in the view. It will also look for a property that begins with Can and ends with the method name (for example CanExecuteSearch), if this property is found it will be used to determine if the command can be raised (which will disable and enable the button for example). Raising a property change for this CanExecuteSearch property will result in the ReflectiveCommand raising the CanExecuteChanged (defined on the ICommand WPF interface). This is pretty great, now all that boring command setup will be handled by infrastructure code! I am in the process of trying to incorporate some of these ideas into parts of a system I currently working on. The conventions defined in the Robs sample app will only get you so far, the nice thing is that it is now very easy to add new ones. The scenario I was working on today was how to hookup a DoubleClick event on a grid item to a method on the view model. If the convention based binding could look for a method ending with "_OpenGridItem" it could then check for the first part of the method, try to find a control matching that first part and hookup the DoubleClickEvent and then call the view model method. First we need to refactor the BindCommands method into something more extendible.
Fist we need an interface:
public interface IMethodBindingConvetion { void Apply(object viewModel, FrameworkElement view, MethodInfo method, IEnumerable<PropertyInfo> properties); }
Now we can move the already existing convention into it's own class:
public class CommandToMethodBindingConvention : IMethodBindingConvetion { public void Apply(object viewModel, FrameworkElement view, MethodInfo method, IEnumerable<PropertyInfo> properties) { var foundControl = view.FindName(method.Name); if (foundControl == null) return; var foundProperty = properties .FirstOrDefault(x => x.Name == "Can" + method.Name); var command = new ReflectiveCommand(viewModel, method, foundProperty); TrySetCommand(foundControl, command); } //... }
Now lets try to create the convention that captures DoubleClick on a infragistic Grid and calls the corresponding OpenGridItem method on the view model.
public class DoubleClickGridToMethodConvention : IMethodBindingConvetion { public void Apply(object viewModel, FrameworkElement view, MethodInfo method, IEnumerable<PropertyInfo> properties) { if (!method.Name.EndsWith("_OpenItem")) return; var gridName = method.Name.Replace("_OpenItem", ""); var gridControl = view.FindName(gridName) as XamDataGrid; if (gridControl == null) throw new ConventionBindingException("Could not find matching control for the method " + method.Name); gridControl.MouseDoubleClick += (sender, e) => { var item = FindClickedItem(sender, e); method.Invoke(viewModel, new object[] {item}); }; } }
If you want the method to be able to return IEnumerable<IResult>, something I talked about in yesterdays post (async programming using coroutines), then you need to handle the return value of the method invocation and pipe that through a ResultEnumerator.
Now that we have delegated the method conventions to specific classes the BindCommands method now looks like this:
private static readonly IList<IMethodBindingConvetion> _methodBindingConventions = new List<IMethodBindingConvetion>() { new CommandToMethodBindingConvention(), new DoubleClickGridToMethodConvention() }; private static void BindCommands(object viewModel, FrameworkElement view, IEnumerable<MethodInfo> methods, IEnumerable<PropertyInfo> properties) { foreach(var method in methods) { foreach (var methodBindingConvention in _methodBindingConventions) methodBindingConvention.Apply(viewModel, view, method, properties); } }
This method should perhaps be renamed to BindMethods. Anyway the point of this post was really to show how the convention based binding ideas in Robs sample application can be expanded to fit the application you are building.