MVVM Command binding for contextemenu of TreeView Element - wpf

I need to bind a command (RelayCommand) of my ViewModel with the click event on an element of the conetxtmenu for a treeviewitem
ViewModel Command
private RelayCommand _myElementCommand;
public RelayCommand MyCommand
{
get
{
return _myElementCommand?? (_myElementCommand= new RelayCommand(
x =>
{
//LoadData();
MessageBox.Show("Clicked!");
}));
}
}
VIEW
<TreeView x:Name="tvUBR" ItemsSource="{Binding UBRList, UpdateSourceTrigger=PropertyChanged}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children}">
<StackPanel Orientation="Horizontal">
<TextBlock VerticalAlignment="Center" Text="{ Binding Description }">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Foreground" Value="Black" />
<Style.Triggers>
<DataTrigger Binding="{Binding IsMatch}"
Value="True">
<Setter Property="Foreground" Value="Green" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
<TextBlock.ContextMenu>
<ContextMenu>
<!--<MenuItem Header="Details" Command="{Binding DropedElementCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}}"/>-->
<MenuItem Header="Details">
<i:Interaction.Triggers>
<!-- EventName ??? -->
<i:EventTrigger EventName="Click">
<i:InvokeCommandAction Command="{Binding Path=PlacementTarget.Tag.DataContext.MyCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</MenuItem>
</ContextMenu>
</TextBlock.ContextMenu>
</TextBlock>
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectedItemChanged">
<i:InvokeCommandAction Command="{Binding SelectedUBRElementCommand}"
CommandParameter="{Binding SelectedItem, ElementName=tvUBR}" />
</i:EventTrigger>
</i:Interaction.Triggers>
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="b:TreeViewItemBehavior.IsBroughtIntoViewWhenSelected" Value="True" />
<Setter Property="IsExpanded" Value="{Binding IsExpanded, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" />
<Setter Property="IsSelected" Value="{Binding IsSelected, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" />
</Style>
</TreeView.ItemContainerStyle>
</TreeView>
Any idea how to achieve this?

You could bind the Tag property of the TextBlock to the view model and then bind to the command using the PlacementTarget property of the ContextMenu:
<HierarchicalDataTemplate ItemsSource="{Binding Children}">
<StackPanel Orientation="Horizontal">
<TextBlock VerticalAlignment="Center" Text="{ Binding Description }">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Tag" Value="{Binding DataContext, RelativeSource={RelativeSource AncestorType=TreeView}}" />
<Setter Property="Foreground" Value="Black" />
<Style.Triggers>
<DataTrigger Binding="{Binding IsMatch}" Value="True">
<Setter Property="Foreground" Value="Green" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
<TextBlock.ContextMenu>
<ContextMenu>
<MenuItem Header="Details"
Command="{Binding PlacementTarget.Tag.MyCommand, RelativeSource={RelativeSource AncestorType=ContextMenu}}" />
</ContextMenu>
</TextBlock.ContextMenu>
</TextBlock>
</StackPanel>
</HierarchicalDataTemplate>

Related

WPF DataTemplate bind ContextMenu property to local value

