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

by Dean 10. November 2010 07:08

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

Tags: , ,

WPF | Silverlight | MVVM | DataBinding | XAML | Events

WPF – Using Attached Properties For Animated Data Change Notifications

by Dean 29. October 2010 09:49

Working in the investment banking industry, we often need to feed live (changing) market data into our WPF controls, and often there is a requirement that certain movements (in value) are highlighted briefly so as to give a strong visual representation of data changes.

The best way to do this (in my opinion) is to have the TextBlock representing the value ‘blink’ a different colour when the value changes. You would normally include rules that for example stipulate that a movement of more than 3% is required before a notification.
You would also like movements ‘up’ to be shown in a different colour to movements ‘down’.

One key characteristic is that these background changes are very short lived – giving a quick ‘blink’ as a notification.

In fact, to add an extra feature, I have effectively removed the acceleration and deceleration phases of the animation to create a ‘toggle’ style blink (rather than pulse), so it suits our scenario better.

There are several ways that you could achieve this, but my favourite is with attached properties and animations.

Here’s the full code

public class ToggleEase : EasingFunctionBase
{
    protected override Freezable CreateInstanceCore()
    {
        return new ToggleEase();
    }

    protected override double EaseInCore(double normalizedTime)
    {
        if (normalizedTime == 1d)
            return 1d;
        return 0d;
    }
}

public class CellHighlight : DependencyObject
{
    public static readonly DependencyProperty ChangeSourceProperty =
        DependencyProperty.RegisterAttached("ChangeSource", typeof(double),
                                            typeof (CellHighlight),
                                            new PropertyMetadata(double.MinValue, DoPropertyChanged));

    public static readonly DependencyProperty MinimumPercentageMovementProperty =
        DependencyProperty.RegisterAttached("MinimumPercentageMovement", typeof(double),
                                            typeof(CellHighlight),
                                            new PropertyMetadata(0d));

    public static readonly DependencyProperty MovementUpColourProperty =
        DependencyProperty.RegisterAttached("MovementUpColour", typeof(Color),
                                            typeof(CellHighlight),
                                            new PropertyMetadata(Colors.LightGreen));

    public static readonly DependencyProperty MovementDownColourProperty =
        DependencyProperty.RegisterAttached("MovementDownColour", typeof(Color),
                                            typeof(CellHighlight),
                                            new PropertyMetadata(Colors.LightSalmon));

    public static void SetChangeSource(DependencyObject d, double useVal)
    {
        d.SetValue(ChangeSourceProperty, useVal);
    }

    public static void SetMinimumPercentageMovement(DependencyObject d, double perc)
    {
        d.SetValue(MinimumPercentageMovementProperty, perc);
    }

    public static void SetMovementUpColour(DependencyObject d, Color color)
    {
        d.SetValue(MovementUpColourProperty, color);
    }

    public static void SetMovementDownColour(DependencyObject d, Color color)
    {
        d.SetValue(MovementDownColourProperty, color);
    }

    private static void DoPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var oldv = (double) e.OldValue;
        var newv = (double) e.NewValue;

        if (oldv == double.MinValue)
            return;

        var percChange = (double) d.GetValue(MinimumPercentageMovementProperty);
        var upColour = (Color) d.GetValue(MovementUpColourProperty);
        var downColour = (Color) d.GetValue(MovementDownColourProperty);

        var diffPerc = Math.Abs((newv - oldv)/oldv*100);

        if (diffPerc <= percChange)
            return;

        var textblock = (TextBlock)d;

        SolidColorBrush brush = new SolidColorBrush();
        textblock.Background = brush;
        ColorAnimation anim = new ColorAnimation
                    {
                        To = oldv > newv ? downColour : upColour,
                        Duration = TimeSpan.FromMilliseconds(125),
                        AutoReverse = true,
                        EasingFunction = new ToggleEase()
                    };
        brush.BeginAnimation(SolidColorBrush.ColorProperty, anim);

    }

}
<Window x:Class="AnimatedCell.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
        xmlns:AnimatedCell="clr-namespace:AnimatedCell" 
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <ListView ItemsSource="{Binding}">
            <ListView.View>
                <GridView>
                    <GridView.Columns>
                        <GridViewColumn Header="ID" DisplayMemberBinding="{Binding ID}" />
                        <GridViewColumn Header="Product Name" DisplayMemberBinding="{Binding ProductName}" />
                        <GridViewColumn Header="Price">
                            <GridViewColumn.CellTemplate>
                                <DataTemplate>
                                    <TextBlock Text="{Binding MktPrice}" 
                                        AnimatedCell:CellHighlight.ChangeSource="{Binding MktPrice}" 
                                        AnimatedCell:CellHighlight.MinimumPercentageMovement="3"
                                        AnimatedCell:CellHighlight.MovementDownColour="Red" 
                                        AnimatedCell:CellHighlight.MovementUpColour="Green"   />
                                </DataTemplate>
                            </GridViewColumn.CellTemplate>
                        </GridViewColumn>
                    </GridView.Columns>
                </GridView>
            </ListView.View>
        </ListView>
    </Grid>
