Context menu on Treeview does not open on selected item - c#

I have a treeview with a context menu and I am using an converter to manage it. I do not want the menu to open on items and just want it to open on nodes.
<BooleanToVisibilityConverter x:Key="VisibilityConverter" />
<ContextMenu x:Key="AddNew" Name="PopMnu" Visibility="{Binding IsFolder,Converter={StaticResource VisibilityConverter}}">
<MenuItem Header="New Symbol..." Click="AddSymbolMenu_Click"/>
<MenuItem Header="New Folder..." Click="NewFolderItem_Click"/>
</ContextMenu>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="ContextMenu" Value="{StaticResource AddNew}"/>
</Style>
When I right click on an item, no menu comes up but now when I right click on a node, the menu comes up but on the location of the item previously right clicked. Also the menu does not dismiss unless you right click again on any item. Any help please?

As Krishna's comment suggested, a solution would be to have a view model for a folder and one for an item
public class Folder : ViewModelBase { }
public class Item : ViewModelBase { }
Then you can define a DataTemplate for each, one containing a context menu the other without.
<TreeView Name="SymbolsTreeView" ItemsSource="{Binding Items}">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type local:Folder}" ItemsSource="{Binding Items}">
<Grid Background="Red">
<Grid.ContextMenu>
<ContextMenu>
</ContextMenu>
</Grid.ContextMenu>
<TextBlock Text="{Binding Name}"/>
</Grid>
</HierarchicalDataTemplate>
<DataTemplate DataType="{x:Type local:Item}" >
<Grid >
<TextBlock Text="{Binding Name}"/>
</Grid>
</DataTemplate>
</TreeView.Resources>
</TreeView>

Related

C# WPF Listbox Context Menu Command not working

Currently I am trying to add a context menu to a ListBox that uses an item template. I am able to get the context menu items added, but when I try to bind the commands, nothing happens.
The Main_Window has a data context set. Here is the XAML for the ListBox. I use a similar Binding style as part of a button in the ListView.ItemTemplate so I would assume this would work, but sadly it is not. What am I missing here? (Only important part of the code is here)
<ListBox x:Name="company_buttons_listbox"
ItemsSource="{Binding Buttons_Binding}"
SelectedIndex="{Binding Selected_Index, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}">
<ListBox.Resources>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="ContextMenu">
<Setter.Value>
<ContextMenu>
<MenuItem Header="Update Frazer Server Link" Foreground="Black" FontFamily="Segoe UI" FontSize="14" FontWeight="Bold"
CommandParameter="{Binding RelativeSource={RelativeSource AncestorType=Window}}"
Command="{Binding ElementName=Main_Window, Path=DataContext.Testing}"/>
</ContextMenu>
</Setter.Value>
</Setter>
<Style.Resources>
<SolidColorBrush x:Key="{x:Static SystemColors.HighlightBrushKey}" Color="LightSteelBlue" Opacity="0.5"/>
<SolidColorBrush x:Key="{x:Static SystemColors.ControlBrushKey}" Color="LightSteelBlue" Opacity="0.5"/>
</Style.Resources>
</Style>
<Style TargetType="{x:Type ListBox}">
<Setter Property="KeyboardNavigation.TabNavigation" Value="Continue"/>
</Style>
</ListBox.Resources>
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Margin="-2,0,-2,0">
<Button CommandParameter="{Binding}"
Command="{Binding ElementName=Main_Window, Path=DataContext.Open_Link}">
</Button>
<Label VerticalContentAlignment="Top"
Margin="5,0,5,0" Height="19" Padding="0"
Foreground="White" FontFamily="Segoe UI" FontSize="12" FontWeight="Bold"
Content="{Binding ItemText}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
So, I solved this by not solving this and instead used a work around.
Essentially the issue comes from this:
System.Windows.Data Error: 4 : Cannot find source for binding with
reference
I found that Context Menus are not part of the Visual Tree (not happy about that) and hence cannot access those elements in the same fashion.
I am not a fan of using Reflection so the ElementSpy method is off the table for me along with. I tried to directly set a Click="some_function" and that also surprisingly DID not work.
I instead just wrapped my entire ListBox in a Grid and used the following. Not really MVVM, but I could care less at this point with how much wasted time I put into finding a solid and reliable solution.
XAML:
<Grid.ContextMenu>
<ContextMenu>
<MenuItem Header="Menu Item Text" Foreground="Black" FontFamily="Segoe UI" FontSize="14" FontWeight="Bold"
Click="menu_item_function"/>
<Separator/>
</Grid.ContextMenu>
Code Behind:
private void menu_item_function(object sender, RoutedEventArgs e)
{
// Get the viewmodel from the DataContext
MainWindowViewModel viewmodel = DataContext as MainWindowViewModel;
// Call command from viewmodel
if ((viewmodel != null) && (viewmodel.View_Model_Function.CanExecute(this)))
{
viewmodel.View_Model_Function.Execute(this);
}
}

