Ancestor level binding not working in MenuItem command - c#

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.

Related

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>

Contextmenu from different sources: Set different Databinding for different menuitems

I want to achieve a context menu behavior like e.g. visual studio has for toolbars, with a list of checkable items, and a list of commands.
The contextmenu items should come from some observablecollection in view models.
VS ContextMenu for Toolboxes
As these come from different sources. I thought of using a composite collection to achieve this. Binding of one collection should be to Command, other to IsChecked/IsChecked. I also would like to use a separator.
The problem I have is about binding. I cannot use a datatemplate for complete menuitem because this does not include the IsChecked property. Therefore, I'm using ItemContainerStyle for it (see https://stackoverflow.com/a/29130774/5381620).
As long as I only use 1 collection container and have 1 source everything is fine.
However, inserting items from another source (or a Separator) will apply the "style" bindings to all menu items what is not intended and in case of 'Separator' will lead to an exception.
<ContextMenu>
<ContextMenu.Resources>
<CollectionViewSource x:Key="ContextMenuColCollection" Source="{Binding RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}, Path= DataContext.HeaderContextMenu}"/>
</ContextMenu.Resources>
<ContextMenu.ItemTemplate>
<DataTemplate DataType="{x:Type vm:Collection1VM}" >
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</ContextMenu.ItemTemplate>
<ContextMenu.ItemsSource>
<CompositeCollection>
<MenuItem Header="Settings"/>
<Separator />
<CollectionContainer Collection="{Binding Source={StaticResource ContextMenuColCollection}}"/>
</CompositeCollection>
</ContextMenu.ItemsSource>
<ContextMenu.ItemContainerStyle>
<Style TargetType="{x:Type MenuItem}">
<Setter Property="IsCheckable" Value="True"/>
<Setter Property="IsChecked" Value="{Binding IsSelected}"/>
</Style>
</ContextMenu.ItemContainerStyle>
</ContextMenu>
After trying a lot I finally found a solution which is appropriate for me. Unfortunately, it contains lots of workarounds for different stuff and isn't quite a straight forward solution. I can't believe it was this difficult to create a simple contextmenu.
As described in question, I was not able to use a datatemplate because this would result in an exception caused by the separator, which doesn't implement some properties, e.g. the IsCheckable.
Moving the Style from ContextMenu.ItemContainerStyle to ContextMenu.Resources only applies this to the real MenuItems
(see H.B.'s answer here https://stackoverflow.com/a/18948356/5381620)
<ContextMenu>
<ContextMenu.Resources>
<Style TargetType="MenuItem">
<Setter Property="Header" Value="{Binding Name}"/>
<Setter Property="IsCheckable" Value="{Binding IsCheckable}"/>
<Setter Property="IsChecked" Value="{Binding IsChecked}"/>
<Setter Property="Command" Value="{Binding Cmd}"/>
<!-- this is necessary to avoid binding error, see explanation below-->
<Setter Property="HorizontalContentAlignment" Value="Left"/>
<Setter Property="VerticalContentAlignment" Value="Center"/>
</Style>
<!-- collectionViewSource necessary for behavior described here
https://social.msdn.microsoft.com/Forums/vstudio/en-US/b15cbd9d-95aa-47c6-8068-7ae9f7dca88a/collectioncontainer-does-not-support-relativesource?forum=wpf
-->
<CollectionViewSource x:Key="MenuCmds" Source="{Binding RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}, Path= DataContext.CmdObsColl}"/>
<CollectionViewSource x:Key="MenuCheckable" Source="{Binding RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}, Path= DataContext.CheckableObsCol}"/>
</ContextMenu.Resources>
<ContextMenu.ItemsSource>
<CompositeCollection>
<CollectionContainer Collection="{Binding Source={StaticResource MenuCmds}}"/>
<Separator />
<CollectionContainer Collection="{Binding Source={StaticResource MenuCheckable}}"/>
</CompositeCollection>
</ContextMenu.ItemsSource>
</ContextMenu>
If using more than one Collection container, there is still some strange binding error, which can be handled by adding the following to the Application.Resources. See the following link from msdn forum for more information.
https://social.msdn.microsoft.com/Forums/vstudio/en-US/42cd1554-de7a-473b-b977-ddbd6298b3d0/binding-error-when-using-compositecollection-for-menuitems?forum=wpf
What I still don't understand is why I still get the binding error, if I only set the ContentAlignment only in Application.Resources or Context.Resources. For some reason it is necessary to set both. If someone could explain this to me I would be quite happy.
<Application.Resources>
<Style TargetType="MenuItem">
<Setter Property="HorizontalContentAlignment" Value="Left" />
<Setter Property="VerticalContentAlignment" Value="Center" />
</Style>
</Application.Resources>
For binding I use some MenuItemVM class, which is more or less like this and where I can set properties depending on if menuitem should be a checkable one or a command.
class ContextMenuItemVM
{
public string Name { get; }
public bool IsCheckable { get; }
public bool IsChecked { get; set; }
public ICommand Cmd { get; }
}
Move the DataTemplate to <ContextMenu.Resources> and remove the ItemTemplate:
<ContextMenu>
<ContextMenu.Resources>
<CollectionViewSource x:Key="ContextMenuColCollection" Source="{Binding RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}, Path= DataContext.HeaderContextMenu}"/>
<DataTemplate DataType="{x:Type vm:Collection1VM}" >
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
<local:Converter x:Key="conv" />
</ContextMenu.Resources>
<ContextMenu.ItemsSource>
<CompositeCollection>
<MenuItem Header="Settings"/>
<Separator />
<CollectionContainer Collection="{Binding Source={StaticResource ContextMenuColCollection}}"/>
</CompositeCollection>
</ContextMenu.ItemsSource>
<ContextMenu.ItemContainerStyle>
<Style TargetType="{x:Type MenuItem}">
<Setter Property="IsCheckable" Value="True"/>
<Setter Property="IsChecked" Value="{Binding Path=., Converter={StaticResource conv}}"/>
</Style>
</ContextMenu.ItemContainerStyle>
</ContextMenu>
Then the DataTemplate should be applied to Collection1VM objects only.
When it comes to the IsChecked property, you could either ignore any binding warnings or implement a converter, e.g.:
public class Converter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
Collection1VM vm = value as Collection1VM;
return vm != null && vm.IsChecked;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return value;
}
}

