wpf 记录有关RelayCommand与RoutedCommand的事件

zzoitvuj  于 2022-11-30  发布在  其他
关注(0)|答案(2)|浏览(236)

我有以下问题:
我需要能够记录绑定到我代码中按钮的命令。我正在使用的系统的所有按钮都是RelayCommand。我找到了一个网站,它解释了如何做到这一点,但使用RoutedCommands。链接是一个帖子的按钮。下面是一个如何使用RoutedCommands的示例:

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)
                {
                    // For the sake of this demo, just allow all
                    // commands to be executed.
                    e.CanExecute = true;
                }
            }
        }

我的问题是这不适用于RelayCommands,我不能把所有的RelayCommands都改成RoutedCommands
有人知道如何用RelayCommands实现这一点吗?
下面是我代码中的一个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));

链接到实现RoutedCommands的网站:https://joshsmithonwpf.wordpress.com/2007/10/25/logging-routed-commands/
我尝试过RelayCommands,但它们似乎没有RoutedCommands的功能,我认为这与RoutedEvents有关,即RoutedCommands绑定。在我看来,有3个选项:
1.不可能
1.我必须将RelayCommands更改为RoutedCommands
1.使用类似RegisterEventHandlers的字符串

vmpqdwk3

vmpqdwk31#

也许听Click活动会适合你?

public MainWindow()
        {
            InitializeComponent();

            AddHandler(ButtonBase.ClickEvent, (RoutedEventHandler)OnClickLoger, true);

        }

        private void OnClickLoger(object sender, RoutedEventArgs e)
        {
            if (e.Source is ButtonBase button && button.Command is ICommand command)
            {
                if (command is RoutedCommand routedCommand)
                {
                    Debug.WriteLine($"Button: Name=\"{button.Name}\"; RoutedCommand=\"{routedCommand.Name}\"; CommandParameter={button.CommandParameter} ");
                }
                else
                {
                    var be = button.GetBindingExpression(ButtonBase.CommandProperty);
                    if (be is null)
                    {
                        Debug.WriteLine($"Button: Name=\"{button.Name}\"; Command=\"{command}\"; CommandParameter={button.CommandParameter} ");
                    }
                    else
                    {
                        Debug.WriteLine($"Button: Name=\"{button.Name}\"; Command Path=\"{be.ParentBinding.Path.Path}\"; CommandParameter={button.CommandParameter} ");
                    }
                }
            }
        }
klh5stk1

klh5stk12#

您可以在使用RelayCommand注册的execute命令处理程序中添加日志记录器输出,甚至可以将日志记录直接移到RelayCommand.Execute方法中。
根据您要记录的内容相关信息,您可能会决定实作可在检视内容中作业的Helper类别,例如,收集已叫用命令之命令来源(通常是控件)的相关信息。
下列范例遗漏取消订阅事件的unregister方法。您必须加入这些方法,才能允许取消订阅事件,以防止内存遗漏。这与类别行程常式无关,但对执行严修行程常式(例如RelayCommand.Executed事件)很重要。
1.为了提供与RoutedCommand相同的信息,例如源、目标和命令名,您需要扩展RelayCommand。为了避免通过引入派生类型破坏现有代码,您可以直接修改RelayCommand源。
以下命令(取自Microsoft文档:Relaying Command Logic)公开了一个Name和一个Target属性以及一个Executed事件。这两个属性是可选的,但如果要提供命令名称和命令目标(执行命令处理程序的类型,例如视图模型类)等信息,则建议使用这两个属性:

中继命令.cs

public class RelayCommand : ICommand
{
  /**** Added members ****/
  public class ExecutedEventArgs : EventArgs
  {
    public ExecutedEventArgs(object commandParameter)
    {
      this.CommandParameter = commandParameter;
    }

    public object CommandParameter { get; }
  }

  public string Name { get; }
  public object Target => this._execute.Target;
  public event EventHandler<ExecutedEventArgs> Executed;

  // Constructor to set the command name
  public RelayCommand(string commandName, Action<object> execute, Predicate<object> canExecute)
  {
    this.Name = commandName;

    if (execute == null)
      throw new ArgumentNullException("execute");
    _execute = execute;
    _canExecute = canExecute;
  }

  // Invoked by ICommand.Execute (added below)
  protected virtual void OnExecuted(object commandParameter)
    => this.Executed?.Invoke(this, new ExecutedEventArgs(commandParameter));

  /**** End added members ****/

