Detect what TreeView node was right-clicked - wpf

I am working on a WPF project and I have added a TreeView to it. I have also created a ContextMenu to the TreeView as below:
<TreeView Name="treeView" ItemsSource="{Binding Elements}">
<TreeView.ContextMenu>
<ContextMenu Name="treeViewContextMenu">
<MenuItem Header="First option"/>
<MenuItem Header="Second Option/>
</ContextMenu>
</TreeView.ContextMenu>
.... </TreeView>
Since I add the treeView nodes dinamically, how can I detect what node was right-clicked in order to open the contextMenu?
Hope someone can help me, thanks in advance

Assuming that I loaded my treeview item's dynamically..
<TreeView Name="treeView" ContextMenuClosing="treeView_ContextMenuClosing">
<TreeView.ContextMenu>
<ContextMenu Name="treeViewContextMenu">
<MenuItem Header="First option"/>
<MenuItem Header="Second Option"/>
</ContextMenu>
</TreeView.ContextMenu>
<TreeViewItem Header="Hello 1"/>
<TreeViewItem Header="Hello 2"/>
</TreeView>
MainWindow.xaml.cs
private void treeView_ContextMenuClosing(object sender, ContextMenuEventArgs e)
{
//Sender should let me determine who sent it from my children/parent
var parent = sender as TreeView;
var children = parent.SelectedItem as TreeViewItem;
MessageBox.Show(children.Header.ToString());
}
It's up to you if you want to know the object when the ContextMenu is Closed/Open or whatever event like when the MenuItem is Clicked.

Related

Binding To the Parent of A Context Menu Which is A Templated Grid

In a WPF .Net 4 application have a master detail situation where a datagrid has rows which can have the detail information as found in the RowDetailsTemplate which has an internal datagrid.
Within the RowDetailsTemplate is a grid to hold the sub details which has a context menu. The problem found is when binding the CommandTarget of one of the details' MenuItem, I am unable to target that details datagrid as generated by the template. The below binding ends up getting the Master datagrid and not the containing datagrid which is holding the details information/contextmenu.
<DataGrid x:Name="dgEditScript" ItemsSource="{Binding CurrentScript}">
<DataGrid.CommandBindings>
<CommandBinding Command="commands:ScriptingCommands.SetChecked"
Executed="CheckAllAfter" />
</DataGrid.CommandBindings>
<DataGrid.RowDetailsTemplate>
<DataTemplate>
<DataGrid ItemsSource="{Binding SubCommands}">
<DataGrid.ContextMenu>
<ContextMenu>
<MenuItem Header="Check All From Selected"
Command="commands:ScriptingCommands.SetChecked"
CommandTarget="{Binding RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}}">
<MenuItem.Icon>
<Image Source="Images/checkboxes.png" />
</MenuItem.Icon>
</MenuItem>
</ContextMenu>
</DataGrid.ContextMenu>
</DataGrid>
</DataTemplate>
</DataGrid.RowDetailsTemplate>
</DataGrid>
The problem when using the above Realtive source binding, it gives me the dgEditScript grid (top level) and not the parent of the context menu, the unnamed holding DataGrid which the context menu was launched from.
How do I get the sub grid in the binding; to target the parent of the context menu?
If I'm understanding your question right, you have a collections of items, and you want the context menu to be attached to the selected item (when you're right clicking on it ...)
Here's some similar code I'm using:
<ListBox x:Name="name_here"
ItemsSource="{Binding source_collection_name}"
SelectedItem="{Binding property_name_on_VM, UpdateSourceTrigger=PropertyChanged}"
>
<ListBox.ContextMenu>
<ContextMenu>
<MenuItem Header ="Edit Item" Command="{Binding EditItem_Command}"
CommandParameter="{Binding property_name_on_VM}"
/>
<MenuItem Header ="Delete Item" Command="{Binding DeleteItem_Command}"
CommandParameter="{Binding property_name_on_VM}"
/>
</ContextMenu>
</ListBox.ContextMenu>
</ListBox>
This way, whenever you click on an item (or right click), it's selected, and then you just send that item as a command parameter, so you have the item you need.
Hope this helps.

context menu for removing items in listview

