[Code Index] by Mike Marynowski

programming for fun and work

Building the Ultimate WPF Event Method Binding Extension

Update (2016-09-25): A newer version of the binding with a revised syntax is available here: Updated Ultimate WPF Event Method Binding, but this is still a relevant read to explain the approach to making it work.

Seeing that .NET 4.5 added support in WPF for markup extensions on events, I looked around to see what was out there in terms of method binding support. I couldn't find anything that quite did everything I needed, so I set out to build the ultimate method binding extension. I had a few goals in mind:

  • Do not place any restrictions on the method signature.
  • Optionally pass in the event sender or the event args as an argument, but again without forcing a particular method signature pattern.
  • Allow the method target to be resolved with full PropertyPath support for methods nested under other properties.
  • Allow bindings to provide method arguments with full support for all features including ElementName, Path, Source, etc.
  • Allow the use of other common extensions to provide method arguments (i.e. StaticResource or x:Static).
  • Resolve the correct method by matching argument types to the method signature if more than one method with the same name exists.
  • Automatically convert method arguments passed as XAML strings to the required method parameter type, so that you don't have to use extensions / static resources or bloat the XAML by defining the binding in element syntax instead of attribute syntax, just to pass in a typed int or double or whatnot.

 

It's worthwhile to point out that I'm a huge advocate of clean XAML. We always work in split view in the designer and hand-clean up anything the XAML designer spits out that is less than ideal. Large projects benefit hugely from the ease of maintenance that beautiful XAML files provide. We use this awesome Visual Studio extension to style our XAML with a bunch of custom settings: XAML Styler Extension. One of my biggest pet peeves is the huge ugly blocks of <TransformGroup> that inevitably end up in any project with lots of animations where you want to be able to use Blend to design them. If you're interested in seeing how we solved that, I did a quick write up of that here: Getting Rid of Ugly TransformGroup Blocks in WPF.

Before I write even a single line of code for an implementation, I like to write out some examples of how I would ideally like to be able to use it, and then go about figuring out how to make that happen. All too often I see developers write out an implementation for a feature that meets the functional end goals, but the API sucks and the way you actually have to use it is less than ideal. I find that if you start with examples of how you would like to use it, you end up with a much better result.

In tune with my desire for beautiful XAML, these are some examples of how I wanted to use a method binding:

                    <!--  Basic usage  -->
                    <Button Click="{data:MethodBinding OpenFromFile}" Content="Open" />

                    <!--  Pass in a binding as a method argument  -->
                    <Button Click="{data:MethodBinding Save, {Binding CurrentItem}}" Content="Save" />

                    <!-- Pass in multiple arguments, resolve method based on argument count and convert XAML string argument to propery bool type automatically -->
                    <Button Click="{data:MethodBinding Save, {Binding CurrentItem}, {Binding SaveAsPath}, True}" Content="Save As" />

                    <!--  Another example of a binding, but this time to a property on another element  -->
                    <ComboBox x:Name="ExistingItems" ItemsSource="{Binding ExistingItems}" />
                    <Button Click="{data:MethodBinding Edit, {Binding SelectedItem, ElementName=ExistingItems}}" />

                    <!--  Pass in a hard-coded method argument, XAML string automatically converted to the proper type  -->
                    <ToggleButton Checked="{data:MethodBinding SetWebServiceState, True}"
                                  Content="Web Service"
                                  Unchecked="{data:MethodBinding SetWebServiceState, False}" />
                    
                    <!--  Pass in sender, and match method signature automatically -->
                    <Canvas PreviewMouseDown="{data:MethodBinding SetCurrentElement, {data:EventSender}, ThrowOnMethodMissing=False}">
                        <controls:DesignerElementTypeA />
                        <controls:DesignerElementTypeB />
                        <controls:DesignerElementTypeC />
                    </Canvas>

                     <!--  Pass in EventArgs  -->
                    <Canvas MouseDown="{data:MethodBinding StartDrawing, {data:EventArgs}}"
                            MouseMove="{data:MethodBinding AddDrawingPoint, {data:EventArgs}}"
                            MouseUp="{data:MethodBinding EndDrawing, {data:EventArgs}}" />

                    <!-- Support binding to methods further in a property path -->
                    <Button Content="SaveDocument" Click="{data:MethodBinding CurrentDocument.DocumentService.Save, {Binding CurrentDocument}}" />

