My previous post was about how to create Custom Markup Extensions for XAML. Until now, it was possible to use a markup extension in XAML to assign a value to a property, but couldn’t do the same to subscribe to an event. WPF 4.5 supports markup extensions for events. While WPF does not define a markup extension to be used for events, third parties are able to create a markup extension that can be used with events. When using the MVVM pattern, we often associates commands of the ViewModel with controls of the view, via binding mechanism. It has some drawbacks.Not all controls have Command property, and when exists, it corresponds to only one event of the control. The solution so far to this issue involved using the Interactivity assembly from the Expression Blend SDK. We can try something similar using the new feature, which will not require the Expression Blend SDK at all.
It is able to bind events directly to ViewModel methods, like this:
<Window x:Class="CustomMarkupForDateTime.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:custom="clr-namespace:CustomMarkupForDateTime" Title="MainWindow" Height="350" Width="525"> <Grid> <Button Width="300" Height="30" Click="{custom:CommandBinder ShowWindowCommand}" /> </Grid> </Window>
With the ShowWindow
method defined in the ViewModel
public class ViewModel { public ICommand ShowWindowCommand { get; set; } public ViewModel() { ShowWindowCommand = new DelegateCommand(ShowWindow); } private void ShowWindow(object obj) { System.Windows.MessageBox.Show("Hello World!"); } }
CommandBinder Markup Extension :
using System; using System.Windows; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Input; using System.Windows.Markup; using System.Reflection; namespace CustomMarkupForDateTime { public class CommandBinder : MarkupExtension { private string _commandPath; private ICommand _command; private Type _eventArgsType; public CommandBinder(string commandPath) { _commandPath = commandPath; } public override object ProvideValue(IServiceProvider serviceProvider) { Delegate customDelegate = null; IProvideValueTarget target = serviceProvider.GetService(typeof(IProvideValueTarget)) as IProvideValueTarget; if (target != null) { EventInfo eventInfo = target.TargetProperty as EventInfo; MethodInfo methodInfo = GetType().GetMethod("InvokeCommand", BindingFlags.NonPublic | BindingFlags.Instance); Type parameterType = null; if (eventInfo != null) parameterType = eventInfo.EventHandlerType; if (parameterType != null) { _eventArgsType = parameterType.GetMethod("Invoke").GetParameters()[1].ParameterType; customDelegate = Delegate.CreateDelegate(parameterType, this, methodInfo); } } return customDelegate; } private void InvokeCommand(object sender, RoutedEventArgs e) { var dataContext = (sender as FrameworkElement).DataContext; if (_commandPath != null) { _command = (ICommand)ParseCommandPath(dataContext, _commandPath); } var cmdParams = Activator.CreateInstance(_eventArgsType); if (_command != null && _command.CanExecute(cmdParams)) _command.Execute(cmdParams); } private Object ParseCommandPath(object target, string commandPath) { return target.GetType().GetProperty(commandPath).GetValue(target); } } }