  #region Fields 
  readonly Action<object> _execute;
  readonly Predicate<object> _canExecute;
  private readonly Action<string> _loggerDelegate;
  #endregion // Fields 
  #region Constructors 
  public RelayCommand(Action<object> execute)
    : this(string.Empty, execute, null)
  { }

  public RelayCommand(Action<object> execute, Predicate<object> canExecute)
    : this(string.Empty, execute, canExecute)
  { }
  #endregion // Constructors 
  #region ICommand Members 
  public bool CanExecute(object parameter)
  {
    return _canExecute == null ? true : _canExecute(parameter);
  }

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

  public void Execute(object parameter)
  {
    _execute(parameter);
    OnExecuted(parameter);
  }
  #endregion // ICommand Members 
}

1.然后创建一个数据类型来携带收集的命令上下文信息:

命令上下文.cs

public class CommandContext
{
  public Type CommandSource { get; }
  public Type CommandTarget { get; }
  public string CommandName { get; }
  public Type Command { get; }
  public object CommandParameter { get; }
  public string CommandSourceElementName { get; }
  public DateTime Timestamp { get; }

  public CommandContext(string commandName, Type command, object commandParameter, Type commandSource, string sourceElementName, Type commandTarget, DateTime timestamp)
  {
    this.CommandSource = commandSource;
    this.CommandTarget = commandTarget;
    this.CommandName = commandName;
    this.Command = command;
    this.CommandParameter = commandParameter;
    this.CommandSourceElementName = sourceElementName;
    this.Timestamp = timestamp;
  }
}

1.创建提供命令执行上下文的实际帮助器类CommandContextTracer
其思想是注册一个全局RoutedCommand处理程序来跟踪RoutedCommand调用并收集上下文信息。
对于“普通”ICommand实现,我们注册一个全局(类级别)ButtonBase.ClickEvent处理程序(假设所有命令都由ButtonBase调用)。
当然,您可以扩展该类,以提供一个方法来显式注册任何命令或使触发事件动态化(例如,侦听Click事件以外的任何其他事件)。
CommandContextTracer将接受它在命令执行时调用的Action<CommandContext>委托。
为了简单起见,类CommandContextTracer是一个static类。如果您使用依赖注入,我强烈建议将static类转换为一个具有示例成员的普通类。然后将一个共享示例注入到您的视图中(或一般定义命令的类)。虽然视图(即扩展UIElement的类型)可以匿名注册,如果命令不是由UIElement调用的,则其他类必须显式地注册它们的命令。

命令上下文跟踪器.cs

public static class CommandContextTracer
{
  private static Dictionary<object, Action<CommandContext>> LoghandlerTable { get; } = new Dictionary<object, Action<CommandContext>>();

  public static void RegisterCommandScopeElement(UIElement commandScopeElement, Action<CommandContext> logHandler)
  {
    if (!LoghandlerTable.TryAdd(commandScopeElement, logHandler))
    {
      return;
    }

    CommandManager.AddPreviewExecutedHandler(commandScopeElement, OnExecutingCommand);
    EventManager.RegisterClassHandler(commandScopeElement.GetType(), ButtonBase.ClickEvent, new RoutedEventHandler(OnEvent), true);
  }

  // Use this method to trace a command that is not invoked by a control.
  // TODO::Provide an Unregister(RelayCommand) method
  public static void RegisterRelayCommandInNonUiContext(RelayCommand relayCommand, Action<CommandContext> logHandler)
  {
    if (!LoghandlerTable.TryAdd(relayCommand, logHandler))
    {
      return;
    }

    relayCommand.Executed += OnNonUiRelayCommandExecuted;
  }

  private static void OnNonUiRelayCommandExecuted(object sender, RelayCommand.ExecutedEventArgs e)
  {
    var command = sender as RelayCommand;
    CommandContext context = new CommandContext(command.Name, command.GetType(), e.CommandParameter, null, string.Empty, command.Target.GetType());
    WriteContext(command, context);
  }

  private static void OnExecutingCommand(object sender, ExecutedRoutedEventArgs e)
  {
    if (e.Source is not ICommandSource commandSource)
    {
      return;
    }

    CommandContext context = CreateCommandContext(e, commandSource);
    WriteContext(sender, context);
  }

  private static void OnEvent(object sender, RoutedEventArgs e)
  {
    if (e.Source is not ICommandSource commandSource
      || commandSource.Command is RoutedCommand)
    {
      return;
    }

    CommandContext context = CreateCommandContext(e, commandSource);
    WriteContext(sender, context);
  }

