Update (2021-04-29): MethodBinding is now posted to GitHub and NuGet.
After we started using our MethodBinding, we quickly ran into a few use cases that it could not accommodate very well so we ended up changing it a bit.
If you haven't read my original article about Building the Ultimate WPF Event Method Binding Extension then that's probably a good place to start. It covers all the basics for how the binding works and the underlying methods used are the same. This isn't going to be a long post, it is just going to cover the changes we made since the original article was posted.
One problem with the original was that the only way to specify which object the method should be invoked on was through a path string passed in like this:
<Button Content="SaveDocument" Click="{data:MethodBinding CurrentDocument.DocumentService.Save, {Binding CurrentDocument}}" />
This was fine as long as there was a path of properties from the control's DataContext
directly to the object with the method, but we found this wasn't always the case. Sometimes the method we wanted to invoke was on another control, or on the DataContext
of a templated parent, or available on an object through a static property.
So we split the path into two separate parameters: the method target and the method name. The example above is now written like this:
<Button Content="SaveDocument" Click="{data:MethodBinding {Binding CurrentDocument.DocumentService}, Save, {Binding CurrentDocument}}" />
The first parameter in this case is a binding that will find the target to call a Save
method on. We can get fancier with the first parameter and do things like:
<!-- Call a method on a static property -->
<Button Content="SaveDocument" Click="{data:MethodBinding {x:Static services:DocumentService.Instance}, Save, {Binding CurrentDocument}}" />
<!-- Call a method on another element -->
<Button Content="SaveDocument" Click="{data:MethodBinding {Binding ElementName=DocumentEditor}, Save, {Binding CurrentDocument}}" />
<!-- Call a method on the data context of templated parent -->
<Button Content="SaveDocument" Click="{data:MethodBinding {Binding RelativeSource={RelativeSource TemplatedParent}, Path=DataContext}, Save, {Binding CurrentDocument}}" />
<!-- Call a method on a static resource -->
<Button Content="SaveDocument" Click="{data:MethodBinding {StaticResource Document}, Save}" />
And well, why stop there...might as well make it completely flexible and allow XAML extensions on the method name as well :) This came in particularly handy in one situation where developers could expose user actions as methods on objects that weren't known by the UI until runtime so we setup the method names as bindings as well.
There's a shorthand syntax available if you are calling methods directly on the DataContext
of the element - you can omit the first parameter and just start with the method name, like so:
<Button Content="SaveDocument" Click="{data:MethodBinding Save}" />
The method binding code is mostly the same as the previous post so I won't go through it in detail again. It has a couple of bug fixes, the attached property code was refactored a bit and the method target and method name now get processed exactly the same as arguments by being assigned to attached properties.
Splitting the path into two components also mitigated an issue where ReSharper was showing an error because it was treating the first parameter as a property path but the method name at the end was not a valid property, which was kind of annoying:
Here is the updated source code: Updated Ultimate WPF Event Method Binding
Update (2018-02-01): The code above has been updated with the following features:
- Support for all event types has been added.
- The method target can be omitted even when using method parameters. If the first argument to the method binding is the name of the method then the current data context will be implicitly used as the method target.
- Properties on the event args object can be mapped to arguments via syntax like
{data:EventArgs ButtonState, Converter={c:ButtonPressedConverter}}
, so your methods can now have more natural arguments if you need to get info from the event args.