Highlight custom DataTemplate in TreeView - c#

I have a simple TreeView that I'm trying to create a custom DataTemplate for. It displays as desired, but when I attempt to select the TreeViewItem, the highlighting does not work if I click the text in the TreeView. However, if I select just to the left of the text, it works:
The source is pretty straight forward, so I'm guessing I'm just missing a styling connection:
xaml
<TreeView x:Name="treeView"
ItemsSource="{Binding TreeViewItems}"
Grid.Row="0">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Path=MenuItems}">
<TreeViewItem Header="{Binding Header}">
<TreeViewItem.InputBindings>
<MouseBinding MouseAction="LeftClick"
Command="{Binding Command}" />
</TreeViewItem.InputBindings>
</TreeViewItem>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
I've tried adding this to the xaml, but it didnt help:
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}"
BasedOn="{StaticResource {x:Type TreeViewItem}}" />
</TreeView.ItemContainerStyle>
TreeViewModel
public class TreeViewModel : BaseNotifyModel, ITreeViewModel
{
public TreeViewModel(ITreeViewService menuService)
{
TreeViewItems =
new ReadOnlyObservableCollection<MenuItemViewModel>(menuService.TreeViewMenu);
}
public ReadOnlyObservableCollection<MenuItemViewModel> TreeViewItems
{
get
{
return Get<ReadOnlyObservableCollection<MenuItemViewModel>>();
}
private set
{
Set(value);
}
}
}
MenuItemViewModel
public class MenuItemViewModel : BaseNotifyModel
{
public MenuItemViewModel()
{
MenuItems =
new ObservableCollection<MenuItemViewModel>();
}
public String Header
{
get
{
return Get<String>();
}
set
{
Set(value);
}
}
public ICommand Command
{
get
{
return Get<ICommand>();
}
set
{
Set(value);
}
}
public ObservableCollection<MenuItemViewModel> MenuItems
{
get
{
return Get<ObservableCollection<MenuItemViewModel>>();
}
set
{
Set(value);
}
}
}

The TreeView is creating a TreeViewItem for each item in ItemsSource, so don't nest another TreeViewItem inside the one that the TreeView already created for you. That doesn't serve any purpose. Your template should just be providing a way for the existing TreeViewItem to display whatever's in its DataContext (your MenuItemViewModel, in this case).
You want to display the Header property in the tree view item; so just do that. Nothing fancy, just a Label or ContentControl, or even a TextBlock if it's String (though it's a lot of fun in WPF to have the flexibility of arbitrary content). When the user clicks on the content, your command executes. The only thing the user can see in the tree is the content. That's the only visible part of the TreeViewItem item, so that's what the user is going to click on.
Problem number two: Once the input binding is getting LeftClick, that breaks selection in the TreeView. It appears to me that you can't get there from here by that method.
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Path=MenuItems}">
<ContentControl
Content="{Binding Header}"
Background="Transparent"
>
<ContentControl.InputBindings>
<!-- This invokes the command, but breaks selection -->
<MouseBinding MouseAction="LeftClick"
Command="{Binding Command}" />
</ContentControl.InputBindings>
</ContentControl>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
Here's what you can do:
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Path=MenuItems}">
<ContentControl
Content="{Binding Header}"
Background="Transparent"
>
</ContentControl>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
<TreeView.ItemContainerStyle>
<Style TargetType="TreeViewItem" BasedOn="{StaticResource {x:Type TreeViewItem}}">
<EventSetter Event="Selected" Handler="MenuTreeViewItem_Click" />
</Style>
</TreeView.ItemContainerStyle>
Codebehind
private void MenuTreeViewItem_Click(object sender, RoutedEventArgs e)
{
((MenuItemViewModel)((FrameworkElement)sender).DataContext).Command.Execute(null);
}
There is a way to bind an event to a command in pure XAML, but it requires some C# code (LOL). But I mean, it's "pure XAML" in the sense that it's a nice generalized reusable attached behavior, not an unsightly event handler in your codebehind. Instead, it does exactly what I did above, but it does it in code that you can more easily avert your eyes from, and that you can reuse in pure XAML.

With help from #Ed Plunket and #Evk, I found a solution that will work. I switched to a ContentPresenter and used the Interaction.Triggers to call the command on the MouseLeftButtonUp action.
<TreeView ItemsSource="{Binding TreeViewItems}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Path=MenuItems}">
<ContentPresenter Content="{Binding Header}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseLeftButtonUp">
<i:InvokeCommandAction Command="{Binding Path=DataContext.Command, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type TreeViewItem}}}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</ContentPresenter>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>

