Command Binding of Submenu item in Context Menu - wpf

I have a TreeView with an hierarchical Data Template (2 levels). I have a context menu created on the first level of the tree view with 3 levels. I want to bind a command of my view model to the second level of the context menu. Unfortunately I can only get it to work when using a command in my Model, which is not what I want to do... If possible, I would like to do it all in XAML.
I have tested the pure xaml solutions given here and here.
In the designer, the "Tag" is underlined blue saying "Cannot resolve Property Tag in data context of type System.Windows.UIElement" or, if I use "Parent.PlacementTarget....", the designer tells me that PlacementTarget cannot be resolved in the data context of type System.Windows.DependencyObject.
The code is compileable, but my command is never reached.
This is what I have:
In a ResourceDictionary:
<DataTemplate DataType="{x:Type viewModel:UpdateToolViewModel}">
<view:UpdateToolView/>
</DataTemplate>
<DataTemplate x:Key="ToolNameDataTemplate" DataType="{x:Type src:Element}">
<Grid>
<TextBlock Text="{Binding Path=NameProperty.Value}" FontSize="12" />
</Grid>
</DataTemplate>
<HierarchicalDataTemplate x:Key="ToolGroupsDataTemplate" ItemsSource="{Binding Elements}" DataType="{x:Type src:ElementGroup}" ItemTemplate="{StaticResource ToolNameDataTemplate}">
<TextBlock Text="{Binding Path=TextProperty.Value}" FontSize="14" FontWeight="Bold" Tag="{Binding ElementName=UpdateToolControl}">
<TextBlock.ContextMenu>
<ContextMenu>
<MenuItem Header="Add Tool" ItemContainerStyle="{StaticResource ToolGroupContextMenuToolsItemStyle}" >
<MenuItem.ItemsSource>
<CompositeCollection>
<MenuItem Header="Add New ..." Command="{Binding PlacementTarget.Tag.DataContext.AddToolCommand, RelativeSource={RelativeSource FindAncestor, AncestorType=ContextMenu}}" />
<CollectionContainer Collection="{Binding Source={StaticResource AddToolContextMenuSource}}"/>
</CompositeCollection>
</MenuItem.ItemsSource>
</MenuItem>
</ContextMenu>
</TextBlock.ContextMenu>
</TextBlock>
</HierarchicalDataTemplate>
In the UserControl:
<UserControl x:Class="...UpdateToolView" ... Name="UpdateToolControl">
<TreeView Name="ToolTreeView" ItemsSource="{Binding AllElementsInGroups}"
ItemTemplate="{StaticResource ToolGroupsDataTemplate}"
ItemContainerStyle="{StaticResource ToolTreeViewItemStyle}"/>
</UserControl>
I am already on the verge of using the command in my model, calling a method in my view model. Not nice, but I just don't seem to get it to work differently.

I was just going on, trying to figure out how to add a command to the CollectionContainer Items, when I found this: CollectionContainer Binding (sorry, its german, but the xaml code is the relevant thing here)
Now I have added the command in the ItemContainerStyle of the MenuItem and suddenly the whole thing works (although "Tag" is still underlined blue in the designer):
<converter:ElementToToolTipConverter x:Key="ElementToToolTipConverter"/>
<Style x:Key="ToolGroupContextMenuToolsItemStyle" TargetType="{x:Type MenuItem}">
<Setter Property="Header" Value="{Binding Name}"/>
<Setter Property="ItemsSource" Value="{Binding Children}"/>
<Setter Property="ToolTip" Value="{Binding Element, Converter={StaticResource ElementToToolTipConverter}}"/>
<Setter Property="Command" Value="{Binding PlacementTarget.Tag.DataContext.AddToolCommand, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ContextMenu}}}"/>
<Setter Property="CommandParameter" Value="{Binding}"/>
</Style>
Sometimes, it already helps thinking about something else... :)

Related

How to make Catel recognise all MultiSelectTreeView properties?

