使用WPF绑定传递两个命令参数

178

我有一个命令,我正在使用以下标准语法从我的XAML文件执行:

<Button Content="Zoom" Command="{Binding MyViewModel.ZoomCommand}"/>

这个方法一开始很好用,但后来我发现为了让用户得到预期的操作结果,需要从视图中获取两个信息(特别是画布的宽度和高度)。

看起来可以将一个数组作为参数传递给我的命令,但我不知道如何在CommandParameter中指定绑定到我的两个画布属性的方式:

<Button Content="Zoom" 
        Command="{Binding MyViewModel.ZoomCommand" 
        CommandParameter="{Binding ElementName=MyCanvas, Path=Width}"/>

如何同时传递宽度和高度给我的命令?似乎使用 XAML 命令不可能实现这一点,我需要在代码后台中连接一个点击处理程序以获取此信息并将其传递给我的缩放方法。


请按照上述解决方案操作。我遇到了相同的问题。 - user1482689
6个回答

285

首先,如果您正在使用MVVM模式,通常您可以通过从视图绑定的单独属性将此信息提供给VM。这样可以避免向命令传递任何参数。

然而,您还可以进行多重绑定并使用转换器来创建参数:

<Button Content="Zoom" Command="{Binding MyViewModel.ZoomCommand">
    <Button.CommandParameter>
        <MultiBinding Converter="{StaticResource YourConverter}">
             <Binding Path="Width" ElementName="MyCanvas"/>
             <Binding Path="Height" ElementName="MyCanvas"/>
        </MultiBinding>
    </Button.CommandParameter>
</Button>

在你的转换器中:

public class YourConverter : IMultiValueConverter
{
    public object Convert(object[] values, ...)
    {
        return values.Clone();
    }

    ...
}

然后,在您的命令执行逻辑中:

public void OnExecute(object parameter)
{
    var values = (object[])parameter;
    var width = (double)values[0];
    var height = (double)values[1];
}

1
谢谢Kent - 这正是我在寻找的。我更喜欢你的第一种方法,这样VM就可以通过绑定知道视图的“状态”,而无需我传递任何参数,但我仍然可以测试它。我不确定这对我是否有用,因为我需要视图尽可能地放大并将此值传递给VM。如果我将其绑定,那么我不必在VM中设置宽度吗?这种情况下,VM与视图绑定了吗? - JasonD
在我的程序中,OnExecute方法的参数是一个包含空值的数组,但在转换器中,这些值是符合预期的。 - Alex David
3
为什么在点击按钮后,在OnExecute方法中发现参数为null,并且YourConverter.Convert()没有被调用? - SubmarineX
4
当按下按钮时,参数为null,这种情况不起作用。 - adminSoftDK
是的,我也遇到了空指针问题。所以我编辑了答案,在下面添加了丹尼尔提供的内容(如果这有帮助,请为他点赞)。 - Justin Pihony
显示剩余3条评论

41
在所选解决方案的转换器中,您应该添加values.Clone()以避免命令参数为空。
public class YourConverter : IMultiValueConverter
{
    public object Convert(object[] values, ...)
    {
        return values.Clone();
    }

    ...
}

7
嗨,使用Clone()添加后,它可以工作 :) 您能否解释一下,这样做有什么区别。因为我不明白为什么需要使用Clone()才能使其工作?谢谢。 - adminSoftDK
1
我可能错了,但是这个(第1267行)看起来像是原因: https://referencesource.microsoft.com/#PresentationFramework/src/Framework/System/Windows/Data/MultiBindingExpression.cs,1267 - maxp

15

在转换器中使用元组(Tuple),并在 OnExecute 方法中将参数对象强制转换回元组(Tuple)。

public class YourConverter : IMultiValueConverter 
{      
    public object Convert(object[] values, ...)     
    {   
        Tuple<string, string> tuple = new Tuple<string, string>(
            (string)values[0], (string)values[1]);
        return (object)tuple;
    }      
} 

// ...

public void OnExecute(object parameter) 
{
    var param = (Tuple<string, string>) parameter;
}

11

如果您的值是静态的,您可以使用 x:Array

<Button Command="{Binding MyCommand}">10
  <Button.CommandParameter>
    <x:Array Type="system:Object">
       <system:String>Y</system:String>
       <system:Double>10</system:Double>
    </x:Array>
  </Button.CommandParameter>
