Reuse context menu - c#

I have created a context menu that I (at the moment) use for some items in my treeview. For that I have created a TreeItem class that holds all the relevant information like header, icon, children, execute target, etc. This is what it looks like:
<ContextMenu DataContext="{Binding PlacementTarget.DataContext, RelativeSource={RelativeSource Self}}"
Visibility="{Binding ShowContextMenu}"
ItemsSource="{Binding ContextMenu}">
<ContextMenu.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children}">
<TextBlock Text="{Binding Header}" />
<HierarchicalDataTemplate.ItemContainerStyle>
<Style TargetType="MenuItem">
<Setter Property="Command"
Value="{Binding Execute}" />
<Setter Property="Icon"
Value="{StaticResource cmIcon}" />
<Setter Property="ToolTip"
Value="{Binding ToolTip}" />
</Style>
</HierarchicalDataTemplate.ItemContainerStyle>
</HierarchicalDataTemplate>
</ContextMenu.ItemTemplate>
<ContextMenu.ItemContainerStyle>
<Style TargetType="MenuItem">
<Setter Property="Command"
Value="{Binding Execute}" />
<Setter Property="Icon"
Value="{StaticResource cmIcon}" />
<Setter Property="ToolTip"
Value="{Binding ToolTip}" />
</Style>
</ContextMenu.ItemContainerStyle>
</ContextMenu>
When I used the context menu only in the tree view, it was attached to the TextBlock in the ItemTemplate. But now I want to use the same context menu for a different control. As I don't want to copy the same code to a different location and maintain it multiple times, I want to reuse it as template. I tried 2 things:
I put the context menu in the resources of the user control (just for testing) and call it like this: <TextBlock Text="{Binding Header}" ContextMenu="{StaticResource myContextMenu}">. It will be displayed, but not be closed and not move. Also this is not really helpful anyway as I want to use the context menu on a different user control.
Then I put the context menu inside a control template in the App.xaml: <ControlTemplate x:Key="TreeContextMenu" TargetType="ContextMenu">. And I call it like this:
<TextBlock.ContextMenu>
<ContextMenu Template="{StaticResource TreeContextMenu}"/>
</TextBlock.ContextMenu>
The program starts, but when I want to open the context menu, I get an exception: 'ContextMenu' cannot have a logical or visual parent.
I have tried to google for a solution, but couldn't find anything helpful.

You are trying to create a context menu inside a context menu. Remove the ControlTemplate tag from the App.xaml and move the x:Key attribute directly to the ContextMenu tag.
Also, delete the TextBlock.ContextMenu and add ContextMenu="{StaticResource TreeContextMenu}" attribute to the TextBlocktag.

Related

Force focus on control via xaml style

I have a treeview and a listbox.
I want to specify on the style trigger-setter option that, when the listbox visibility is Hidden the focus have to return on the treeview.
Is it possibile to condition an user control focus on the state of another user control?
something like
<Style TargetType="{x:Type TreeView}">
<Style.Triggers>
<Trigger Property="Visibility" Value="Hidden">
<Setter Property="IsFocus" Value="True" />
</Trigger>
</Style.Triggers>
</Style>
Yes it is, see the example here
FOR CHILDREN IN CONTAINERS
<StackPanel FocusManager.FocusedElement="{Binding ElementName=lol}">
<TextBox x:Name="lol"/>
<TextBox x:Name="lul"/>
</StackPanel>
FOR SELF
<TextBox FocusManager.FocusedElement="{Binding RelativeSource={RelativeSource Self}}"/>

Command binding to top-level MenuItem of Menu doesn't work