Bind Expanded & Collapsed event of TreeViewItem to viewmodel

Some time ago i asked here how to bind the expanded event to the viewmodel and came to a solution using AttachedCommandBehavior:
<TreeView Name="tv" ItemsSource="{Binding TreeViewElements}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children, Mode=OneTime}">
<StackPanel Orientation="Horizontal">
<Label Content="{Binding Title, Mode=OneTime}" />
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="local:CommandBehavior.Event" Value="Expanded"></Setter>
<Setter Property="local:CommandBehavior.Command" Value="{Binding DataContext.ExpandCommand, ElementName=tv}"></Setter>
<Setter Property="local:CommandBehavior.CommandParameter" Value="{Binding}"></Setter>
</Style>
</TreeView.ItemContainerStyle>
</TreeView>
Now it is necessary to bind the collapsed event, too.
Adding this to the style section like this does not work, only the collapsed event is binded:
<Setter Property="local:CommandBehavior.Event" Value="Expanded"></Setter>
<Setter Property="local:CommandBehavior.Command" Value="{Binding DataContext.ExpandCommand, ElementName=tv}"></Setter>
<Setter Property="local:CommandBehavior.CommandParameter" Value="{Binding}"></Setter>
<Setter Property="local:CommandBehavior.Event" Value="Collapsed"></Setter>
<Setter Property="local:CommandBehavior.Command" Value="{Binding DataContext.CollapseCommand, ElementName=tv}"></Setter>
<Setter Property="local:CommandBehavior.CommandParameter" Value="{Binding}"></Setter>
Then i found an example on the AttachedCommandBehavior homepage to use a collection of behaviors:
<local:CommandBehaviorCollection.Behaviors>
<local:BehaviorBinding Event="MouseLeftButtonDown" Action="{Binding DoSomething}" CommandParameter="An Action on MouseLeftButtonDown"/>
<local:BehaviorBinding Event="MouseRightButtonDown" Command="{Binding SomeCommand}" CommandParameter="A Command on MouseRightButtonDown"/>
</local:CommandBehaviorCollection.Behaviors>
The problem is that adding such a collection in the style section does not work, visual studio gives the error that the behavior property can not be attached to style.
Has anybody an idea how i can bind both events to the viewmodel?
Do it like this:
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="local:CommandBehaviorCollection.Behaviors">
<Setter.Value>
<local:BehaviorBinding Event="MouseLeftButtonDown" Action="{Binding DoSomething}" CommandParameter="An Action on MouseLeftButtonDown"/>
<local:BehaviorBinding Event="MouseRightButtonDown" Command="{Binding SomeCommand}" CommandParameter="A Command on MouseRightButtonDown"/>
</Setter.Value>
</Setter>
</Style>

Reuse context menu

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.

Relative binding in style of control inside ListBoxItem template

My problem is with the following code, with binding the IsAvailable property of the MyListBoxItem class. My current solution:
<ListBox ItemTemplate="{StaticResource myTemplate}">
<ListBox.Resources>
<DataTemplate x:Key="myTemplate" DataType="{x:Type local:MyListBoxItem}">
<Label Foreground="Green" Content="{Binding Title}" Tag="{Binding IsAvailable}">
<Label.Style>
<Style TargetType="{x:Type Label}">
<Style.Triggers>
<DataTrigger Binding="{Binding Tag, RelativeSource={RelativeSource Self}}" Value="True">
<Setter Property="FontWeight" Value="Bold"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Label.Style>
</Label>
</DataTemplate>
... (more datatemplates)
</ListBox.Resources>
</ListBox>
My question: In my solution the value of IsAvailable "goes through" two bindings. The first one binds the value to the Tag property of the Label and then in the style triggers, a trigger checks its value and sets a property of the Label. When I used Binding="{Binding IsAvailable, RelativeSource={RelativeSource AncestorType={x:Type local:MyListBoxItem}}}" it didn't work, because the Style can't see any ancestor of the Label (or something similar reason), it resulted binding errors (with code 4 or 40 maybe), for each item added to the ListBox.
So finally: can I make the solution more simple, or there is no another (better) one?
An important thing I've forgot to mention, sorry: I put the DataTemplate in the ListBox's resources because I have more templates (they are basically differ, so I can't style them with triggers), which I have to switch between sometimes...
The ItemTemplate will take the type that the ItemsSource is bound to. Therefore you should be able to simply bind to IsAvailable, as the ListBox's item type is MyListBoxItem. Try this:
<ListBox ItemsSource="...">
<ListBox.ItemTemplate>
<DataTemplate>
<Label Foreground="Green" Content="{Binding Title}" Tag="{Binding IsAvailable}">
<Label.Style>
<Style TargetType="{x:Type Label}">
<Style.Triggers>
<DataTrigger Binding="{Binding Tag, RelativeSource={RelativeSource Self}}" Value="True">
<Setter Property="FontWeight" Value="Bold"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Label.Style>
</Label>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
You'll need to set your ItemsSource property to a Binding to the MyListBoxItem collection.

Categories

Resources