Ramblings of General Geekery

Making WPF controls double-clickable

A common UI pattern features the ability to double-click on a control to go in “edit” mode. So for example, you have a TextBlock that shows you the name of an object, and you can double-click it to change it into a TextBox where you can edit that name. At this point, it’s easy to hook up the MouseDoubleClick event, or some other mouse event, but that’s not very MVVM-like, is it?

Thankfully, WPF gives us the awesome attached dependency property API, which we already used for adding auto-complete to TextBoxes. The goal is to be able to write something like this:

<TextBlock Text="{Binding Name}" mi:ExtendedCommands.DoubleClickCommand="{Binding EditNameCommand}" mi:ExtendedCommands.DoubleClickCommandParameter="42" />

When the TextBlock would get double-clicked, the “EditNameCommandcommand object on the DataContext would get called with “42” as the parameter. This is super easy to do:

public static class ExtendedCommands
{
    public static readonly DependencyProperty DoubleClickCommandProperty;
    public static readonly DependencyProperty DoubleClickCommandParameterProperty;

    static ExtendedCommands()
    {
        DoubleClickCommandProperty = DependencyProperty.RegisterAttached("DoubleClickCommand", typeof(ICommand), typeof(ExtendedCommands), new UIPropertyMetadata(null, OnDoubleClickCommandPropertyChanged));
        DoubleClickCommandParameterProperty = DependencyProperty.RegisterAttached("DoubleClickCommandParameter", typeof(object), typeof(ExtendedCommands), new UIPropertyMetadata(null));
    }

    public static ICommand GetDoubleClickCommand(DependencyObject obj)
    {
        return (ICommand)obj.GetValue(DoubleClickCommandProperty);
    }

    public static void SetDoubleClickCommand(DependencyObject obj, ICommand value)
    {
        obj.SetValue(DoubleClickCommandProperty, value);
    }

    public static object GetDoubleClickCommandParameter(DependencyObject obj)
    {
        return (object)obj.GetValue(DoubleClickCommandParameterProperty);
    }

    public static void SetDoubleClickCommandParameter(DependencyObject obj, object value)
    {
        obj.SetValue(DoubleClickCommandParameterProperty, value);
    }

    private static void OnDoubleClickCommandPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var element = d as UIElement;
        if (element != null)
        {
            if (e.OldValue == null && e.NewValue != null)
            {
                element.MouseDown += new MouseButtonEventHandler(Control_MouseDown);
            }
            else if (e.OldValue != null && e.NewValue == null)
            {
                element.MouseDown -= new MouseButtonEventHandler(Control_MouseDown);
            }
        }
    }

    private static void Control_MouseDown(object sender, MouseButtonEventArgs e)
    {
        if (e.ClickCount == 2)
        {
            var element = sender as UIElement;
            if (element != null)
            {
                var command = GetDoubleClickCommand(element);
                var parameter = GetDoubleClickCommandParameter(element);
                if (command != null && command.CanExecute(parameter))
                {
                    e.Handled = true;
                    command.Execute(parameter);
                }
            }
        }
    }
}

This code is pretty naive, but it gets the job done and illustrates quickly how to do it: just register 2 attached dependency properties in a new custom class, and whenever someone uses it on a UI element, start listening to the “MouseDown” event on that element and check against the “ClickCount” property to figure out if it’s a double-click. If it is indeed a double-click, just run the command that was specified for this control!

Obviously, you’ll need to handle error cases a bit better (like for instance if these properties are used on a non-UIElement), make it a bit better (for example register the MouseDoubleClick event instead of the MouseDown event if the target object is a Control), and other stuff like that. But, well, by now, you should know not to copy/paste code found on blogs without using your brain anyway.