I've been practicing MVVM pattern and come across the problem which I don't know how to solve. The problem is pretty simple and I hope the solution as well. The point is that I'm trying to use a command and binding for an element, when I'm setting up it's style, but I can't do it at the same time.
I have the following style for ListBoxItem:
<Style x:Key="OptionDieStyle" TargetType="ListBoxItem">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBoxItem">
<Border Width="Auto"
BorderThickness="1.5"
CornerRadius="10"
Height="30"
Background="Transparent"
Margin="5">
<TextBlock Margin="5"
Text="{Binding}"
Foreground="White"
VerticalAlignment="Center"/>
<Border.InputBindings>
<MouseBinding MouseAction="LeftClick" Command="#Omitted"
</Border.InputBindings>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
This ListBox is filled with strings which are displayed in particular way because of the style. That means that when I want to handle user's click on that element, using command, I need to set DataContext, which contains ViewModel, where command is located, for this item, but if I do it no content will be displayed in ListBox Items. Certainly, I could set event for this Border like "MouseDown" but it would be the wrong way to use MVVM.
If you have some thoughts how to solve this using commands please share them.
CodePudding user response:
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, false);
CanExecute = (s, e) => e.CanExecute = PrivateCanExecute(CheckSender(s), e.Parameter, false);
PreviewExecuted = (s, e) => PrivateExecuted(CheckSender(s), e.Parameter, true);
PreviewCanExecute = (s, e) => e.CanExecute = PrivateCanExecute(CheckSender(s), e.Parameter, true);
}
private void PrivateExecuted(UIElement sender, object parameter, bool preview)
{
ICommand command = GetCommand(sender, preview);
ExecuteCommand(command, parameter);
}
private bool PrivateCanExecute(UIElement sender, object parameter, bool preview)
{
ICommand command = GetCommand(sender, preview);
return CanExecuteCommand(command, parameter);
}
private UIElement CheckSender(object sender)
{
if (sender is not UIElement element)
throw new NotImplementedException("Implemented only for UIElement.");
return element;
}
private ICommand GetCommand(UIElement sender, bool preview)
{
DependencyProperty commandProp = preview
? PreviewCommandProperty
: CommandProperty;
BindingBase commandBinding = preview
? PreviewBinding
: Binding;
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);
}
private void ExecuteCommand(ICommand command, object parameter)
{
if (command is not null && CanExecuteCommand(command, parameter))
{
command.Execute(parameter);
}
}
private bool CanExecuteCommand(ICommand command, object parameter)
{
if (command is null)
{
return true;
}
return command.CanExecute(parameter);
}
}
}
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>