I have a ListView which displays a list of string values. I want to add a context menu entry for each item in the list to remove the selected item. My XAML looks like this:
<ListView x:Name="itemsListView" ItemsSource="{Binding MyItems}">
<ListView.ContextMenu>
<ContextMenu>
<MenuItem Header="Remove"
Command="{Binding RemoveItem}"
CommandParameter="{Binding ElementName=itemsListView, Path=SelectedItem}" />
</ContextMenu>
</ListView.ContextMenu>
</ListView>
The problem is that the CommandParameter value is always null. I've added an additional button to remove the selected item to check if my command works. The button has exactly the same binding and removing items via the button works. The button looks like this:
<Button Content="Remove selected item"
Command="{Binding RemoveItem}"
CommandParameter="{Binding ElementName=itemsListView, Path=SelectedItem}"/>
The command looks like this:
private ICommand _removeItem;
public ICommand RemoveItem
{
get { return _removeItem ?? (_removeItem = new RelayCommand(p => RemoveItemCommand((string)p))); }
}
private void RemoveItemCommand(string item)
{
if(!string.IsNullOrEmpty(item))
MyItems.Remove(item);
}
Any ideas why the selected item is null when opening the context menu? Maybe a focus problem of the listview?
H.B. is right. but you can also use RelativeSource Binding
<ListView x:Name="itemsListView" ItemsSource="{Binding MyItems}">
<ListView.ContextMenu>
<ContextMenu>
<MenuItem Header="Remove"
Command="{Binding RemoveItem}"
CommandParameter="{Binding RelativeSource={RelativeSource AncestorType=ContextMenu}, Path=PlacementTarget.SelectedItem}" />
</ContextMenu>
</ListView.ContextMenu>
</ListView>
ContextMenus are disconnected, you cannot use ElementName bindings. One workaround would be using Binding.Source and x:Reference which requires you to extract parts that use it to be in the resources (due to cyclical dependency errors). You can just put the whole context menu there.
An example:
<ListBox Name="lb" Height="200">
<ListBox.Resources>
<ContextMenu x:Key="cm">
<MenuItem Header="{Binding ActualHeight, Source={x:Reference lb}}" />
</ContextMenu>
</ListBox.Resources>
<ListBox.ContextMenu>
<StaticResource ResourceKey="cm" />
</ListBox.ContextMenu>
</ListBox>
This work for me CommandParameter="{Binding}"

Binding command to a MenuItem using HierarchicalDataTemplate

I currently have a treeview that displays data using a hierarchicaldatatemplate and when the user clicks on the Remove button, it removes the selected node(s). This works as intended.
However, Instead of having the user click a button I'm trying to have the command be executed by right clicking a node and selecting the appropriate menu item.
This is proving to be much more difficult because it is being picked up by the ViewModel of a node (which doesn't know anything about the View) instead of the View's corresponding ViewModel.
Is there someway to hand the control over to the ViewModel of the View instead?
Here is the code for the Remove Button:
View:
<Button Content="Remove" Grid.Row="2" Height="23" VerticalAlignment="Top" Name="removeButton"
Width="75" Margin="5,20,5,0" Command="{Binding Path=RemoveCommand}" />
ViewModel:
public RelayCommand RemoveCommand
{
get
{
if (_removeCommand == null)
{
_removeCommand = new RelayCommand(
() => this.Remove()
);
}
return _removeCommand;
}
}
public void Remove()
{
_organLocationTree2.RemoveOrganLocations(ProjectOrganLocationView.GetExtendedTreeView().SelectedItems);
ProjectOrganLocationView.GetExtendedTreeView().SelectedItems.Clear();
base.RaisePropertyChanged("DestOrganTree");
}
And the XAML for the menu item:
<local:ExtendedTreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding SubOrganLocations}">
<TextBlock Text="{Binding OrganName}" >
<TextBlock.ContextMenu>
<ContextMenu>
<MenuItem Header ="Add SubNode" Command="{Binding Path=MenuItem_Add}"></MenuItem>
<MenuItem Header ="Remove Node" Command="{Binding Path=RemoveCommand}"></MenuItem>
<MenuItem Header ="Edit Node" Command="{Binding Path=ProjMenuItem_Edit}"
CommandParameter="{Binding DestOrganTree, Path=Selected.OrganName}"></MenuItem>
</ContextMenu>
</TextBlock.ContextMenu>
</TextBlock>
</HierarchicalDataTemplate>
</local:ExtendedTreeView.ItemTemplate>
</local:ExtendedTreeView>
I tried to implement a Remove command in the ViewModel of the node, but since it doesn't know anything about the View it got very messy very quickly.
Well, I found my mistake, I was binding the context menu to the nodes of the tree instead of the tree itself. I moved the context menu outside of the declaration and it now works as intended.
Here's my updated xaml for anyone else that has this issue:
<local:ExtendedTreeView.ContextMenu>
<ContextMenu>
<MenuItem Header ="Add SubNode" Command="{Binding Path=MenuItem_Add}"></MenuItem>
<MenuItem Header ="Remove Node" Command="{Binding Path=RemoveCommand}"></MenuItem>
<MenuItem Header ="Edit Node" Command="{Binding Path=ProjMenuItem_Edit}"
CommandParameter="{Binding DestOrganTree, Path=Selected.OrganName}"></MenuItem>
</ContextMenu>
</local:ExtendedTreeView.ContextMenu>
<local:ExtendedTreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding SubOrganLocations}">
<TextBlock Text="{Binding OrganName}" >
</TextBlock>
</HierarchicalDataTemplate>
</local:ExtendedTreeView.ItemTemplate>

