WPF: ContextMenu in templated ListBox Item (InvalidCastException) - c#

I'm trying to add a context menu to a ListBoxItem. I'm using ListBox.ItemTemplate and DataTemplate (with a Grid) to define the layout of the item and the ListBoxItem is styled.
In a search that this should be the way to go:
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="ContextMenu">
<Setter.Value>
<ContextMenu>
<MenuItem Header="Rename" Click="Rename_Click" />
</ContextMenu>
</Setter.Value>
</Setter>
</Style>
</ListBox.ItemContainerStyle>
But this throws an XamlParseException/InvalidCastException saying
Couldn't cast an object of the type
System.Windows.Controls.MenuItem to the type
System.Windows.Controls.Grid
I tried adding the context menu to the Grid in the ItemTemplate, but then it only works when you click on one of the elements in the Grid (there is some empty space) (or if I add a background to the Grid, but that overrides/"covers" the styling, for hover & selected, of the Item itself)
I can't find any similar issues when searching, and I can't figure out the logic of the Exception..

You could overcome this by defining the ContextMenu as a resource:
<ListBox>
<ListBox.Resources>
<ContextMenu x:Key="cm">
<MenuItem Header="Rename" Click="Rename_Click" />
</ContextMenu>
</ListBox.Resources>
<ListBox.ItemTemplate>
...
</ListBox.ItemTemplate>
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="ContextMenu" Value="{StaticResource cm}" />
</Style>
</ListBox.ItemContainerStyle>
</ListBox>

Use the TargetType="ListBoxItem" string of code. Because the coding langauge needs to know the Listbox!

Related

ContextMenu based on object binding wpf

So I have a list view that displays post items (Delivery date, type, tracking number etc) and I have a context menu set up that either opens up the tracking website or copies the tracking number to the clipboard.
What I want is for the contextmenu only to appear for listitems that have a tracking number. I've got the idea of changing the visibility of the contextmenu but it's the binding to the tracking number I'm having the trouble with.
<ContextMenu x:Key="MyElementMenu">
<MenuItem Header="Track Item" Click="MenuItem_Click"></MenuItem>
<MenuItem Header="Copy to Clipboard" Click="MenuItem_CopyToClipboard"></MenuItem>
</ContextMenu>
<!--Sets a context menu for each ListBoxItem in the current ListBox-->
<Style TargetType="{x:Type ListViewItem}">
<Setter Property="ContextMenu" Value="{StaticResource MyElementMenu}"/>
</Style>
This is what I have currently.
<MyControl.Resources>
<BooleanToVisibilityConverter x:Key="BoolToVis"/>
</MyControl.Resources>
<!--Sets a context menu for each ListBoxItem in the current ListBox-->
<Style TargetType="{x:Type ListViewItem}">
<Setter Property="ContextMenu">
<Setter.Value>
<ContextMenu IsEnabled="{Binding HasTrackingNumber}" Visibility="{Binding HasTrackingNumber, Converter={StaticResource BoolToVis}">
<MenuItem Header="Track Item" Click="MenuItem_Click"></MenuItem>
<MenuItem Header="Copy to Clipboard" Click="MenuItem_CopyToClipboard"></MenuItem>
</ContextMenu>
</Setter.Value>
</Setter>
</Style>
This should give you what you need. Not sure if you use the ContextMenu elsewhere, but if you dont you can always just set it in the style of the ListViewItem Style. Then you dont need to have it referenced from elsewhere. Either way its more about adding the Binding from the item. In your ListItem Viewmodel you could add something like:
public bool HasTrackingNumber => TrackingNumber == 0 || TrackingNumber == null;
(I dont know what type your tracking number is so you can do your own logic checks to know if it "has" a valid tracking number)
That seems like a case for a trigger:
<Style TargetType="{x:Type ListViewItem}">
<Setter Property="ContextMenu" Value="{StaticResource MyElementMenu}"/>
<Style.Triggers>
<!--
Maybe the tracking number property is called something else, maybe it's 0
instead of null when absent. You didn't say.
-->
<DataTrigger Binding="{Binding TrackingNumber}" Value="{x:Null}">
<Setter Property="ContextMenu" Value="{x:Null}"/>
</DataTrigger>
</Style.Triggers>
</Style>

WPF MenuItem Icon sharing

I want to bind icons to the MenuItem controls where these items are dynamically created. I tried to set the x:Shared attribute to False but always only the last item has icon.
Here is my style for the MenuItems ItemContainerStyle code:
<Window.Resources>
<Style TargetType="{x:Type MenuItem}" x:Key="MenuItemStyle" x:Shared="False">
<Setter Property="Icon">
<Setter.Value>
<Image Source="{Binding IconSource}" />
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
And the MenuItem definition:
<MenuItem Header="Workspaces" ItemsSource="{Binding WorkspaceItems}" Icon="{StaticResource BranchIcon}" ItemContainerStyle="{StaticResource MenuItemStyle}" />
I have already tried to set this Shared attribute on the Image control but no luck.
Any suggestion?
You are almost there!
First of all: don't be confuse by Template vs Style.
When you are setting Icon property to an Image control, only one copy is created. As a control can have only one parent, it is removed from the previous parent each time it's re-assigned.
That's why you see only one icon.
You have 2 solutions for what you want:
use datatemplate instead, and redefine the whole Template of a MenuItem
use a style with a shared image component (what you tried to achieve)
In your example the only error is that the Shared attribute should be false on the Image resource, not on the whole style. This should work:
<Window.Resources>
<Image x:Key="MenuIconImage" x:Shared="false" Source="{Binding IconSource}"/>
<Style TargetType="{x:Type MenuItem}" x:Key="MenuItemStyle" BasedOn="{StaticResource {x:Type MenuItem}}">
<Setter Property="Icon" Value="{StaticResource MenuIconImage}">
</Setter>
</Style>
</Window.Resources>
Hope it helps.

