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

如何在不替换RelayCommand的情况下实现按钮命令日志记录?

如何记录WPF中RelayCommand的执行日志?

问题背景

需要记录系统中所有绑定到按钮的命令执行情况,但当前系统按钮全部使用RelayCommand,找到的日志方案仅支持RoutedCommand,且无法将所有RelayCommand替换为RoutedCommand,需要针对RelayCommand实现日志功能。

现有RoutedCommand日志方案示例

该方案通过CommandManager全局钩子捕获路由命令的执行事件,但不适用于RelayCommand:

public partial class Window1 : System.Windows.Window
{
    public Window1()
    {
        InitializeComponent();

        CommandManager.AddPreviewExecutedHandler(this, this.OnPreviewCommandExecuted);
        CommandManager.AddCanExecuteHandler(this, this.OnCommandCanExecute);
    }

    void OnPreviewCommandExecuted(object sender, ExecutedRoutedEventArgs e)
    {
        StringBuilder msg = new StringBuilder();
        msg.AppendLine();

        RoutedCommand cmd = e.Command as RoutedCommand;
        string name = cmd == null ? "n/a" : cmd.Name;

        msg.AppendFormat("  Name={0}; Parameter={1}; Source={2}", name, e.Parameter, e.Source);
        msg.AppendLine();

        Logger.Log(msg.ToString());
    }

    void OnCommandCanExecute(object sender, CanExecuteRoutedEventArgs e)
    {
        // 示例中允许所有命令执行
        e.CanExecute = true;
    }
}

自身代码示例

RelayCommand定义

private RelayCommand _closePopupCommand = new RelayCommand(() => Window.PopUpViewModel = null);
public RelayCommand ClosePopupCommand
{
    get => _closePopupCommand;
    set
    {
        _closePopupCommand = value;
        RaisePropertyChanged();
    }
}

自定义路由事件代码

public readonly RoutedEvent ConditionalClickEvent = EventManager.RegisterRoutedEvent("test", RoutingStrategy.Direct, typeof(RoutedEventHandler), typeof(Button));

解决方案

由于RelayCommand是普通ICommand实现,不依赖WPF路由系统,无法通过CommandManager全局钩子捕获,可通过以下几种方式实现日志:

方案1:扩展RelayCommand类

继承原RelayCommand,重写Execute和CanExecute方法嵌入日志逻辑:

public class LoggingRelayCommand : RelayCommand
{
    private readonly string _commandName;

    public LoggingRelayCommand(Action execute, string commandName) : base(execute)
    {
        _commandName = commandName;
    }

    public LoggingRelayCommand(Action execute, Func<bool> canExecute, string commandName) : base(execute, canExecute)
    {
        _commandName = commandName;
    }

    public override void Execute(object parameter)
    {
        Logger.Log($"执行命令: {_commandName}; 参数: {parameter}");
        base.Execute(parameter);
    }

    public override bool CanExecute(object parameter)
    {
        bool canExecute = base.CanExecute(parameter);
        Logger.Log($"命令{_commandName}可执行状态: {canExecute}; 参数: {parameter}");
        return canExecute;
    }
}

使用时替换原RelayCommand:

private RelayCommand _closePopupCommand = new LoggingRelayCommand(() => Window.PopUpViewModel = null, nameof(ClosePopupCommand));
public RelayCommand ClosePopupCommand
{
    get => _closePopupCommand;
    set
    {
        _closePopupCommand = value;
        RaisePropertyChanged();
    }
}

方案2:装饰器模式包装现有命令

无需修改原RelayCommand类,通过装饰器包装命令添加日志:

public class CommandLogger : ICommand
{
    private readonly ICommand _innerCommand;
    private readonly string _commandName;

    public CommandLogger(ICommand innerCommand, string commandName)
    {
        _innerCommand = innerCommand;
        _commandName = commandName;
        _innerCommand.CanExecuteChanged += (s, e) => CanExecuteChanged?.Invoke(s, e);
    }

    public bool CanExecute(object parameter)
    {
        bool canExecute = _innerCommand.CanExecute(parameter);
        Logger.Log($"命令{_commandName}可执行状态: {canExecute}; 参数: {parameter}");
        return canExecute;
    }

    public void Execute(object parameter)
    {
        Logger.Log($"执行命令: {_commandName}; 参数: {parameter}");
        _innerCommand.Execute(parameter);
    }

    public event EventHandler CanExecuteChanged;
}

使用时包装原命令:

private ICommand _closePopupCommand = new CommandLogger(new RelayCommand(() => Window.PopUpViewModel = null), nameof(ClosePopupCommand));
public ICommand ClosePopupCommand
{
    get => _closePopupCommand;
    set
    {
        _closePopupCommand = value;
        RaisePropertyChanged();
    }
}

方案3:附加属性自动包装命令

通过WPF附加属性,自动给按钮的Command添加日志包装,无需修改ViewModel:

public static class CommandLogging
{
    public static readonly DependencyProperty LogCommandProperty =
        DependencyProperty.RegisterAttached("LogCommand", typeof(bool), typeof(CommandLogging), new PropertyMetadata(false, OnLogCommandChanged));

    public static bool GetLogCommand(DependencyObject obj)
    {
        return (bool)obj.GetValue(LogCommandProperty);
    }

    public static void SetLogCommand(DependencyObject obj, bool value)
    {
        obj.SetValue(LogCommandProperty, value);
    }

    private static void OnLogCommandChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        if (d is Button button && (bool)e.NewValue)
        {
            button.CommandProperty.AddValueChanged(button, OnButtonCommandChanged);
        }
    }

    private static void OnButtonCommandChanged(object sender, EventArgs e)
    {
        if (sender is Button button && button.Command is ICommand command && !(command is CommandLogger))
        {
            string commandName = button.Tag as string ?? command.GetType().Name;
            button.Command = new CommandLogger(command, commandName);
        }
    }
}

XAML中配置按钮:

<Button Command="{Binding ClosePopupCommand}" local:CommandLogging.LogCommand="True" Tag="ClosePopupCommand" Content="关闭弹窗"/>

对原选项的说明

  1. 无法实现:错误,上述方案均可实现RelayCommand的日志记录
  2. 必须替换为RoutedCommands:不需要,无需修改现有命令体系即可实现
  3. 使用类似RegisterEventHandlers的方法:可通过附加属性监听按钮Command变化,本质是包装命令而非直接注册事件,但能达到全局日志的效果

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.10 04:25:42