Tree View Context Menu - Pass selected item to command? - c#

I'm wondering how to pass the selected item to a command from a treeview / HierarchicalDataTemplate ?
Here is the code that I have so far, it displays the context menu but I haven't bound the commands to it yet. The command binding is the easy part, but how do I tell which node it came from ?
<HierarchicalDataTemplate
DataType="{x:Type viewModel:UsersViewModel}"
ItemsSource="{Binding Children}">
<StackPanel Orientation="Horizontal">
<Image Width="16" Height="16" Margin="3,0" Source="Images\Region.png" />
<TextBlock Text="{Binding UserName}">
<TextBlock.ContextMenu>
<ContextMenu>
<MenuItem Header="Edit" />
<MenuItem Header="Delete"/>
</ContextMenu>
</TextBlock.ContextMenu>
</TextBlock>
</StackPanel>
</HierarchicalDataTemplate>

Just {Binding} should be the whole item.
(To pass it to the Command bind the CommandParameter, in Execute and CanExecute it will become the method parameter (which you then need to cast to your item-type))

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

ProgressBar into ListBoxItem becomes unselectable while performing steps in XAML

I have ListBox dynamically generated. Each item contains a ProgressBar which I can stop, pause or resume by the means of clicking the relative voice on its ContextMenu.
Here is the XAML code:
<ListBox Grid.Column="1" Name="TransfersList" Margin="30,10,-0.444,34.889" ItemsSource="{Binding DataTx}"
SelectionChanged="TransfersList_SelectionChanged" Grid.Row="1" Grid.ColumnSpan="3"
HorizontalContentAlignment="Stretch">
<ListBox.ContextMenu>
<ContextMenu>
<MenuItem Header="Ottieni informazioni" Click="GetInfo" />
<MenuItem Header="Metti in pausa" Click="PauseTransfer" />
<MenuItem Header="Riprendi trasferimento" Click="ResumeTransfer" />
<MenuItem Header="Annulla trasferimento" Click="StopTransfer" />
</ContextMenu>
</ListBox.ContextMenu>
<ListBox.ItemTemplate>
<DataTemplate>
<ProgressBar Height="20" Minimum="0" Maximum="{Binding NChunks}" Name="gasparino_il_carbonaro"
Value="{Binding PbStatus}" Foreground="{Binding Color}" ToolTip="{Binding TooltipInfo}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
When I click, for example, the ContextMenu "GetInfo" voice, I get always the '-1' SelectedIndex (even if I have many bars).
I suppose that the problem is due to the ProgressBar filling (performing steps and so XAML refreshing), when the ProgressBar is filling/refreshing the "system" becomes unable to understand the selected index (which bar I selected with right click?).
My concise question is:
How can I bind the right-clicked bar with the ContextMenuItem relative
method?

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>

WPF HierarchicalDataTemplate ContextMenu not Opening

I have a treeview that is loading based on datatype. If the HierarchicalDataTemplates are nested like below the cMenuBSTool ContextMenu will display on the first child InventoryBSVM but not on any InventoryBSVM children of the first child InventoryBSVM:
<HierarchicalDataTemplate DataType="{x:Type v:ServiceGWDVM}" ItemsSource="{Binding Inventory}" ItemTemplateSelector="{StaticResource ToolSelector}">
<TextBlock Text="{Binding Name}" Style="{StaticResource ServiceStyle}" ContextMenu="{StaticResource cMenuServiceGWD}"/>
<HierarchicalDataTemplate.ItemTemplate>
<HierarchicalDataTemplate DataType="{x:Type v:InventoryBSVM}" ItemsSource="{Binding ChildItems}" ItemTemplateSelector="{StaticResource ToolSelector}">
<TextBlock Text="{Binding Name}" Style="{StaticResource ToolStyle}" ContextMenu="{StaticResource cMenuBSTool}"/>
</HierarchicalDataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
If I place the InventoryBSVM HierarchicalDataTemplate outside of the ServiceGWDVM HierarchicalDataTemplate the cMenuBSTool ContextMenu will not display for any InventoryBSVM Nodes. The number of InventoryBSVM children of other InventoryBSVM is not known in advance. The ContextMenu is very simple as can be seen below:
<ContextMenu x:Key="cMenuBSTool">
<MenuItem Header="Delete Tool" Command="{Binding CmdDelete}">
<MenuItem.Triggers>
<EventTrigger RoutedEvent="MenuItem.Click"/>
</MenuItem.Triggers>
</MenuItem>
<MenuItem Header="Create Tool" Command="{Binding CmdCreate}" CommandParameter="{StaticResource wizCreateBS}">
<MenuItem.Triggers>
<EventTrigger RoutedEvent="MenuItem.Click"/>
</MenuItem.Triggers>
</MenuItem>
</ContextMenu>
I hope there is something simple that I am missing. I have spent two days on this now and am stuck. This is my first post on Stack Overflow and any help would be greatly appreciated.

Accessing ItemsSource source item

I'm creating an error list control similar to the in Visual Studio. Each error is represented by a class with three values: type (enum: Error/Warning/Message), text (string) and time (DateTime). The class has also two more read only getters: TimeString (returns time as HH:MM) and Icon (returns icon path based on type).
I have an ItemsControl bound to an ObservableCollection of objects via ItemsSource property.
I now want to implement a context menu for each of the items with two actions: Copy to clipboard and Delete from list.
How can I access the original item from the collection from the context menu item click handler?
Here is my XAML code:
<ItemsControl Name="itemsControl" ItemsSource="{Binding Items, ElementName=ConsoleWindow}">
<ItemsControl.ItemTemplate>
<DataTemplate DataType="Console.Items">
<Border Name="itemBorder" BorderBrush="LightGray" BorderThickness="0,0,0,1" SnapsToDevicePixels="True" Padding="4">
<Border.ContextMenu>
<ContextMenu>
<MenuItem Header="Copy to clipboard" />
<MenuItem Header="Delete" />
</ContextMenu>
</Border.ContextMenu>
<DockPanel>
<Image Width="16" Height="16" Source="{Binding Icon}" Margin="0,3,4,0" VerticalAlignment="Top" DockPanel.Dock="Left" />
<TextBlock VerticalAlignment="Center" TextWrapping="Wrap" DockPanel.Dock="Left">
<Run Text="{Binding Text}" />
<TextBlock Foreground="Gray" FontSize="9">
<Run Text=" (" /><Run Text="{Binding TimeString, Mode=OneWay}" /><Run Text=") " />
</TextBlock>
</TextBlock>
</DockPanel>
Thanks for any help
The DataContext property of any of the FrameworkElement derived elements (i.e. the TextBlock or Image or MenuItem) in the DataTemplate should have the original data item (the child automatically inherits the datasource of its parent unless otherwise set).
As part of the click event handler you get the element that is the source of the event, so cast it to MenuItem and check its DataContext property.
#slugster's answer would work. A more WPF-esque way of doing this would be to use a command for each menu item and set the parameter to {Binding}. WPF comes with commands for copy and possibly delete, so you might reuse those.

Categories

Resources