I have a problem with binding local values to ContextMenu placed in DataContext of ListViewItem. It only works for ListViewItem directly.
Here is my code:
This is SideBarPlayList UserControl:
<UserControl
xmlns:local="clr-namespace:chkam05.MyApp.Controls"
...
DataContext="{Binding RelativeSource={RelativeSource Self}}">
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="../Resources/Styles/Components/Styles.xaml"/>
</ResourceDictionary.MergedDictionaries>
<!-- Style for components:ExtendedListView -->
<Style x:Key="PlayList_ExtendedListViewItemStyle" BasedOn="{StaticResource ExtendedListViewItemStyle}" TargetType="{x:Type components:ExtendedListViewItem}">
<EventSetter Event="MouseDoubleClick" Handler="PlayListItem_DoubleClick"/>
<Setter Property="Foreground" Value="{Binding Path=Configuration.ForegroundColorBrush, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:SideBarPlayList}}}"/>
<Setter Property="HoveredBackground" Value="{Binding Path=Configuration.AccentHoveredColorBrush, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:SideBarPlayList}}}"/>
<Setter Property="SelectedActiveBackground" Value="{Binding Path=Configuration.AccentSelectedColorBrush, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:SideBarPlayList}}}"/>
<Setter Property="SelectedInactiveBackground" Value="{Binding Path=Configuration.SelectedInactiveColorBrush, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:SideBarPlayList}}}"/>
</Style>
<!-- Styles for ContextMenu. -->
<Style x:Key="ControlItem_ExtendedContextMenuStyle" BasedOn="{StaticResource ExtendedContextMenuStyle}" TargetType="{x:Type components:ExtendedContextMenu}">
<Setter Property="Background" Value="{Binding Path=Configuration.ThemeDarkerColorBrush, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:SideBarPlayList}}}"/>
<Setter Property="BorderBrush" Value="{Binding Path=Configuration.AccentColorBrush, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:SideBarPlayList}}}"/>
<Setter Property="Foreground" Value="{Binding Path=Configuration.ForegroundColorBrush, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:SideBarPlayList}}}"/>
</Style>
<Style x:Key="ControlItem_ExtendedContextMenuItemStyle" BasedOn="{StaticResource ExtendedContextMenuItemStyle}" TargetType="{x:Type components:ExtendedContextMenuItem}">
<Setter Property="HoveredBackground" Value="{Binding Path=Configuration.AccentHoveredColorBrush, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:SideBarPlayList}}}"/>
<Setter Property="Foreground" Value="{Binding Path=Configuration.ForegroundColorBrush, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:SideBarPlayList}}}"/>
</Style>
<!-- Data Templates. -->
<DataTemplate x:Key="NowPlayingDataTemplate">
<StackPanel Orientation="Horizontal">
<materialDesign:PackIcon
Kind={Binding IconKind}
Height="32"
Width="auto"/>
<TextBlock
Margin="8,0"
Text="{Binding FileName}"
VerticalAlignment="Center"/>
<StackPanel.ContextMenu>
<components:ExtendedContextMenu
Style="{StaticResource ControlItem_ExtendedContextMenuStyle}">
<components:ExtendedContextMenuItem
Header="Play"
Icon="Play"
Style="{StaticResource ControlItem_ExtendedContextMenuItemStyle}"
Click="ItemExtendedContextMenuItemPlay_Click"/>
<components:ExtendedContextMenuItem
Header="Remove"
Icon="Trash"
Style="{StaticResource ControlItem_ExtendedContextMenuItemStyle}"
Click="ItemExtendedContextMenuItemRemove_Click"/>
</components:ExtendedContextMenu>
</StackPanel.ContextMenu>
</StackPanel>
</DataTemplate>
</ResourceDictionary>
</UserControl.Resources>
<Grid>
<!-- ... -->
<components:ExtendedListView
ItemContainerStyle="{StaticResource PlayList_ExtendedListViewItemStyle}"
ItemsSource="{Binding Player.PlayList.DataContext, Mode=TwoWay}"
ItemTemplate="{StaticResource NowPlayingDataTemplate}"
Grid.Row="1"
Padding="4,0"
Style="{StaticResource ExtendedListViewStyle}"/>
<!-- ... -->
</Grid>
</UserControl>
{Binding
Path=Configuration.ForegroundColorBrush,
RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:SideBarPlayList}}}"
It works for extended ListViewItem and in ContextMenu that is not placed in DataTemplate. But for ContextMenu that is not placed in DataTemplate I used direct Binding to local value with setting Path only.
All classes have:
static ExtendedListView()
{
DefaultStyleKeyProperty.OverrideMetadata(
typeof(ExtendedListView),
new FrameworkPropertyMetadata(typeof(ExtendedListView)));
}
// Same as ExtendedListViewItem and ExtendedContextMenuItem
static ExtendedContextMenu()
{
DefaultStyleKeyProperty.OverrideMetadata(
typeof(ExtendedContextMenu),
new FrameworkPropertyMetadata(typeof(ExtendedContextMenu)));
}
And I'm not using
<Setter Property="OverridesDefaultStyle" Value="True"/>
It didn't work anyway.

How can I add a Click Event Handler to a DataGrid Context Menu?

How can I add a Click Event Handler to a DataGrid's dynamically-generated ContextMenu?
I see people say use the 'Tag' attribute, but I'm not sure how to add the code in XAML, or whether that needs to be done in the code-behind.
Thanks.
<DataGrid ItemsSource="{Binding MyModules}" AutoGenerateColumns="False" x:Name="dataGrid">
<DataGrid.ItemContainerStyle>
<Style TargetType="DataGridRow">
<Setter Property="ContextMenu">
<Setter.Value>
<ContextMenu ItemsSource="{Binding Configuration.Commands}">
<ContextMenu.ItemContainerStyle>
<Style TargetType="MenuItem">
<Setter Property="Header" Value="{Binding Caption}" />
<!-- Instead of the following two lines where I set the CommandAction and CommandParameter, I need to have a Click Event Handler. How can I achieve that? -->
<!-- <Setter Property="Command" Value="{Binding CommandAction}" />
<Setter Property="CommandParameter" Value="{Binding CommandId}" /> -->
</Style>
</ContextMenu.ItemContainerStyle>
</ContextMenu>
</Setter.Value>
</Setter>
</Style>
</DataGrid.ItemContainerStyle>
<DataGrid.Columns>
<DataGridTemplateColumn Header="Module Name" Width="*" IsReadOnly="True">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Configuration.Name}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="Module Caption" Width="3*" IsReadOnly="True">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Configuration.Description}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
You could use an EventSetter:
<Setter Property="ContextMenu">
<Setter.Value>
<ContextMenu ItemsSource="{Binding Configuration.Commands}">
<ContextMenu.ItemContainerStyle>
<Style TargetType="MenuItem">
<Setter Property="Header" Value="{Binding Caption}" />
<EventSetter Event="Click" Handler="MenuItem_Click" />
</Style>
</ContextMenu.ItemContainerStyle>
</ContextMenu>
</Setter.Value>
</Setter>