I am a fairly novice Visual Basic developer trying to use the MultiSelectTreeView control (https://github.com/ygoe/MultiSelectTreeView) in a MVVM application I am developing. To become familiar with using them together, I translated the MultiSelectTreeView demo to VB, and started implementing viewmodels using Catel.
What I found is that Catel seems to make some MultiSelectTreeView properties unavailable. On running the test using Catel, the demo window opens and mostly behaves as expected, but the immediate window shows a number of errors like:
System.Windows.Data Warning: 40 : BindingExpression path error: '(Controls:MultiSelectTreeView.HoverHighlighting)' property not found on 'object' ''MultiSelectTreeView' (Name='TheTreeView')'. BindingExpression:Path=(Controls:MultiSelectTreeView.HoverHighlighting); DataItem='MultiSelectTreeView' (Name='TheTreeView'); target element is 'MultiSelectTreeViewItem' (Name=''); target property is 'HoverHighlighting' (type 'Boolean')
Similar messages are present for the MultiSelectTreeView IsKeyboardMode and ItemIndent properties. Setting these properties in code or directly in the MainWindow XAML has no effect, and the MultiSelectTreeView's HoverHighlighting effect no longer works if Catel is used.
I have uploaded my test project to GitHub (https://github.com/AnotherKiwi/MultiSelectTreeViewDemoVB). The master branch contains the VB translation of MultiSelectTreeView demo, with all features working. In the ImplementingMainWindowViewModel branch I have started implementing a viewmodel using Catel. Most features of the demo work, except the ones involving the properties mentioned above.
I would really appreciate it if someone could provide guidance on why Catel seems to be interfering with these MultiSelectTreeView properties!
Some further information added after the answer from #Geert:
The declaration of the MultiSelectTreeView control in MainWindow.xaml is as follows
<controls:MultiSelectTreeView
x:Name="TheTreeView"
ItemsSource="{Binding RootNode.Children}"
AllowEditItems="{Binding AllowEditItems}"
VerticalRulers="{Binding VerticalRulers}">
<controls:MultiSelectTreeView.ContextMenu>
...
</controls:MultiSelectTreeView.ContextMenu>
<i:Interaction.Triggers>
...
</i:Interaction.Triggers>
<controls:MultiSelectTreeView.ItemContainerStyle>
<Style TargetType="{x:Type controls:MultiSelectTreeViewItem}">
<Setter Property="DisplayName" Value="{Binding DisplayName}"/>
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}"/>
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}"/>
<Setter Property="IsEnabled" Value="{Binding IsEnabled, Mode=TwoWay}"/>
<Setter Property="IsVisible" Value="{Binding IsVisible, Mode=TwoWay}"/>
<Setter Property="IsEditable" Value="{Binding IsEditable, Mode=TwoWay}"/>
<Setter Property="IsEditing" Value="{Binding IsEditing, Mode=TwoWay}"/>
<Setter Property="Remarks" Value="{Binding Remarks}"/>
<Setter Property="IsKeyboardMode" Value="{Binding IsKeyboardMode, Mode=TwoWay}"/>
<Setter Property="HoverHighlighting" Value="{Binding HoverHighlighting}"/>
<Setter Property="ItemIndent" Value="{Binding ItemIndent}"/>
<Setter Property="ToolTip" Value="{Binding ToolTip}" />
<Setter Property="ContentTemplateEdit">
<Setter.Value>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Border Background="YellowGreen" CornerRadius="3" Width="16" Height="16"/>
<controls:EditTextBox
Text="{Binding DisplayName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Padding="2,0,0,0"/>
</StackPanel>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</controls:MultiSelectTreeView.ItemContainerStyle>
<controls:MultiSelectTreeView.Resources>
<!--
Here the general item appearance is defined, for the ViewModel.TreeItemViewModel type
-->
<HierarchicalDataTemplate DataType="{x:Type vm:TreeItemViewModel}" ItemsSource="{Binding DataContext.Children}">
<StackPanel Orientation="Horizontal">
<Image Source="{Binding DataContext.ImageSource}" Width="16" Height="16" SnapsToDevicePixels="True"/>
<TextBlock Text="{Binding DataContext.DisplayName}" VerticalAlignment="Center" Padding="4,0,2,0"/>
</StackPanel>
</HierarchicalDataTemplate>
</controls:MultiSelectTreeView.Resources>
</controls:MultiSelectTreeView>
I have tried applying the advice given in #Geert's answer, but when I run the application the MultiSelectTreeView is not displayed. I'm very new to WPF and I'm probably not modifying the appropriate XAML statements. Some more help with this would be really appreciated!
Note that items in an itemscontrol get a new DataContext (which is the item), so you cannot bind directly to the VM inside an ItemTemplate.
If you need to bind to the vm, you should do something like this:
<ItemsControl x:Name="myItemsControl" ItemsSource="{Binding MyItems}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Content="{Binding ElementName=myItemsControl, Path=DataContext.SomePropertyOnTheVm}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>