</Window>
internal class Trade : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    private double mktPrice;
    public double MktPrice
    {
        get { return mktPrice; }
        set
        {
            if (mktPrice == value)
                return;
            mktPrice = value;
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs("MktPrice"));
        }
    }

    public string ID { get; set; }
    public string ProductName { get; set; }

    internal static List<Trade> GetTestDataCollection()
    {
        var rand = new Random(Guid.NewGuid().GetHashCode());
        var data = new List<Trade>();
        Enumerable.Range(0, 10).ToList().ForEach((i) =>
            data.Add(new Trade { ID = rand.Next(1000, 9999).ToString(), 
                mktPrice = rand.NextDouble(), ProductName = "Prod" + i.ToString() }));
        return data;
    }

}
public partial class MainWindow : Window
{
    private readonly DispatcherTimer timer = new DispatcherTimer();
    public MainWindow()
    {
        InitializeComponent();

        var rand = new Random(Guid.NewGuid().GetHashCode());
        var data = Trade.GetTestDataCollection();
        timer.Interval = TimeSpan.FromSeconds(1);
        timer.Tick += (o, e) => data.ForEach((t) => t.MktPrice = rand.NextDouble());

        DataContext = data;

        timer.Start();
    }

}

And heres a quick screen show (obviously you cant see the animation as such)

celanim

 

Dean

Tags: , ,

C# | DataBinding | Events | WPF | Animation

Weak Events Without The Fuss With A ‘WeakReference’ Event Proxy

by Dean 28. February 2010 12:42

We all get clobbered at least once by memory leaks in our shiny new applications. One of the main causes of this is when objects that are expected to be garbage collected are not because we have not (or are unable) to unsubscribe them from the event handlers of longer living (static) objects.

The de-facto solution is to implement Microsoft’s ‘WeakEventManager’, but it takes a lot of coding, and adds another layer of complexity to your applications.

Wouldn’t it be great to do something a bit easier like:

public partial class MainWindow : Window
{
    private EventProxy<RoutedEventHandler> proxy = new EventProxy<RoutedEventHandler>();
    public MainWindow()
    {
        InitializeComponent();
        Loaded += WindowLoaded;
    }
 
    private void WindowLoaded(object sender, RoutedEventArgs e)
    {
        AddPublishers();
        var customer = new Customer();
        proxy.Subscribe("ButtonClick", customer.DoCustomerUpdate);
    }
 
    private void AddPublishers()
    {
        updateButton.Click += proxy.AttachPublisher("ButtonClick");
    }
}

 

You could simply subscribe to a number of event publishers on your long living objects (buttons in your main window for example), and then whenever you need to wire-up connections to these events in your short-lived objects, you can simply subscribe to them – as above.

The class that provides all of the ‘glue’ for this solution is called ‘EventProxy’

public class EventProxy<T> where T : class
{
    private readonly List<EventPublisher> subscriptions = new List<EventPublisher>();
 
    public void Subscribe(string sourceEvent, T handler)
    {
        var handlerDelegate = handler as MulticastDelegate;
        if (handlerDelegate == null)
            return;
        var evt = subscriptions.SingleOrDefault(k => k.Name == sourceEvent);
        if (evt == null)
            return;
        evt.TargetHandlers.Add(new EventSubscriber() { Method = handlerDelegate.Method, 
            Instance = new WeakReference(handlerDelegate.Target)});
    }
 
    public T AttachPublisher(string publisherEvent)
    {
        var evt = subscriptions.SingleOrDefault(k => k.Name == publisherEvent);
        if (evt == null)
        {
            evt = new EventPublisher() { Name = publisherEvent };
            evt.Publisher = Delegate.CreateDelegate(typeof (T), evt, EventPublisher.Target, true) as T;
            subscriptions.Add(evt);
        }
        return evt.Publisher;
    }
 
    private class EventPublisher
    {
        public static readonly MethodInfo Target = typeof(EventPublisher).GetMethod(
            "HandleEvent",BindingFlags.Instance | BindingFlags.Public);
        public string Name { get; set; }
        public T Publisher { get; set; }
        public List<EventSubscriber> TargetHandlers { get; private set; }
 
        public EventPublisher()
        {
            TargetHandlers = new List<EventSubscriber>();   
        }
 
        public void HandleEvent(object sender, object e)
        {
            TargetHandlers.RemoveAll(w => w.Instance.Target == null);
            foreach (var reference in TargetHandlers)
            {
                var inst = reference.Instance.Target;
                if (reference.Instance.IsAlive)
                    reference.Method.Invoke(inst, new[] {sender, e});
            }
        }
    }
 
    private class EventSubscriber
    {
        public WeakReference Instance { get; set; }
        public MethodInfo Method { get; set; }
    }
}

 

With this solution, as soon as the short-lived subscriber has gone out of scope, it can be garbage collected as it’s subscription to events is now via a ‘WeakReference’ and my custome ‘EventProxy’ class.

I have done some testing, and it seems to be robust and thread-safe, but Im sure there are room for improvements if you were serious about this type of approach.

 

Dean

Tags: ,

Events

RecentComments

Comment RSS
Disclaimer
The opinions expressed herein are my own personal opinions and do not represent my employer's view in anyway.

© Copyright 2012 Dean Chalk's Blog