HowTo implement commands for selection of treenodes in WPF treeview

Again I'm a bit lost in WPF treeview.
I populated my treeview with some data and I'd like to fire a command, when clicking on a node and get its values in that command.
My treeview-xaml looks like this:
<TreeView DataContext="{Binding ProjectTree}" ItemsSource="{Binding ProjectNode}" DockPanel.Dock="Left" Margin="0 0 2 0" x:Name="ProjectTree" Grid.Column="0">
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}"/>
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}"/>
<Setter Property="FontWeight" Value="Normal"/>
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="FontWeight" Value="Bold"/>
</Trigger>
</Style.Triggers>
</Style>
</TreeView.ItemContainerStyle>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children}">
<StackPanel Orientation="Horizontal">
<Image Margin="3" Source="{Binding ItemType, Converter={x:Static misc:TreeItemImageConverter.Instance }}" Width="20" />
<TextBlock VerticalAlignment="Center" Text="{Binding Name}"/>
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
How can I trigger a command on click on a treeviewitem here?
SOLUTION
After some experiments, It works for me that way:
xaml:
<TreeView DataContext="{Binding ProjectTree}" ItemsSource="{Binding ProjectNode}" DockPanel.Dock="Left"
x:Name="ProjectTree" Margin="0 0 2 0" Grid.Column="0">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectedItemChanged">
<i:InvokeCommandAction Command="{Binding TreeNodeSelected}"
CommandParameter="{Binding ElementName=ProjectTree, Path=SelectedItem}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}"/>
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}"/>
<Setter Property="FontWeight" Value="Normal"/>
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="FontWeight" Value="Bold"/>
</Trigger>
</Style.Triggers>
</Style>
</TreeView.ItemContainerStyle>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children}">
<StackPanel Orientation="Horizontal">
<Image Margin="3" Source="{Binding ItemType, Converter={x:Static misc:TreeItemImageConverter.Instance }}" Width="20" />
<TextBlock VerticalAlignment="Center" Text="{Binding Name}"/>
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
In the Viewmodel of the treeview:
C#
public RelayCommand TreeNodeSelected { get; private set; }
readonly ReadOnlyCollection<PlcAddressViewModel> _rootNodes;
readonly PlcAddressViewModel _rootAddress;
#region Constructor
public ProjectTreeviewModel(PlcAddress rootAddress)
{
_rootAddress = new PlcAddressViewModel(rootAddress);
_rootNodes = new ReadOnlyCollection<PlcAddressViewModel>(
new PlcAddressViewModel[]
{
_rootAddress
});
TreeNodeSelected = new RelayCommand(ExecuteTreeNodeSelected, canExecuteMethod);
}
#endregion Constructor
#region Commands
private bool canExecuteMethod(object parameter)
{
return true;
}
private void ExecuteTreeNodeSelected(object parameter)
{
PlcAddressViewModel selectedNode = (PlcAddressViewModel)parameter;
Console.WriteLine("Found this node: " + selectedNode.Name);
return;
}
#endregion Commands
Thanks to #mm8 and #BionicCode
You could handle an event like for example SelectedItemChanged using an interaction trigger:
<TreeView DataContext="{Binding ProjectTree}"
ItemsSource="{Binding ProjectNode}"
x:Name="ProjectTree"
xmlns:i="http://schemas.microsoft.com/xaml/behaviors">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectedItemChanged" >
<i:InvokeCommandAction Command="{Binding MouseEnterCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
<TreeView.ItemContainerStyle>
...
</TreeView.ItemContainerStyle>
...
</TreeView>
Handling events in an MVVM WPF application
How to add System.Windows.Interactivity to project?
But why don't you just bind the SelectedItem property to a source property and handle your logic in the setter of this one? This would be MVVM way of doing this.
Edit: Since the SelectedItem property a TreeView is read-only, you'll have to use a behaviour to be able to bind to it. There is an example of how to do this here.