C+Ctrl KeyBinding is not causing copying to happen

I have set up a ListBox like so:
<ListBox ItemsSource="{Binding Logs, Mode=OneWay}" x:Name="logListView">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=.}">
<TextBlock.InputBindings>
<KeyBinding Key="C"
Modifiers="Ctrl"
Command="Copy"/>
</TextBlock.InputBindings>
<TextBlock.CommandBindings>
<CommandBinding Command="Copy"
Executed="KeyCopyLog_Executed"
CanExecute="CopyLog_CanExecute"/>
</TextBlock.CommandBindings>
<TextBlock.ContextMenu>
<ContextMenu>
<MenuItem Command="Copy">
<MenuItem.CommandBindings>
<CommandBinding Command="Copy"
Executed="MenuCopyLog_Executed"
CanExecute="CopyLog_CanExecute"/>
</MenuItem.CommandBindings>
</MenuItem>
</ContextMenu>
</TextBlock.ContextMenu>
</TextBlock>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
As you can see, in the template, each TextBlock has a context menu that allows the user to copy the text. This works.
Also in the TextBlock there is an KeyBinding to ctrl+c and a CommandBinding to copy. When I press ctrl+c the method KeyCopyLog_Executed is not executed. I've checked with the debugger.
How should I be binding the keys to the TextBlock?
There are a couple of problems here.
First of all, KeyBindings will work only if currently focused element is located inside the element where KeyBindings are defined. In your case you have a ListBoxItem focused, but the KeyBindings are defined on the child element - TextBlock. So, defining a KeyBindings on a TextBlock will not work in any case, since a TextBlock cannot receive focus.
Second of all, you probably need to know what to copy, so you need to pass the currently selected log item as parameter to the Copy command.
Furthermore, if you define a ContextMenu on a TextBlock element it will be opened only if your right-click exactly on the TextBlock. If you click on any other part of the list item, it will not open. So, you need to define the ContextMenu on the list box item itself.
Considering all of that, what I believe you are trying to do can be done in the following way:
<ListBox ItemsSource="{Binding Logs, Mode=OneWay}"
x:Name="logListView"
IsSynchronizedWithCurrentItem="True">
<ListBox.InputBindings>
<KeyBinding Key="C"
Modifiers="Ctrl"
Command="Copy"
CommandParameter="{Binding Logs/}" />
</ListBox.InputBindings>
<ListBox.CommandBindings>
<CommandBinding Command="Copy"
Executed="CopyLogExecuted"
CanExecute="CanExecuteCopyLog" />
</ListBox.CommandBindings>
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="ContextMenu">
<Setter.Value>
<ContextMenu>
<MenuItem Command="Copy"
CommandParameter="{Binding}" />
</ContextMenu>
</Setter.Value>
</Setter>
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Here we define a KeyBinding on the ListBox itself and specify as a CommandParameter currently selected list box item (log entry).
CommandBinding is also defined at the ListBox level and it is a single binding for both right click menu and the keyboard shortcut.
The ContextMenu we define in the style for ListBoxItem and bind CommandParameter to the data item represented by this ListBoxItem (log entry).
The DataTemplate just declares a TextBlock with binding to current data item.
And finally, there is only one handler for the Copy command in the code-behind:
private void CopyLogExecuted(object sender, ExecutedRoutedEventArgs e) {
var logItem = e.Parameter;
// Copy log item to the clipboard
}
private void CanExecuteCopyLog(object sender, CanExecuteRoutedEventArgs e) {
e.CanExecute = true;
}
Thanks to Pavlov Glazkov for explaining that the key and command bindings need to go at the ListBox level, rather than the item template level.
This is the solution I now have:
<ListBox ItemsSource="{Binding Logs, Mode=OneWay}">
<ListBox.InputBindings>
<KeyBinding Key="C"
Modifiers="Ctrl"
Command="Copy"/>
</ListBox.InputBindings>
<ListBox.CommandBindings>
<CommandBinding Command="Copy"
Executed="KeyCopyLog_Executed"
CanExecute="CopyLog_CanExecute"/>
</ListBox.CommandBindings>
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=.}">
<TextBlock.ContextMenu>
<ContextMenu>
<MenuItem Command="Copy">
<MenuItem.CommandBindings>
<CommandBinding Command="Copy"
Executed="MenuCopyLog_Executed"
CanExecute="CopyLog_CanExecute"/>
</MenuItem.CommandBindings>
</MenuItem>
</ContextMenu>
</TextBlock.ContextMenu>
</TextBlock>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Where KeyCopyLog_Executed is:
private void KeyCopyLog_Executed(object sender, System.Windows.Input.ExecutedRoutedEventArgs e)
{
if ((sender as ListBox).SelectedItem != null)
{
LogItem item = (LogItem) (sender as ListBox).SelectedItem;
Clipboard.SetData("Text", item.ToString());
}
}
and MenuCopyLog_Executed is:
private void MenuCopyLog_Executed(object sender, System.Windows.Input.ExecutedRoutedEventArgs e)
{
LogItem item = (LogItem) ((sender as MenuItem).TemplatedParent as ContentPresenter).DataContext;
Clipboard.SetData("Text", item.ToString());
}

