CommandParameter always empty wpf MVVM - c#

I want to get the NamePlaylistof the Item i click on a MenuContext. But the parameter is always empty i don't know why.
It's a Menu in ListView
XML
<MenuItem Header="Add" ItemsSource="{Binding Path=ItemSourcePlaylist}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<i:InvokeCommandAction Command="{Binding TestCall}" CommandParameter="{Binding NamePlaylist}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
<MenuItem.ItemTemplate>
<DataTemplate>
<TextBlock Name="NamePlaylistText" Text="{Binding Path=NamePlaylist}"/>
</DataTemplate>
</MenuItem.ItemTemplate>
</MenuItem>
CommandProvider
public class CommandProvider : ICommand
{
#region Constructors
public CommandProvider(Action<object> execute) : this(execute, null) { }
public CommandProvider(Action<object> execute, Predicate<object> canExecute)
{
_execute = execute;
_canExecute = canExecute;
}
#endregion
#region ICommand Members
public event EventHandler CanExecuteChanged;
public bool CanExecute(object parameter)
{
return _canExecute != null ? _canExecute(parameter) : true;
}
public void Execute(object parameter)
{
if (_execute != null)
_execute(parameter);
}
public void OnCanExecuteChanged()
{
CanExecuteChanged(this, EventArgs.Empty);
}
#endregion
private readonly Action<object> _execute = null;
private readonly Predicate<object> _canExecute = null;
}
Call
public ICommand TestCall { get { return new RelayCommand(obj => this._settingsFunction.TestFunction(obj)); } }
Function
public void TestFunction(object o)
{
var tmp = o as string;
Console.WriteLine(tmp);
Console.WriteLine("TestMdr");
}

If I understand your view model then ItemSourcePlaylist and TestCall are in one class and NamePlaylist is the property of the ItemSourcePlaylist item. If that's the case I would suggest use ItemsContainerStyle and bind Command property to parent's DataContext and CommandParameter to current item's DataContext
<MenuItem Header="Add" ItemsSource="{Binding Path=ItemSourcePlaylist}">
<MenuItem.ItemContainerStyle>
<Style TargetType="{x:Type MenuItem}">
<Setter Property="Command" Value="{Binding RelativeSource={RelativeSource AncestorType={x:Type MenuItem}}, Path=DataContext.TestCall}"/>
<Setter Property="CommandParameter" Value="{Binding NamePlaylist}"/>
</Style>
</MenuItem.ItemContainerStyle>
<MenuItem.ItemTemplate>
<DataTemplate>
<TextBlock Name="NamePlaylistText" Text="{Binding Path=NamePlaylist}"/>
</DataTemplate>
</MenuItem.ItemTemplate>
</MenuItem>

Related

"Collection was modified error", but only on Key press and not Button click with same method call

