WPF: programmatically opening context menu loses data context - wpf

I would like to reuse the context menu of a ListViewItem by adding a toggle button within the item itself which opens it.
The context menu itself works correctly,
however when I open it through the toggle button (simply setting its property IsOpen to True) the commands inside don't work anymore.
It seems the context menu does not have the DataContext anymore.
<ListView>
<ListView.ItemContainerStyle>
<Style TargetType="{x:Type ListViewItem}">
<Setter Property="ContextMenu">
<Setter.Value>
<ContextMenu>
<MenuItem
Header="Restore"
Command="{Binding RestoreCommand}" />
<MenuItem
Header="Delete"
Command="{Binding DeleteCommand}"/>
</ContextMenu>
</Setter.Value>
</Setter>
</Style>
</ListView.ItemContainerStyle>
<ListView.View>
<GridView>
<!-- My columns here -->
<GridViewColumn>
<GridViewColumn.CellTemplate>
<DataTemplate>
<ToggleButton IsChecked="{Binding Path=ContextMenu.IsOpen, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListViewItem}}}"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
Any suggestions?
Thanks

The issue is that the context menu opened indirectly in this way does not have a DataContext.
In order to "assign" it one, you should use the property PlacementTarget to indicate on which control it is bound to.
To achieve this task you can either create a custom behavior, or, if you prefer a purely xaml solution:
<!-- xmlns:b="http://schemas.microsoft.com/xaml/behaviors" -->
<GridViewColumn>
<GridViewColumn.CellTemplate>
<DataTemplate>
<ToggleButton
x:Name="MoreActionsButton"
IsChecked="{Binding ContextMenu.IsOpen, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListViewItem}}, Mode=OneWayToSource}">
<b:Interaction.Triggers>
<b:EventTrigger EventName="Checked">
<b:ChangePropertyAction
TargetObject="{Binding ContextMenu, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListViewItem}}}"
PropertyName="PlacementTarget"
Value="{Binding ElementName=MoreActionsButton}" />
</b:EventTrigger>
</b:Interaction.Triggers>
</ToggleButton>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>

Related

How can I track which element of ItemsControl caused ContexMenu to open?

In my book editor app I need to display themes and subthemes in an hierarchical order, so I made such an markup:
<StackPanel>
<ScrollViewer>
<Grid ColumnDefinitions="auto, *" RowDefinitions="auto">
<ItemsControl Grid.Column="0" Grid.Row="0" Items="{Binding Book.Themes}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel>
<Button Content="{Binding Name}" Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.UpdateSubthemesVisibilityCommand}" CommandParameter="{Binding Name}"/>
<ItemsControl Items="{Binding Subthemes}" IsVisible="{Binding AreSubthemesVisible}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Content="{Binding Name}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ContextMenu>
<ContextMenu>
<MenuItem Header="Rename" />
<MenuItem Header="Delete"/>
</ContextMenu>
</ItemsControl.ContextMenu>
</ItemsControl>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ContextMenu>
<ContextMenu>
<MenuItem Header="Rename" Command="{Binding RenameThemeCommand}" CommandParameter="...How to bind it to button which cause menu to open"/>
<MenuItem Header="Delete"/>
</ContextMenu>
</ItemsControl.ContextMenu>
</ItemsControl>
</Grid>
</ScrollViewer>
</StackPanel>
Maybe markup isn't quite clear, but the point is that I have external ItemsControl and internal one. External one's items have structure: button (theme name) + internal ItemsControl which presents subthemes' names as buttons too. Here is Screenshot:
Each ItemsControl has its own ContextMenu, but I can't bind them properly. I want "Rename" MenuItems to be bind to Button.Content of Button which right click caused menu to open. How should I do this here?
I am using MVVM Avalonia, but I hope in WPF it's working the same way so that I could add WPF hashtag to this question.
You don't need to add ContextMenu to ItemsControl. It needs to be added to the items ItemsControl.
This is done in ItemContainerStyle.
In such a case, in ContextMenu DataContext will contain the element on which the ContextMenu is called.
Example:
<Window.Resources>
<spec:StringCollection x:Key="strings">
<sys:String>First</sys:String>
<sys:String>Second</sys:String>
<sys:String>Third</sys:String>
</spec:StringCollection>
<Style x:Key="ItemStyle" TargetType="ContentPresenter">
<Setter Property="ContextMenu">
<Setter.Value>
<ContextMenu>
<TextBlock Text="{Binding}"/>
</ContextMenu>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<ItemsControl ItemsSource="{StaticResource strings}"
ItemContainerStyle="{StaticResource ItemStyle}"/>

