Context sub menu with items source and additional items - c#

I have a context menu in wpf. One of the items in the menu has a sub menu that gets populated from the ItemsSource of the header menu item. This sub menu is a list of commands that can be sent to another portion of the app. The list is basically a mru list restricted to 10 items. I want to add a separator and then a "More" option below the list of 10 items so the user can see the entire list of available commands. I can't seem to figure out how to add these extra items. I can get the list to populate dynamically from the ItemsSource of the parent menu item but I can't seem to figure out how to add the additional items to the bottom of the child menu. I don't want to put them in the items source and the "More" item needs to have its own command.
<MenuItem x:Name="ExecuteCommandMenuItem" Height="22" Style="{StaticResource RightClickMenuItemStyle}"
ItemsSource="{Binding Path=PanelCommands}">
<MenuItem.Header>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Panel Command" HorizontalAlignment="Left" Width="100"/>
</StackPanel>
</MenuItem.Header>
<MenuItem.ItemContainerStyle>
<Style TargetType="MenuItem" BasedOn="{StaticResource RightClickMenuItemStyle}">
<Setter Property="MenuItem.Header" Value="{Binding}" />
<Setter Property="MenuItem.Command" Value="CommonCommands:CommandRepository.ExecutePanelCommand" />
<Setter Property="MenuItem.CommandParameter">
<Setter.Value>
<MultiBinding Converter="{CommonConverter:PanelCommandArgsConverter}">
<MultiBinding.Bindings>
<Binding Path="DataContext" RelativeSource="{RelativeSource FindAncestor,
AncestorType={x:Type ContextMenu}}"/>
<Binding Path="Command" />
</MultiBinding.Bindings>
</MultiBinding>
</Setter.Value>
</Setter>
</Style>
</MenuItem.ItemContainerStyle>
</MenuItem>
Thanks.

<DataGrid x:Class="UICCNET.BaseControls.UserControls.BaseDataGrid"
Tag="{Binding RelativeSource={RelativeSource Self}, Path=Columns}">
<DataGrid.ContextMenu>
<ContextMenu Tag="{Binding RelativeSource={RelativeSource Self},Path=PlacementTarget.Tag}">
<MenuItem Header="Колонки">
<MenuItem >
<MenuItem.Template>
<ControlTemplate>
<ListBox Name="MyComboBox" ItemsSource="{Binding RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}},Path=Tag}">
<ListBox.ItemTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding Path=Visibility, Mode=TwoWay, Converter={StaticResource BooleanToVisibilityConverter1}}" Content="{Binding Path=Header}"></CheckBox>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</ControlTemplate>
</MenuItem.Template>
</MenuItem>
</MenuItem>
<MenuItem Header="Друк"></MenuItem>
</ContextMenu>
</DataGrid.ContextMenu>

I dont think you can because it's bound to a source. So either add them to the source OR use a templateselector and do your logic in there. Define a normal template and then a "more" template.
Or, you can do some control nesting like
<menu>
<stackpanel>
<Menu Items>
</menu Items>
<break />
<Button>More</button>
</stackpanel>
</menu>
Sorry this is just off the top of my head. Can you post your XAML?

Related

WPF Context Menu Click event works in one place, not in another