Related

Custom control: binding in ItemContainerStyle

I'm working in a Explorer tree view (custom wpf control designed by me). I have this code in Generic.xaml:
<Style TargetType="{x:Type local:ExplorerControl}">
<Setter Property="Template" >
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:ExplorerControl}">
<Border>
<TreeView Name="myTreeView" >
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="ContextMenu">
<Setter.Value>
<ContextMenu>
<MenuItem x:Name="myTemplate" Header="Remove" Command="{TemplateBinding RemoveCommand}"></MenuItem>
</ContextMenu>
</Setter.Value>
</Setter>
</Style>
</TreeView.ItemContainerStyle>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Nodes}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}" VerticalAlignment="Center" />
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
In ExplorerControl I have my Dependency Property:
public class ExplorerControl : Control{
public ExplorerControl()
{
Nodes = new ObservableCollection<Node>();
}
private ObservableCollection<Node> Nodes { get; }
public ICommand RemoveCommand
{
get { return (ICommand)GetValue(RemovedCommandProperty); }
set { SetValue(RemovedCommandProperty, value); }
}
public static readonly DependencyProperty RemovedCommandProperty =
DependencyProperty.Register("RemoveCommand", typeof(ICommand), typeof(ExplorerControl));
}
Node class
public class Node {
public string Name {get;set;}
}
My problem is that I don't know how to get that the MenuItem Command works
I have tried these:
If I use the same code with a button after the TreeView (with both in a Stackpanel) it works. So I think that the problem is the MenuItem DataContext
I tried to change the MenuItem DataContext however I didn't get it.
I hope you can help me.
Edit: I delete the part of code about DataContext. Thanks for you answers.
I use this control in my MainView:
<treeViewExplorerControl:ExplorerControl
SelectedItemName="{Binding SelectedItemName}"
SelectedItemPath="{Binding SelectedItemPath}"
RemoveCommand="{Binding ExplorerControlItemRemovedCommand}"/>
Proposed Solution:
In your MenuItem Commands try using Ancestral Binding.
<MenuItem x:Name="myTemplate" Header="Remove"
Command="{Binding RelativeSource={RelativeSource Path=RemoveCommand, AncestorType={x:Type ExplorerControl}, Mode=FindAncestor" />
I believe the reason your DataContext is changing is because you're pointing to Nodes and showing each Node in the MenuItem. However, Node does not contain the command you are trying to bind to.
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Nodes}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}" VerticalAlignment="Center" />
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
If you are unsure of your DataContext you can use Snoop to see what is the current DataContext.
Extra Info:
I don't think you need to point DataContext of your TreeView. It's passed down automatically.
<TreeView.DataContext>
<local:ExplorerControl x:Name="explorer" />
</TreeView.DataContext>
You don't have to use a DependencyProperty with an ICommand. In the constructor of your ExplorerControl you can instantiate the ICommand to a DelegateCommand.
Make a DelegateCommand class that inherits from ICommand. This will be your concrete implementation of ICommand. You can find that here:http://www.wpftutorial.net/delegatecommand.html
Instantiate your ICommands with a DelegateCommand and pass your method, that is on ExplorerControl, to the constructor.
For example:
public class ExplorerControl : UserControl
{
public DelegateCommand RemoveCommand { get; set; }
public ExplorerControl()
{
RemoveCommand = new DelegateCommand(Remove);
}
private void Remove()
{
// Do something here.
}
}
Finally I found a solution.
First of all I found that about ContextMenu:
Because a ContextMenu in WPF does not exist within the visual tree of your page/window/control per se, data binding can be a little tricky.
Source
With the example, I wrote this code and I got that it works well:
<Style TargetType="{x:Type local:ExplorerControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:ExplorerControl}">
<Border>
<TreeView Name="myTreeView">
<TreeView.Resources>
<SolidColorBrush x:Key="{x:Static SystemColors.HighlightBrushKey}" Color="#FF003BB0" />
</TreeView.Resources>
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsExpanded" Value="True" />
</Style>
</TreeView.ItemContainerStyle>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Nodes}">
<StackPanel Orientation="Horizontal"
Tag="{Binding TemplatedParent,RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type TreeView}}}">
<TextBlock Text="{Binding Name}" VerticalAlignment="Center" />
<StackPanel.ContextMenu>
<ContextMenu>
<MenuItem x:Name="myTemplate"
Header="Remove"
DataContext="{Binding PlacementTarget, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ContextMenu}}}"
Command="{Binding Path=Tag.RemoveCommand}"
CommandParameter="{Binding Path=DataContext}">
</MenuItem>
</ContextMenu>
</StackPanel.ContextMenu>
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
I save in the Stackpanel tag, the reference of my explorerControl, then I use PlacementTarget to get the Stackpanel reference
I hope this code helps other people in future.

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
}