I'm learning WPF and developing a dynamic Menu which is driven by data binding of it's ItemsSource to an ObservableCollection. To do this I have a simple MenuItemViewModel and a HierarchicalDataTemplate for automatic binding of MenuItems to it.
The problem I have is that Command property doesn't work for top level menu items. Despite it is set, a MenuItem doesn't react on mouse click and doesn't get disabled if Command cannot be executed. Simply it's like it's not getting bound.
For lower level menu items though, it works as intended. I think this should be a problem of my HierarchicalDataTemplate but I can't find it, because as I see there's no code in template which might affect command binding of only top-level MenuItems.
MenuItemViewModel implements INotifyPropertyChanged and contains following public properties:
string Text
Uri ImageSource
ICommand Command
ObservableCollection<MenuItemViewModel> Children
HierarchicalDataTemplate for MenuItem in my Window.Resources is as follows:
<HierarchicalDataTemplate DataType="{x:Type common:MenuItemViewModel}"
ItemsSource="{Binding Path=Children}">
<HierarchicalDataTemplate.ItemContainerStyle>
<Style TargetType="MenuItem">
<Setter Property="Command"
Value="{Binding Command}" />
</Style>
</HierarchicalDataTemplate.ItemContainerStyle>
<StackPanel Orientation="Horizontal">
<Image Source="{Binding ImageSource}" />
<TextBlock Text="{Binding Text}" VerticalAlignment="Center"/>
</StackPanel>
</HierarchicalDataTemplate>
Can you please point me on my mistake?
EDIT: Top-level MenuItem doesn't contain any children (i.e. Children collection of associated ViewModel is empty).
Thanks to #sTrenat comment I've come to solution below.
<Menu.Resources>
<!-- cancel sharing of image so Icon will work properly -->
<Image x:Key="MenuIcon" Source="{Binding ImageSource}" x:Shared="False"/>
</Menu.Resources>
<Menu.ItemContainerStyle>
<Style TargetType="{x:Type MenuItem}"
BasedOn="{StaticResource {x:Type MenuItem}}">
<Setter Property="Header" Value="{Binding Text}" />
<Setter Property="Icon" Value="{StaticResource MenuIcon}"/>
<Setter Property="Command" Value="{Binding Command}"/>
<Setter Property="ItemsSource" Value="{Binding Children}" />
<!-- centering MenuItem's Header -->
<Setter Property="HeaderTemplate">
<Setter.Value>
<DataTemplate>
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" Content="{Binding}" />
</DataTemplate>
</Setter.Value>
</Setter>
<!-- setting Icon to null when ImageSource isn't specified -->
<Style.Triggers>
<DataTrigger Binding="{Binding ImageSource}"
Value="{x:Null}">
<Setter Property="Icon" Value="{x:Null}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Menu.ItemContainerStyle>

Add a separator to a Context Menu bound to models in WPF with MVVM Light

I have a context menu in WPF with the following constraints:
Menu items should be bound to a list of models
The menu may contain separators
Menu items can have sub menu items
Menu items may be turned off based on state
The order of the menu items may not change (i.e. menu item 1 must appear above menu item 2, if present)
I have seen solutions where the view model contains a list of controls, however, this is not an acceptable solution.
The approach I've taken almost works.
<ContextMenu DataContext="{Binding Data.ContextMenuViewModel, Source={StaticResource proxy}}"
ItemsSource="{Binding Data.ContextMenuViewModel.MenuItems, Source={StaticResource proxy}}"
Visibility="{Binding CellContextMenuOpen, Converter={StaticResource BoolToVisibilityConverter}}" >
<ContextMenu.ItemContainerStyle>
<Style TargetType="{x:Type MenuItem}">
<Setter Property="Command" Value="{Binding Command}" />
<Setter Property="CommandParameter" Value="{Binding CommandParameter}" />
<Setter Property="ItemsSource" Value="{Binding SubMenuItems}" />
<Setter Property="IsCheckable" Value="{Binding IsCheckable, FallbackValue=False}" />
<Setter Property="IsChecked" Value="{Binding IsChecked, FallbackValue=False}" />
</Style>
</ContextMenu.ItemContainerStyle>
<ContextMenu.ItemTemplateSelector>
<ccm:MenuItemTemplateSelector>
<ccm:MenuItemTemplateSelector.SeparatorTemplate>
<DataTemplate>
<Separator HorizontalAlignment="Stretch" IsEnabled="False" Margin="0" />
</DataTemplate>
</ccm:MenuItemTemplateSelector.SeparatorTemplate>
<ccm:MenuItemTemplateSelector.MenuItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Header}" />
</DataTemplate>
</ccm:MenuItemTemplateSelector.MenuItemTemplate>
</ccm:MenuItemTemplateSelector>
</ContextMenu.ItemTemplateSelector>
However, I've discovered that some properties in ItemsContainerStyle and the DataTemplate are mutually exclusive. For example, if I were to add a visibility binding to the items container style, the data template wont be applied at all.
The problem with the solution above is that the MenuItem itself wont collapse based on a binding, I can only get the header to collapse. If I did not need the separators, I could put everything in an ItemContainerStyle and be done with it.
Any suggestions would be greatly appreciated.