WPF ContextMenu itemtemplate, menuitem inside menuitem

I have the following xaml:
<ContextMenu ItemsSource="{Binding TestItems}">
<ContextMenu.ItemTemplate>
<DataTemplate DataType="models:TestItemModel">
<MenuItem IsChecked="{Binding IsSelected}" Header="{Binding Header}" />
</DataTemplate>
</ContextMenu.ItemTemplate>
</ContextMenu>
The TestItemModel class only consists of a IsSelected boolean property and a Header string property.
TestItems is a list of TestItemModels.
The data is binded to the contextmenu but it is reflected in the UI as a MenuItem inside a MenuItem (with the additional margins as such, making the menu very big). I can fix this by changing the MenuItem inside the DataTemplate to a TextBox, but then I cannot bind the IsSelected anymore (which I need for visualization properties).
There are a couple of questions I have regarding this:
Why is there a MenuItem inside a MenuItem? This doesn't make sense to me as it's not binded to a menuitem list but to a list of TestItemModels.
How can I resolve this?
Because MenuItem is the container type and when it translates your view model into visual item it will wrap your template in MenuItem. In the same way ListBox will create ListBoxItem or ListView will use ListViewItem. To bind properties of the wrapper you need to use ItemContainerStyle
<ContextMenu ItemsSource="{Binding TestItems}">
<ContextMenu.ItemContainerStyle>
<Style TargetType="{x:Type MenuItem}">
<Setter Property="IsChecked" Value="{Binding IsSelected}"/>
<Setter Property="Header" Value="{Binding Header}"/>
</Style>
</ContextMenu.ItemContainerStyle>
</ContextMenu>
or, if you prefer, you can do it partially with ItemTemplate and ItemContainerStyle
<ContextMenu ItemsSource="{Binding TestItems}">
<ContextMenu.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Header}"/>
</DataTemplate>
</ContextMenu.ItemTemplate>
<ContextMenu.ItemContainerStyle>
<Style TargetType="{x:Type MenuItem}">
<Setter Property="IsChecked" Value="{Binding IsSelected}"/>
</Style>
</ContextMenu.ItemContainerStyle>
</ContextMenu>
In this scenario whatever is in ItemTemplate will become MenuItem.Header but IsChecked property still needs to be bound in ItemContainerStyle

Caliburn Micro Action in ContextMenu with ControlTemplate

I have a custom component with a ContextMenu in a ControlTemplate. I've read a couple of posts with people having trouble getting their actions to work in a ContextMenu. I've tried the workarounds posted there but I cannot get it to work. I keep getting No target found for method Open. None of these solutions used a ControlTemplate, however. I've tried to bind the model to the contextmenu, to the menuitems, to use TargetWithoutContext property, but none seem to work.
<components2:ImageBlock Background="Transparent" x:Name="ShareButton" Margin="0,0,7,0" >
<components2:ImageBlock.Style>
<Style TargetType="{x:Type components2:ImageBlock }">
<Setter Property="ToolTipService.IsEnabled" Value="False"/>
<Setter Property="Cursor" Value="Hand"/>
<Setter Property="FocusVisualStyle" Value="{x:Null}"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type components2:ImageBlock }">
<Grid x:Name="ContentGrid" Background="{TemplateBinding Background}" MinHeight="30" ToolTip="{TemplateBinding ToolTip}">
<Grid.ContextMenu>
<ContextMenu>
<MenuItem Header="Facebook" cal:Message.Attach="Open(0)"/>
<MenuItem Header="Twitter" cal:Message.Attach="Open(1)]" />
<MenuItem Header="Tumblr" cal:Message.Attach="Open(2)]" />
</ContextMenu>
</Grid.ContextMenu>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</components2:ImageBlock.Style>
</components2:ImageBlock>
How can I get the Open to work at my viewmodel behind the view?
Got it to work through a DependencyObject that is added to the ImageBlock, where I had to set the PlacementTarget of the ContextMenu to the ImageBlock. Strangely, setting the PlacementTarget of the ContextMenu through {Binding ...} to ImageBlock directly didn't work.

How to show file context menu in a grid with files

I've got a WPF grid in which each row represents a file on disk.
What I'd like to do is; when someone right-clicks on a row to show the same context menu as Windows Explorer shows. Any ideas how to implement this? I'm hoping that it will be relative easy but have not sure where to start with this.
Some clue...
<Window.Resources>
<Style TargetType="DataGridRow">
<Setter Property="ContextMenu">
<Setter.Value>
<ContextMenu>
<ContextMenu.Template>
<ControlTemplate>
<TextBox Text="{Binding <the property with which column is bound to>}" Height="30" Width="40" />
</ControlTemplate>
</ContextMenu.Template>
</ContextMenu>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>

Categories

Resources