C# WPF Listbox Context Menu Command not working - c#

Currently I am trying to add a context menu to a ListBox that uses an item template. I am able to get the context menu items added, but when I try to bind the commands, nothing happens.
The Main_Window has a data context set. Here is the XAML for the ListBox. I use a similar Binding style as part of a button in the ListView.ItemTemplate so I would assume this would work, but sadly it is not. What am I missing here? (Only important part of the code is here)
<ListBox x:Name="company_buttons_listbox"
ItemsSource="{Binding Buttons_Binding}"
SelectedIndex="{Binding Selected_Index, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}">
<ListBox.Resources>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="ContextMenu">
<Setter.Value>
<ContextMenu>
<MenuItem Header="Update Frazer Server Link" Foreground="Black" FontFamily="Segoe UI" FontSize="14" FontWeight="Bold"
CommandParameter="{Binding RelativeSource={RelativeSource AncestorType=Window}}"
Command="{Binding ElementName=Main_Window, Path=DataContext.Testing}"/>
</ContextMenu>
</Setter.Value>
</Setter>
<Style.Resources>
<SolidColorBrush x:Key="{x:Static SystemColors.HighlightBrushKey}" Color="LightSteelBlue" Opacity="0.5"/>
<SolidColorBrush x:Key="{x:Static SystemColors.ControlBrushKey}" Color="LightSteelBlue" Opacity="0.5"/>
</Style.Resources>
</Style>
<Style TargetType="{x:Type ListBox}">
<Setter Property="KeyboardNavigation.TabNavigation" Value="Continue"/>
</Style>
</ListBox.Resources>
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Margin="-2,0,-2,0">
<Button CommandParameter="{Binding}"
Command="{Binding ElementName=Main_Window, Path=DataContext.Open_Link}">
</Button>
<Label VerticalContentAlignment="Top"
Margin="5,0,5,0" Height="19" Padding="0"
Foreground="White" FontFamily="Segoe UI" FontSize="12" FontWeight="Bold"
Content="{Binding ItemText}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>

So, I solved this by not solving this and instead used a work around.
Essentially the issue comes from this:
System.Windows.Data Error: 4 : Cannot find source for binding with
reference
I found that Context Menus are not part of the Visual Tree (not happy about that) and hence cannot access those elements in the same fashion.
I am not a fan of using Reflection so the ElementSpy method is off the table for me along with. I tried to directly set a Click="some_function" and that also surprisingly DID not work.
I instead just wrapped my entire ListBox in a Grid and used the following. Not really MVVM, but I could care less at this point with how much wasted time I put into finding a solid and reliable solution.
XAML:
<Grid.ContextMenu>
<ContextMenu>
<MenuItem Header="Menu Item Text" Foreground="Black" FontFamily="Segoe UI" FontSize="14" FontWeight="Bold"
Click="menu_item_function"/>
<Separator/>
</Grid.ContextMenu>
Code Behind:
private void menu_item_function(object sender, RoutedEventArgs e)
{
// Get the viewmodel from the DataContext
MainWindowViewModel viewmodel = DataContext as MainWindowViewModel;
// Call command from viewmodel
if ((viewmodel != null) && (viewmodel.View_Model_Function.CanExecute(this)))
{
viewmodel.View_Model_Function.Execute(this);
}
}

Related

how to select the item of list veiw data template in wpf?

I made a list view, and i used a data Template as you seeing
below and now i want to select the Which item,the user selected and put the text of the selected item textblock to a variable
<ListView.ItemTemplate>
<DataTemplate >
<Border CornerRadius="20"
BorderThickness="2"
BorderBrush="Red"
Width="70"
Height="70"
Margin="5,5">
<Button x:Name="calender_btn"
Background="Transparent"
BorderBrush="Transparent"
Style="{StaticResource calender_btn_style}"
Click="calender_btn_Click">
<TextBlock x:Name="calender_txt_block"
Text="{Binding _outputdate}"
Foreground="White"
Width="55"
FontSize="14"
Margin="3,5"
TextWrapping="Wrap"
VerticalAlignment="Center"
HorizontalAlignment="Center"
TextAlignment="Center"
>
</TextBlock>
<Button.Resources>
<Style TargetType="Border">
<Setter Property="CornerRadius" Value="20"/>
</Style>
</Button.Resources>
</Button>
</Border>
</DataTemplate>
</ListView.ItemTemplate>
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel/>
</ItemsPanelTemplate>
</ListView.ItemsPanel>
I have no idea how I can do it, because I am new in wpf, if you know how I can do it.
Depending on how your data looks like, you probably get a data object to each button that contains the text. You can find that object in the buttons data context.
private void calender_btn_Click(object sender, RoutedEventArgs e)
{
var dataObject = ((Button)sender).DataContext;
}
If you are using ItemsSource you can get the selected value by binding to ListView SelectedItem (SelectedValue in some cases).
SelectedItem="{Binding SelectedData, Mode=TwoWay}"
Assuming that you have SelectedData property in your DataContext with same type as your data in the shown collection.
Google for MVVM

