如何使用 ReactiveUI 在 WPF UserControl 中使用带参数的多个命令

How to use multiple commands with parameters in a WPF UserControl using ReactiveUI

我想创建一个简单的 wpf 用户控件,它有 2 个命令,每个命令都有一个由 XAML 中的 DependencyProperty 设置的 CommandParameter。

但是绑定到 CommandParameter 的 属性 的值不会传递给 CommandHandler。我正在使用 ReactiveUI 来实现命令。

正在按预期调用命令,但是 CommandParameter 始终为 0。所以我有 2 个问题:

问题 1 即使我有一个名为“Command”的命令 DependencyProperty 和一个名为“CommandParameter”的 DependencyProperty 参数,该参数也不会传递。

问题2 Command 和 CommandParameter 属性如何链接在一起?它是命名约定吗?如果是,它是否支持多个命令(例如,Command1 是否链接到 Command1Parameter?)。

我知道将多个参数传递给单个命令,但这是一个不同的场景,我有 2 个单独的命令。在这种情况下,我实际上想将相同的值传递给两个命令。

我创建了一个简单的测试应用程序来演示该问题; MainWindow.xaml 是

<Window x:Class="WpfApp1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfApp1"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid>
        <StackPanel>
            <local:WidgetUserControl x:Name="Widget1" Command="{Binding WidgetCommand1}" 
                                     CommandParameter="1" Command2="{Binding WidgetCommand2}" />
            
            <local:WidgetUserControl x:Name="Widget2" Command="{Binding WidgetCommand1}" 
                                     CommandParameter="1" Command2="{Binding WidgetCommand2}"/>
        </StackPanel>
    </Grid>
</Window>

MainViewModel 是:

public class MainViewModel : ReactiveObject
{
    public ReactiveCommand<int, Unit> WidgetCommand1 { get; }
    public ReactiveCommand<int, Unit> WidgetCommand2 { get; }

    public MainViewModel()
    {
        WidgetCommand1 = ReactiveCommand.Create<int>(ExecuteWidgetCommand1);
        WidgetCommand2 = ReactiveCommand.Create<int>(ExecuteWidgetCommand2);
    }

    private void ExecuteWidgetCommand1(int arg)
    {
        MessageBox.Show($"Widget Command 1: Parameter {arg}");
    }

    private void ExecuteWidgetCommand2(int arg)
    {
        MessageBox.Show($"Widget Command 2: Parameter {arg}");
    }
}

WidgetUserControl XAML 是:

<UserControl x:Class="WpfApp1.WidgetUserControl"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:WpfApp1"
             mc:Ignorable="d" 
             d:DesignHeight="450" d:DesignWidth="800">
    <Grid>
    <Border BorderBrush="Aqua" BorderThickness="2">
        <StackPanel Orientation="Horizontal" Margin="5">
            <Button Content="Button1"  
                Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=UserControl, AncestorLevel=1}, Path=Command}"/>

            <Button Content="Button2" 
                Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=UserControl, AncestorLevel=1}, Path=Command2}"/>
        </StackPanel>
    </Border>
    </Grid>
</UserControl>

后面的 WidgetUserControl 代码如下所示:

 public partial class WidgetUserControl : UserControl
    {
        public WidgetUserControl()
        {
            InitializeComponent();
        }

        public static readonly DependencyProperty CommandProperty = DependencyProperty.Register("Command", typeof(ICommand), typeof(WidgetUserControl));
        public static readonly DependencyProperty CommandParameterProperty = DependencyProperty.Register("CommandParameter", typeof(object), typeof(WidgetUserControl), 
            new PropertyMetadata(OnCommandParameterCallback));

        public static readonly DependencyProperty Command2Property = DependencyProperty.Register("Command2", typeof(ICommand), typeof(WidgetUserControl));
        public static readonly DependencyProperty Command2ParameterProperty = DependencyProperty.Register("Command2Parameter", typeof(object), typeof(WidgetUserControl),
            new PropertyMetadata(OnCommandParameterCallback));

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

        public object CommandParameter
        {
            get => GetValue(CommandParameterProperty);
            set => SetValue(CommandParameterProperty, value);
        }

        public ICommand Command2
        {
            get => (ICommand)GetValue(Command2Property);
            set => SetValue(Command2Property, value);
        }

        public object Command2Parameter
        {
            get => GetValue(Command2ParameterProperty);
            set => SetValue(Command2ParameterProperty, value);
        }

        private static void OnCommandParameterCallback(DependencyObject sender, DependencyPropertyChangedEventArgs e)
        {
            // Not sure if this is needed
            var control = sender as WidgetUserControl;        
           control.CommandParameter = e.NewValue;
        }
    }

在您的代码中,您为每个按钮分配了两个命令。当这些按钮执行它们分配给的命令时,它们将传递自己的命令 parameter.Below 是实际执行工作的 ButtonBaseMS.Internal.Commands.CommandHelpers 的源代码。

protected virtual void OnClick()
    {
        RoutedEventArgs newEvent = new RoutedEventArgs(ButtonBase.ClickEvent, this);
        RaiseEvent(newEvent);

        MS.Internal.Commands.CommandHelpers.ExecuteCommandSource(this);
    }

[SecurityCritical, SecurityTreatAsSafe]
internal static void ExecuteCommandSource(ICommandSource commandSource)
{
    CriticalExecuteCommandSource(commandSource, false);
}    

[SecurityCritical]
    internal static void CriticalExecuteCommandSource(ICommandSource commandSource, bool userInitiated)
    {
        ICommand command = commandSource.Command;
        if (command != null)
        {
            object parameter = commandSource.CommandParameter;
            IInputElement target = commandSource.CommandTarget;

            RoutedCommand routed = command as RoutedCommand;
            if (routed != null)
            {
                if (target == null)
                {
                    target = commandSource as IInputElement;
                }
                if (routed.CanExecute(parameter, target))
                {
                    routed.ExecuteCore(parameter, target, userInitiated);
                }
            }
            else if (command.CanExecute(parameter))
            {
                command.Execute(parameter);
            }
        }
    }

所以基本上,如果你想写一个 UserControl 来支持 ICommand,你必须填写你自己的逻辑,比如何时以及如何执行它们。或者至少,将 CommandParameter 传递给实际执行命令的控件。

您可以使用 xaml 绑定中的 CommandParameter 将参数传递给命令。这在支持命令的按钮或类似控件上可用。