You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

不借助MVVMLight,如何将sender与EventArgs传递给命令?

解决方案

因为你需要在纯UI项目中,将模板内元素的事件sender和EventArgs同时传递给命令,且不想引入额外包,以下是两种可行的实现方式:

方式一:自定义附加属性(完全原生,无额外依赖)

这种方式通过自定义WPF附加属性,自动绑定指定事件,并在事件触发时将sender和EventArgs打包传递给命令,完全不需要第三方库。

1. 实现附加属性类

using System;
using System.Reflection;
using System.Windows;

public static class EventToCommand
{
    // 命令附加属性
    public static readonly DependencyProperty CommandProperty =
        DependencyProperty.RegisterAttached(
            "Command",
            typeof(ICommand),
            typeof(EventToCommand),
            new PropertyMetadata(null, OnCommandChanged));

    // 事件名附加属性
    public static readonly DependencyProperty EventNameProperty =
        DependencyProperty.RegisterAttached(
            "EventName",
            typeof(string),
            typeof(EventToCommand),
            new PropertyMetadata(null, OnCommandChanged));

    // 属性访问器
    public static void SetCommand(DependencyObject obj, ICommand value) => obj.SetValue(CommandProperty, value);
    public static ICommand GetCommand(DependencyObject obj) => (ICommand)obj.GetValue(CommandProperty);

    public static void SetEventName(DependencyObject obj, string value) => obj.SetValue(EventNameProperty, value);
    public static string GetEventName(DependencyObject obj) => (string)obj.GetValue(EventNameProperty);

    // 属性变化时绑定/解绑事件
    private static void OnCommandChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var element = d as UIElement;
        if (element == null) return;

        var eventName = GetEventName(element);
        if (string.IsNullOrEmpty(eventName)) return;

        var eventInfo = element.GetType().GetEvent(eventName);
        if (eventInfo == null) return;

        // 移除旧事件绑定
        if (e.OldValue != null)
        {
            var handler = Delegate.CreateDelegate(eventInfo.EventHandlerType, typeof(EventToCommand).GetMethod(nameof(OnEventTriggered), BindingFlags.NonPublic | BindingFlags.Static));
            eventInfo.RemoveEventHandler(element, handler);
        }

        // 添加新事件绑定
        if (e.NewValue != null)
        {
            var handler = Delegate.CreateDelegate(eventInfo.EventHandlerType, typeof(EventToCommand).GetMethod(nameof(OnEventTriggered), BindingFlags.NonPublic | BindingFlags.Static));
            eventInfo.AddEventHandler(element, handler);
        }
    }

    // 事件触发时执行命令
    private static void OnEventTriggered(object sender, EventArgs e)
    {
        var element = sender as DependencyObject;
        if (element == null) return;

        var command = GetCommand(element);
        var param = new CommandParams { Sender = sender, EventArgs = e };
        if (command?.CanExecute(param) == true)
        {
            command.Execute(param);
        }
    }

    // 用于打包参数的辅助类
    public class CommandParams
    {
        public object Sender { get; set; }
        public EventArgs EventArgs { get; set; }
    }
}

2. 实现自定义RelayCommand(无依赖)

如果项目中没有现成的RelayCommand实现,可自行编写一个轻量版本:

using System;
using System.Windows.Input;

public class RelayCommand<T> : ICommand
{
    private readonly Action<T> _execute;
    private readonly Predicate<T> _canExecute;

    public RelayCommand(Action<T> execute) : this(execute, null) { }

    public RelayCommand(Action<T> execute, Predicate<T> canExecute)
    {
        _execute = execute ?? throw new ArgumentNullException(nameof(execute));
        _canExecute = canExecute;
    }

    public bool CanExecute(object parameter) => _canExecute?.Invoke((T)parameter) ?? true;

    public void Execute(object parameter) => _execute((T)parameter);

    public event EventHandler CanExecuteChanged
    {
        add => CommandManager.RequerySuggested += value;
        remove => CommandManager.RequerySuggested -= value;
    }
}