It seems like the "Collection was modified; enumeration operation may not execute" error has popped up a lot here, but none of them seem to solve the issue I'm having. The odd problem I'm having is that the exception only presents itself when I press a Key which is bound to a command, and not when I click a button tied to the same command.
XAML:
<Window.InputBindings>
<KeyBinding Key="F4" Command="{Binding UpdateItemsA}"/>
<KeyBinding Key="F5" Command="{Binding UpdateItemsB}"/>
</Window.InputBindings>
<DockPanel>
<Button Content="Update A" Command="{Binding UpdateItemsA}" />
<Button Content="Update B" Command="{Binding UpdateItemsB}" />
<ListBox ItemsSource="{Binding ItemsList}"
IsSynchronizedWithCurrentItem="True">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Columns="3" />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListBoxItem}" >
<Grid Background="{TemplateBinding Background}">
<Border BorderThickness="5" BorderBrush="{TemplateBinding BorderBrush}">
<ContentPresenter />
</Border>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="BorderBrush" Value="Yellow" />
<Setter Property="FocusManager.FocusedElement" Value="{Binding RelativeSource={RelativeSource Self}}" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
</DockPanel>
And here's the code-behind of an ItemStuff class:
bool canExecute;
ICommand updateItemsA, updateItemsB;
List<string> _itemsList;
List<string> ItemsA;
List<string> ItemsB;
public ItemStuff()
{
canExecute = true;
ItemsA = new List<string> { "item A", "item B", "item C" };
ItemsB = new List<string> { "item D", "item E", "item F" };
ItemsList = ItemsA;
}
public ICommand UpdateItemsA
{
get
{
return updateItemsA
?? (updateItemsA = new CommandHandler(() => UpdateMainItemsA(), canExecute));
}
}
public ICommand UpdateItemsB
{
get
{
return updateItemsB
?? (updateItemsB = new CommandHandler(() => UpdateMainItemsB(), canExecute));
}
}
void UpdateMainItemsA()
{
ItemsList = ItemsA;
}
void UpdateMainItemsB()
{
ItemsList = ItemsB;
}
public List<string> ItemsList
{
get
{
return _itemsList;
}
set
{
_itemsList = value;
OnPropertyChanged("ItemsList");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
public class CommandHandler : ICommand
{
private Action _action;
private bool _canExecute;
public CommandHandler(Action action, bool canExecute)
{
_action = action;
_canExecute = canExecute;
}
public bool CanExecute(object parameter)
{
return _canExecute;
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
_action();
}
}
I've identified the following three areas in the XAML where if I remove them, the exception doesn't occur.
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Columns="3" />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox IsSynchronizedWithCurrentItem="True" />
<Setter Property="FocusManager.FocusedElement" Value="{Binding RelativeSource={RelativeSource Self}}" />
So I think the problem might have something to do with the listbox having keyboard focus and then pressing a key to switch items, because like I mentioned when you click the button to switch items there is no problem. Keyboard focus is a requirement for the app which the user should be able to navigate without ever using the mouse.
There is a part in your code which we can't see here obviously and you are doing some work with that "ImageDatabase[CurrentSelection].ImagePaths" while the code is in foreach loop.. you can use .ToList() in foreach line OR you can copy your ImagePaths to a temporary variable and start your foreach loop with that temp variable.. But these are not the correct things you should do, find where do you modify this list while the program is in foreach loop

Get the parent controler in a listview

I have a ListView with a list of name and I want to be able to rename each value by double click or with a button.
I already did this for the doubleclick and it's working using this :
WPF
<ListView Grid.Row="0" x:Name="ListProfileView"
ItemsSource="{Binding ProfilesCollection}">
<ListView.ItemTemplate>
<DataTemplate>
<TextBox Text="{Binding Name}" IsReadOnly="True" VerticalAlignment="Center">
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseDoubleClick">
<i:InvokeCommandAction
Command="{Binding DataContext.RenameCommand, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}">
<i:InvokeCommandAction.CommandParameter>
<MultiBinding Converter="{StaticResource MultiConverter}">
<Binding RelativeSource="{RelativeSource AncestorType={x:Type TextBox}}"/>
<Binding Source="{x:Static classes:BooleanHelper.False}"/>
</MultiBinding>
</i:InvokeCommandAction.CommandParameter>
</i:InvokeCommandAction>
</i:EventTrigger>
<i:EventTrigger EventName="LostFocus">
<i:InvokeCommandAction
Command="{Binding DataContext.RenameCommand, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}">
<i:InvokeCommandAction.CommandParameter>
<MultiBinding Converter="{StaticResource MultiConverter}">
<Binding RelativeSource="{RelativeSource AncestorType={x:Type TextBox}}"/>
<Binding Source="{x:Static classes:BooleanHelper.True}"/>
</MultiBinding>
</i:InvokeCommandAction.CommandParameter>
</i:InvokeCommandAction>
</i:EventTrigger>
</i:Interaction.Triggers>
</TextBox>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
c# (MVVM model with ICommand):
private ICommand _renameCommand;
/// <summary>
/// Command used to change the name of the selected profile.
/// </summary>
public ICommand RenameCommand
{
get
{
return _renameCommand ?? (_renameCommand = new RelayCommand<object>(obj =>
{
if(!(obj is object[] values)) return;
if(!(values[0] is TextBox txtBox) || !(values[1] is bool value)) return;
txtBox.IsReadOnly = value;
if (!value)
{
txtBox.Focus();
}
}));
}
}
But for the button, I don't know how to get the path to the textbox to use the same command.
I tried things like that :
<Button Grid.Column="3" Content="{x:Static dictionnaries:ColorConfigurationDictionnary.rename}"
FontWeight="SemiBold"
Command="{Binding RenameCommand}">
<Button.CommandParameter>
<MultiBinding Converter="{StaticResource MultiConverter}">
<Binding ElementName="ListProfileView" Path="ItemContainerGenerator"/>
<Binding Source="{x:Static classes:BooleanHelper.False}"/>
</MultiBinding>
</Button.CommandParameter>
</Button>
But I'm out of idea... Is that possible ?
It seems that there is some sort of misinformation going about so let me describe how MvvM works in the best way I can think of.
Model is where you store your data so let's call that a profile:
namespace Model
{
public class Profile
{
public string Name { get; set; }
}
}
Now what you need is a ViewModel which will provide Information which is manipulated data:
using VM.Commands;
namespace VM
{
public class MainViewModel : BaseViewModel
{
public MainViewModel()
{
ProfilesCollection = new List<Profile>();
for (int i = 0; i < 100; i++)
{
ProfilesCollection.Add(new Profile() {Name = $"Name {i}"});
}
RenameCommand = new TestCommand(renameCommandMethod, (o) => true);
}
void renameCommandMethod(object parameter)// to manipulate the colleciton you use the Commands which you already do but without the need for converters or any UI elements. Makes it much easier to handle.
{
string renameTo = parameter.ToString();
foreach (var profile in ProfilesCollection)
{
profile.Name = renameTo;
}
}
private List<Profile> profilesCollection;
public List<Profile> ProfilesCollection
{
get { return profilesCollection; }
set { profilesCollection = value; OnPropertyChanged(); }
}
private ICommand renameCommand;
public ICommand RenameCommand
{
get { return renameCommand; }
set { renameCommand = value; }
}
And the implementation of the RelayCommand:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
namespace VM.Commands
{
public class TestCommand : ICommand
{
private Action<object> _execute;
private Predicate<object> _canExecute;
public TestCommand(Action<object> execute, Predicate<object> canExecute)
{
_execute = execute;
_canExecute = canExecute;
}
#region Implementation of ICommand
public bool CanExecute(object parameter)
{
return _canExecute?.Invoke(parameter) ?? true;
}
public void Execute(object parameter)
{
_execute?.Invoke(parameter);
}
public event EventHandler CanExecuteChanged;
#endregion
}
}
Then UI looks like this:
<Window x:Class="SO_app.MainWindow"
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:vm="clr-namespace:VM;assembly=VM"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:converter="clr-namespace:SO_app.Converters"
xmlns:validation="clr-namespace:SO_app.Validation"
xmlns:scm="clr-namespace:System.ComponentModel;assembly=WindowsBase"
xmlns:local="clr-namespace:SO_app"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
xmlns:model="clr-namespace:Model;assembly=Model"
mc:Ignorable="d"
d:DataContext="{d:DesignInstance Type=vm:MainViewModel, IsDesignTimeCreatable=True}"
Title="MainWindow" Height="452.762" Width="525" Closing="Window_Closing">
<Window.Resources>
<CollectionViewSource Source="{Binding ProfilesCollection}" x:Key="profiles"/>
</Window.Resources>
<Window.DataContext>
<vm:MainViewModel/>
</Window.DataContext>
<Window.Background>
<VisualBrush>
<VisualBrush.Visual>
<Rectangle Width="50" Height="50" Fill="ForestGreen"></Rectangle>
</VisualBrush.Visual>
</VisualBrush>
</Window.Background>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<ListView ItemsSource="{Binding Source={StaticResource profiles}}"
VirtualizingPanel.VirtualizationMode="Recycling">
<ListView.ItemTemplate>
<DataTemplate>
<DataTemplate.Resources>
<ToolTip x:Key="Tip">
<TextBlock>
<Run>Some text here</Run>
<LineBreak/>
<Run Text="{Binding Name, StringFormat='Actual Text: {0}'}"/>
</TextBlock>
</ToolTip>
</DataTemplate.Resources>
<TextBlock Text="{Binding Name}" ToolTip="{StaticResource Tip}"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<StackPanel Grid.Column="1">
<Button Content="Rename" Command="{Binding RenameCommand}" CommandParameter="NewName"></Button>
</StackPanel>
</Grid>
What this gives you is:
* Clean UI without any converters
* Every operation is done in the ViewModel without passing any UI elements.
* In UI you would do stuff like Styles with animation or setting font for text elements. But avoid handling clicks there. It is possible and sometimes it can't be avoided but try to utilise your ViewModel to manipulate the data.
BTW there are no controllers in here.
If you have any questions just ask.
Here is what I did:
that let me change the name of one value only by disabling the readonly of the textboxes in the list view.
I wrote that in the GUI code behind.
private ICommand _renameCommand;
/// <summary>
/// Command used to change the name of the selected profile.
/// </summary>
public ICommand RenameCommand
{
get
{
return _renameCommand ?? (_renameCommand = new RelayCommand<object>(obj =>
{
if(!(obj is object[] values)) return;
if(!(values[0] is TextBox || values[0] is SetConfiguration) || !(values[1] is bool value)) return;
if (values[0] is TextBox txtBox)
{
txtBox.IsReadOnly = value;
if (!value)
{
txtBox.Focus();
txtBox.SelectAll();
}
}
if (values[0] is SetConfiguration config)
{
var listView = ListProfileView.ItemContainerGenerator.ContainerFromItem(config) as ListViewItem;
var presenter = FindVisualChild<ContentPresenter>(listView);
if(!(presenter.ContentTemplate.FindName("ProfileName", presenter) is TextBox txtBoxItem)) return;
if (!value)
{
txtBoxItem.Focus();
txtBoxItem.SelectAll();
}
txtBoxItem.IsReadOnly = value;
}
}));
}
}
private static TChildItem FindVisualChild<TChildItem>(DependencyObject obj)
where TChildItem : DependencyObject
{
for (var i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)
{
var child = VisualTreeHelper.GetChild(obj, i);
if (child is TChildItem item)
return item;
var childOfChild = FindVisualChild<TChildItem>(child);
if (childOfChild != null)
return childOfChild;
}
return null;
}

Why this Button Command Binding is not working?

I did this like 50 times before. I really don't know why it is not working this time. I have a WPF application and my only dependency is MahApps.Metro. I'm using it's MetroWindow and Dynamic Style on my Button.
Here is the latest xaml:
<ItemsControl Grid.Column="0" Grid.Row="1" ItemsSource="{Binding ServerList}" Margin="5">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border Background="LightGray">
<StackPanel Orientation="Horizontal">
<Button Style="{DynamicResource MetroCircleButtonStyle}" Content="{StaticResource appbar_monitor}" Command="{Binding VM.ServerSelectedCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Controls:MetroWindow}}" CommandParameter="{Binding .}"></Button>
<Label Content="{Binding .}" HorizontalAlignment="Center" VerticalAlignment="Center"></Label>
</StackPanel>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Here is my ServerSelectedCommand in my ViewModel:
private ViewModelCommand _ServerSelectedCommand;
public ViewModelCommand ServerSelectedCommand
{
get
{
if (_ServerSelectedCommand == null)
{
_ServerSelectedCommand = new ViewModelCommand(
p => { SelectServer(p); },
p => true
);
}
return _ServerSelectedCommand;
}
set { _ServerSelectedCommand = value; }
}
private void SelectServer(object parameter)
{
}
ViewModelCommand class is like RelayCommand. Here it is:
public class ViewModelCommand : Observable, ICommand
{
public bool CanExecuteValue
{
get { return CanExecute(null); }
}
public ViewModelCommand(
Action<object> executeAction,
Predicate<object> canExecute)
{
if (executeAction == null)
throw new ArgumentNullException("executeAction");
_executeAction = executeAction;
_canExecute = canExecute;
}
private readonly Predicate<object> _canExecute;
public bool CanExecute(object parameter)
{
return _canExecute == null ? true : _canExecute(parameter);
}
public event EventHandler CanExecuteChanged;
public void OnCanExecuteChanged()
{
OnPropertyChanged(() => CanExecuteValue);
if (CanExecuteChanged != null)
CanExecuteChanged(this, EventArgs.Empty);
}
private readonly Action<object> _executeAction;
public void Execute(object parameter)
{
_executeAction(parameter);
}
}
Sorry for a lot of code. But I need to add them in order to find the problem which I can't see. So lets turn back to first xaml, that is the latest one I tried. Here are the codes that I tried for problematic Button line.
Command="{Binding ServerSelectedCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ItemsControl}}"
Command="{Binding ServerSelectedCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type local:ViewModel}}}"
This also doesn't provide anything!
Command="{Binding RelativeSource={RelativeSource AncestorType=Controls:MetroWindow}}"
Thanks!
This binding looks like it is looking for ServerSelectedCommand on the ItemsControl:
Command="{Binding ServerSelectedCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ItemsControl}}"
try this instead:
Command="{Binding DataContext.ServerSelectedCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ItemsControl}}"
Assuming of course that the DataContext of the ItemsControl is your ViewModel.

Leftmouseclick on stackpanel

I have a stackpanel with image and button in it. I want to fire event when user clicks on a button in stackPanel. My code in xaml is
<StackPanel x:Uid="TemperatureMonitor" Orientation="Horizontal" HorizontalAlignment="Left" ToolTip="{DynamicResource InstrumentZweiMesswert}" Height="35">
<i:Interaction.Triggers>
<i:EventTrigger EventName="PreviewMouseLeftButtonDown">
<ei:CallMethodAction TargetObject="{Binding}" MethodName="OnAddUserControl"/>
</i:EventTrigger>
</i:Interaction.Triggers>
<Image Width="35" Height="35" x:Uid="Image_15" Source="/Resources\png\TemperatureMonitor.png"/>
<Button x:Uid="TemperatureMonitor" Content="Temperatur Monitor" x:Name="TemperatureMonitor" IsEnabled="True" Width="135"/>
</StackPanel>
And method OnAddUserControl in my viewModel is
public void OnAddUserControl(object sender, RoutedEventArgs e)
{
//some code
}
The problem it that I don't get into OnAddUserControl. Any ideas why?
I want to fire this event when user makes leftMouseClick on a button. So I don't know why, but RelayCommand also doesn't help and not fires method OnAddUserControl. When I moved iteraction code to my button and it looks like this :
<StackPanel Background="Black" x:Uid="TemperatureMonitor" Orientation="Horizontal" HorizontalAlignment="Left" ToolTip="{DynamicResource InstrumentZweiMesswert}" Height="35">
<Image Width="35" Height="35" x:Uid="Image_15" Source="/Resources\png\TemperatureMonitor.PNG"/>
<Button x:Uid="TemperatureMonitor" Content="Temperatur Monitor" x:Name="TemperatureMonitor" IsEnabled="True" Width="135" >
<i:Interaction.Triggers>
<i:EventTrigger EventName="PreviewMouseLeftButtonDown">
<ei:CallMethodAction TargetObject="{Binding}" MethodName="OnAddUserControl"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
</StackPanel>
i've get during runtime mistake that says "For object Type"DockSite" cannot find methodname "OnAddUserControl"". I will appreciate any ideas or help
You can use RelayCommand for this purpose.
Add RelayCommand.cs to your project.
class RelayCommand : ICommand
{
private Action<object> _action;
public RelayCommand(Action<object> action)
{
_action = action;
}
#region ICommand Members
public bool CanExecute(object parameter)
{
return true;
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
if (parameter != null)
{
_action(parameter);
}
else
{
_action("Hello World");
}
}
#endregion
}
And this is your ViewModel. I called this MainWindowViewModel. So, add MainWindowViewModel.cs class to your solution.
class MainWindowViewModel
{
private ICommand m_ButtonCommand;
public ICommand ButtonCommand
{
get
{
return m_ButtonCommand;
}
set
{
m_ButtonCommand = value;
}
}
public MainWindowViewModel()
{
ButtonCommand=new RelayCommand(new Action<object>(ShowMessage));
}
public void ShowMessage(object obj)
{
MessageBox.Show(obj.ToString());
}
}
And this is your xaml:
<Window.DataContext>
<local:MainWindowViewModel/>
</Window.DataContext>
<StackPanel>
<Button Width="220" Content="Click me" Command={Binding ButtonCommand} CommandParameter="StackOverflow" />
</StackPanel>
It will show you messageBox after clicking button. So you change your project for handing Button Click event in this way.

Keybinding on a treeview item, with CommandParameter

I'm trying to execute a command located on my ViewModel, using a TreeViewItem with a KeyBinding, and a MenuContext.
Currently, using the context menu, the command is invoked on the correct ViewModel instance.
However, when I select a TreeViewItem and press the "C" key, the command is invoked on the "root" ViewModel.
I tried extending KeyBinding class as well ( Keybinding a RelayCommand ) with no luck.
Maybe I'm going to the wrong path : I just want to display the correct MessageBox, if I use the context menu or the key.
Code sample for a WPF project named WpfTest.
MainWindow.xaml
<Window x:Class="WpfTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:WpfTest"
Title="MainWindow" Height="350" Width="525">
<Grid>
<TreeView ItemsSource="{Binding}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Child}" DataType="{x:Type vm:ViewModel}">
<TextBlock Text="{Binding Name}" />
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="ContextMenu">
<Setter.Value>
<ContextMenu>
<MenuItem Header="{Binding Name}" Command="{Binding SomeCommand}" CommandParameter="{Binding}"/>
</ContextMenu>
</Setter.Value>
</Setter>
<Setter Property="vm:MyAttached.InputBindings">
<Setter.Value>
<InputBindingCollection>
<KeyBinding Key="C" Command="{Binding SomeCommand}" CommandParameter="{Binding}"/>
</InputBindingCollection>
</Setter.Value>
</Setter>
</Style>
</TreeView.ItemContainerStyle>
</TreeView>
</Grid>
</Window>
MainWindow.xaml.cs:
namespace WpfTest
{
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Windows;
using System.Windows.Input;
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new List<ViewModel>
{
new ViewModel
{
Name = "Parent",
Child = new ObservableCollection<ViewModel>
{
new ViewModel { Name = "Child 1" },
new ViewModel { Name = "Child 2" },
new ViewModel { Name = "Child 3" }
}
}
};
}
}
public class ViewModel
{
public string Name { get; set; }
public ObservableCollection<ViewModel> Child { get; set; }
public ICommand SomeCommand { get; set; }
public ViewModel()
{
this.SomeCommand = new RelayCommand<ViewModel>(OnCommandExecuted);
}
private void OnCommandExecuted(ViewModel parameter)
{
MessageBox.Show("CommandExecuted on " + Name + " with parameter " + parameter.Name);
}
}
public class MyAttached
{
public static readonly DependencyProperty InputBindingsProperty =
DependencyProperty.RegisterAttached("InputBindings", typeof(InputBindingCollection), typeof(MyAttached),
new FrameworkPropertyMetadata(new InputBindingCollection(),
(sender, e) =>
{
var element = sender as UIElement;
if (element == null) return;
element.InputBindings.Clear();
element.InputBindings.AddRange((InputBindingCollection)e.NewValue);
}));
public static InputBindingCollection GetInputBindings(UIElement element)
{
return (InputBindingCollection)element.GetValue(InputBindingsProperty);
}
public static void SetInputBindings(UIElement element, InputBindingCollection inputBindings)
{
element.SetValue(InputBindingsProperty, inputBindings);
}
}
public class RelayCommand<T> : ICommand
{
readonly Action<T> _execute = null;
public RelayCommand(Action<T> execute) { _execute = execute; }
public bool CanExecute(object parameter) { return true; }
public void Execute(object parameter) { _execute((T)parameter); }
public event EventHandler CanExecuteChanged { add { CommandManager.RequerySuggested += value; } remove { CommandManager.RequerySuggested -= value; } }
}
}
Here is the problem: The Style only creates one InputBindingCollection for all ListViewItems, you have to be very careful with Setter.Values for that reason.
And here is the fix:
<TreeView ItemsSource="{Binding}">
<TreeView.Resources>
<!-- x:Shared="False" forces the new creation of that object whenever referenced -->
<InputBindingCollection x:Shared="False" x:Key="InputBindings">
<KeyBinding Key="C" Command="{Binding SomeCommand}" CommandParameter="{Binding}" />
</InputBindingCollection>
</TreeView.Resources>
<!-- ... -->
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<!-- ... -->
<Setter Property="vm:MyAttached.InputBindings" Value="{StaticResource InputBindings}"/>
</Style>
</TreeView.ItemContainerStyle>
</TreeView>

Categories

Resources