Передача двух параметров команды с использованием привязки 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.