I have the following XAML that produces a ListBox where each item contains another ListBox inside an Expander, and I have defined PageContextMenu as the context menu for the top level list and FrameContextMenu for the lower level list.
The problem: Both are displayed correctly, but click events are only working on the top level context menu and not the lower level context menu. For example, clicking on Delete Selected in PageContextMenu correctly invokes the associated handler, but clicking on Delete Selected Frame(s) in FrameContextMenu does NOT fire the associated handler. I'm not seeing any indication of an error, and even if I put a breakpoint in ContextDeleteFrames_Click it doesn't get hit. It's as if there's no handler associated with that menu entry at all.
I've looked at a number of other questions relating to context menus not working, but none seemed applicable. Is there some problem with the two list boxes being nested?
XAML:
<ListBox Name="PageListBox" ItemsSource="{Binding CurrentPack.Pages}" HorizontalAlignment="Stretch" SelectionMode="Extended">
<ListBox.Resources>
<ContextMenu x:Key="PageContextMenu">
<MenuItem Header="_Add" Name="ContextAddAddPage"/>
<MenuItem Header="_Edit" Name="ContextEditPage"/>
<MenuItem Header="_Delete Selected" Name="ContextDeletePage" Click="ContextDeletePage_Click"/>
</ContextMenu>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="ContextMenu" Value="{StaticResource PageContextMenu}"/>
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}"/>
<Setter Property="HorizontalAlignment" Value="Stretch"/>
</Style>
</ListBox.Resources>
<ListBox.ItemTemplate>
<DataTemplate >
<Border BorderThickness="2" BorderBrush="White" HorizontalAlignment="Stretch">
<StackPanel HorizontalAlignment="Stretch">
<Label Content="{Binding PresentationName}"/>
<Expander VerticalAlignment="Top" HorizontalAlignment="Stretch">
<Expander.Header>
<Label Content="{Binding FrameStatusText}"/>
</Expander.Header>
<ListBox Name="FrameListBox" ItemsSource="{Binding Frames}" HorizontalAlignment="Stretch" SelectionMode="Extended">
<ListBox.Resources>
<ContextMenu x:Key="FrameContextMenu">
<MenuItem Header="_Add Frame" Name="ContextAddFrame"/>
<MenuItem Header="_Edit Frame" Name="ContextEditFrame"/>
<MenuItem Header="_Delete Selected Frame(s)" Name="ContextDeleteFrames" Click="ContextDeleteFrames_Click"/>
<MenuItem Header="Show _Preview" Name="ContextShowPreview" Click="ContextShowPreview_Click"/>
</ContextMenu>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="ContextMenu" Value="{StaticResource FrameContextMenu}"/>
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}"/>
</Style>
</ListBox.Resources>
<ListBox.ItemTemplate>
<DataTemplate>
<Label Content="{Binding PresentationName}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Expander>
</StackPanel>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
Code Behind:
private void ContextDeletePage_Click(object sender, RoutedEventArgs e)
{ //this works
Workspace.Content.DeleteSelectedPages();
}
private void ContextDeleteFrames_Click(object sender, RoutedEventArgs e)
{ //this doesn't!
Workspace.Content.DeleteSelectedFrames();
}
Don't use events in a DataTemplate. They won't work.
Either move your second context menu from the DataTemplate's resources into the PageListBox resources like this:
<ListBox Name="PageListBox">
<ListBox.Resources>
<!-- ... other resources... -->
<ContextMenu x:Key="FrameContextMenu">
<MenuItem Header="_Add Frame" Name="ContextAddFrame"/>
<MenuItem Header="_Edit Frame" Name="ContextEditFrame"/>
<MenuItem Header="_Delete Selected Frame(s)" Click="ContextDeleteFrames_Click"/>
<MenuItem Header="Show _Preview" Name="ContextShowPreview" Click="ContextShowPreview_Click"/>
</ContextMenu>
</ListBox.Resources>
</ListBox>
...or use commands instead of events:
<MenuItem Header="_Delete Selected Frame(s)" Command="{Binding DeleteFrameCommand}"/>
where DeleteFrameCommand is a property of type ICommand or RoutedCommand.
If you want to use commands, you should be aware that a context menu is not in the visual tree of its PlacementTarget, so you'll have to use some helpers to make the bindings work (a binding proxy or PlacementTarget.Tag property etc.)

Cant get InputBinding to apply to entire ListBoxItem using DataTemplate WPF

Trying to get some keybindings onto my ListBoxItems in a ListBox in WPF. I am using MVVM, and binding the ItemSource of the ListBox to a list of ViewModels. This ViewModel has a string and a boolean for 'Selected'. I wish to display Selected as a property to a CheckBox.
I am trying to make it so that if I navigate the list items with the up and down arrows on the keyboard, and then press enter/space/whatever, I can toggle the Checkbox. However, I have to press tab first, to get focus to the StackPanel which contains the checkbox.
<DataTemplate x:Key="MyTemplate" DataType="{x:Type ViewModel}">
<Border Width="2" BorderBrush="Blue">
<i:Interaction.Triggers>
<i:EventTrigger EventName="KeyUp">
<i:InvokeCommandAction Command="{Binding EnterCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
<CheckBox VerticalAlignment="Center"
Content="{Binding Name}"
IsChecked="{Binding Selected}"
Margin="3" />
</Border>
</DataTemplate>
=======================
<Popup x:Name="FilterPopup" Grid.Column="1"
IsOpen="{Binding IsChecked, ElementName=FilterButton}"
StaysOpen="False"
PlacementTarget="{Binding ElementName=FilterButton}"
Placement="Top">
<ListBox ItemsSource="{Binding ViewModels}"
ItemTemplate="{StaticResource MyTemplate}" />
</Popup>
Have I missed something obvious???
The triggers above are fired inside the data template, not inside item container. So if the last one is focused there is no effect.
To avoid this, the triggers should be specified on item container level:
<ListBox.ItemContainerStyle>
<Style>
<Setter Property="l:Attach.InputBindings">
<Setter.Value>
<InputBindingCollection>
<KeyBinding Command="{Binding EnterCommand}" Key="Enter" />
<KeyBinding Command="{Binding EnterCommand}" Key="Space" />
</InputBindingCollection>
</Setter.Value>
</Setter>
</Style>
</ListBox.ItemContainerStyle>
I've taken a way to set input bindings from style from this question.