How to apply a KeyBinding in a CellTemplate of a DataGridTemplateColumn?

I've got a bit of a curly problem that I cannot find any complete and definitive information on...
My requirement is very simple: I want to apply a KeyBinding to a DataGridTemplateColumn. The purpose of this is to execute a command when the Delete key is hit for that column - a different command is executed if Delete is hit anywhere else on the grid. Both Delete commands exist in the VM of the grid.
While I could have a single keybinding defined at the grid level and then in the command test which column the active cell belongs to, this is a messy solution because:
that doesn't scale well when the grid is used on several different pages
the column in question is defined as a resource in a ResourceDictionary
Besides, I've seen nothing that indicates that this isn't possible... yet it eludes me.
I believe my problem is because focus is not automatically applied to the CellTemplate when that cell becomes the grid's current cell. There is a focus rectangle on the cell, but no focus events get triggered inside the cell (I've checked this). While readonly controls like TextBlocks can receive focus, if they don't then the keybinding cannot be triggered. The commands for the keybinding are correctly bound, so binding is not an issue.
The grid is defined like this:
<DataGrid x:Name="MyGrid"
ItemsSource="{Binding Path=FilteredSortedData}"
>
<DataGrid.Columns>
<StaticResource ResourceKey="StatusColumn" />
<StaticResource ResourceKey="MySpecialDeleteColumn" />
<StaticResource ResourceKey="...etc..." />
</DataGrid.Columns>
</DataGrid>
Then the column:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
x:Class="MyNamespace.GridColumns"
>
<FrameworkElement x:Key="GridColProxy" Name="GridColProxy" DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}, NotifyOnSourceUpdated=True, NotifyOnTargetUpdated=True, Mode=OneWay}" />
<DataGridTemplateColumn x:Key="MySpecialDeleteColumn"
x:Shared="False"
Header="My Special Delete Column"
IsReadOnly="False"
>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<StackPanel >
<!-- keybinding doesn't work at this level -->
<StackPanel.Style >
<Style TargetType="StackPanel">
<Setter Property="localBehaviours:InputBindingsBehaviour.AttachedKeyBindings">
<Setter.Value>
<localBehaviours:KeyBindingObservable >
<KeyBinding Key="Delete" Command="{Binding Path=DataContext.DeleteItemsCommand, Source={StaticResource GridColProxy}, Mode=OneWay, NotifyOnTargetUpdated=True}" />
</localBehaviours:KeyBindingObservable>
</Setter.Value>
</Setter>
</Style>
</StackPanel.Style>
<Grid >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0"
Text="{Binding SomeProperty}"
>
<TextBlock.Style>
<Style TargetType="TextBlock" >
<Style.Triggers>
<DataTrigger Binding="{Binding Path=MySpecialFlag, FallbackValue=false}" Value="True">
<Setter Property="localBehaviours:InputBindingsBehaviour.AttachedKeyBindings">
<Setter.Value>
<localBehaviours:KeyBindingObservable >
<KeyBinding Key="Delete" Command="{Binding Path=DataContext.DeleteItemsCommand, Source={StaticResource GridColProxy}, Mode=OneWay, NotifyOnTargetUpdated=True}" />
</localBehaviours:KeyBindingObservable>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
<Image Source="{Binding ...}"
Grid.Column="1"
/>
</Grid>
</StackPanel>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellStyle>
<Style TargetType="{x:Type DataGridCell}" >
<Style.Triggers>
<!-- placing the keybinding here to apply to the grid cell doesn't have any effect... -->
<DataTrigger Binding="{Binding Path=MySpecialFlag, FallbackValue=false}" Value="True">
<Setter Property="localBehaviours:InputBindingsBehaviour.AttachedKeyBindings">
<Setter.Value>
<localBehaviours:KeyBindingObservable >
<KeyBinding Key="Delete" Command="{Binding Path=DataContext.DeleteItemsCommand, Source={StaticResource GridColProxy}, Mode=OneWay, NotifyOnTargetUpdated=True}" />
</localBehaviours:KeyBindingObservable>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</DataGridTemplateColumn.CellStyle>
</DataGridTemplateColumn>
</ResourceDictionary>
Notes:
ignore the localBehaviours:KeyBindingObservable, it is simply an attached property to assist with intercepting and inspecting keybindings before assigning them to the InputBindings property of the attached control. This works great, there is no issue here.
I have shown every place I have tried the keybindings, none of them have worked (been triggered). The DataTrigger bindings work correctly, so just ignore those.
CellEditingTemplate has been omitted for brevity
So the question I have is:
how can I force the CellTemplate items to receive (keyboard) focus, preferably with XAML? Or is there a way to apply the keybinding at cell level so that it works regardless of the template being shown?
If a XAML solution is not possible then a small amount of event triggered (PreviewKeyDown, etc) code in the code behind of the ResourceDictionary could be used.