WPF Databinding ContextMenu of Button inside a DataTemplate inside an ItemsControl

I am trying to figure out how I can bind the ContextMenu of the Button that is being added in the ItemsControl I have. Basically, I'm wanting to be able to right click on a button and remove it from the observable collection that sits on my viewmodel. I understand that ContextMenu's are not part of the VisualTree, so using RelativeSource to walk up the tree to find my DataContext hasn't been useful to me.
The end goal of what I want to do is Bind the Command on the MenuItem to the RemoveCommand on my ViewModel and then pass in the Content property of the Button that you right click on so that I can remove it from the observable collection.
Any help on this would be greatly appreciated.
Model:
public class Preset
{
public string Name { get; set; }
}
ViewModel:
public class SettingsWindowViewModel
{
public ObservableCollection<Preset> MyPresets { get; } = new ObservableCollection<Preset>();
private ICommand _plusCommand;
public ICommand PlusCommand => _plusCommand ?? (_plusCommand = new DelegateCommand(AddPreset));
private ICommand _removeCommand;
public ICommand RemoveCommand => _removeCommand ?? (_removeCommand = new DelegateCommand<string>(RemovePreset));
private void AddPreset()
{
var count = MyPresets.Count;
MyPresets.Add(new Preset {Name = $"Preset{count+1}"});
}
private void RemovePreset(string name)
{
var preset = MyPresets.FirstOrDefault(x => string.Equals(x.Name, name, StringComparison.CurrentCultureIgnoreCase));
if (preset!= null)
{
MyPresets.Remove(preset);
}
}
}
XAML:
<Window x:Class="WpfTesting.Esper.Views.SettingsWindow"
x:Name="MainSettingsWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:viewModels="clr-namespace:WpfTesting.Esper.ViewModels"
mc:Ignorable="d"
Title="SettingsWindow" Height="470" Width="612">
<Window.DataContext>
<viewModels:SettingsWindowViewModel/>
</Window.DataContext>
<Window.Resources>
<Style BasedOn="{StaticResource {x:Type MenuItem}}" TargetType="{x:Type MenuItem}" x:Key="PopupMenuItem">
<Setter Property="OverridesDefaultStyle" Value="True"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type MenuItem}">
<Border>
<ContentPresenter ContentSource="Header"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="1*"/>
<RowDefinition Height="35"/>
</Grid.RowDefinitions>
<StackPanel Grid.Row="2" Orientation="Horizontal">
<Button Width="70" Content="Load"/>
<Button Width="70" Content="Save As"/>
<ItemsControl ItemsSource="{Binding MyPresets}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Width="70" Content="{Binding Name}">
<Button.ContextMenu>
<ContextMenu>
<MenuItem Style="{StaticResource PopupMenuItem}" Header="Remove">
<!--
I need to set up binding a Command to a method on the DataContext of the Window, and I need to pass in the Content of the Button that is the parent of the ContextMenu
-->
</MenuItem>
</ContextMenu>
</Button.ContextMenu>
</Button>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<Button Width="20" Background="Transparent" BorderBrush="Transparent" Content="+" FontSize="21.333" HorizontalAlignment="Center" VerticalAlignment="Center" Command="{Binding PlusCommand}"/>
</StackPanel>
</Grid>
</Window>
Using WPF: Binding a ContextMenu to an MVVM Command as an introduction to what Tags can do, I figured out how to do what I was looking for by using multiple Tags to save the Context of what I was looking for.
I first made sure to give my window a x:Name
<Window x:Name="MainSettingsWindow"
Next, on the Button inside my DataTemplate of my ItemsControl, I set a Tag and set it to my Window
<ItemsControl ItemsSource="{Binding MyPresets}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Width="70" Content="{Binding Name}" Tag="{Binding ElementName=MainSettingsWindow}">
Next, in the ContextMenu, I seth the DataContext of the ContextMenu to the Tag I set on the Button, I also needed to create a Tag on the ContextMenu and point it back to the Content Property of the Button so that I can pass that into the CommandParameter
<ContextMenu DataContext="{Binding Path=PlacementTarget.Tag, RelativeSource={RelativeSource Mode=Self}}" Tag="{Binding PlacementTarget.Content, RelativeSource={RelativeSource Mode=Self}}">
At this point, I can now bind my MenuItem correctly using the Command from my ViewModel and the Content Property from the Button
This is the final XAML for my ItemsControl:
<ItemsControl ItemsSource="{Binding MyPresets}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Width="70" Content="{Binding Name}" Tag="{Binding ElementName=MainSettingsWindow}">
<Button.ContextMenu>
<ContextMenu DataContext="{Binding Path=PlacementTarget.Tag, RelativeSource={RelativeSource Mode=Self}}" Tag="{Binding PlacementTarget.Content, RelativeSource={RelativeSource Mode=Self}}">
<MenuItem Header="Remove"
Style="{StaticResource PopupMenuItem}"
Command="{Binding Path=DataContext.RemoveCommand}"
CommandParameter="{Binding Path=Tag, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ContextMenu}}"/>
</ContextMenu>
</Button.ContextMenu>
</Button>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
One thing to note is that I had to change the CommandParameter on my ViewModel to take an Object instead of a String. The reason I did this was because I was getting an exception on the CanExecute method in my DelegateCommand
This is the exception I was getting:
Unable to cast object of type 'MS.Internal.NamedObject' to type 'System.String'.
I'm not sure exactly what's causing that exception to throw, but changing it to Object works ok for me.
I had basically a similar problem, and the solution I found was to use the Messenger class some MVVM frameworks like Devexpress or Mvvm Light have.
Basically you can register in a viewModel to listen for incoming messages. The class itself, at least in the Devexpress implementation works with weak references, so you may not even unregister message handlers and it will not cause memory leaks.
I had used this method for removing on right click tabs from a ObservableCollection, so it was similar to your scenario.
You can have a look here :
https://community.devexpress.com/blogs/wpf/archive/2013/12/13/devexpress-mvvm-framework-interaction-of-viewmodels-messenger.aspx
and here :
https://msdn.microsoft.com/en-us/magazine/jj694937.aspx