WPF MenuItem not filling available ContextMenu space

I'm having a weird problem with a simple ContextMenu using MahApps.Metro without any additional styling. When moving the cursor on top of the text or slightly around it, there is no problem. But when moving it further away, still inside the ContextMenu bounds, the Cursor is no longer on top of the MenuItem. Clicking now also doesn't result in any action at all besides closing the ContextMenu.
<ContextMenu ItemsSource="{Binding ContextItems}">
<ContextMenu.ItemTemplate>
<DataTemplate>
<MenuItem Header="{Binding Text}" Command="{Binding Command}"/>
</DataTemplate>
</ContextMenu.ItemTemplate>
</ContextMenu>
What am I doing wrong? Why doesn't the MenuItem use the available space?
If your ContextItems holds a collection with viewmodels then I think this could help you (not tested):
<ContextMenu ItemsSource="{Binding ContextItems}">
<ContextMenu.ItemContainerStyle>
<Style TargetType="MenuItem">
<Setter Property="Header" Value="{Binding Text}" />
<Setter Property="Command" Value="{Binding Command}" />
</Style>
</ContextMenu.ItemContainerStyle>
</ContextMenu>
Command and Text should be the properties on the viewmodel object.
I havent used MahApps.Metro . Though you can override the template like this
<ContextMenu.ItemTemplate>
<DataTemplate>
<MenuItem Header="{Binding Text}" Command="{Binding Command}"/>
<MenuItem.Template>
<ControlTemplate>
<ContentPresenter Content="{Binding Header,RelativeSource={RelativeSource TemplatedParent}}">
</ContentPresenter>
</ControlTemplate>
</MenuItem.Template>
</MenuItem>
</DataTemplate>
</ContextMenu.ItemTemplate>
I hope this will help.

Binding ContextMenu to Datagrid Columns

I am trying to bind DataGrid column header to its own ContextMenu like this:
<DataGrid x:Name="AllLogs">
<DataGrid.ContextMenu>
<ContextMenu>
<MenuItem Header="Show/Hide Columns"
ItemsSource="{Binding ElementName=AllLogs, Path=Columns}">
<MenuItem.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Header}"></TextBlock>
</DataTemplate>
</MenuItem.ItemTemplate>
</MenuItem>
</ContextMenu>
</DataGrid.ContextMenu>
</DataGrid>
Its always sends the following error in output:
Cannot find source for binding with reference
'ElementName=AllLogs'. BindingExpression:Path=Columns;
DataItem=null; target element is 'MenuItem' (Name=''); target property
is 'ItemsSource' (type 'IEnumerable')
EDIT: Binding with a ComboBox works as expected
<ComboBox ItemsSource="{Binding ElementName=AllLogs, Path=Columns}">
<ComboBox.ItemTemplate>
<DataTemplate>
<CheckBox Content="{Binding Header}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
You should set first the DataContext of ContextMenu so that ItemsSource bind to Menu Item can inherit the same DataContext.
<ContextMenu DataContext="{Binding PlacementTarget.DataContext, RelativeSource={RelativeSource Self}}">
<MenuItem Header="Show/Hide Columns"
ItemsSource="{Binding Columns}">
<MenuItem.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Header}"></TextBlock>
</DataTemplate>
</MenuItem.ItemTemplate>
</MenuItem>
</ContextMenu>
The reason that ContextMenu didn't work but ComboBox did is that ContextMenu is a Popup, that means it's not part of DataGrid's visual tree, so ElementName would not work as ComboBox did. In fact, #user1672994 was vary close to the answer.
<DataGrid x:Name="AllLogs">
<DataGrid.ContextMenu>
<ContextMenu>
<MenuItem Header="Show/Hide Columns"
ItemsSource="{Binding PlacementTarget.Columns, RelativeSource={RelativeSource AncestorType=ContextMenu}}">
<MenuItem.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Header}"/>
</DataTemplate>
</MenuItem.ItemTemplate>
</MenuItem>
</ContextMenu>
</DataGrid.ContextMenu>
</DataGrid>
The problem/misunderstanding is that ContextMenu isn't a part of a visual tree.
So ContextMenu.PlacementTarget is your "connection" to the UIElement, which is in the visual tree, therefore you have to go via PlacementTarget to access the elements from visual tree.
MSDN about PlacementTarget:
When the ContextMenu is assigned to the FrameworkElement.ContextMenu
or FrameworkContentElement.ContextMenu property, the
ContextMenuService changes this value of this property to the owning
FrameworkElement or FrameworkContentElement when the ContextMenu opens
In answer below you don't have to traverse searching an ancestor type, but do use an UIElement as DataContext for ContextMenu:
<DataGrid.ContextMenu>
<ContextMenu DataContext="{Binding PlacementTarget, RelativeSource={RelativeSource Self}}">
<MenuItem Header="Show/Hide Columns" ItemsSource="{Binding Columns}">
<MenuItem.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Header}"/>
</DataTemplate>
</MenuItem.ItemTemplate>
</MenuItem>
</ContextMenu>
</DataGrid.ContextMenu>
I don't know the RadGrivView control per say, but the error means that it cannot find an IEnumerable property called "Columns" on your element. Are you sure it is a publicly accessible collection for the control?
ElementName uses VisualTree to find out desired element, it its not part of the current visual tree - up or down as it is with context menu you will get exception.
You could use Binding Source={x:Reference AllLogs} that does not use VisualTree, but unfortunately in your use case you would get exception of circular reference if you use it directly without style.
What you need to use is RelativeSource binding.
<Window.Resources>
<ContextMenu x:Key="headerMenu">
<ContextMenu.Items>
<MenuItem
Header="Show/Hide Columns"
ItemsSource="{Binding Columns, RelativeSource={RelativeSource AncestorType=DataGrid}}">
<MenuItem.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Header}"/>
</DataTemplate>
</MenuItem.ItemTemplate>
</MenuItem>
</ContextMenu.Items>
</ContextMenu>
<Style TargetType="{x:Type DataGrid}">
<Setter Property="ContextMenu" Value="{StaticResource headerMenu}" />
</Style>
</Window.Resources>
<Grid>
<DataGrid x:Name="AllLogs">
<DataGrid.Columns>
<DataGridTextColumn Header="ID"></DataGridTextColumn>
<DataGridTextColumn Header="Name"></DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
</Grid>
This would work as well - with Reference:
<Window.Resources>
<ContextMenu x:Key="headerMenu">
<ContextMenu.Items>
<MenuItem
Header="Show/Hide Columns"
ItemsSource="{Binding Source={x:Reference AllLogs}, Path=Columns}">
<MenuItem.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Header}"/>
</DataTemplate>
</MenuItem.ItemTemplate>
</MenuItem>
</ContextMenu.Items>
</ContextMenu>
<Style TargetType="{x:Type DataGrid}">
<Setter Property="ContextMenu" Value="{StaticResource headerMenu}" />
</Style>
</Window.Resources>
<Grid>
<DataGrid x:Name="AllLogs">
<DataGrid.Columns>
<DataGridTextColumn Header="ID"></DataGridTextColumn>
<DataGridTextColumn Header="Name"></DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
</Grid>

