MVVM – Simple Generic<T> Event Commands With Attached Properties

One of the challenges of MVVM is to favour command bindings over handling routed events, as currently all event handling must happen in the ‘code behind’ file rather than the ViewModel – which breaks the pattern.

With WPF4 we get the new InputBindings feature, that allows us to hook-up mouse and keyboard gestures to commands, but this still doesn't solve all scenarios where event handling seems to be our only option.

There are many elaborate and highly engineered strategies that I have seen to solve this, but maybe we could use a simple attached property implementation to achieve the desired effect.

Also, WPF bindings (including command bindings) don't support generics, but maybe we could use type inference to create a generic implementation.

In the example below, I have created an attached property that hooks up the ‘Selector’ (base control for ListBox and ListView) ‘SelectionChanged’ routed event to a custom ICommand that can be utilised in our ViewModel

Here's the attached property and command:

 

public class SelectionChangedCommand<T> : ICommand
{
    private readonly Action<List<T>, List<T>> action;

    public SelectionChangedCommand(Action<List<T>, List<T>> action)
    {
        this.action = action;
    }

    public void Execute(object parameter)
    {
        var args = parameter as SelectionChangedEventArgs;
        if (args == null)
            return;
        List<T> added = (
                from object item
                in args.AddedItems
                select (T)item
                ).ToList();
        List<T> removed = (
                from object item
                in args.RemovedItems
                select (T)item
                ).ToList();
        action(added, removed);
    }

    public bool CanExecute(object parameter)
    {
        return true;
    }

    public event EventHandler CanExecuteChanged;
}

public static class SelectionChangedCommand
{
    public static DependencyProperty BindProperty = DependencyProperty.RegisterAttached("Bind",
                    typeof(ICommand),
                    typeof(SelectionChangedCommand),
                    new PropertyMetadata(AttachCommand));

    public static void SetBind(DependencyObject d, ICommand command)
    {
        d.SetValue(BindProperty, command);
    }

    private static void AttachCommand(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        Selector selector = d as Selector;
        if (selector == null)
            return;
        ICommand oldCommand = e.OldValue as ICommand;
        ICommand newCommand = e.NewValue as ICommand;
        if (oldCommand != null || newCommand == null)
            return;
        selector.SelectionChanged += (o, a) => newCommand.Execute(a);
    }
}

Here’s a test ViewModel where we supply data to the View and well as our command binding:

public class MainViewModel
{
    public List<string> MainList 
    { get; set; }
    public SelectionChangedCommand<string> MainListChanged
    { get; set; }

    public MainViewModel()
    {
        MainList = new List<string> { 
            "testitem1", "testitem2", "testitem3", "testitem4", "testitem5" };
        MainListChanged = new SelectionChangedCommand<string>(DoChange);
    }

    private void DoChange(List<string> addedItems, List<string> removedItems)
    {
        // do viewmodel update here
    }
}

Heres our View

<Window x:Class="MVVMEvents.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
        xmlns:e="clr-namespace:MVVMEvents" 
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <ListBox ItemsSource="{Binding MainList}" SelectionMode="Multiple"
               e:SelectionChangedCommand.Bind="{Binding MainListChanged}"/>
    </Grid>
</Window>

And here’s where we hook up our ViewModel to the View (usual code):

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        DataContext = new MainViewModel();
    }
}

And there it is, we’ve hooked up a non-generic Routed Event (SelectionChanged) to a generic command command that fits nicely into our MVVM pattern implementation.

NOTE: This simple implementation doesn't support Weak Events, which would be a worthwhile improvement.

Dean

Add comment

  Country flag

biuquote
  • Comment
  • Preview
Loading

About the author

I am a senior .NET contract (freelance) software developer specialising in WPF and WinRT application development with C#, F#, C++. I mainly work in the Investment Bnking industry building performant and robust user interfaces for front office and middle office systems

RecentComments

Comment RSS

Month List