Property 'Content' was not found in type 'FrameworkElement' using DataTrigger

I'm new to WPF.
I'm trying to display a ToolTip on a ListBox item only when the grouping is equal to "Search Results".
I'm getting an error that says :
"Property 'Content' was not found in type 'FrameworkElement'."
Can anyone tell me what's wrong with the code below?
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=code}">
<TextBlock.ToolTip>
<ToolTip>
<ToolTip.Triggers>
<DataTrigger Binding="{Binding Path=grouping}" Value="Search Results">
<Setter Property="Content" Value="{Binding Path=grouping}"/>
</DataTrigger>
</ToolTip.Triggers>
</ToolTip>
</TextBlock.ToolTip>
</TextBlock>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
It works ok without the trigger like the code below so it confuses me why it says that the property was not found.
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=code}">
<TextBlock.ToolTip>
<ToolTip Content="{Binding Path=grouping}" />
</TextBlock.ToolTip>
</TextBlock>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
The Triggers collection of FrameworkElement is only for event triggers, not for DataTriggers or PropertyTriggers. Define a style for the ToolTip which contains the DataTrigger:
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=code}">
<TextBlock.ToolTip>
<ToolTip>
<Tooltip.Style>
<Style TargetType="ToolTip">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=grouping}" Value="Search Results">
<Setter Property="Content" Value="{Binding Path=grouping}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ToolTip.Style>
</ToolTip>
</TextBlock.ToolTip>
</TextBlock>
</DataTemplate>
</ListBox.ItemTemplate>
MSDN says:
Note that the collection of triggers established on an element only
supports EventTrigger, not property triggers (Trigger). If you require
property triggers, you must place these within a style or template and
then assign that style or template to the element either directly
through the Style property, or indirectly through an implicit style
reference.
That doesn't describe your problem directly, but read it as: Set Triggers in styles.
This article gets more specific: Dr. WPF Blog
There is also a Triggers collection on FrameworkElement, but it can
only contain event triggers… not property or data triggers.

MenuItem databinding

