【问题标题】:How to create an event of the Custom control to support converting to the Command?如何创建自定义控件的事件以支持转换为命令?
【发布时间】:2019-12-25 10:31:59
【问题描述】:

我创建的自定义控件有一个事件如下。

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的Command并使用它。

例如,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 这样的命令转换 (我不想在CustomControl中创建Command,因为事件数量很多)

我应该怎么做才能实现这个目标?

感谢您的阅读。

【问题讨论】:

    标签: wpf event-handling command custom-controls


    【解决方案1】:

    如果你想将你的事件参数传递给你的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

    【讨论】:

    • 这是个好主意,但我真正想要的是像 Visual Studio 的基本控件一样使用 CustomControl,而无需创建其他模块。 (例如:ListView 示例)有没有办法实现这个目标?
    • 不容易支持。这里有一篇文章:weblogs.asp.net/alexeyzakharov/…
    • 哦,谢谢你的信息。如果明天早上没有更好的答案,那么我会采纳这篇文章。
    猜你喜欢
    • 2016-08-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-12-01
    • 2014-10-31
    • 2019-03-09
    • 2022-01-16
    相关资源
    最近更新 更多