3. XAML中使用

在TabControl的HeaderTemplate中绑定附加属性:

<TabControl x:Name="TargetTabControl">
    <TabControl.HeaderTemplate>
        <DataTemplate>
            <TextBlock Text="{Binding}"
                      local:EventToCommand.EventName="MouseMove"
                      local:EventToCommand.Command="{Binding TabHeaderDragCommand, RelativeSource={RelativeSource AncestorType=Window}}" />
        </DataTemplate>
    </TabControl.HeaderTemplate>
    <!-- 你的TabItem内容 -->
</TabControl>

4. 代码隐藏中实现命令逻辑

在窗口的代码隐藏里定义命令并编写拖放逻辑:

using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;

public partial class MainWindow : Window
{
    public ICommand TabHeaderDragCommand { get; }

    public MainWindow()
    {
        InitializeComponent();
        TabHeaderDragCommand = new RelayCommand<EventToCommand.CommandParams>(ExecuteTabHeaderDrag);
    }

    private void ExecuteTabHeaderDrag(EventToCommand.CommandParams param)
    {
        var headerElement = param.Sender as UIElement;
        var mouseArgs = param.EventArgs as MouseEventArgs;
        if (headerElement == null || mouseArgs == null) return;

        // 查找当前Header关联的TabItem
        var tabItem = FindVisualParent<TabItem>(headerElement);
        if (tabItem == null) return;

        // 启动拖放操作
        if (mouseArgs.LeftButton == MouseButtonState.Pressed)
        {
            DragDrop.DoDragDrop(headerElement, tabItem.Content, DragDropEffects.Move);
            
            // 将TabItem从原TabControl移除,并创建独立窗口承载内容
            TargetTabControl.Items.Remove(tabItem);
            var newWindow = new Window 
            { 
                Content = tabItem.Content,
                Title = tabItem.Header.ToString(),
                Width = 400,
                Height = 300
            };
            newWindow.Show();
        }
    }

    // 辅助方法:向上查找视觉树中的父元素
    private T FindVisualParent<T>(DependencyObject child) where T : DependencyObject
    {
        var parent = VisualTreeHelper.GetParent(child);
        while (parent != null && !(parent is T))
        {
            parent = VisualTreeHelper.GetParent(parent);
        }
        return parent as T;
    }
}

方式二:基于现有Behaviors框架扩展(如果你已经在使用该框架)

如果你已经依赖Behaviors框架,可以自定义一个TriggerAction来同时传递sender和EventArgs:

1. 自定义TriggerAction

using System.Windows;
using System.Windows.Interactivity;

public class PassSenderAndArgsToCommand : TriggerAction<DependencyObject>
{
    public static readonly DependencyProperty CommandProperty =
        DependencyProperty.Register(nameof(Command), typeof(ICommand), typeof(PassSenderAndArgsToCommand));

    public ICommand Command
    {
        get => (ICommand)GetValue(CommandProperty);
        set => SetValue(CommandProperty, value);
    }

    protected override void Invoke(object parameter)
    {
        var param = new { Sender = AssociatedObject, EventArgs = parameter };
        if (Command?.CanExecute(param) == true)
        {
            Command.Execute(param);
        }
    }
}

2. XAML中使用

<TabControl x:Name="TargetTabControl">
    <TabControl.HeaderTemplate>
        <DataTemplate>
            <TextBlock Text="{Binding}">
                <i:Interaction.Triggers>
                    <i:EventTrigger EventName="MouseMove">
                        <local:PassSenderAndArgsToCommand Command="{Binding TabHeaderDragCommand, RelativeSource={RelativeSource AncestorType=Window}}" />
                    </i:EventTrigger>
                </i:Interaction.Triggers>
            </TextBlock>
        </DataTemplate>
    </TabControl.HeaderTemplate>
</TabControl>

命令逻辑和方式一一致,只需将参数类型改为dynamic或自定义打包类即可。


内容的提问来源于stack exchange,提问作者Notrum666

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.05 14:47:04