I've been puzzling over this for a day or so with no luck - I'm probably missing something obvious. Basically, I have a context menu with two items. One is statically declared, and bound to a command. The other has no command of its own, but binds to a collection of viewmodels. So visually the menu should look something like:
Delete
Add
Item 1
Item 2
Where the items vary depending on the thing the context menu was bound to. Originally I had something like this:
<ContextMenu x:Key="itemContextMenu">
<MenuItem Header="_Delete"
Command="{Binding DeleteCommand}" />
<MenuItem Header="_Add" DataContext=""
ItemsSource="{Binding AvailableTypes}">
<MenuItem.ItemTemplate>
<DataTemplate>
<MenuItem Header="{Binding Path=ItemType.Name}"
Command="{Binding Path=AddItemCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Grid}}"
CommandParameter="{Binding}" />
</DataTemplate>
</MenuItem.ItemTemplate>
</MenuItem>
</ContextMenu>
Which works, but gives me the nested MenuItems others have experienced. Based on a couple threads here on stackoverflow, I then tried this:
<ContextMenu x:Key="itemContextMenu">
<MenuItem Header="_Delete"
Command="{Binding DeleteCommand}" />
<MenuItem Header="_Add"
ItemsSource="{Binding AvailableTypes}">
<MenuItem.ItemContainerStyle>
<Style TargetType="MenuItem">
<Setter Property="Header"
Value="{Binding Path=ItemType.Name}" />
<Setter Property="Command"
Value="{Binding Path=AddItemCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Grid}}" />
<Setter Property="CommandParameter"
Value="{Binding}" />
</Style>
</MenuItem.ItemContainerStyle>
</MenuItem>
</ContextMenu>
However, when I do that my bindings all fail with errors like:
BindingExpression path error: 'ItemType' property not found on 'object' ''String'
BindingExpression path error: 'AddItemCommand' property not found on 'object' ''Grid'
To me that says that the DataContext is getting lost when I use the ItemContainerStyle. What am I missing?
edit:
I think I had some red herrings in here, so I've simplified the examples further to try and narrow down the problem.
Working but screwy layout-wise:
<ContextMenu x:Key="itemContextMenu">
<MenuItem Header="_Delete" />
<MenuItem Header="_Add" DataContext=""
ItemsSource="{Binding AvailableTypes}">
<MenuItem.ItemTemplate>
<DataTemplate>
<MenuItem Header="{Binding Path=ItemType.Name}" />
</DataTemplate>
</MenuItem.ItemTemplate>
</MenuItem>
</ContextMenu>
Non-working with a BindingExpression error:
<ContextMenu x:Key="itemContextMenu">
<MenuItem Header="_Delete" />
<MenuItem Header="_Add"
ItemsSource="{Binding AvailableTypes}">
<MenuItem.ItemContainerStyle>
<Style TargetType="MenuItem">
<Setter Property="Header"
Value="{Binding Path=ItemType.Name}" />
</Style>
</MenuItem.ItemContainerStyle>
</MenuItem>
</ContextMenu>
Apparently this is a bug in 3.5. I upgraded my project to 4.0, and now everything works as expected.

Sub Menu Item Command MVVM

<MenuItem Header="Flag(s)" ItemsSource="{Binding Path=LineItemFlags}" Command="{Binding AssignFollowupCommand}">
<MenuItem.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding FlagName}">
</TextBlock>
</DataTemplate>
</MenuItem.ItemTemplate>
</MenuItem>
For the parent Item i mean menu item i have a command it's invoking i want to know how to setup command for Sub Menu item
Rather than setting the ItemTemplate, set the ItemContainerStyle. Give it a style that sets the Header and Command properties of the menu item.
<MenuItem Header="_Recent Files" ItemsSource="{Binding RecentFiles}">
<MenuItem.ItemContainerStyle>
<Style>
<Setter Property="MenuItem.Header" Value="{Binding FileName}"/>
<Setter Property="MenuItem.Command" Value="{Binding Open}"/>
</Style>
</MenuItem.ItemContainerStyle>
</MenuItem>
Full example and description on Code Project.

Resources