To make these scenarios easier, I've derived a class from CommandBindin. In which he added the ability to bind to ViewModel commands. You can set the binding to both Execute and PreviewExecute.
using System;
using System.Windows;
using System.Windows.Data;
using System.Windows.Input;
namespace CommonCore.AttachedProperties
{
public class CommandBindingHelper : CommandBinding
{
// Using a DependencyProperty as the backing store for MyProperty. This enables animation, styling, binding, etc...
protected static readonly DependencyProperty CommandProperty =
DependencyProperty.RegisterAttached(
"Command",
typeof(ICommand),
typeof(CommandBindingHelper),
new PropertyMetadata(null));
// Using a DependencyProperty as the backing store for MyProperty. This enables animation, styling, binding, etc...
protected static readonly DependencyProperty PreviewCommandProperty =
DependencyProperty.RegisterAttached(
"PreviewCommand",
typeof(ICommand),
typeof(CommandBindingHelper),
new PropertyMetadata(null));
public BindingBase Binding { get; set; }
public BindingBase PreviewBinding { get; set; }
public CommandBindingHelper()
{
Executed += (s, e) => PrivateExecuted(CheckSender(s), e.Parameter, CommandProperty, Binding);
CanExecute += (s, e) => e.CanExecute = PrivateCanExecute(CheckSender(s), e.Parameter, CommandProperty, Binding);
PreviewExecuted += (s, e) => PrivateExecuted(CheckSender(s), e.Parameter, PreviewCommandProperty, PreviewBinding);
PreviewCanExecute += (s, e) => e.CanExecute = PrivateCanExecute(CheckSender(s), e.Parameter, PreviewCommandProperty, PreviewBinding);
}
private static void PrivateExecuted(UIElement sender, object parameter, DependencyProperty commandProp, BindingBase commandBinding)
{
ICommand command = GetCommand(sender, commandProp, commandBinding);
if (command is not null && command.CanExecute(parameter))
{
command.Execute(parameter);
}
}
private static bool PrivateCanExecute(UIElement sender, object parameter, DependencyProperty commandProp, BindingBase commandBinding)
{
ICommand command = GetCommand(sender, commandProp, commandBinding);
return command?.CanExecute(parameter) ?? true;
}
private static UIElement CheckSender(object sender)
{
if (sender is not UIElement element)
throw new NotImplementedException("Implemented only for UIElement.");
return element;
}
private static ICommand GetCommand(UIElement sender, DependencyProperty commandProp, BindingBase commandBinding)
{
BindingBase binding = BindingOperations.GetBindingBase(sender, commandProp);
if (binding != commandBinding)
{
if (commandBinding is null)
{
BindingOperations.ClearBinding(sender, commandProp);
}
else
{
BindingOperations.SetBinding(sender, commandProp, commandBinding);
}
}
return (ICommand)sender.GetValue(CommandProperty);
}
}
}
An example of its use:
using Simplified; // This is the space of my ViewModelBase implementation
using System.Collections.ObjectModel;
namespace Core2023.SO.ASTERY.CommandInListItem
{
public class ListItemsViewModel : ViewModelBase
{
public ObservableCollection<string> Items { get; } = new("first second third fourth fifth".Split());
public RelayCommand RemoveCommand => GetCommand<string>(item => Items.Remove(item));
}
}
<Window x:Class="Core2023.SO.ASTERY.CommandInListItem.ListItemsWindow"
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:Core2023.SO.ASTERY.CommandInListItem"
xmlns:ap="clr-namespace:CommonCore.AttachedProperties;assembly=CommonCore"
mc:Ignorable="d"
Title="ListItemsWindow" Height="450" Width="800"
FontSize="20">
<Window.DataContext>
<local:ListItemsViewModel/>
</Window.DataContext>
<Window.CommandBindings>
<ap:CommandBindingHelper Command="Delete" Binding="{Binding RemoveCommand}"/>
</Window.CommandBindings>
<Grid>
<ItemsControl ItemsSource="{Binding Items}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<UniformGrid Rows="1" Margin="5">
<TextBlock Text="{Binding}"/>
<Button Content="Remove"
Command="Delete"
CommandParameter="{Binding}"/>
</UniformGrid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</Window>
What do i do if I want to set different name?
- The easiest way is to create a command in Window or (better) App resources.
<Application.Resources>
<RoutedUICommand x:Key="commands.Remove" Text="Delete Item" />
</Application.Resources>
<Button Content="Remove"
Command="{StaticResource commands.Remove}"
CommandParameter="{Binding}"/>
- Create a static property containing the command. But it should be created at the View level, not the ViewModel.
public static class MyCommands
{
public static RoutedUICommand Remove { get; }
= new RoutedUICommand("Delete Item", "Remove", typeof(MyCommands));
public static RoutedUICommand Add { get; }
= new RoutedUICommand("Add Item", "Add", typeof(MyCommands));
}
<Button Content="Remove"
Command="{x:Static local:MyCommands.Remove}"
CommandParameter="{Binding}"/>
- Adding a markup extension to the previous version to make it easier to use in XAML.
public class MyCommandsExtension : MarkupExtension
{
public string? CommandName { get; set; }
public MyCommandsExtension() { }
public MyCommandsExtension(string commandName) => CommandName = commandName;
public override object ProvideValue(IServiceProvider serviceProvider)
=> CommandName switch
{
nameof(MyCommands.Remove) => MyCommands.Remove,
nameof(MyCommands.Add) => MyCommands.Add,
_ => throw new NotImplementedException()
};
}
<Button Content="Remove"
Command="{local:MyCommands Remove}"
CommandParameter="{Binding}"/>