如何使用 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 是实际执行工作的 ButtonBase
和 MS.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
将参数传递给命令。这在支持命令的按钮或类似控件上可用。
我想创建一个简单的 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 是实际执行工作的 ButtonBase
和 MS.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
将参数传递给命令。这在支持命令的按钮或类似控件上可用。