I wanted this to work with the following pseudo signatures on the view model:

        public void OpenFromFile();
        public void Save(DocumentModel model);
        public void Save(DocumentModel model, string path, bool overwrite);
        public void Edit(DocumentModel model);
        
        public void SetWebServiceState(bool state);

        public void SetCurrentElement(DesignerElementTypeA element);
        public void SetCurrentElement(DesignerElementTypeB element);
        public void SetCurrentElement(DesignerElementTypeC element);

        public void StartDrawing(MouseEventArgs e);
        public void AddDrawingPoint(MouseEventArgs e);
        public void EndDrawing(MouseEventArgs e);

        // Last document example:
 
        public Document CurrentDocument { get; set; }
 
        public class Document
        {
            // Fetches the document service for handling this document
            public DocumentService DocumentService { get; }
        }

        public class DocumentService
        {
            public void Save(Document document);
        }

None of the examples of method binding implementations I could find had this level of flexibility, and usually relied on some kind of custom PropertyPath parser and resolver. This was undesirable for several reasons, the biggest being that I wanted all the features of all the Bindings to just work, exactly the way you expect them to including FallbackValue, ElementName, full PropertyPath with indexers, etc. I didn't want subtle differences in behavior, and reimplementing all the binding features and testing everything would have been cumbersome. Now we had a problem...we might have methods with 1 parameter, or 100 parameters, and we might have more than one method binding on an element. How do I attach all these bindings to the element? A single attached property can't hold all that information. I could hard-code a dozen or so attached properties and limit the number of method bindings and arguments to that, but that felt sloppy and unnecessarily arbitrary.

The Approach

I took an approach that I haven't seen used anywhere else yet, and it works very well. It delegates all the binding work to the framework and consumes the fruits of its labor. The only "custom resolution" required is to find and invoke the actual method on the method target object that the binding engine returns.

In order to store an arbitrary number of argument values with possible bindings and other markup extensions that need to be resolved, I turned to attached dependency properties. You can apply bindings to attached properties and easily fetch their resolved values, but how do we create an attached property that can store multiple arguments? Simple, we use multiple attached properties! To support an arbitrary number of method bindings on each element with an arbitrary number of arguments, I started with this:

    public class MethodBindingExtension : MarkupExtension
    {
        // List of attached properties shared by all MethodBindings
        private static readonly List<DependencyProperty> StorageProperties = new List<DependencyProperty>();

        private DependencyProperty GetUnusedStorageProperty(DependencyObject obj)
        {
            foreach (var property in StorageProperties)
            {
                if (obj.ReadLocalValue(property) == DependencyProperty.UnsetValue)
                    return property;
            }

            var newProperty = DependencyProperty.RegisterAttached("Storage" + StorageProperties.Count, typeof(object), typeof(MethodBindingExtension), new PropertyMetadata());
            StorageProperties.Add(newProperty);

            return newProperty;
        }
}

GetUnusedStorageProperty() checks the existing list of registered attached properties. If it finds an attached property that hasn't been used yet on the element then it returns it. If all properties have already been used on the element then it dynamically registers a new attached property, calls it "StorageX" where X is an incrementing number, and adds it to the list so that other MethodBindings can use it to store their values on their target element and we aren't needlessly registering more attached properties or leaking memory as UI elements are created and destroyed over and over and registering more and more properties. This will become clearer several later after you see how we use this method. 

I wanted to use as much of the binding engine as possible to resolve the target for our method. Because the methods being bound to can be nested down in a property path, we need to split the path into two components: everything up to just before the method name, which is delegated to the binding engine to find the method target object, and the method name, which gets resolved dynamically on that method target with reflection.

So using {data:MethodBinding CurrentDocument.DocumentService.Save ...} as an example, it will be split into:

  1. PropertyPath set to CurrentDocument.DocumentService that will be used in a binding we create to resolve the target object, and
  2. The method name Save to find on that target object

This is what the method binding constructors look like:

        private readonly object[] _methodArguments;

        public string MethodName { get; }
        public PropertyPath MethodTargetPath { get; }

        public MethodBindingExtension(string path) : this(path, null) { }
        public MethodBindingExtension(string path, object argument) : this(path, new object[] { argument }) { }
        public MethodBindingExtension(string path, object arg0, object arg1) : this(path, new object[] { arg0, arg1 }) { }
        // and so on...

        public MethodBindingExtension(string path, object[] arguments)
        {            
            _methodArguments = arguments ?? new object[0];
            
            int pathSeparatorIndex = path.LastIndexOf('.');
                        
            if (pathSeparatorIndex != -1)
            {
                MethodTargetPath = new PropertyPath(path.Substring(0, pathSeparatorIndex), null);
            }
            
            MethodName = path.Substring(pathSeparatorIndex + 1);
        }