Disallow/Block selection of disabled combobox item in wpf

I'm writing an application wherein I would like to disable few items in the ComboBox and also want to disallow/block selection of disabled items. Please note ComboBox in main window has another ComboBox as ComboBox Item init (that is decided at run time by DataTemplateSelector).
With below code I'm able to disable a ComboBox within ComboBox but it would not stop user from selecting that disabled ComboBox item. Any help in disallow/block selection of disabled items would be helpful.
Below are the code snippets
ComboBox in main window:
<Grid>
<ComboBox HorizontalAlignment="Left" VerticalAlignment="Top"
Width="120" Margin="87.2,44.8,0,0"
ItemsSource="{Binding Cars}"
ItemsPanel="{DynamicResource ItemsPanelTemplateHorizontal}"
ItemTemplateSelector="{StaticResource QualityComboBoxTemplateSelector}"
SelectedItem="{Binding SelectedItm}"/>
</Grid>
Data template selector:
public class QualityComboBoxTemplateSelector : DataTemplateSelector
{
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
var element = container as FrameworkElement;
var dataTemplate = element.FindResource(((item is string) && item.Equals("Ferrari")) ?
"DataTemplateTopLevelCombobox2" : "DataTemplateTopLevelCombobox1") as DataTemplate;
return dataTemplate;
}
}
Data templates for above ComboBox:
<DataTemplate x:Key="DataTemplateTopLevelCombobox1">
<Border BorderBrush="Black" BorderThickness="1" >
<TextBlock HorizontalAlignment="Left"
TextWrapping="Wrap" Text="{Binding}"
VerticalAlignment="Top"/>
</Border>
</DataTemplate>
<DataTemplate x:Key="DataTemplateTopLevelCombobox2">
<Border Width="100">
<ComboBox Text="Custom" Height="21.96"
ItemsSource="{Binding DataContext.Models, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}"
IsEnabled="{Binding DataContext.EnableCombo, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}" />
</Border>
</DataTemplate>
You can achieve this by setting IsEnabled property of a ComboBoxItem to false;
So each item in ComboBox's ItemSource (i.e. Cars in your case) can be an object having some property (say IsSelectable) specifying whether it should be enabled or disabled and then use it with a style to make an item un-selectable. something like this -
<Style TargetType="ComboBoxItem">
<Setter Property="IsEnabled" Value="{Binding IsSelectable}"/>
</Style>
Update:
<Grid>
<ComboBox
Width="120"
Margin="87.2,44.8,0,0"
HorizontalAlignment="Left"
VerticalAlignment="Top"
ItemTemplateSelector="{StaticResource QualityComboBoxTemplateSelector}"
ItemsPanel="{DynamicResource ItemsPanelTemplateHorizontal}"
ItemsSource="{Binding Cars}"
SelectedItem="{Binding SelectedItm}">
<ComboBox.ItemContainerStyle>
<Style TargetType="ComboBoxItem">
<Setter
Property="IsEnabled"
Value="{Binding IsSelectable}" />
</Style>
</ComboBox.ItemContainerStyle>
</ComboBox>
</Grid>
To solve the problem pointed by #JordyBoom.
ItemsContainerGenerator does not generate items until dropdown is opened at least once.
So if you open the drop down and close it again in window’s loaded event handler then all supposed to work fine with mouse as well as with keyboard selection.
public MainWindow()
{
InitializeComponent();
this.Loaded += new RoutedEventHandler(onLoaded);
}
private void onLoaded(object sender, RoutedEventArgs e)
{
cmbx.IsDropDownOpen = true;
cmbx.IsDropDownOpen = false;
}
source: WPF: Making combo box items disabled – also when accessed using the keyboard

Categories