wpf - 如何创建自定义控件的事件以支持转换为命令?
问题描述
我创建的自定义控件有一个事件如下。
public class Editor : Control
{
...
public event EventHandler<AlarmCollection> AlarmFired = null;
...
}
当触发 TextChanged 事件时,将调用上述事件(如果不为 null)。
private async void TextArea_TextChanged(object sender, TextChangedEventArgs e)
{
...
this.AlarmFired?.Invoke(this, this.alarmList);
...
}
现在我尝试将上述事件从外部绑定到 ViewModel,如下所示。
<DataTemplate DataType="{x:Type documentViewModels:EditorTypeViewModel}">
<editor:Editor FontSize="15" FontFamily="Arial"
KeywordForeground="LightBlue">
<i:Interaction.Triggers>
<i:EventTrigger EventName="AlarmFired">
<mvvm:EventToCommand Command="{Binding AlarmFiredCommand}"
PassEventArgsToCommand="True"
EventArgsConverter="{localConverters:RemoveObjectConverter}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</editor:Editor>
</DataTemplate>
EditorTypeViewModel 的定义如下图所示。
public class EditorTypeViewModel : DocumentViewModel
{
public event EventHandler<AlarmCollection> AlarmFired = null;
public EditorTypeViewModel(string title) : base(title)
{
}
private RelayCommand<AlarmCollection> alarmFiredCommand = null;
public RelayCommand<AlarmCollection> AlarmFiredCommand
{
get
{
if (this.alarmFiredCommand == null)
this.alarmFiredCommand = new RelayCommand<AlarmCollection>(this.OnAlarmFired);
return this.alarmFiredCommand;
}
}
private void OnAlarmFired(AlarmCollection alarmInfos)
{
this.AlarmFired?.Invoke(this, alarmInfos);
}
}
当我执行上述程序时,没有调用与 RelayCommand 连接的 OnAlarmFired 方法。我试图找出原因,发现了一个可疑点。
一个可疑点是在调用Editor 的TextChanged 方法时,Editor 的AlarmFired 事件的值为null。它显示在下面。
我认为 AlarmFired 不为空,因为它会与 Command 连接,但事实并非如此。
我尝试做的是将 CustomControl 的事件绑定到 ViewModel 的命令并使用它。
例如,ListView 的 MouseDoubleClick 事件可以绑定到 MouseDoubleClickCommand,如下所示。当触发 MouseDoubleClick 事件时,MouseDoubleClickCommand 将获得控制权。
<ListView>
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseDoubleClick">
<mvvm:EventToCommand Command="{Binding MouseDoubleClickCommand}"
PassEventArgsToCommand="True"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</ListView>
我想创建一个事件来支持转换为ListView的MouseDoubleClick之类的Command (我不想在CustomControl中创建Command,因为事件的数量很多)
我应该怎么做才能实现这个目标?
感谢您的阅读。
解决方案
如果你想将你的事件参数传递给你的ViewModel
,你应该像这样创建一个新的Behavior
:
public class EventToCommandBehavior : Behavior<FrameworkElement>
{
private Delegate _handler;
private EventInfo _oldEvent;
// Event
public string Event
{
get => (string)GetValue(EventProperty);
set => SetValue(EventProperty, value);
}
public static readonly DependencyProperty EventProperty = DependencyProperty.Register("Event", typeof(string), typeof(EventToCommandBehavior), new PropertyMetadata(null, OnEventChanged));
// Command
public ICommand Command
{
get => (ICommand)GetValue(CommandProperty);
set => SetValue(CommandProperty, value);
}
public static readonly DependencyProperty CommandProperty = DependencyProperty.Register("Command", typeof(ICommand), typeof(EventToCommandBehavior), new PropertyMetadata(null));
// PassArguments (default: false)
public bool PassArguments
{
get => (bool)GetValue(PassArgumentsProperty);
set => SetValue(PassArgumentsProperty, value);
}
public static readonly DependencyProperty PassArgumentsProperty = DependencyProperty.Register("PassArguments", typeof(bool), typeof(EventToCommandBehavior), new PropertyMetadata(false));
private static void OnEventChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var beh = (EventToCommandBehavior)d;
if (beh.AssociatedObject != null) // is not yet attached at initial load
beh.AttachHandler((string)e.NewValue);
}
protected override void OnAttached()
{
AttachHandler(Event); // initial set
}
/// <summary>
/// Attaches the handler to the event
/// </summary>
private void AttachHandler(string eventName)
{
// detach old event
if (_oldEvent != null)
_oldEvent.RemoveEventHandler(AssociatedObject, _handler);
// attach new event
if (!string.IsNullOrEmpty(eventName))
{
var ei = AssociatedObject.GetType().GetEvent(eventName);
if (ei != null)
{
var mi = GetType().GetMethod("ExecuteCommand", BindingFlags.Instance | BindingFlags.NonPublic);
if (mi != null) _handler = Delegate.CreateDelegate(ei.EventHandlerType, this, mi);
ei.AddEventHandler(AssociatedObject, _handler);
_oldEvent = ei; // store to detach in case the Event property changes
}
else
throw new ArgumentException($"The event '{eventName}' was not found on type '{AssociatedObject.GetType().Name}'");
}
}
/// <summary>
/// Executes the Command
/// </summary>
// ReSharper disable once UnusedParameter.Local
private void ExecuteCommand(object sender, EventArgs e)
{
object parameter = PassArguments ? e : null;
if (Command == null) return;
if (Command.CanExecute(parameter))
Command.Execute(parameter);
}
}
并像这样使用:
<ListView>
<i:Interaction.Behaviors>
<behaviors:EventToCommandBehavior Command="{Binding AlarmFiredCommand}" Event="AlarmFired" PassArguments="True" />
</i:Interaction.Behaviors>
</ListView>
注意:如果你想传递你的参数应该为PassArguments
属性设置 True
推荐阅读
- json - 无法使用 JQ 获取 JSON 中的项目
- cmake - CLion 未能检测到 C/C++ 编译器 ABI 信息
- nhibernate - NHibernate事务下大数据查询
- excel - Excel宏:将单元格值与外部文件名/文件夹内容进行比较
- scala - Scalatests 在 Gradle 下是否默认并行运行?
- azure-devops - 构建代理发布时未找到应用程序依赖项
- java - 在 Java 7 中合并不同的对象类型
- javascript - Google Scripts 2D 数组按值排序
- mysql - 错误查询SQL“组函数使用无效”
- android - Android - 使用管理策略在多台设备上远程配置应用首选项