Dynamically create ContextMenu

In my application I want to dynamically build up a context-menu. The first MenuItem is static and the second one should be a Separator. All items after the Separator are dynamically created at runtime.
I don't want to use code-behind becaus I'm working with the MVVM-Pattern.
My idea now was to create an interface called IAppMenuItem with the following three implementations
ModifyMenuItem (Static MenuItem)
SeparatorMenuItem
ExecuteMenuItem (Dynamic MenuItem
In the viewmodel of my application I've created an ObservableCollection<IAppMenuItem> which contains the ContextMenu-Items.
Until here everything works fine. My problem is the presentation of the ContextMenu-Items in the UI.
I tried to set the correct controls with the following DataTemplates in the Resources of the view.
<DataTemplate DataType="{x:Type model:SeparatorMenuItem}">
<Separator/>
</DataTemplate>
<DataTemplate DataType="{x:Type model:ModifyMenuItem}">
<MenuItem Header="Edit items"/>
</DataTemplate>
<DataTemplate DataType="{x:Type model:ExecuteMenuItem}">
<MenuItem Header="{Binding DisplayText}"/>
</DataTemplate>
The definition of my ContextMenu is just:
<ContextMenu ItemsSource="{Binding MenuItemsCollection}"/>
The DataTemplates are working fine, but the controls are drawn inside a MenuItem. So for example for the Separator I see in the UI a Separator-Control inside a MenuItem-Control. But I need the Separator to be the Control.
Anyone have an idea how to set the Controls inside the DataTemplates directly to the contextmenu?
Update:
The complete ContextMenu looks like:
<ToggleButton Margin="0,0,10,0"
AutomationProperties.Name="Update"
AutomationProperties.AutomationId="Update_List"
Content="Update"
AttachedProperties:ButtonExtensions.IsDropDownButton="True"
Style="{StaticResource GenericToggleButtonStyle}">
<ToggleButton.ContextMenu>
<controls:CustomContextMenu ItemsSource="{Binding MenuItemsCollection}">
<ContextMenu.Resources>
<ResourceDictionary>
<DataTemplate DataType="{x:Type model:ExecuteMenuItem}">
<MenuItem Header="{Binding DisplayText}"/>
</DataTemplate>
<DataTemplate DataType="{x:Type model:ModifyMenuItem}">
<MenuItem Header="Edit items"/>
</DataTemplate>
</ResourceDictionary>
</ContextMenu.Resources>
</controls:CustomContextMenu>
</ToggleButton.ContextMenu>
</ToggleButton>
The GenericToggleButtonStyle is just:
<Style x:Key="GenericToggleButtonStyle" TargetType="ToggleButton"
BasedOn="{StaticResource {x:Type ToggleButton}}">
<Setter Property="MinWidth" Value="80" />
<Setter Property="Height" Value="22" />
<Setter Property="Padding" Value="3,1" />
</Style>
Here is a screenshot of the MenuItems
It seems when you set the ItemSource property on a ContextMenu the default ItemContainer used is MenuItem. This is why the Separator is rendered as a MenuItem.
You can fix this by implementing your own ContextMenu control that inherits from ContextMenu.
You need to override the IsItemItsOwnContainerOverride and GetContainerForItemOverride methods.
Please see my example below:
public class CustomContextMenu
: ContextMenu
{
private bool _mustGenerateAsSeparator = false;
protected override bool IsItemItsOwnContainerOverride(object item)
{
_mustGenerateAsSeparator = (item is SeparatorMenuItem);
return base.IsItemItsOwnContainerOverride(item);
}
protected override System.Windows.DependencyObject GetContainerForItemOverride()
{
if (_mustGenerateAsSeparator)
{
return new Separator { Style = this.FindResource(MenuItem.SeparatorStyleKey) as System.Windows.Style };
}
else
{
return base.GetContainerForItemOverride();
}
}
}
Style = this.FindResource(MenuItem.SeparatorStyleKey) as System.Windows.Style is needed as you have to apply the default MenuItem.SeparatorStyleKey style to get the default separator style.
This link pointed me into the right direction http://drwpf.com/blog/category/item-containers/
How to use the custom control in XAML:
window declaration :xmlns:cnt="your namespace"
<Label Content="Dynamic Menu">
<Label.ContextMenu>
<cnt:CustomContextMenu x:Name="contextMenu" ItemsSource="{Binding MenuItemsCollection}">
<ContextMenu.Resources>
<ResourceDictionary>
<DataTemplate DataType="{x:Type model:ExecuteMenuItem}">
<MenuItem Header="{Binding DisplayText}"></MenuItem>
</DataTemplate>
<DataTemplate DataType="{x:Type model:ModifyMenuItem}">
<MenuItem Header="{Binding DisplayText}"></MenuItem>
</DataTemplate>
</ResourceDictionary>
</ContextMenu.Resources>
</model:CustomContextMenu>
</Label.ContextMenu>
</Label>
Second Part of the question:
Update your data templates to use TextBlock as they will be rendered inside a MenuItem, please see below:
<DataTemplate DataType="{x:Type model:ModifyMenuItem}">
<TextBlock Header="Edit items"/>
</DataTemplate>
<DataTemplate DataType="{x:Type model:ExecuteMenuItem}">
<TextBlock Header="{Binding DisplayText}"/>
</DataTemplate>

How to correctly bind a ViewModel (which Include Separators) to WPF's Menu?

I'm using MVVM and I want to data bind my list of MenuViewModels to my maim menu. Which consists of a set of menu items and separators.
Here's my MenuItemViewModel code:
public interface IMenuItemViewModel
{
}
[DebuggerDisplay("---")]
public class SeparatorViewModel : IMenuItemViewModel
{
}
[DebuggerDisplay("{Header}, Children={Children.Count}")]
public class MenuItemViewModel : IMenuItemViewModel, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public MenuItemViewModel(string header, ICommand command, ImageSource imageSource)
{
Header = header;
Command = command;
ImageSource = imageSource;
Children = new List<IMenuItemViewModel>();
}
public string Header { get; private set; }
public ICommand Command { get; private set; }
public ImageSource ImageSource { get; private set; }
public IList<IMenuItemViewModel> Children { get; private set; }
}
And my Main window looks like this:
<Window.Resources>
<HierarchicalDataTemplate DataType="{x:Type ViewModel:MenuItemViewModel}"
ItemsSource="{Binding Children}">
<MenuItem Header="{Binding Header}"
Command="{Binding Command}"/>
</HierarchicalDataTemplate>
<DataTemplate DataType="{x:Type ViewModel:SeparatorViewModel}">
<Separator />
</DataTemplate>
</Window.Resources>
<DockPanel>
<Menu DockPanel.Dock="Top"
ItemsSource="{Binding MenuItems}">
</Menu>
</DockPanel>
Should be very simple stuff. Unfortunately, either the menu item looks wrong or the separator is an empty menuItem (depending on what I've tried).
So, how do I get my Menu to find my two DataTemplates?
Solved my own question
After spending several hours searching the web, I found lots of examples that work against the WPF's natural intentions but none that worked with it.
Here's how to work with the Menu control and not against it...
A little Background
WPF's Menu control will normally auto create MenuItem objects for you when it is binded to a POCO collection, using the ItemsSource property.
However, this default behavior can be overridden! Here's how...
The Solution
First, you must create a class that derives from ItemContainerTemplateSelector. Or use the simple class I've created:
public class MenuItemContainerTemplateSelector : ItemContainerTemplateSelector
{
public override DataTemplate SelectTemplate(object item, ItemsControl parentItemsControl)
{
var key = new DataTemplateKey(item.GetType());
return (DataTemplate) parentItemsControl.FindResource(key);
}
}
Second, you must add a reference to the MenuItemContainerTemplateSelector class to your Windows resources object, like so:
<Window.Resources>
<Selectors:MenuItemContainerTemplateSelector x:Key="_menuItemContainerTemplateSelector" />
Third, you must set two properties (UsesItemContainerTemplate, and ItemContainerTemplateSelector) on both the Menu and the MenuItem (which is defined in the HierarchicalDataTemplate).
Like so:
<HierarchicalDataTemplate DataType="{x:Type ViewModel:MenuItemViewModel}"
ItemsSource="{Binding Children}">
<MenuItem Header="{Binding Header}"
Command="{Binding Command}"
UsesItemContainerTemplate ="true"
ItemContainerTemplateSelector=
"{StaticResource _menuItemContainerTemplateSelector}"/>
</HierarchicalDataTemplate>
<Menu DockPanel.Dock="Top"
ItemsSource="{Binding MenuItems}"
UsesItemContainerTemplate="True"
ItemContainerTemplateSelector=
"{StaticResource _menuItemContainerTemplateSelector}">
</Menu>
Why it Works
For optimization purposes, the Menu uses the UsesItemContainerTemplate flag (which has a default value of false) to skip the DataTemplate lookup and just returns a normal MenuItem object. Therefore, we needed to set this value to true and then our ItemContainerTemplateSelector works as expected.
Happy Coding!
A solution without the TemplateSelector:
provide ItemContainerTemplates instead of the DataTemplates :
<ContextMenu ItemsSource="{Binding Path=MenuItems}" UsesItemContainerTemplate="True">
<ContextMenu.Resources>
<ResourceDictionary>
<ItemContainerTemplate DataType="{x:Type ViewModel:MenuItemViewModel }">
<MenuItem Header="{Binding Path=Header}" Command="{Binding Path=Command}" UsesItemContainerTemplate="True">
<MenuItem.Icon>
<Image Source="{Binding Path=ImageSource}"/>
</MenuItem.Icon>
</MenuItem>
</ItemContainerTemplate>
<ItemContainerTemplate DataType="{x:Type ViewModel:SeparatorViewModel}">
<Separator >
<Separator.Style>
<Style TargetType="{x:Type Separator}" BasedOn="{StaticResource ResourceKey={x:Static MenuItem.SeparatorStyleKey}}"/>
</Separator.Style>
</Separator>
</ItemContainerTemplate>
</ResourceDictionary>
</ContextMenu.Resources>
</ContextMenu>
Notes:
I haven't tried Children
the separator styled wrong: I had to manually re-apply the style
Another approach is to:
have a Boolean property on your menu item ViewModel that indicates whether an item is a separator or not
use a trigger based on this property to change the ControlTemplate of the MenuItem so that it uses a Separator control instead
Like so:
<Menu ItemsSource="{Binding MenuItems}">
<Menu.Resources>
<Style TargetType="{x:Type MenuItem}">
<Setter Property="Header" Value="{Binding Header}" />
<Setter Property="Command" Value="{Binding Command}" />
<Style.Triggers>
<DataTrigger Binding="{Binding IsSeparator}" Value="True">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type MenuItem}">
<Separator />
</ControlTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
<HierarchicalDataTemplate DataType="{x:Type ViewModel:MenuItemViewModel}"
ItemsSource="{Binding Children}" />
</Menu.Resources>
</Menu>