Ancestor level binding not working in MenuItem command

We have use the hierarchical template to populate the menuitem
<UserControl.DataContext>
<local:MenuViewModel/>
</UserControl.DataContext>
<Grid>
<!--Initialize the Menu-->
<Menu Name="Part_Menu" ItemsSource="{Binding MenuCollection}" Background="#E5E5E5" VerticalAlignment="Center">
<Menu.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding MenuItemCollection}">
<TextBlock Text="{Binding Header}" />
<HierarchicalDataTemplate.ItemContainerStyle>
<Style TargetType="MenuItem">
<Setter Property="CommandParameter" Value="{Binding Header}"/>
<Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property="Command"
Value="{Binding RelativeSource={RelativeSource AncestorType={x:Type local:MenuViewModel}, AncestorLevel=2,Mode=FindAncestor},Path=MenuClick}"></Setter>
</Style>
</HierarchicalDataTemplate.ItemContainerStyle>
</HierarchicalDataTemplate>
</Menu.ItemTemplate>
</Menu>
</Grid>
In this i have tried to bind the MenuClick(ICommand) to MenuItem , but it did not bind correctly
I have check the binding in the following forum link
[http://stackoverflow.com/questions/23941314/wpf-how-can-i-create-menu-and-submenus-using-binding?rq=1][1]
In this command added in the MenuModel , i need to Command in the MenuViewmodel
This way of binding :
{Binding RelativeSource={RelativeSource AncestorType={x:Type local:MenuViewModel},
AncestorLevel=2, Mode=FindAncestor}
..is not working, because the AncestorType does not derive from UIElement.
The Path of the binding should be DataContext.MenuClick, and the AncestorType should be Menu. Putting it all together :
<Setter Property="Command"
Value="{Binding Path=DataContext.MenuClick,
RelativeSource={RelativeSource AncestorType={x:Type Menu},
AncestorLevel=2}}">
</Setter>
Mode=FindAncestor is the default mode, so i left that out.
In the MSDN: RelativeSource.AncestorType Documentation it is only stated that any Type could theoretically be used, however, FindAncestor inspects the visual tree to try and find the given ancestor, so whatever type you are looking for must be present in the visual tree. Hope this helps.

Binding the Setter Value in a Style to the main model

i want to write a WPF-Application that shows different pages/scenes. So, I have a ViewModel (MainViewModel) that provides a list of scenes (SceneViewModel). Every scene has a name which can be accessed by a property SceneName.
I create the MainViewModel in Window.DataContext:
<Window.DataContext>
<!-- Declaratively create an instance of the main model -->
<models:MainViewModel />
</Window.DataContext>
I then want a menu-item that lists all scenes. When one of the menu-items is clicked, the scene should change. Therefor i created an Command in the MainViewMode: ChangeSceneCommand.
In XAML I want to create the menu-list in the following way:
<Menu Grid.Row="0">
<Menu.Resources>
<Style x:Key="SetSceneCommandItem" TargetType="{x:Type MenuItem}">
<Setter Property="Header" Value="{Binding SceneName}"/>
<Setter Property="Command" Value="{Binding SetSceneCommand}"/> <-- Here is the problem
<Setter Property="IsChecked" Value="False" />
</Style>
</Menu.Resources>
<MenuItem Header="Scenes" ItemsSource="{Binding Scenes}" <-- Scenes is a list with scenes^^
ItemContainerStyle="{StaticResource SetSceneCommandItem}" /> <-- Here the style is set
</Menu>
The Item-Header binds well but the "SetSceneCommand" can not be found, because wpf tries to find the "SetSceneCommand"-property in the SceneViewModel. How can i say wpf to access the model in the datacontext out of the style?
PS: You may have noticed that SetSceneCommand will need a scene as parameter to work, but I wanted to implement that later.
You can use RelativeSource. If SetSceneCommand is part of the same context used by Menu then it will look like this:
<Setter
Property="Command"
Value="{Binding RelativeSource={RelativeSource AncestorType={x:Type Menu}}, Path=DataContext.SetSceneCommand}"/>
this tells Binding to go up the visual tree until Menu and take DataContext.SetSceneCommand from there

Categories

Resources