How can I have a context menu in a DataTrigger on treeview? The code below does not trigger the context menu eg I want the menu on "Symbols" as well. Although I have a context menu on HierarchicalDataTemplate which works fine but only on child elements. The root on the treeview does not have a menu
<HierarchicalDataTemplate x:Key="NameTemplate" ItemsSource="{Binding Path=ChildPlanner}">
<HierarchicalDataTemplate.ItemContainerStyle>
<Style>
<Style.Triggers>
<DataTrigger Binding="{Binding IsFolder}" Value="True">
<Setter Property="TreeViewItem.ContextMenu" Value="{StaticResource AddNewSymbol}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</HierarchicalDataTemplate.ItemContainerStyle>
<StackPanel Orientation="Horizontal" Margin="2">
<TextBlock Text="{Binding Path=Name}" FontWeight="Bold">
</TextBlock>
</StackPanel>
</HierarchicalDataTemplate>
<TreeView Name="SymbolsTreeView">
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Style.Triggers>
<DataTrigger Binding="{Binding IsFolder}" Value="True">
<Setter Property="ContextMenu" Value="{StaticResource AddNewSymbol}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TreeView.ItemContainerStyle>
<TreeViewItem Header="Symbols" IsExpanded="True" ItemsSource="{Binding PlannerTreeList}" ItemTemplate="{StaticResource NameTemplate}"/>
</TreeView>
Imagine my tree is
Symbols
Current
Menu1Folder
Menu2Folder
Menu2Item
Menu2AnotherItem
Current1Item
The HierarchicalDataTemplate's menu works for menu1folder onwards which is ok. But I want it to work for Current1Item, Current and Symbols. Since Current1Item is not a folder, there should be no menu for it but Current and Symbols are folders
<TreeView.Resources>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="ContextMenu" Value="{StaticResource AddNewSymbol}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsFolder,RelativeSource={RelativeSource Self}}" Value="False">
<Setter Property="ContextMenu" Value="{x:Null}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TreeView.Resources>
Edit - Try this new code. I am using a converter to show and hide the contextmenu based on your property. It works with my sample code. Let me know if you want my sample code.
<Grid>
<Grid.Resources>
<BooleanToVisibilityConverter x:Key="VisibilityConverter" />
<ContextMenu x:Key="MenuOne" Visibility="{Binding IsFolder,Converter={StaticResource VisibilityConverter}}">
<MenuItem Header="Add Folder" Command="{Binding AddFolderCommand}"/>
<MenuItem Header="Add Item" Command="{Binding AddItemCommand}"/>
</ContextMenu>
</Grid.Resources>
<TreeView Name="SymbolsTreeView" ItemsSource="{Binding Items}">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type local:MyTreeViewItem}" ItemsSource="{Binding Items}">
<ContentControl>
<TextBlock Text="{Binding Name}"/>
</ContentControl>
</HierarchicalDataTemplate>
<Style TargetType="TreeViewItem">
<Setter Property="ContextMenu" Value="{StaticResource MenuOne}"/>
<Setter Property="IsExpanded" Value="True"/>
</Style>
</TreeView.Resources>
</TreeView>
</Grid>
Related
I am using the following code for the Treeview object:
<TreeView x:Name="RegisteredServer" ItemsSource="{Binding RegisteredServerList}" Grid.Row="1">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children}">
<StackPanel Orientation="Horizontal">
<Image Margin="0,0,4,0" Source="{Binding Icon}" Width="16" Height="16" />
<TextBlock Text="{Binding Name}" FontWeight="SemiBold" />
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="Foreground" Value="{x:Reference Name=LabelForeground}"></Setter>
<Setter Property="IsExpanded" Value="True"/>
<EventSetter Event="PreviewMouseRightButtonDown"
Handler="OnPreviewMouseRightButtonDown" />
<EventSetter Event="Selected" Handler="Tree_SelectedItemChanged"/>
<EventSetter Event="MouseDoubleClick" Handler="OnItemMouseDoubleClick"/>
</Style>
</TreeView.ItemContainerStyle>
</TreeView>
And I have the following code in the style file named Style.xaml:
<Style TargetType="TreeView">
<Setter Property="Control.Background" Value="Red"></Setter>
<Style.Resources>
<Style TargetType="TreeViewItem">
<Setter Property="Background" Value="Orange"></Setter>
<Setter Property="Foreground" Value="White"></Setter>
</Style>
</Style.Resources>
</Style>
Since I use in the part where I assign the event, the code in my style file does not work.
I cannot write event and style codes together in because I use theme structure.
How should I proceed here? Thank you from now.
I've got a ListBox as such:
<ListBox Margin="5" ItemsSource="{Binding NetworkAdapters, Mode=OneWay}" SelectedItem="{Binding SelectedNetworkAdapter}" SelectionChanged="{s:Action SelectedNetworkAdapterChanged}">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Columns="2" VerticalAlignment="Top"/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Ellipse Width="15" Height="15" Margin="5">
<Ellipse.Style>
<Style TargetType="Ellipse">
<Setter Property="Fill" Value="Gray"></Setter>
<Style.Triggers>
<DataTrigger Binding="{Binding Status}" Value="{x:Static wpf:NetworkAdapterStatus.Up}">
<Setter Property="Fill" Value="Green"></Setter>
</DataTrigger>
<DataTrigger Binding="{Binding Status}" Value="{x:Static wpf:NetworkAdapterStatus.Down}">
<Setter Property="Fill" Value="Red"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</Ellipse.Style>
</Ellipse>
<StackPanel Margin="5,0,0,0">
<TextBlock Text="{Binding Name}" FontWeight="Bold"/>
<TextBlock Text="{Binding Description}"></TextBlock>
<TextBlock Text="{Binding Speed, StringFormat='Speed: {0}'}" FontSize="10"/>
<TextBlock Text="{Binding Status}" FontSize="10"/>
</StackPanel>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
NetworkAdapters is a collection of View Models that implement INotifyDataErrorInfo.
With the current XAML, if there is an error in any of the View Models the whole ListBox will be highlighted red, but I would like just the single ListBoxItems that contains errors to be highlighted.
I had a look at similar questions such as:
WPF ListBox ErrorTemplate and
Validating a ListBoxItem rather than a ListBox
But I still can't make this work. Any help would be appreciated.
UPDATE:
As per Krzysztof's advice, I tried wrapping the StackPanel around a border and using a DataTrigger as such:
<ListBox.ItemTemplate>
<DataTemplate>
<Border BorderThickness="1">
<Border.Resources>
<Style TargetType="Border">
<Style.Triggers>
<DataTrigger Binding="{Binding HasErrors}" Value="True">
<Setter Property="BorderBrush" Value="Red"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Resources>
<StackPanel> ... </StackPanel>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
However, what this produces is the following:
Which is slightly different from what I expected. I would like to have the highlight around the whole ListBoxItem not just part of it as per the image.
You need to implement an ItemContainerStyle as below:
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="BorderThickness" Value="1" />
<Setter Property="BorderBrush" Value="Transparent" />
<Style.Triggers>
<DataTrigger Binding="{Binding Validation.HasErrors}" Value="True">
<Setter Property="BorderBrush" Value="Red"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ListBox.ItemContainerStyle>
This will enable you to change the border of the ListBoxItem itself, so the whole things as you want.
You can forget about ErrorTemplate and just use DataTrigger to bind to Validation.HasErrors:
<StackPanel>
<StackPanel.Resources>
<Style TargetType="{x:Type StackPanel}" BasedOn="{StaticResource {x:Type StackPanel}}">
<Style.Triggers>
<DataTrigger Binding="{Binding Validation.HasErrors}" Value="True"> <!-- change all text to red -->
<Setter Property="Foreground" Value="Red"/>
</DataTrigger>
</Style.Triggers>
</Style>
</StackPanel.Resources>
</StackPanel>
If you want a highlight, you can wrap StackPanel with a Border and set its color to red in a style.
I've been doing a lot of research and I don't understand exaclty how to properly bind an ICommand to my TreeView item.
I have a VM that holds a Data Object.
This data object holds an ObservableCollection object of TreeNode item objects.
This TreeNode Item has properties such as Tag, IsSelected, Header and even a ContextMenu.
However I can't figure out how to bind an ICommand to a single TreeView item.
Here is my XAML of my TreeView
<TreeView Grid.Column="0" Grid.Row="0"
Grid.ColumnSpan="1" Grid.RowSpan="5"
x:Name="TestPlanTreeView"
ItemsSource="{Binding Data.TestPlanCollection}"
dd:DragDrop.IsDragSource="True"
dd:DragDrop.IsDropTarget="True">
<TreeView.ItemContainerStyle>
<Style TargetType="TreeViewItem">
<Setter Property="Focusable" Value="{Binding Focusable, Mode=TwoWay}"/>
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}"/>
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}"/>
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="FontWeight" Value="Bold"/>
<Setter Property="FontStyle" Value="Italic"/>
</Trigger>
</Style.Triggers>
</Style>
</TreeView.ItemContainerStyle>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children}">
<TextBlock Text="{Binding Header}" Margin="2">
<TextBlock.ContextMenu>
<ContextMenu ItemsSource="{Binding MenuCollection}">
<ContextMenu.ItemContainerStyle>
<Style TargetType="MenuItem">
<Setter Property="Header" Value="{Binding Header}"/>
<Setter Property="Command" Value="{Binding Command}"/>
<Setter Property="CommandParameter" Value="{Binding CommandParameter}"/>
</Style>
</ContextMenu.ItemContainerStyle>
</ContextMenu>
</TextBlock.ContextMenu>
</TextBlock>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
I have a TreeView that is filled with different types of items. The items can either be of type Node (then they may have children) or of type Entry (then they don't have children). For that, I bound my TreeView to my ViewModel property AllNodesAndEntries which is an ObservableCollection<object>. For different looks of Node and Entry I defined two DataTemplates. Here is the code:
<TreeView ItemsSource="{Binding AllNodesAndEntries}">
<TreeView.Resources>
<HierarchicalDataTemplate ItemsSource="{Binding Children}"
DataType="{x:Type local:Node}">
<TextBlock Text="{Binding Name}"
Background="LightBlue"/>
</HierarchicalDataTemplate>
<DataTemplate DataType="{x:Type local:Entry}">
<TextBlock Text="{Binding Name}"
Background="LightSalmon"/>
</DataTemplate>
</TreeView.Resources>
</TreeView>
Now I want to make the Entry elements unfocusable if a certain condition is met (that is, if my ViewModel property MyProp is true).
So I added a trigger into the DataTemplate for Entry like this:
<DataTemplate DataType="{x:Type local:Entry}">
<TextBlock Text="{Binding Name}"
Background="LightSalmon"/>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding MyProp}" Value="True">
<Setter Property="Focusable" Value="False"/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
But it does not work, I can still select entries after MyProp was set to true. What am I doing wrong? How do I make it work?
I did put a NotifyPropertyChanged(nameof(MyProp)); in the setter of MyProp, so changes to MyProp will be reported to the View.
Using the IsNodeConverter you've posted,
you can implement a MultiDataTrigger that only fires when both conditions are fulfilled:
ViewModel MyProp = true
TreeViewItem of type Entry
XAML
<Window.Resources>
<local:IsNodeConverter x:Key="IsNodeConverter"/>
</Window.Resources>
...
<TreeView ItemsSource="{Binding AllNodesAndEntries}">
<TreeView.Resources>
...
</TreeView.Resources>
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding DataContext.MyProp, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
Value="True"/>
<Condition Binding="{Binding Converter={StaticResource IsNodeConverter}}"
Value="False"/>
</MultiDataTrigger.Conditions>
<MultiDataTrigger.Setters>
<Setter Property="Focusable" Value="False"></Setter>
</MultiDataTrigger.Setters>
</MultiDataTrigger>
</Style.Triggers>
</Style>
</TreeView.ItemContainerStyle>
</TreeView>
The problem is you set the Focusable Property to the DataTemplate which doesn't affect the selection of the TreeViewItem.
Instead you should set it on the TreeViewItem like this:
<TreeView ItemsSource="{Binding Items}">
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Style.Triggers>
<DataTrigger Binding="{Binding MyProp}"
Value="True">
<Setter Property="Focusable"
Value="False" />
</DataTrigger>
</Style.Triggers>
</Style>
</TreeView.ItemContainerStyle>
</TreeView>
It is no problem, you just need to set the Focusable property at the TreeViewItem. Just add the following to your TreeView.Resources:
<TreeView.Resources>
<DataTemplate DataType="{x:Type local:Entry}">
<TextBlock Text="{Binding Name}" Background="LightSalmon"/>
</DataTemplate>
<Style TargetType="TreeViewItem">
<Style.Triggers>
<DataTrigger Binding="{Binding MyProp}" Value="True">
<Setter Property="Focusable" Value="False" />
</DataTrigger>
</Style.Triggers>
</Style>
</TreeView.Resources>
This style now occurs at all your TreeViewItems. If you like to prevent your Nodes from this style you could do it in code behind or just bind your item object and use a Converter.
How can I disable the listboxitem context menu when none or only one item is selected?
ListBox has a SelectedItems property, but it is read only and you cannot bind to it.
<ListBox ItemsSource="{Binding Items}" SelectionMode="Extended">
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="ContextMenu">
<Setter.Value>
<ContextMenu>
<MenuItem Header="GOGO" />
</ContextMenu>
</Setter.Value>
</Setter>
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}" HorizontalAlignment="Center" VerticalAlignment="Center" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
This should work:
<ListBox ItemsSource="{Binding Items}" SelectionMode="Extended">
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="ContextMenu">
<Setter.Value>
<ContextMenu>
<MenuItem Header="GOGO" />
</ContextMenu>
</Setter.Value>
</Setter>
<Style.Triggers>
<DataTrigger Binding="{Binding SelectedItems.Count, RelativeSource={RelativeSource AncestorType={x:Type ListBox}}}" Value="0">
<Setter Property="ContextMenu" Value="{x:Null}" />
</DataTrigger>
<DataTrigger Binding="{Binding SelectedItems.Count, RelativeSource={RelativeSource AncestorType={x:Type ListBox}}}" Value="1">
<Setter Property="ContextMenu" Value="{x:Null}" />
</DataTrigger>
</Style.Triggers>
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}" HorizontalAlignment="Center" VerticalAlignment="Center" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Adding two DataTriggers checking whether the SelectedItems.Count is 0 or 1 in which case it sets the ContextMenu to {x:Null}.