Invoking a Templated Control method from a DataTemplate

Are you looking for a way to invoke a method on a templated control from a datatemplate? Read further to learn how each item in a list calls the same method with different parameters

An often used method for manipulating lists, is to put an action button on each row in the List. But to what do we bind the button commands? Should each item have it’s own commands? Or do you want to execute a command on the parent viewmodel and pass the datacontext? But how do you do that?

Download the demo project at github.

list

First, the problem:

Templated Controls can access control properties with TemplateBinding like this:

<ItemsControl ItemsSource="{TemplateBinding Names}"/>
<Button Command="{TemplateBinding EditCommand}" />

Problems with DataTemplates

If your control template contains a DataTemplate, you need a RelativeSource with mode set to FindAncestor to get to your control properties.

Silverlight 4 and WinRT (Windows Store Apps) have no RelativeSource FindAncestor mode. But what if you have a Button in a DataTemplate that needs to execute some control method? There is a workaround where you bind to an ICommand via a ViewModelLocator but then your control needs to know about “the outside world”.

What we need is this:

control

Attached Properties to the rescue

With this ControlMethodInvoker attached property you can subscribe to the button_click and invoke a method on the control:

public static readonly DependencyProperty MethodPathProperty =
    DependencyProperty.RegisterAttached("MethodPath",
                                        typeof(string),
                                        typeof(ControlMethodInvoker),
                                        new PropertyMetadata(null, OnMethodPathChanged));

private static void OnMethodPathChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
    var source = d as Button;
    if (source != null) {
        var value = (string)e.NewValue;
        source.Click += (s, a) => {
            var path = value.Split(':');
            Type t = Type.GetType(path[0]);
            object tc = null;
            try {
                tc = GetVisualParent(source, t);
            } catch {
                throw new Exception("Cannot find method '" + path[1] + "' in templated control '" + path[0] + "'. Please check your MethodPath (namespace.controltype:methodname) property in the XAML");
            }
            MethodInfo methodInfo = tc.GetType().GetRuntimeMethod(path[1], new Type[1] { typeof(object) });
            ParameterInfo[] parameters = methodInfo.GetParameters();
            methodInfo.Invoke(tc, new object[] { source });
        };
    }
}

private static object GetVisualParent(DependencyObject elem, Type type) {
    if (elem == null) {
        throw new ArgumentNullException("elem");
    } else if (elem.GetType() == type) {
        return (object)elem;
    } else {
        return GetVisualParent(VisualTreeHelper.GetParent(elem), type);
    }
}

public static string GetMethodPath(DependencyObject obj) {
    return (string)obj.GetValue(MethodPathProperty);
}

public static void SetMethodPath(DependencyObject obj, string value) {
    obj.SetValue(MethodPathProperty, value);
}

You can attach this property to a Button. The MethodPath is a combination of Namespace.ControlClass:MethodName.
On a button click the attached property wil try to find a parent that matched the ControlClass type and invoke the Method while it passes the sender as an argument.

Assume your Control has this Method:

public void Edit(object sender) {
    EditCommand.Execute(((Button)sender).DataContext);
}

Then the MethodPath could be ProjectNamespace.ControlType:EditCommand.

This code is highly experimental. The attached property is made for Buttons only and if the Method cannot be found through reflection, it might crash spectacularly. So use at your own risk.