Forked from mikernet/MethodBindingExtension.cs
Last active
September 22, 2016 19:58
-
-
Save alexis-/1b1d8b9791cba7c6fae5905c81d22766 to your computer and use it in GitHub Desktop.
Ultimate WPF Event Method Binding Extension
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Ultimate WPF Event Method Binding implementation by Mike Marynowski | |
// View the article here: http://www.singulink.com/CodeIndex/post/building-the-ultimate-wpf-event-method-binding-extension | |
// This is an incredibly useful piece of code. I had some issues with using it and tweaked some bits to get it working. | |
namespace Singulink.Windows.Data | |
{ | |
public class MethodBinding : MarkupExtension | |
{ | |
private static readonly List<DependencyProperty> StorageProperties = new List<DependencyProperty>(); | |
public string MethodName { get; } | |
public PropertyPath MethodTargetPath { get; } | |
public bool ThrowOnMethodMissing { get; set; } = true; | |
private object[] _methodArguments; | |
private DependencyProperty _methodTargetProperty; | |
private List<DependencyProperty> _argumentProperties = new List<DependencyProperty>(); | |
public MethodBindingExtension() | |
{ | |
} | |
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 }) { } | |
public MethodBindingExtension(string path, object arg0, object arg1, object arg2) : this(path, new object[] { arg0, arg1, arg2 }) { } | |
public MethodBindingExtension(string path, object arg0, object arg1, object arg2, object arg3) : this(path, new object[] { arg0, arg1, arg2, arg3 }) { } | |
public MethodBindingExtension(string path, object arg0, object arg1, object arg2, object arg3, object arg4) : this(path, new object[] { arg0, arg1, arg2, arg3, arg4 }) { } | |
public MethodBindingExtension(string path, object[] arguments) | |
{ | |
if (path == null) | |
throw new ArgumentNullException("path"); | |
_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); | |
} | |
public override object ProvideValue(IServiceProvider serviceProvider) | |
{ | |
var provideValueTarget = serviceProvider.GetService(typeof(IProvideValueTarget)) as IProvideValueTarget; | |
var target = provideValueTarget.TargetObject as FrameworkElement; | |
var eventInfo = provideValueTarget.TargetProperty as EventInfo; | |
if (target == null || eventInfo == null) | |
{ | |
throw new NotSupportedException("MethodBindingExtension can only be applied on a FramworkElement event."); | |
} | |
_methodTargetProperty = GetUnusedStorageProperty(target); | |
var methodTargetBinding = new Binding(); | |
methodTargetBinding.Path = MethodTargetPath; | |
target.SetBinding(_methodTargetProperty, methodTargetBinding); | |
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); | |
} | |
return CreateEventHandler(target, eventInfo); | |
} | |
private Delegate CreateEventHandler(FrameworkElement target, EventInfo eventInfo) | |
{ | |
EventHandler handler = (sender, eventArgs) => | |
{ | |
var methodTarget = target.GetValue(_methodTargetProperty); | |
if (methodTarget == null) | |
return; | |
var arguments = new object[_argumentProperties.Count]; | |
for (int i = 0; i < _argumentProperties.Count; i++) | |
{ | |
var argValue = target.GetValue(_argumentProperties[i]); | |
if (argValue is EventSenderParameter) | |
{ | |
argValue = sender; | |
} | |
else if (argValue is EventArgsParameter) | |
{ | |
argValue = eventArgs; | |
} | |
arguments[i] = argValue; | |
} | |
var methodTargetType = methodTarget.GetType(); | |
// Try invoking the method by resolving it based on the arguments provided | |
try | |
{ | |
methodTargetType.InvokeMember(MethodName, BindingFlags.InvokeMethod, null, methodTarget, arguments); | |
return; | |
} | |
catch (MissingMethodException) { } | |
// Couldn't match a method with the raw arguments, so check if we can find a method with the same name | |
// and parameter count and try to convert any XAML string arguments to match the method parameter types | |
var method = methodTargetType.GetMethods().Where(m => m.Name == MethodName && m.GetParameters().Length == arguments.Length).SingleOrDefault(); | |
if (method != null) | |
{ | |
var parameters = method.GetParameters(); | |
for (int i = 0; i < _methodArguments.Length; i++) | |
{ | |
if (arguments[i] == null) | |
{ | |
if (parameters[i].ParameterType.IsValueType) | |
goto Error; | |
} | |
else if (_methodArguments[i] is string && parameters[i].ParameterType != typeof(string)) | |
{ | |
// The original value provided for this argument was a XAML string so try to convert it | |
arguments[i] = TypeDescriptor.GetConverter(parameters[i].ParameterType).ConvertFromString((string)_methodArguments[i]); | |
} | |
else if (!parameters[i].ParameterType.IsInstanceOfType(arguments[i])) | |
{ | |
goto Error; | |
} | |
} | |
method.Invoke(methodTarget, arguments); | |
return; | |
} | |
Error: | |
if (ThrowOnMethodMissing) | |
throw new MissingMethodException($"Method binding could not find a method '{MethodName}' on target type '{methodTarget.GetType()}' that accepts the parameters provided."); | |
}; | |
return Delegate.CreateDelegate(eventInfo.EventHandlerType, handler.Target, handler.Method); | |
} | |
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(null)); | |
StorageProperties.Add(newProperty); | |
return newProperty; | |
} | |
private class ServiceProvider : IServiceProvider, IProvideValueTarget | |
{ | |
public object TargetObject { get; set; } | |
public object TargetProperty { get; set; } | |
public ServiceProvider(object targetObject, object targetProperty) | |
{ | |
TargetObject = targetObject; | |
TargetProperty = targetProperty; | |
} | |
public object GetService(Type serviceType) | |
{ | |
if (serviceType.IsInstanceOfType(this)) | |
return this; | |
return null; | |
} | |
} | |
} | |
public class EventSenderParameter : MarkupExtension | |
{ | |
#region Methods | |
/// <summary> | |
/// When implemented in a derived class, returns an object that is provided as the value | |
/// of the target property for this markup extension. | |
/// </summary> | |
/// <param name="serviceProvider"> | |
/// A service provider helper that can provide services for the | |
/// markup extension. | |
/// </param> | |
/// <returns>The object value to set on the property where the extension is applied.</returns> | |
public override object ProvideValue(IServiceProvider serviceProvider) | |
{ | |
return this; | |
} | |
#endregion | |
} | |
public class EventArgsParameter : MarkupExtension | |
{ | |
#region Methods | |
/// <summary> | |
/// When implemented in a derived class, returns an object that is provided as the value | |
/// of the target property for this markup extension. | |
/// </summary> | |
/// <param name="serviceProvider"> | |
/// A service provider helper that can provide services for the | |
/// markup extension. | |
/// </param> | |
/// <returns>The object value to set on the property where the extension is applied.</returns> | |
public override object ProvideValue(IServiceProvider serviceProvider) | |
{ | |
return this; | |
} | |
#endregion | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment