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.
Related
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>
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>
I Have a tree view set up like so:
<TreeView ItemsSource="{Binding TreeRoot}" x:Name="HierarchyTreeView">
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}>
<Setter Property="Visibility" Value="{Binding IsVisible, Converter={...}}" />
...
</Style>
</TreeView.ItemContainerStyle>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}" ... />
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
Users have the ability to hide items in the treeview. However, when all items' visibility under a node has been set to Collapsed or Hidden, the expander remains.
Is there any way to hide the expander when every child's visibility under an item is set to hidden or collapsed?
I was facing this issue and came across this question. I saw there were some answers where you needed to make this change in the treeview item style which required you to edit the default style.
However, like the case above I too just needed the expander to be not visible when the node items are hidden.
A simple way to do it is to expand all your nodes by default and use a property in the datatrigger for the treeviewItem style to set the treeview item visibility.
This is how I was able to achieve it.
<Style x:Key="myTreeItemtyle" TargetType="{x:Type TreeViewItem}">
<Setter Property="IsExpanded" Value="True"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsVisible}" Value="True">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
<DataTrigger Binding="{Binding IsVisible}" Value="False">
<Setter Property="Visibility" Value="Collapsed"/>
</DataTrigger>
</Style.Triggers>
</Style>
Now, use this style as the treeview item container style:
<TreeView Name="treeView" ItemsSource="{Binding TreeItems}" ItemContainerStyle="{StaticResource myTreeItemtyle}" BorderThickness="0"/>
I have the following question : what do I need to add in the code below to tell my RadioButton to bind on "IsFacturation" boolean that is attached to datagrid item ? I use a DataTrigger which defines its own binding onto datagrid readonly state, so I need to "get back" in binding definition, probably by looking at appropriate parent. I think I have to play with RelativeSource...
I observe that when a datagrid item has IsFacturation boolean set to true, the radio button isn't checked as it should be.
DataGrid items are an observable collection of "Adresse" objects, which define an "IsFacturation" property.
<DataGrid x:Name="AddressGrid" SelectionUnit="Cell" ItemsSource="{Binding Path=Adresses}" SelectionMode="Single">
<DataGrid.Columns>
<!-- Region Facturation -->
<DataGridTemplateColumn Header="Facturation" SortMemberPath="IsFacturation" HeaderStyle="{StaticResource CenterAlignmentColumnHeaderStyle}" >
<DataGridTemplateColumn.CellTemplate >
<DataTemplate>
<ContentControl>
<ContentControl.Style>
<Style TargetType="{x:Type ContentControl}">
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=AddressGrid,Path=IsReadOnly}" Value="True">
<Setter Property="ContentTemplate">
<Setter.Value>
<!-- Possibly create another contentcontrol which differentiates between errors -->
<DataTemplate>
<Image Source="Resources/Images/Check-icon.png" Visibility="Visible"/>
</DataTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
<DataTrigger Binding="{Binding ElementName=AddressGrid,Path=IsReadOnly}" Value="False">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<RadioButton GroupName="grpRadioButtonFacturationAddresses"
IsChecked="{Binding Path=IsFacturation, UpdateSourceTrigger=LostFocus, Mode=TwoWay}" VerticalAlignment="Center" HorizontalAlignment="Center" Visibility="Visible"/>
</DataTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
The goal of such code is to display an image when datagrid is readonly, and a radio button when it's not. I still have to work on image visibility (easy), but radio button state is directly linked to datagrid item property of my choice.
Thanks a lot
This is just answering your comment, not your question. One way that you could use DataTriggers without a ContentControl is to move them to the actual controls:
<DataTemplate>
<Grid>
<Image Source="Resources/Images/Check-icon.png" Visibility="Visible">
<Image.Style>
<Style TargetType="{x:Type Image}">
<Setter Visibility="Collapsed" />
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=AddressGrid,Path=IsReadOnly}" Value="True">
<Setter Property="Visibility" Value="Visible">
</DataTrigger>
</Style.Triggers>
</Style>
</Image.Style>
</Image>
<RadioButton GroupName="grpRadioButtonFacturationAddresses" IsChecked="{Binding Path=IsFacturation, UpdateSourceTrigger=LostFocus, Mode=TwoWay}" VerticalAlignment="Center" HorizontalAlignment="Center">
<RadioButton.Style>
<Style TargetType="{x:Type RadioButton}">
<Setter Visibility="Visible" />
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=AddressGrid,Path=IsReadOnly}" Value="True">
<Setter Property="Visibility" Value="Collapsed">
</DataTrigger>
</Style.Triggers>
</Style>
</RadioButton.Style>
</RadioButton>
</Grid>
</DataTemplate>
I think I found the cause of our problem.
I suppress the ContentControl and the binding of my radiobutton working now.
Edit: Oh, I had not seen your response Sheridan :)