With all the setup done, it's time to tackle the guts of the method binding logic. As a MarkupExtension, MethodBinding needs to implement the following method: object ProvideValue(IServiceProvider serviceProvider). This method will wire all the required bindings and values to attached properties (which are used later to resolve those values when the event is fired) and return the delegate that gets attached to the event. Here is how we add our method target binding:

            var provideValueTarget = serviceProvider.GetService(typeof(IProvideValueTarget)) as IProvideValueTarget;
            var target = provideValueTarget.TargetObject as FrameworkElement;

            _methodTargetProperty = GetUnusedStorageProperty(target);

            var methodTargetBinding = new Binding();
            methodTargetBinding.Path = MethodTargetPath;
            target.SetBinding(_methodTargetProperty, methodTargetBinding);

Notice we grab an unused storage property (using the method we created earlier) and apply the path we extracted in the constructor to the method target binding. Here is how all the arguments are stored:

            foreach (var argument in _methodArguments)
            {
                var argumentProperty = GetUnusedStorageProperty(target);
                var markupExtension = argument as MarkupExtension;

                if (markupExtension != null)
                {
                    var value = markupExtension.ProvideValue(new ServiceProvider(target, argumentProperty));
                    target.SetValue(argumentProperty, value);
                }
                else
                {
                    target.SetValue(argumentProperty, argument);
                }

                _argumentProperties.Add(argumentProperty);
            }

Again, an unused storage property is fetched, and it's value is either set to whatever was passed in, or if a nested markup extension was provided (such as a Binding) then it's ProvideValue method is called. We need to pass in a custom ServiceProvider object for nested markup extensions because the one we got as an argument to the ProvideValue method points to the event being set right now, not our attached properties. You can view the code for that in the full code listing at the bottom of this post, but it's very simple - it just returns our element as the target object and the attached property as the target property. At the end of the loop, each attached property used for storing argument values is added into the _argumentProperties list so that they can be accessed later to get their values when the event fires.

As you can see, each MethodBinding stores the method target and all the arguments in attached properties on the target element itself. If you create another MethodBinding later on another element, the attached properties created here are reused by GetUnusedStorageProperty() as they won't have values set on that element yet. The maximum number of attached properties that gets registered is thus equal to highest number of MethodBindings plus arguments on a single element.

The last step is to return from ProvideValue with a delegate that can be attached to the event:

            var eventInfo = provideValueTarget.TargetProperty as EventInfo;
            return CreateEventHandler(target, eventInfo);

The code for CreateEventHandler is a little bit lengthy and I will leave dissecting it as an exercise for the reader, but I'll summarize. It returns a handler typed to the event signature using Delegate.CreateDelegate that does the following:

  1. Gets the method target object by getting the value of the attached property we stored in _methodTargetProperty previously.
  2. Resolves all the argument values by getting the value of the attached properties we stored in _argumentProperties before.
  3. If any of those values are {data:EventSender} or {data:EventArgs} extensions, it replaces them with the event sender or EventArgs value, respectively.
  4. It attempts to invoke a method on the method target object that matches the name and signature of the resolved arguments.
  5. If that fails, possibly because one of the arguments is a XAML string and not typed to the method parameter type, it tries to find a single method on the target object that matches the name and number of arguments provided. If it does, it converts any argument values passed in as XAML strings to the types of the method parameters and attempts to invoke the method again.

It will only attempt step #5 if it finds exactly one method with the name. This is to avoid ambiguities in situations such as:

        public void Method(string text, int number);
        public void Method(int number, string text);

Which method should this call: "{data:MethodBinding Method, 5, 5}"? I don't know, so the code will throw an error. Note that if you add this method:

public void Method(string text, string text2);

There is no longer an ambiguity - the above method binding will always call this method. If you have a situation like this (which I gander can always be avoided but who knows...640k ought to be enough for anybody, right?) and you want to call Method(string, int), you will have to resort to something like this:

<Button Click="{data:MethodBinding Method, 5, {data:Int32 5}}" />

So that the second parameter is strongly typed to an int. You can find this and various other ways of strongly typing values passed to markup extensions here: wpf - how to pass an integer as ConverterParameter? - Stack Overflow

You can find the full class source code in this gist I setup for ya'll: Ultimate WPF Event Method Binding Extension.

And so there we have it, my super sexy method binding extension. Enjoy :)

 Hi, I am Mike, overlord of Singulink :)

This is where I post mostly software development related stuff I think might be useful and interesting.

Posts by Date