Интерфейс ICommand. Как передать экземпляр в параметры метода?

Я использую MVVM.Есть список, в который по нажатию кнопки <Button Command="{Binding AddKernel}"/> добавляются элементы.

(View)Список:

<StackPanel Grid.Column="0" Grid.Row="1" VerticalAlignment="Top" >
        <ListBox ItemsSource = "{Binding Kernels}" HorizontalAlignment="Stretch">
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <StackPanel Orientation="Horizontal" Margin="5">
                        <Button Command="{Binding RemoveKernel}" Content="Delete" Margin="5" Width="40" Height="18" VerticalAlignment="Center" />
                        <TextBlock  Text="{Binding Title}" Margin="5" FontSize="16" VerticalAlignment="Center" />
                        <CheckBox IsChecked="{Binding IsVisualizationKernel}" Margin="5" FontSize="16"  VerticalAlignment="Center" />
                    </StackPanel>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
    </StackPanel> 

(Model)В модели есть метод, который должен удалять элемент списка. В параметры этого метода мне нужно передать экземпляр Kernel.

public static void RemoveKernel(object kernel)
    {
        if (kernel is Kernel)
        {
            Kernels.Remove((Kernel)kernel);
        }
        else
        {
            System.Console.WriteLine("TRIWOGA!!!");
        }
    } 

(ViewModel)Этот метод вызываю вот так:

public ICommand RemoveKernel
    {
        get
        {
            return new RelayCommand(Well.RemoveKernel);
        }
    }

Я не могу сообразить как передать в параметры метода RemoveKernel экземпляр Kernel?


Ответы (1 шт):

Автор решения: aepot

Зависит от того, какую именно реализацию класса RelayCommand вы используете. Не все реализации данного класса из тех что я видел, поддерживают передачу параметра в команду.

Допустим, что вы используете эту реализацию:

public class RelayCommand : ICommand
{
    private readonly Action<object> _execute;
    private readonly Func<object, bool> _canExecute;

    public event EventHandler CanExecuteChanged
    {
        add { CommandManager.RequerySuggested += value; }
        remove { CommandManager.RequerySuggested -= value; }
    }

    public RelayCommand(Action<object> execute, Func<object, bool> canExecute = null)
    {
        _execute = execute;
        _canExecute = canExecute;
    }

    public bool CanExecute(object parameter) => _canExecute == null || _canExecute(parameter);
    public void Execute(object parameter) => _execute(parameter);
}

Тогда передача элемента списка в команду будет выглядеть вот так

<Button Command="{Binding RemoveKernel}" CommandParameter="{Binding}" Content="Delete" Margin="5" Width="40" Height="18" VerticalAlignment="Center" />

CommandParameter={Binding} передает текущий объект, элемент списка в команду целиком.

А реализация вот так

private ICommand _removeKernel;
public ICommand RemoveKernel => _removeKernel ?? (_removeKernel = new RelayCommand(parameter =>
{
    // сопоставление шаблона 'is' позволяет сразу присвоить его результат переменной
    if (parameter is Kernel kernel)
        Kernels.Remove(kernel);
    else
        Debug.WriteLine("TRIWOGA!!!");
}));

Обратите внимание на местонахождение команды. Если она находится в ViewModel окна, а не внутри класса Kernel, а судя по тому, как вы вызываете удаление элемента, это так и есть, то вам нужно биндить команду по релативному пути к Window, через его DataContext.

<Button Command="{Binding DataContext.RemoveKernel, RelativeSource={RelativeSource AncestorType=Window}}" CommandParameter={Binding} Content="Delete" Margin="5" Width="40" Height="18" VerticalAlignment="Center" />

Еще можно запретить выполнение команды, задизаблить кнопку, если объект элемента коллекции не является классом Kernel, с помощью CanExecute команды:

private ICommand _removeKernel;
public ICommand RemoveKernel => _removeKernel ?? (_removeKernel = new RelayCommand(parameter =>
{
    // исключения при касте не будет, потому что это уже проверено в CanExecute
    Kernels.Remove((Kernel)parameter);
}, parameter => parameter is Kernel));

Кнопка сама задизаблится, если условие CanExecute вернет false. Это очень удобно. :)

→ Ссылка