Передача двух параметров команды с использованием привязки WPF

У меня есть команда, которую я выполняю из моего файла XAML, используя следующий стандартный синтаксис:

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

Это работало нормально, пока я не понял, что мне нужно ДВА фрагмента информации из представления, чтобы эта операция завершилась так, как ожидают пользователи (в частности, ширина и высота холста).

Кажется, что возможно передать массив в качестве аргумента моей команде, но я не вижу способа указать привязку к двум моим свойствам холста в CommandParameter:

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

Как передать ширину и высоту моей команде? Не похоже, что это возможно при использовании команд из XAML, и мне нужно подключить обработчик щелчков в моем коде, чтобы эта информация передавалась моему методу масштабирования.

6 ответов

Решение

Во-первых, если вы работаете с MVVM, эта информация обычно доступна для вашей виртуальной машины через отдельные свойства, связанные с представлением. Это избавляет вас от необходимости передавать какие-либо параметры вашим командам.

Тем не менее, вы также можете использовать несколько связей и использовать конвертер для создания параметров:

<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];
}

В конвертере выбранного решения вы должны добавить values.Clone(), иначе параметры в команде end null

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

    ...
}

Используйте Tuple в Converter и в 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;
}

Если ваши значения статичны, вы можете использовать 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>

Что касается использования Tuple в Converter, было бы лучше использовать "объект" вместо "строка", чтобы он работал для всех типов объектов без ограничения объекта "строка".

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;
}

и multi-bind с конвертером для создания параметров (с двумя объектами TextBox)

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

Эту задачу можно решить и другим способом. Вместо того, чтобы программировать преобразователь и увеличивать код в XAML, вы также можете агрегировать различные параметры в ViewModel. В результате ViewModel имеет еще одно свойство, содержащее все параметры.

Пример моего текущего приложения, которое также позволило мне разобраться с темой. Требуется общая RelayCommand: /questions/44205135/pochemu-relaycommand/44205157#44205157

Здесь ViewModelBase расширяется командой SaveAndClose. Универсальный тип — это именованный кортеж, представляющий различные параметры.

      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.

Другие вопросы по тегам