Binding ContextMenu to Datagrid Columns - c#

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>

Related

Access Data Context properties from Context Menu

I try to learn a little bit about how a listboxview combined with context menu works, so I've made the following XAML code:
<UserControl>
...
<ListView
x:Name="level1Lister"
Grid.Row="1"
behaviours:AutoScrollListViewBehaviour.ScrollOnNewItem="True"
ItemsSource="{Binding LogBuffer, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="Padding" Value="1" />
<Setter Property="Margin" Value="2,0" />
<Setter Property="BorderThickness" Value="0" />
</Style>
</ListView.ItemContainerStyle>
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical" />
</ItemsPanelTemplate>
</ListView.ItemsPanel>
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel>
<StackPanel.ContextMenu>
<ContextMenu>
<MenuItem Command="{Binding Path=DataContext.ValidateAllCommand, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ContextMenu}}, FallbackValue=9999999999}" Header="Copy" />
</ContextMenu>
</StackPanel.ContextMenu>
<TextBlock Foreground="{Binding Color, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Text="{Binding Message, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</UserControl>
My main problem is that I'm not able to access my "ValidateAllCommand" function, for some reason... I think it has to do with the "RelativeSource={RelativeSource FindAncestor ... " part but I can't figure out how.
The "problem" with the ContextMenu is that it is not part of the visual tree. The reason is that the MenuItem uses a Popup to host the content. While the Popup itself is part of the visual tree, its Child content is disconnected as it is rendered in a new Window instance. We know there can can only be a single Window in the tree and this Window must be the root.
Since Binding.RelativeSource traverses the visual tree, starting within the detached tree of the Popup, to find the binding source, the Bindig does not resolve.
The Popup.Child content inherits the DataContext of the Popup. In case of the ContextMenu it means that the MenuItem inherits the DataContext from the ContextMenu or from the parent of the ContextMenu, to be more precise. In your scenario the DataContext is the data model of the ListBoxItem i.e. the DataContext of the DataTemplate.
This means, one solution could be to implement the command in the item model.
The second solution is to make use of routed commands. This solution might be more reasonable in your scenario. The routed command/event can cross the boundary between the two trees.
In the following example, I use the ApplicationCommands.Copy command, one of the predefined routed commands. The MenuItem.Parameter is bound to the DataContext, which is the item data model (inherited from the ContextMenu, as mentioned before). This way the command handler can know the data source.
The event handler can be attached to any parent element using the UIElement.CommandBindings property. In the example the handler is attached to the ListBox element:
MainWindow.xaml
<ListBox>
<ListBox.CommandBindings>
<CommandBinding Command="{x:Static ApplicationCommands.Copy}"
Executed="CopyCommand_Executed" />
</ListBox.CommandBindings>
<ListBox.ItemTemplate>
<DataTemplate DataType="{x:Type local:DataItem}">
<StackPanel>
<StackPanel.ContextMenu>
<ContextMenu>
<!--
Here we are in a detached visual tree.
The DataContext is inherited from the ContextMenu element.
The framework allows routed events to cross the boundaries between the trees.
-->
<MenuItem Command="{x:Static ApplicationCommands.Copy}"
CommandParameter="{Binding}"
Header="Copy" />
</ContextMenu>
</StackPanel.ContextMenu>
<TextBlock />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
MainWindow.xaml.cs
private void CopyCommand_Executed(object sender, ExecutedRoutedEventArgs e)
{
var listBoxItemModel = e.Parameter as LogBufferItem;
// TODO::Handle Copy command. For example:
var commandTarget = this.DataContext as ICommandModel;
commandTarget.ValidateAllCommand.Execute(listBoxItemModel);
}
A third, but not recommended solution is to bind the DataContext of the ContextMenu parent to the context of interest:
<ListBox.ItemTemplate>
<DataTemplate DataType="{x:Type local:DataItem}">
<StackPanel>
<!-- DataTemplate DataContext -->
<Grid DataContext="{Binding RelativeSource={RelativeSource AncestorType=ListBox}, Path=DataContext}">
<!-- ListBox DataContext -->
<Grid.ContextMenu>
<ContextMenu>
<!--
Here we are in a detached visual tree.
The DataContext is inherited from the ContextMenu/Grid element.
-->
<MenuItem Command="{Binding ValidateAllCommand}"
Header="Copy" />
</ContextMenu>
</Grid.ContextMenu>
</Grid>
<!-- DataTemplate DataContext -->
<TextBlock />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox

Bind contextMenu to a different viewmodel from treeview

I want to show a contextMenu item when I am using rightclick on a treeview item.
After that, I want to use a command when I click on my MenuItem, but I need to bind the command with a different viewmodel and the command parameter with the good viewmodel who come from my treeview selected item.
So for the moment, I have something like that :
<TreeView x:Name="TreeViewProtocolsAndEquipments" AllowDrop="True"
ItemsSource="{Binding ModuleParams}">
<TreeView.Resources>
<!-- CONTEXT MENU -->
<!-- Protocol -->
<ContextMenu x:Key="ContextMenuProtocol">
<MenuItem Header="Add new equipment" Command="{Binding AddNewEquipmentCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=UserControl}}" CommandParameter="{Binding RelativeSource={RelativeSource Self}}">
<MenuItem.Icon>
<Image Source="Images/Add.png" />
</MenuItem.Icon>
</MenuItem>
<Separator />
</ContextMenu>
<!-- MODULE XXX -->
<!-- ModuleParam > xxx -->
<HierarchicalDataTemplate DataType="{x:Type xxx:ModuleParamXXXViewModel}" ItemsSource="{Binding ModuleItems}">
<TextBlock Text="XXX" Foreground="Green" ContextMenu="{StaticResource ContextMenuProtocol}"/>
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
For the moment my command is bind to xxx:ModuleParamXXXViewModel if I just let { binding }
Can I bind my Command to my ActivatedProtocolsAndEquipmentsTreeViewModel (the datacontext of this usercontrol) and keep on the CommandParameter my xxx:ModuleParamXXXViewModel (who is the Item from the treeview where we triggered the right click to show the contextMenu) ?
How can I achieve this in an other way with MVVM practice ?
I also tried to use this but it didn't work too :
<MenuItem Header="Add new equipment" Command="{Binding Path=DataContext.AddNewEquipmentCommand, Source={x:Reference TreeViewProtocolsAndEquipments}}" CommandParameter="{Binding RelativeSource={RelativeSource Self}}">
And with this i get Object Reference not set to an instance of an object
The UserControl is not a visual ancestor of the MenuItem since a ContextMenu resides in its own visual tree.
Bind the Tag property of the TextBlock to the UserControl and then bind the Command property to the PlacementTarget of the ContextMenu:
<TreeView x:Name="TreeViewProtocolsAndEquipments" AllowDrop="True"
ItemsSource="{Binding ModuleParams}">
<TreeView.Resources>
<!-- CONTEXT MENU -->
<!-- Protocol -->
<ContextMenu x:Key="ContextMenuProtocol">
<MenuItem Header="Add new equipment"
Command="{Binding PlacementTarget.Tag.DataContext.AddNewEquipmentCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ContextMenu}}"
CommandParameter="{Binding}">
<MenuItem.Icon>
<Image Source="Images/Add.png" />
</MenuItem.Icon>
</MenuItem>
<Separator />
</ContextMenu>
<!-- MODULE XXX -->
<!-- ModuleParam > xxx -->
<HierarchicalDataTemplate DataType="{x:Type xxx:ModuleParamXXXViewModel}" ItemsSource="{Binding ModuleItems}">
<TextBlock Text="XXX" Foreground="Green"
Tag="{Binding RelativeSource={RelativeSource AncestorType=UserControl}}"
ContextMenu="{StaticResource ContextMenuProtocol}"/>
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>

Binding command - Ancestor

Okay, guys. I've been trying this for about 3 days now, and no amount of Googling is helping. Below is a snippet of my XAML (should be enough to follow along).
My problem is the command for the "ContextMenu".
As you can see I have DeleteTagCommand. Now that command works if I throw it in the position of CheckBoxCommand, which is great.. But it just will be called in it's current location, and it's driving me insane.
<ScrollViewer Grid.Column="0">
<StackPanel Orientation="Vertical">
<ItemsControl ItemsSource="{Binding Tags, UpdateSourceTrigger=PropertyChanged}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<CheckBox Content="{Binding Value}" Margin="10,5,10,5" Command="{Binding DataContext.CheckBoxCommand,
RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type Grid}}}"
CommandParameter="{Binding }">
<CheckBox.ContextMenu>
<ContextMenu>
<MenuItem Header="Delete" Command="{Binding DataContext.DeleteTagCommand,
RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type Grid}}}"
CommandParameter="{Binding}" />
</ContextMenu>
</CheckBox.ContextMenu>
</CheckBox>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</ScrollViewer>
I've tried:
Calling 'ElementName' from all over the show, but it's never being picked up
Changing the 'AncestorLevel' to obscene numbers, hoping that was the problem
And more..
Not sure what would be useful for you guys, but below is the output message I get
Cannot find source for binding with reference 'RelativeSource FindAncestor, AncestorType='System.Windows.Controls.Grid', AncestorLevel='1''. BindingExpression:Path=DataContext.DeleteTagCommand; DataItem=null; target element is 'MenuItem' (Name=''); target property is 'Command' (type 'ICommand')
Thanks
ContextMenus aren't actually part of the same visual tree as their parents so they can't directly bind to any elements within it. They can, however, still bind to StaticResources. The trick is to thus use an intermediate proxy such as the BindingProxy class shown on this page. Start by adding an instance to your ItemsControl resource block:
<ItemsControl.Resources>
<local:BindingProxy x:Key="Proxy" Data="{Binding}" />
</ItemsControl.Resources>
Then use it to bind your ContextMenu command:
<ContextMenu>
<MenuItem Header="Delete" Command="{Binding Data.DeleteTagCommand, Source={StaticResource Proxy}}" CommandParameter="{Binding}" />
</ContextMenu>

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

Context sub menu with items source and additional items

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?

Categories

Resources