  private static CommandContext CreateCommandContext(RoutedEventArgs e, ICommandSource commandSource)
  {
    string elementName = e.Source is FrameworkElement frameworkElement
      ? frameworkElement.Name
      : string.Empty;

    string commandName = commandSource.Command switch
    {
      RelayCommand relayCommand => relayCommand.Name,
      RoutedCommand routedCommand => routedCommand.Name,
      _ => string.Empty
    };

    Type? commandTarget = commandSource.Command switch
    {
      RelayCommand relayCommand => relayCommand.Target?.GetType(),
      RoutedCommand routedCommand => commandSource.CommandTarget?.GetType(),
      _ => null
    };

    return new CommandContext(
      commandName,
      commandSource.Command.GetType(),
      commandSource.CommandParameter,
      commandSource.GetType(),
      elementName,
      commandTarget,
      DateTime.Now);
  }

  public static void WriteContext(object contextScopeElement, CommandContext context)
    => LoghandlerTable[contextScopeElement].Invoke(context);
}

用法示例

主窗口.xaml.cs

第一种情况将记录源为控件的所有命令调用:

partial class MainWindow : Window
{
  public static RoutedCommand NextPageCommand { get; } = new RoutedCommand("NextPageCommand", typeof(MainWindow));

  public MainWindow()
  {
    InitializeComponent();
    this.DataContext = new TestViewModel();

    // Trace RoutedCommands and other ICommand
    CommandContextTracer.RegisterCommandScopeElement(this, WriteCommandContextToLogger);
  }
  
  // The actual log handler
  private void WriteCommandContextToLogger(CommandContext commandContext)
  {
    string message = $"[{commandContext.Timestamp}] CommandName={commandContext.CommandName}; Command={commandContext.Command}; Parameter={commandContext.CommandParameter}; Source={commandContext.CommandSource}; SourceElementName={commandContext.CommandSourceElementName}; Target={commandContext.CommandTarget}";

    Logger.Log(message);
    // Debug.WriteLine(message);
  }
}

文本视图模型.cs

第二个案例会记录来源不是控件的命令引动过程。
它还显示了如何创建修改后的RelayCommand的示例:

public class TestViewModel : INotifyPropertyChanged
{
  public RelayCommand TestCommand { get; }

  public TestViewModel()
  {
    this.TestCommand = new RelayCommand(nameof(this.TestCommand, ExecuteTestCommand);

    // Explicit command tracing. Only use when the command is not invoked by a control (non UI scenario)
    CommandContextTracer.RegisterRelayCommandInNonUiContext(this.TestCommand, WriteCommandContextToLogger);
  }

  private void WriteCommandContextToLogger(CommandContext commandContext)
  {
    string message = $"<From TestViewModel>[{commandContext.Timestamp}] CommandName={commandContext.CommandName}; Command={commandContext.Command}; Parameter={commandContext.CommandParameter}; Source={commandContext.CommandSource}; SourceElementName={commandContext.CommandSourceElementName}; Target={commandContext.CommandTarget}";

    Logger.Log(message);
    // Debug.WriteLine(message);
  }
}

主窗口.xaml

<Window>
  <StackPanel>
    <Button x:Name="RelayCommandTestButton"
            Content="RelayCommand"
            Command="{Binding TestCommand}"
            CommandParameter="1" />
    <Button x:Name="RoutedCommandTestButton"
            Content="RoutedCommand"
            Command="{x:Static local:MainWindow.NextPageCommand}"
            CommandParameter="2" />
  </StackPanel>
</Window>

日志消息

"[01/01/2022 00:00:00] CommandName=TestCommand; Command=Net.Wpf.RelayCommand; Parameter=1; Source=System.Windows.Controls.Button; SourceElementName=RelayCommandTestButton; Target=Net.Wpf.TestViewModel"  
"[01/01/2022 00:00:00] CommandName=NextPageCommand; Command=System.Windows.Input.RoutedCommand; Parameter=2; Source=System.Windows.Controls.Button; SourceElementName=RoutedCommandTestButton; Target="  
"<From TestViewModel>[01/01/2022 00:00:00] CommandName=TestCommand; Command=Net.Wpf.RelayCommand; Parameter=2; Source=unknown; SourceElementName=; Target=Net.Wpf.TestViewModel"

相关问题