Binding Button Click Event to a Method in ViewModel with Interaction.Triggers when Buttons are dynamically created at runtime

In my C# WPF MVVM pattern application, I have an ItemsControl in my View that draws Lines and Buttons on a Canvas based on a bound ItemsSource, defined in XAML as below:
<Window.DataContext>
<viewmodels:MainWindowViewModel />
</Window.DataContext>
.
.
.
<ItemsControl
x:Name="DiagramViewCanvas"
ItemsSource="{Binding DiagramObjects, UpdateSourceTrigger=PropertyChanged}">
<ItemsControl.Resources>
<DataTemplate DataType="{x:Type local:LineObject}">
<Line
X1="{Binding XStart}"
Y1="{Binding YStart}"
X2="{Binding XEnd}"
Y2="{Binding YEnd}"
Stroke="White"
StrokeThickness="1"
SnapsToDevicePixels="True"/>
</DataTemplate>
<DataTemplate DataType="{x:Type local:ButtonObject}">
<Button
Style="{DynamicResource MyDiagramButtonStyle}"
Width="225"
Height="30"
Content="{Binding Content}"
FontSize="13"
SnapsToDevicePixels="True">
</Button>
</DataTemplate>
</ItemsControl.Resources>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas Background="Black" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style TargetType="ContentPresenter">
<Setter Property="Canvas.Left" Value="{Binding XPosition, UpdateSourceTrigger=PropertyChanged}" />
<Setter Property="Canvas.Top" Value="{Binding YPosition, UpdateSourceTrigger=PropertyChanged}" />
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
This code works completely fine. My question is how to bind the Buttons' Click event to a method in the ViewModel (MainWindowViewModel).
Option 1 (which I don't want to use due MVVM pattern): If I try a simple Click event definition as below ...
<Button
Style="{DynamicResource MyDiagramButtonStyle}"
Width="225"
Height="30"
Content="{Binding Content}"
FontSize="13"
SnapsToDevicePixels="True"
Click="OnButtonClick"/>
... where OnButtonClick is defined in my XAML codebehind, the OnButtonClick method is successfully called and executed for each Button that is created at runtime. It works fine.
Option 2: However, if I try to use Interaction.Triggers as below (which is the approach I regularly use without any problems in my code) to avoid placing code in code behind ...
<Button
Style="{DynamicResource MyDiagramButtonStyle}"
Width="225"
Height="30"
Content="{Binding Content}"
FontSize="13"
SnapsToDevicePixels="True">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<i:CallMethodAction TargetObject="{Binding}" MethodName="OnButtonClick"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
... where OnButtonClick is defined in my MainWindowViewModel ...
public void OnButtonClick(object sender, RoutedEventArgs e)
{
if (sender is Button btn)
{
// do something
}
}
... I get the following error:
System.ArgumentException: 'Could not find method named 'OnButtonClick' on object of type 'ButtonObject' that matches the expected signature.'
Question 1: Am I making a basic mistake in my implementation of interaction triggers (I have many other interaction triggers in my code that work completely fine)? Or is it that Interaction.Triggers do not work in this scenario where the Buttons are created dynamically at runtime?
Question 2: Should I be using ICommand instead (for example as mentioned in Binding Commands to Events?)?
Thanks for any direction on what I am doing wrong.
Found a solution using Interaction.Triggers:
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<i:CallMethodAction TargetObject="{Binding RelativeSource={RelativeSource AncestorType={x:Type Canvas}}, Path=DataContext}" MethodName="OnButtonClick"/>
</i:EventTrigger>
</i:Interaction.Triggers>

Update datagrid's GroupHeader when item property changes

I'm having some difficulty updating a certain binding.
I have a class DeviceList that loads some devices, it inherits from ObservableCollection and is listed as a resource in my XAML:
<local:DeviceList x:Key="Devices" />
Then, I have a CollectionViewSource that uses this devicelist as source, and groups it by a property from the Device:
<CollectionViewSource x:Key="cvsDevices" Source="{StaticResource Devices}" Filter="CollectionViewSource_Filter">
<CollectionViewSource.GroupDescriptions>
<PropertyGroupDescription PropertyName="GroupId" />
</CollectionViewSource.GroupDescriptions>
</CollectionViewSource>
A Datagrid binding to this CVS, which has a group header style:
<DataGrid x:Name="dataGrid" ItemsSource="{Binding Source={StaticResource cvsDevices}}">
<DataGrid.GroupStyle>
<GroupStyle ContainerStyle="{StaticResource GroupHeaderStyle}">
<GroupStyle.Panel>
<ItemsPanelTemplate>
<DataGridRowsPresenter />
</ItemsPanelTemplate>
</GroupStyle.Panel>
</GroupStyle>
</DataGrid.GroupStyle>
<DataGrid.Columns>
bla bla
</DataGrid.Columns>
</DataGrid>
And then finally the Group Header style in the resources:
<Style x:Key="GroupHeaderStyle" TargetType="{x:Type GroupItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupItem}">
<Expander IsExpanded="True" Background="White" Foreground="Black">
<Expander.Header>
<StackPanel Orientation="Horizontal" Height="30">
<Border Margin="5" Width="20" Height="20" Background="{Binding Path=Items, Converter={StaticResource DeviceGroupToColorConverter}}" CornerRadius="10" />
<TextBlock VerticalAlignment="Center" Padding="3" Text="{Binding Name, Converter={StaticResource DeviceGroupToGroupTitleConverter}}" />
<TextBlock VerticalAlignment="Center" Padding="3" Text="{Binding ItemCount, Converter={StaticResource ItemCountToStringConverter}}"/>
</StackPanel>
</Expander.Header>
<ItemsPresenter />
</Expander>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
As you can see, there's a Border there that binds to "Items". This is a property of "CollectionViewGroup": https://learn.microsoft.com/en-us/dotnet/api/system.windows.data.collectionviewgroup?view=netcore-3.1
Basically each of my devices has a property "Connection", and when this property changes, I would like to set the color of this border in the corresponding group header.
The binding works fine the first time, but after that the DeviceGroupToColorConverter isn't called anymore when a connection changes. Device implements INotifyPropertyChanged, but I have no idea how to propagate that event to CollectionViewGroup's Items property. In fact, I have no idea where CollectionViewGroup instances live. I only have access to the CollectionViewSource.
I would like to avoid refreshing the entire DataGrid. I've read that it resets my expanders and also, why refresh the entire datagrid when only a certain group's header should change?
I have solved it by changing the binding to a MultiBinding and adding a binding with a Source set to the DeviceList:
<Border Margin="5" Width="20" Height="20" CornerRadius="10">
<Border.Background>
<MultiBinding Converter="{StaticResource DeviceGroupToColorConverter}">
<Binding Source="{StaticResource Devices}" Path="Devices" />
<Binding Path="Name" />
</MultiBinding>
</Border.Background>
</Border>
the "Devices" property of the DeviceList class is a simple getter that returns "this":
public ObservableCollection<Device> Devices
{
get
{
return this;
}
}
I let the DeviceList listen to any property changes on device, and invoke PropertyChanged on the "Devices" property of DeviceList to pass on this event to the MultiBinding.
I then use the Name binding in the MultiBinding to filter my devices based on the group that they're in. Now I don't need to refresh the whole grid and my performance is good.

WPF MenuItem Click event called two times instead of one time

I have a Menu in my XAML that look like this
<Menu DockPanel.Dock="Top" Height="20">
<MenuItem Width="20" Height="20" x:Name="MenuItem_AddNewQuality">
<MenuItem.Resources>
<Style TargetType="MenuItem">
<EventSetter Event="Click" Handler="MenuItem_AddNewQuality_Click"></EventSetter>
</Style>
</MenuItem.Resources>
<MenuItem.Background>
<ImageBrush ImageSource="icons8-add-50.png"/>
</MenuItem.Background>
<ItemsControl.ItemTemplateSelector>
<local:DataTemplateSelector_MenuItem_AddNewQuality_SelectType>
<local:DataTemplateSelector_MenuItem_AddNewQuality_SelectType.DataTemplate_Enabled>
<DataTemplate>
<TextBlock Text="{Binding Name}"></TextBlock>
</DataTemplate>
</local:DataTemplateSelector_MenuItem_AddNewQuality_SelectType.DataTemplate_Enabled>
<local:DataTemplateSelector_MenuItem_AddNewQuality_SelectType.DataTemplate_Disenabled>
<DataTemplate>
<TextBlock Text="{Binding Name}" IsEnabled="False"></TextBlock>
</DataTemplate>
</local:DataTemplateSelector_MenuItem_AddNewQuality_SelectType.DataTemplate_Disenabled>
</local:DataTemplateSelector_MenuItem_AddNewQuality_SelectType>
</ItemsControl.ItemTemplateSelector>
</MenuItem>
<MenuItem Width="20" Click="Button_RemoveSelectedQuality_Click" IsEnabled="{Binding HasItemSelectedandSelectedItemHasQuality, ElementName=window, Mode=OneWay}" Height="20">
<MenuItem.Background>
<ImageBrush ImageSource="icons8-delete-bin-50.png"/>
</MenuItem.Background>
</MenuItem>
</Menu>
However the MenuItem_AddNewQuality_Click() was executed two times.
Then I tried to remove
<EventSetter Event="Click" Handler="MenuItem_AddNewQuality_Click"></EventSetter>
But this time there are no thing executed.
So can any one please tell me what's wrong with it?
If you set in your event handler, that event was handled, then you will get it called only one time:
private void MenuItem_AddNewQuality_Click(object sender, RoutedEventArgs e)
{
//....
e.Handled = true;
}
Another way would be set Click event not as implicit style for all nested menu items(this is what you have with double call), but explicitly in MenuItem:
<MenuItem Width="20" Height="20" x:Name="MenuItem_AddNewQuality" Click="MenuItem_AddNewQuality_Click">
Another way is to declare an explicit style and apply it to the MenuItem:
<Style x:Key="MenItemStyle" TargetType="MenuItem">
<EventSetter Event="Click" Handler="MenuItem_AddNewQuality_Click"/>
</Style>
<MenuItem Style="{StaticResource ResourceKey=MenItemStyle}">

WPF Databinding ContextMenu of Button inside a DataTemplate inside an ItemsControl

I am trying to figure out how I can bind the ContextMenu of the Button that is being added in the ItemsControl I have. Basically, I'm wanting to be able to right click on a button and remove it from the observable collection that sits on my viewmodel. I understand that ContextMenu's are not part of the VisualTree, so using RelativeSource to walk up the tree to find my DataContext hasn't been useful to me.
The end goal of what I want to do is Bind the Command on the MenuItem to the RemoveCommand on my ViewModel and then pass in the Content property of the Button that you right click on so that I can remove it from the observable collection.
Any help on this would be greatly appreciated.
Model:
public class Preset
{
public string Name { get; set; }
}
ViewModel:
public class SettingsWindowViewModel
{
public ObservableCollection<Preset> MyPresets { get; } = new ObservableCollection<Preset>();
private ICommand _plusCommand;
public ICommand PlusCommand => _plusCommand ?? (_plusCommand = new DelegateCommand(AddPreset));
private ICommand _removeCommand;
public ICommand RemoveCommand => _removeCommand ?? (_removeCommand = new DelegateCommand<string>(RemovePreset));
private void AddPreset()
{
var count = MyPresets.Count;
MyPresets.Add(new Preset {Name = $"Preset{count+1}"});
}
private void RemovePreset(string name)
{
var preset = MyPresets.FirstOrDefault(x => string.Equals(x.Name, name, StringComparison.CurrentCultureIgnoreCase));
if (preset!= null)
{
MyPresets.Remove(preset);
}
}
}
XAML:
<Window x:Class="WpfTesting.Esper.Views.SettingsWindow"
x:Name="MainSettingsWindow"
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:viewModels="clr-namespace:WpfTesting.Esper.ViewModels"
mc:Ignorable="d"
Title="SettingsWindow" Height="470" Width="612">
<Window.DataContext>
<viewModels:SettingsWindowViewModel/>
</Window.DataContext>
<Window.Resources>
<Style BasedOn="{StaticResource {x:Type MenuItem}}" TargetType="{x:Type MenuItem}" x:Key="PopupMenuItem">
<Setter Property="OverridesDefaultStyle" Value="True"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type MenuItem}">
<Border>
<ContentPresenter ContentSource="Header"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="1*"/>
<RowDefinition Height="35"/>
</Grid.RowDefinitions>
<StackPanel Grid.Row="2" Orientation="Horizontal">
<Button Width="70" Content="Load"/>
<Button Width="70" Content="Save As"/>
<ItemsControl ItemsSource="{Binding MyPresets}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Width="70" Content="{Binding Name}">
<Button.ContextMenu>
<ContextMenu>
<MenuItem Style="{StaticResource PopupMenuItem}" Header="Remove">
<!--
I need to set up binding a Command to a method on the DataContext of the Window, and I need to pass in the Content of the Button that is the parent of the ContextMenu
-->
</MenuItem>
</ContextMenu>
</Button.ContextMenu>
</Button>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<Button Width="20" Background="Transparent" BorderBrush="Transparent" Content="+" FontSize="21.333" HorizontalAlignment="Center" VerticalAlignment="Center" Command="{Binding PlusCommand}"/>
</StackPanel>
</Grid>
</Window>
Using WPF: Binding a ContextMenu to an MVVM Command as an introduction to what Tags can do, I figured out how to do what I was looking for by using multiple Tags to save the Context of what I was looking for.
I first made sure to give my window a x:Name
<Window x:Name="MainSettingsWindow"
Next, on the Button inside my DataTemplate of my ItemsControl, I set a Tag and set it to my Window
<ItemsControl ItemsSource="{Binding MyPresets}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Width="70" Content="{Binding Name}" Tag="{Binding ElementName=MainSettingsWindow}">
Next, in the ContextMenu, I seth the DataContext of the ContextMenu to the Tag I set on the Button, I also needed to create a Tag on the ContextMenu and point it back to the Content Property of the Button so that I can pass that into the CommandParameter
<ContextMenu DataContext="{Binding Path=PlacementTarget.Tag, RelativeSource={RelativeSource Mode=Self}}" Tag="{Binding PlacementTarget.Content, RelativeSource={RelativeSource Mode=Self}}">
At this point, I can now bind my MenuItem correctly using the Command from my ViewModel and the Content Property from the Button
This is the final XAML for my ItemsControl:
<ItemsControl ItemsSource="{Binding MyPresets}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Width="70" Content="{Binding Name}" Tag="{Binding ElementName=MainSettingsWindow}">
<Button.ContextMenu>
<ContextMenu DataContext="{Binding Path=PlacementTarget.Tag, RelativeSource={RelativeSource Mode=Self}}" Tag="{Binding PlacementTarget.Content, RelativeSource={RelativeSource Mode=Self}}">
<MenuItem Header="Remove"
Style="{StaticResource PopupMenuItem}"
Command="{Binding Path=DataContext.RemoveCommand}"
CommandParameter="{Binding Path=Tag, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ContextMenu}}"/>
</ContextMenu>
</Button.ContextMenu>
</Button>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
One thing to note is that I had to change the CommandParameter on my ViewModel to take an Object instead of a String. The reason I did this was because I was getting an exception on the CanExecute method in my DelegateCommand
This is the exception I was getting:
Unable to cast object of type 'MS.Internal.NamedObject' to type 'System.String'.
I'm not sure exactly what's causing that exception to throw, but changing it to Object works ok for me.
I had basically a similar problem, and the solution I found was to use the Messenger class some MVVM frameworks like Devexpress or Mvvm Light have.
Basically you can register in a viewModel to listen for incoming messages. The class itself, at least in the Devexpress implementation works with weak references, so you may not even unregister message handlers and it will not cause memory leaks.
I had used this method for removing on right click tabs from a ObservableCollection, so it was similar to your scenario.
You can have a look here :
https://community.devexpress.com/blogs/wpf/archive/2013/12/13/devexpress-mvvm-framework-interaction-of-viewmodels-messenger.aspx
and here :
https://msdn.microsoft.com/en-us/magazine/jj694937.aspx

Categories