不借助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
相关产品推荐
相关产品推荐