Method in ContextMenu for (Hierarchical)DataTemplate calls TreeViewItem's method instead of the one in ViewModel

I have a TreeView with multiple HierarchicalDataTemplate & DataTemplate items and I'm using Caliburn Micro for mvvm. The ItemsSource for the treeview is pointing to a collection in the viewmodel called 'TreeData' and I tried adding a specific ContextMenu for each HierarchicalDataTemplate & DataTemplate.
In the ContextMenu I use the caliburn functionality "cal:Message.Attach" to call a function in the
I made a smaller example of the treeview to illustrate the problem.
In the ViewModel (the collection object):
public class MyViewModel
{
// TreeData object
public ObservableCollection<TestRoot> TreeData = new ObservableCollection<TestRoot>()
{
new TestRoot()
{
Name = "Root item"
}
};
// the function on the viewmodel that should be called
public void DoSomething(object item)
{
MessageBox.Show("MyViewModel - DoSomething called");
}
}
The collection object:
public class TestRoot
{
public string Name { get; set; }
// caliburn calls this instead of the one on the viewmodel
public void DoSomething(object item)
{
MessageBox.Show("TestRoot - DoSomething called");
}
}
MyView.xaml treeview with only one (Hierarchical)DataTemplate:
<TreeView Margin="5" ItemsSource="{Binding TreeData}">
<TreeView.Resources>
<DataTemplate DataType="{x:Type vm:TestRoot}" >
<StackPanel Orientation="Horizontal">
<StackPanel.ContextMenu>
<ContextMenu>
<!-- caliburn (?) chooses the method on the collection object, not the viewmodel -->
<MenuItem Header="test dosomething" cal:Message.Attach="DoSomething($dataContext)"></MenuItem>
</ContextMenu>
</StackPanel.ContextMenu>
<TextBlock Text="{Binding Name}"/>
</StackPanel>
</DataTemplate>
</TreeView.Resources>
</TreeView>
In another piece of code, I placed the ContextMenu in the TreeView.ContextMenu. There it worked as it 'should', pointing to the method on the viewmodel.
Looking around for a solution, I see things like "inheritance context". I think it might have something to do with it, but I'm not sure. How can I tell caliburn it must look in the viewmodel for my method, instead of the item in the TreeView I clicked on?
Or is there another possibility? For example: defining the different ContextMenus in the Resources and pointing them to the DataTemplates? But, wont that cause the exact same problem?
Please note that I'd like to keep the code-behind as minimal as possible. Thanks
update
For the completeness, here's the real development code. This should be right, no?
<TreeView ItemsSource="{Binding OrderTreeViewData.OrderTreeViewCategories}"
cal:Message.Attach="[Event SelectedItemChanged] = [Action OnSelectedItemChanged($this)]">
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<!-- We have to select the item which is right-clicked on -->
<EventSetter Event="TreeViewItem.PreviewMouseRightButtonDown"
Handler="TreeViewItem_PreviewMouseRightButtonDown"/>
<!-- set expanded -->
<Setter Property="TreeViewItem.IsExpanded" Value="True"/>
</Style>
</TreeView.ItemContainerStyle>
<TreeView.Resources>
<!-- dredge nodes -->
<HierarchicalDataTemplate DataType="{x:Type programs:DredgeRoot}"
ItemsSource="{Binding Dredgezones}">
<StackPanel>
<StackPanel.ContextMenu>
<ContextMenu DataContext="{Binding Path=PlacementTarget, RelativeSource={RelativeSource Self}}">
<MenuItem Header="Add dredge zone" cal:Message.Attach="TreeViewAddDredgeZone($datacontext)"></MenuItem>
</ContextMenu>
</StackPanel.ContextMenu>
<TextBlock Text="{Binding Name}"/>
</StackPanel>
</HierarchicalDataTemplate>
<!-- omitted other templates -->
</TreeView.Resources>
</TreeView>
Unfortunately there is still one tricky part to deal with. Due to the specific Popup behavior it doesn't inherit DataContext. To access proper context you have to get the PlacementTarget:
<StackPanel Tag="{Binding DataContext, RelativeSource={RelativeSource Self}}">
<StackPanel.ContextMenu>
<ContextMenu DataContext="{Binding Path=PlacementTarget.Tag, RelativeSource={RelativeSource Self}}">
<MenuItem Header="test dosomething" cal:Message.Attach="DoSomething"/>
</ContextMenu>
</StackPanel.ContextMenu>
</StackPanel>
I think you have the nearly same problem as me, maybe see this topic : Bind contextMenu to a different viewmodel from treeview
You can try to use a command :
Try to change you code to :
<ContextMenu x:Key="MyContextMenu">
<MenuItem Header="Add dredge zone" Command="{Binding PlacementTarget.Tag.DataContext.TreeViewAddDredgeZoneCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ContextMenu}}"
CommandParameter="{Binding}"></MenuItem>
</ContextMenu>
Then add to your hierarchicalDataTemplate a Tag and ContextMenu
<HierarchicalDataTemplate DataType="{x:Type programs:DredgeRoot}"
ItemsSource="{Binding Dredgezones}" Tag="{Binding RelativeSource={RelativeSource AncestorType=UserControl}} ContextMenu="{StaticResource MyContextMenu}">
And in your viewmodel you can add your command with something like this :
public ICommand TreeViewAddDredgeZoneCommand
{
//your code here
}

Categories