DataBinding: Disable ComboBox Item

I'm trying to apply this solution to my case. Only difference is that my ComboBox is taking its items from an enum list.
I always got a binding expression error to the property "IsProgrammabile" in the ComboBox style.
My code:
<ListView ItemsSource="{Binding SchedaSelezionata.ListaIngressi}" SelectionMode="Single">
<ListView.View>
<GridView>
<GridViewColumn Header="NR." DisplayMemberBinding="{Binding Numero}" />
<GridViewColumn Header="FUNCTION" >
<GridViewColumn.CellTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding Source={helpers:EnumBindingSource {x:Type models:INGRESSI}}}" SelectedItem="{Binding Funzione}"
ToolTip="{Binding Descrizione}" IsEnabled="{Binding ConfigurabileDaUtente}" Width="150" >
<ComboBox.ItemContainerStyle>
<Style TargetType="ComboBoxItem">
<Setter Property="IsEnabled" Value="{Binding Path=IsProgrammabile, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ComboBox}}}"/>
</Style>
</ComboBox.ItemContainerStyle>
</ComboBox>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
Note that the "IsProgrammabile" property belongs to same object as the other properties (Numero, Funzione, Descrizione, ConfigurabileDaUtente).
Setting the AncestorType to GridView or ListView doesn't help.
Can you provide the solution and explain me what I do not understand in this context?
Thanks in advance
The ComboBox itself has no property named IsProgrammabile but its DataContext may have so you should add "DataContext." to the binding path:
<Setter Property="IsEnabled" Value="{Binding Path=DataContext.IsProgrammabile,
RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ComboBox}}}"/>

Horizontal stretch WPF ContextMenu MenuItem in HierarchicalDataTemplate.ItemTemplate

I have a WPF system traybar application. When right clicking traybar icon there is a ContextMenu using a HierarchicalDataTemplate to get a 2 level dynamically populated menu. It works but the "clickable" part of the items on the 2nd level does not properly stretch to the available width of the parent control. Instead See picture:
Now the user has to click the darker section (where the text is) of the MenuItem to perform the Command of this item. I want the whole of the menu row to be able to to trigger the Command.
Here is my XAML:
<CollectionViewSource x:Key="Items" Source="{Binding Path=Items}" />
<ContextMenu x:Shared="false" x:Key="Menu" HorizontalContentAlignment="Stretch">
<ContextMenu.ItemTemplate>
<HierarchicalDataTemplate DataType="SystemTrayItemsViewModel" ItemsSource="{Binding Items}">
<StackPanel Orientation="Horizontal">
<Image Source="{Binding Converter={StaticResource TabIconConverter}}" />
<TextBlock Text="{Binding Name}" />
</StackPanel>
<HierarchicalDataTemplate.ItemTemplate>
<DataTemplate>
<MenuItem Header="{Binding Text}" ToolTip="{Binding ToolTip}" Command="{Binding ToClipBoardCommand}" HorizontalContentAlignment="Stretch" />
</DataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
</ContextMenu.ItemTemplate>
<ContextMenu.ItemsSource>
<CompositeCollection>
<CollectionContainer Collection="{Binding Source={StaticResource Items}}">
</CollectionContainer>
<Separator />
<MenuItem Header="Exit" cal:Message.Attach="ExitApplication" />
</CompositeCollection>
</ContextMenu.ItemsSource>
</ContextMenu>
For full source code, check https://github.com/kasperhlund/textgrunt
I just found the answer to this in another SO question.
It seems that the problem is specifying MenuItem within the ItemTemplate. The ContextMenu is apparently wrapping another MenuItem around the ItemTemplate, causing this nesting effect. Istead, you have to do this via the MenuItem's Style:
<ContextMenu>
<ContextMenu.Resources>
<Style TargetType="{x:Type MenuItem}">
<Setter Property="Header" Value="{Binding Text}"/>
<Setter Property="ToolTip" Value="{Binding ToolTip}"/>
<Setter Property="Command" Value="{Binding ToClipBoardCommand}"/>
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
</Style>
</ContextMenu.Resources>
</ContextMenu>