How to find out if ComboBox item is in dropdown list or inside it

I want selected item in ComboBox look differently from it's instance in drop down list.
<ComboBox ItemsSource="{Binding ViewList}" SelectedItem="{Binding SelectedView}">
<ComboBox.Resources>
<DataTemplate DataType="{x:Type vm:View}">
<StackPanel Orientation="Horizontal">
<c:Icon x:Name="Icon" IconShape="{DynamicResource z.Users}" Margin="5,0" Background="{Binding Foreground, RelativeSource={RelativeSource Self}}"/>
<StackPanel>
<StackPanel>
<TextBlock x:Name="CurrentView" Text="Current View"
Foreground="{DynamicResource Pallete.Primary.Brighter}"
Visibility="{Binding IsSelected, Converter={StaticResource bool2VisibilityConverter}}"/>
<TextBlock x:Name="Title" Text="{Binding Title}"/>
</StackPanel>
</StackPanel>
</StackPanel>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding Type}" Value="SeparatorView">
<Setter TargetName="Icon" Property="Visibility" Value="Collapsed"/>
<Setter TargetName="Title" Property="FontWeight" Value="Bold"/>
</DataTrigger>
<DataTrigger Binding="{Binding Type}" Value="YearView">
<Setter TargetName="Icon" Property="IconShape" Value="{DynamicResource z.Bookmark}"/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</ComboBox.Resources>
</ComboBox>
Is there something I could use in CurrentView's Visibility Property or in triggers?
Here is an example of your you could modify the Foreground of a TextBlock when it it is selected in the ComboBox using a DataTrigger:
<ComboBox ItemsSource="{Binding ListOfStrings}">
<ComboBox.Resources>
<DataTemplate DataType="{x:Type sys:String}">
<TextBlock x:Name="txt" Text="{Binding}" Foreground="Red" />
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource AncestorType=ComboBoxItem}}" Value="{x:Null}">
<Setter TargetName="txt" Property="Foreground" Value="Green"/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</ComboBox.Resources>
</ComboBox>
I believe that the following is what you are looking for:
<DataTrigger Binding="{Binding RelativeSource={RelativeSource AncestorType=ComboBoxItem}}" Value="{x:Null}">

How to apply style trigger to datatemplate in WPF

I've got the following..
<ComboBox Grid.Row="2" Grid.Column="2" Grid.RowSpan="2" ItemsSource="{Binding ShipperAddresses}" Text="{Binding ShipperAddress}" Margin="85,2,0,2">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBox AcceptsReturn="True" Width="200" Height="100"/>
<DataTemplate.Resources>
<Style TargetType="{x:Type TextBox}">
<Setter Property="IsReadOnly" Value="True">
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type GroupBox}}, Path=Tag}" Value="False"/>
</Style.Triggers>
</Setter>
</Style>
</DataTemplate.Resources>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
The problem is that you can't apply a Style.Trigger like I'm trying to do inside a DataTemplate. So my question is how would you apply create a trigger so that a property on the DataTemplate changes based on the parent?
FINAL SOLUTION:
I took what Souvik gave me and fixed it up since there were a few problems. Here is the end result.
<ComboBox Grid.Row="2" Grid.Column="2" Grid.RowSpan="2" ItemsSource="{Binding ShipperAddresses}" Text="{Binding ShipperAddress}" DisplayMemberPath="Value" Margin="85,2,0,2">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBox AcceptsReturn="True" Width="200" Height="100" Text="{Binding Path=Value}"/>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ComboBox}}, Path=IsEditable}" Value="False">
<Setter Property="IsEnabled" Value="False"/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</ComboBox.ItemTemplate>
<ComboBox.Resources>
<Style TargetType="{x:Type ComboBox}">
<Setter Property="IsEditable" Value="True"/>
<Style.Triggers>
<Trigger Property="IsDropDownOpen" Value="True" >
<Setter Property="IsEditable" Value="False"/>
</Trigger>
</Style.Triggers>
</Style>
</ComboBox.Resources>
Have DataTemplate trigger instead of Style trigger:
<ComboBox Grid.Row="2" Grid.Column="2" Grid.RowSpan="2" ItemsSource="{Binding ShipperAddresses}" Text="{Binding ShipperAddress}" Margin="85,2,0,2">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBox AcceptsReturn="True" Width="200" Height="100"/>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type GroupBox}}, Path=Tag}" Value="False">
<Setter Property="IsEnabled" Value="False"/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>

Resources