Binding a TreeView with ContextMenu in Xaml

I'm pretty new to Xaml and need some advise.
A TreeView should be bound to a hierarchical object structure. The TreeView should have a context menu, which is specific for each object type.
I've tried the following:
<TreeView>
<TreeView.Resources>
<DataTemplate x:Key="RoomTemplate">
<TreeViewItem Header="{Binding Name}">
<TreeViewItem.ContextMenu>
<ContextMenu>
<MenuItem Header="Open" />
<MenuItem Header="Remove" />
</ContextMenu>
</TreeViewItem.ContextMenu>
</TreeViewItem>
</DataTemplate>
</TreeView.Resources>
<TreeViewItem Header="{Binding Name}" Name="tviRoot" IsExpanded="True" >
<TreeViewItem Header="Rooms"
ItemsSource="{Binding Rooms}"
ItemTemplate="{StaticResource RoomTemplate}">
<TreeViewItem.ContextMenu>
<ContextMenu>
<MenuItem Header="Add room"></MenuItem>
</ContextMenu>
</TreeViewItem.ContextMenu>
</TreeViewItem>
</TreeViewItem>
But with this markup the behavior is as intended, but the child items (the rooms) are indented too much.
Anyway all the bining samples I could find use TextBlock instead of TreeViewItem in the DataTemplate, but wonder how to integrate the ContextMenu there.
You would not normally create a DataTemplate containing a TreeViewItem, because the binding infrastructure will be creating the TreeViewItem for you -- all your DataTemplate needs to do is specify what should be displayed as the content of the TreeViewItem. That's why the samples you've found use TextBlocks instead of TreeViewItems in the DataTemplate.
I suspect the use of TreeViewItem rather than TextBlock causes the excessive indenting because you have a (manually created) TreeViewItem in your DataTemplate (which incurs one level of indent) inside another (automatic) TreeViewItem (which incurs another level of indent). Therefore, using a TextBlock instead of a TreeViewItem should cure this. Integrating the ContextMenu shouldn't be an issue because TextBlock has a ContextMenu property too.
So you should be able to just change your DataTemplate as follows:
<DataTemplate x:Key="RoomTemplate">
<TextBlock Text="{Binding Name}">
<TextBlock.ContextMenu>
<ContextMenu>
<MenuItem Header="Open" />
<MenuItem Header="Remove" />
</ContextMenu>
</TextBlock.ContextMenu>
</TextBlock>
</DataTemplate>
Incidentally for TreeViews it is common to use a HierarchicalDataTemplate rather than a plain DataTemplate because this allows for multiple levels of items via the HierarchicalDataTemplate.ItemsSource property. This may not be required in your scenario though.

Resources