How to bind button inside listview

I cant bind button inside listview to the GoToTask property, every other binding, include second button in the grid is work well.
<Grid>
<ListView ItemsSource="{Binding Projects}" SelectedItem="{Binding SelectedProject, Mode=TwoWay}" Grid.Row="1">
<ListView.Resources>
<Style TargetType="{x:Type GridViewColumnHeader}">
<Setter Property="Visibility" Value="Collapsed"/>
</Style>
</ListView.Resources>
<ListView.View>
<GridView>
<GridViewColumn DisplayMemberBinding="{Binding Name}"/>
<GridViewColumn Header="Go">
<GridViewColumn.CellTemplate>
<DataTemplate>
<Button Content="Go" Command="{Binding Path=GoToTasks, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=UserControl}}" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
<Button Height="20" Width="50" HorizontalAlignment="Right" Command="{Binding GoToTasks}" Content="Go"/>
</Grid>
Everything is inside UserControl.
What is wrong?
GridViewColumn is not part of VisualTree and therefore you are not allowed to use FindAncestor.
Similar here. Try ElementName, as far as I remember it uses LogicalTree.

Create separate context menu for listview and for listview item

I have working context menu for listview control in my WPF app. I'd like to have context menu items enabled if user right clicks on listview item but disabled them is click occurs on panel area of listview.
Thanks
MK
Update: this is the my list view that works but I'd like to disable MenuItems "Remove" and "Calculate" when user clicks on panel area. Thanks for responding
<ListView Name="lb_proplist" DisplayMemberPath ="Name" HorizontalAlignment="Left" ToolTip="Use right click to see more options"
ItemsSource="{Binding Converter={StaticResource FilterByPropTypeConverter}}" Margin="0,0,0,0"
ContextMenuOpening="ContextMenu_ContextMenuOpening" >
<ListView.ContextMenu>
<ContextMenu >
<MenuItem Name="cmi_addNew" Header="Add New"
Command="{Binding AddNewItemItem}"
CommandParameter="{Binding RelativeSource={RelativeSource AncestorType=ContextMenu}}" />
<MenuItem Name="cmi_remove" Header="Remove"
Command="{Binding RemoveItem}"
CommandParameter="{Binding RelativeSource={RelativeSource AncestorType=ContextMenu},
Path=PlacementTarget.SelectedItem}" />
<MenuItem Name="cmi_calculate" Header="Calculate"
Command="{Binding CalculateItem}"
CommandParameter="{Binding RelativeSource={RelativeSource AncestorType=ContextMenu},
Path=PlacementTarget.SelectedItem}" />
</ContextMenu>
</ListView.ContextMenu>
<ListView.View>
<GridView>
<GridViewColumn Header="Name" DisplayMemberBinding="{Binding Name}" Width="200"/>
</GridView>
</ListView.View>
</ListView>
Well, this can be done using ItemsTemplate property for ListView:
<ListView.ItemTemplate>
<DataTemplate>
<DockPanel Tag = "{Binding DataContext, ElementName=myListView}">
<TextBlock Text="{Binding}"/>
<DockPanel.ContextMenu>
<ContextMenu>
<MenuItem Header="Local Item 1"
Command="{Binding Path=PlacementTarget.Tag.CommandName, RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}}"/>
<MenuItem Header="Local Item 2"/>
</ContextMenu>
</DockPanel.ContextMenu>
</DockPanel>
</DataTemplate>
</ListView.ItemTemplate>
In example, ContextMenu is created only for items, but not for whole control.
Also, Tag is added into DockPanel to access original DataContext.
Well, in case of GridView you need to override style for ListViewItem, and bind ContextMenu as StaticResource.
<ListView.Resources>
<ContextMenu x:Key="ItemContextMenu">
<MenuItem Header="Add New"/>
</ContextMenu>
</ListView.Resources>
<ListView.ItemContainerStyle>
<Style TargetType="{x:Type ListViewItem}">
<Setter Property="ContextMenu" Value="{StaticResource ItemContextMenu}"/>
</Style>
</ListView.ItemContainerStyle>
Please, take a look at this article and answer.

Resources