How to display a button or label when right clicking on a ListBox item

I'm new to WPF and MVVM, I've build few things, and now tryining to display a delete "button" when user right clicks on a ListBox item.
My listbox looks like this righ now
<ListBox DisplayMemberPath="QUERYNAME"
SelectedValuePath="USERQUERYID"
ItemsSource="{Binding RS.SavedQueryList, UpdateSourceTrigger=PropertyChanged}"
SelectedValue="{Binding RS.SelectedValue, UpdateSourceTrigger=PropertyChanged}"
Height="300" HorizontalAlignment="Left" Name="listBox2" VerticalAlignment="Top" Width="101" Margin="521,74,0,0" TabIndex="0">
Thanks
You can add the button and label (in fact, any element you want) to a ContextMenu and assign that ContextMenu to ListBoxItems. For example, in my Window, I'll have something like this:
<Window.Resources>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="ContextMenu">
<Setter.Value>
<ContextMenu>
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
<TextBlock Text="Delete This Item" Margin="10"/>
<Button Content="Delete"/>
</StackPanel>
</ContextMenu>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
This will use the power of Styles to apply a customized ContextMenu to all ListBoxItems the window. After that you can bind Button.Command to your ViewModel.
<ListBox DisplayMemberPath="QUERYNAME"
SelectedValuePath="USERQUERYID"
ItemsSource="{Binding RS.SavedQueryList, UpdateSourceTrigger=PropertyChanged}"
SelectedValue="{Binding RS.SelectedValue, UpdateSourceTrigger=PropertyChanged}"
Height="300" HorizontalAlignment="Left" Name="listBox1" VerticalAlignment="Top" Width="101" Margin="521,74,0,0" TabIndex="0">
<ListBox.ContextMenu>
<ContextMenu>
<MenuItem Header="Delete">
<MenuItem.Icon>
<Image Width="16" Height="16" Source="pack://application:,,,/Img/Delete.png" />
</MenuItem.Icon>
</MenuItem>
</ContextMenu>
<ListBox.ContextMenu>
</ListBox>
Obviously you'll need to use some Command with your MenuItem...
you can do that by using the MVVM light behavior EventToCommand see http://msdn.microsoft.com/en-us/magazine/dn237302.aspx
Set the event to MouseRightButtonUp

Categories

Resources