</Button>

如果您的值是静态的:什么是静态资源?例如,问题提到了画布宽度和高度。这些值不是恒定的,但它们是静态的吗?在这种情况下,XAML会是什么样子? - mins
3
我应该写“constant”而不是“static”。静态资源是在执行期间不会更改的资源。例如,如果您使用SystemColors,则应该使用DynamicResource而不是StaticResource,因为用户可以通过控制面板更改系统颜色。Canvas的WidthHeight不是资源,也不是静态的。它们是从FrameworkElement继承的实例属性。 - Maxence

7

关于在转换器中使用元组,最好使用“对象”而不是“字符串”,这样它可以适用于所有类型的对象,而不受“字符串”对象的限制。

public class YourConverter : IMultiValueConverter 
{      
    public object Convert(object[] values, ...)     
    {   
        Tuple<object, object> tuple = new Tuple<object, object>(values[0], values[1]);
        return tuple;
    }      
} 

那么Command中的执行逻辑可能是这样的。
public void OnExecute(object parameter) 
{
    var param = (Tuple<object, object>) parameter;

    // e.g. for two TextBox object
    var txtZip = (System.Windows.Controls.TextBox)param.Item1;
    var txtCity = (System.Windows.Controls.TextBox)param.Item2;
}

可以使用多绑定和转换器来创建参数(使用两个文本框对象)

<Button Content="Zip/City paste" Command="{Binding PasteClick}" >
    <Button.CommandParameter>
        <MultiBinding Converter="{StaticResource YourConvert}">
            <Binding ElementName="txtZip"/>
            <Binding ElementName="txtCity"/>
        </MultiBinding>
    </Button.CommandParameter>
</Button>

1
我喜欢这个,因为它更清楚转换器支持多少个参数。对于只有两个参数的情况非常好!(此外,您展示了XAML和Command执行函数以实现全面覆盖) - Caleb W.

0

这个任务也可以用不同的方法解决。而不是编写一个转换器并在XAML中扩展代码,您还可以在ViewModel中聚合各种参数。结果,ViewModel将具有包含所有参数的另一个属性。

以下是我当前应用程序的示例,它也让我处理了这个主题。 需要一个通用的RelayCommand:https://dev59.com/dGEh5IYBdhLWcg3wjEDn#22286816

在此处通过命令SaveAndClose扩展了ViewModelBase。泛型类型是表示各种参数的命名元组。

public ICommand SaveAndCloseCommand => saveAndCloseCommand ??= new RelayCommand<(IBaseModel Item, Window Window)>
    (execute =>
    {
        execute.Item.Save();
        execute.Window?.Close(); // if NULL it isn't closed.
    },
    canExecute =>
    {
        return canExecute.Item?.IsItemValide ?? false;
    });
private ICommand saveAndCloseCommand;

然后它包含一个根据泛型类型的属性:

public (IBaseModel Item, Window Window) SaveAndCloseParameter 
{ 
    get => saveAndCloseParameter ; 
    set 
    {
        SetProperty(ref saveAndCloseParameter, value);
    }
}
private (IBaseModel Item, Window Window) saveAndCloseParameter;

视图的XAML代码如下所示: (注意经典的点击事件)

<Button 
    Command="{Binding SaveAndCloseCommand}" 
    CommandParameter="{Binding SaveAndCloseParameter}" 
    Click="ButtonApply_Click" 
    Content="Apply"
    Height="25" Width="100" />
<Button 
    Command="{Binding SaveAndCloseCommand}" 
    CommandParameter="{Binding SaveAndCloseParameter}" 
    Click="ButtonSave_Click" 
    Content="Save"
    Height="25" Width="100" />

并在视图的代码后台中,然后评估点击事件,之后设置参数属性。

private void ButtonApply_Click(object sender, RoutedEventArgs e)
{
    computerViewModel.SaveAndCloseParameter = (computerViewModel.Computer, null);
}

private void ButtonSave_Click(object sender, RoutedEventArgs e)
{
    computerViewModel.SaveAndCloseParameter = (computerViewModel.Computer, this);
}

就我个人而言,我认为使用点击事件并不违反MVVM模式。程序流程控制仍然位于ViewModel的范畴。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接