HasItems Property in TreeViewItem true, even if all children are Visibility = Collapsed - c#

Can you please give me a hint, to make HasItems Property better.
I have a TreeView like this:
<TreeView ItemsSource="{Binding Customers}">
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Style.Triggers>
<DataTrigger Binding="{Binding HasItems, RelativeSource={RelativeSource Self}}" Value="True">
<Setter Property="Foreground" Value="Red" />
</DataTrigger>
<DataTrigger Binding="{Binding HasItems, RelativeSource={RelativeSource Self}}" Value="False">
<Setter Property="Foreground" Value="Blue" />
</DataTrigger>
</Style.Triggers>
<Setter Property="AutomationProperties.AutomationId" Value="{Binding AutomationId}" />
<Setter Property="IsExpanded" Value="True" />
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
</Style>
</TreeView.ItemContainerStyle>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Customers}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}" />
<TextBlock Text="{Binding FamilyName}" Margin="5,0,0,0" />
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
Code behind:
public ObservableCollection<Customer> Customers { get; set; }
public MainWindow()
{
InitializeComponent();
DataContext = this;
Customers = new ObservableCollection<Customer>();
var homer = new Customer("Homer", "Simpson");
homer.Customers.Add(new Customer("Bart", "Simpson"));
homer.Customers.Add(new Customer("Lisa", "Simpson"));
homer.Customers.Add(new Customer("Maggie", "Simpson"));
var chief = new Customer("Chief", "Wiggum");
chief.Customers.Add(new Customer("Ralf", "Wiggum"));
Customers.Add(homer);
Customers.Add(chief);
}
The Class Customer implements INotifyPropertyChanged and everything is fine.
As you see here, I have a DataTrigger to change color depending on "HasItems" Property of the TreeViewItem.
The problem is: HasItems is true, even if all children are Hidden or Collapsed.
See here: I made the VISIBILITY of son of "Chief Wiggum" Collapsed. And "Chief Wiggum"-TreeViewItem is still red.

Well, as you could figure out, the fact the items are hidden doesn't mean the tree view has no items.
One possible approach is changing your DataTrigger in the following way:
<DataTrigger Binding="{Binding Items, Converter={StaticResource HasVisibleItemsConverter}, RelativeSource={RelativeSource Self}}" Value="True">
Create a HasVisibleItemsConverter converter class that implements IValueConverter, there you should check if there are any items that are visible - I'll leave that for your own exercise.
Then you create an instance of HasVisibleItemsConverter in the Resources area (either Window.Resources or UserControl.Resources):
<Window.Resources>
<conv:HasVisibleItemsConverter x:Key="HasVisibleItemsConverter" />
</Window.Resources>
And don't forget to add conv="..." in the namespace definition for your Window/UserControl pointing to the assembly and namespace where your converter is.

Related

Command binding to top-level MenuItem of Menu doesn't work

I'm learning WPF and developing a dynamic Menu which is driven by data binding of it's ItemsSource to an ObservableCollection. To do this I have a simple MenuItemViewModel and a HierarchicalDataTemplate for automatic binding of MenuItems to it.
The problem I have is that Command property doesn't work for top level menu items. Despite it is set, a MenuItem doesn't react on mouse click and doesn't get disabled if Command cannot be executed. Simply it's like it's not getting bound.
For lower level menu items though, it works as intended. I think this should be a problem of my HierarchicalDataTemplate but I can't find it, because as I see there's no code in template which might affect command binding of only top-level MenuItems.
MenuItemViewModel implements INotifyPropertyChanged and contains following public properties:
string Text
Uri ImageSource
ICommand Command
ObservableCollection<MenuItemViewModel> Children
HierarchicalDataTemplate for MenuItem in my Window.Resources is as follows:
<HierarchicalDataTemplate DataType="{x:Type common:MenuItemViewModel}"
ItemsSource="{Binding Path=Children}">
<HierarchicalDataTemplate.ItemContainerStyle>
<Style TargetType="MenuItem">
<Setter Property="Command"
Value="{Binding Command}" />
</Style>
</HierarchicalDataTemplate.ItemContainerStyle>
<StackPanel Orientation="Horizontal">
<Image Source="{Binding ImageSource}" />
<TextBlock Text="{Binding Text}" VerticalAlignment="Center"/>
</StackPanel>
</HierarchicalDataTemplate>
Can you please point me on my mistake?
EDIT: Top-level MenuItem doesn't contain any children (i.e. Children collection of associated ViewModel is empty).
Thanks to #sTrenat comment I've come to solution below.
<Menu.Resources>
<!-- cancel sharing of image so Icon will work properly -->
<Image x:Key="MenuIcon" Source="{Binding ImageSource}" x:Shared="False"/>
</Menu.Resources>
<Menu.ItemContainerStyle>
<Style TargetType="{x:Type MenuItem}"
BasedOn="{StaticResource {x:Type MenuItem}}">
<Setter Property="Header" Value="{Binding Text}" />
<Setter Property="Icon" Value="{StaticResource MenuIcon}"/>
<Setter Property="Command" Value="{Binding Command}"/>
<Setter Property="ItemsSource" Value="{Binding Children}" />
<!-- centering MenuItem's Header -->
<Setter Property="HeaderTemplate">
<Setter.Value>
<DataTemplate>
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" Content="{Binding}" />
</DataTemplate>
</Setter.Value>
</Setter>
<!-- setting Icon to null when ImageSource isn't specified -->
<Style.Triggers>
<DataTrigger Binding="{Binding ImageSource}"
Value="{x:Null}">
<Setter Property="Icon" Value="{x:Null}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Menu.ItemContainerStyle>

Set the ItemTemplateSelector of a treeview inside a user control

I have a problem with the user control I created. This control consists of a search textbox and a treeview. The treeview shows different data templates for different node types. So I created the usercontrol with a dependency property of type datatemplate which can be bound when using my control. Inside the control, the treeview binds to the dependency property. But sadly the treeviewtemplate selector doesn't get called.
<UserControl x:Class="yyy.yyy.yyy.UI.UserControls.SearchableTreeView.SearchableTreeView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:behaviours="clr-namespace:yyy.yyy.yyy.UI.Behaviours;assembly=yyy"
mc:Ignorable="d"
x:Name="parent"
d:DesignHeight="300" d:DesignWidth="300">
<DockPanel DataContext="{Binding ElementName=parent}">
<TextBox DockPanel.Dock="Top" Margin="5" Text="{Binding SearchText, UpdateSourceTrigger=PropertyChanged}"/>
<TreeView DockPanel.Dock="Top" Margin="5" ItemsSource="{Binding TreeViewItems}" ItemTemplateSelector="{Binding TreeViewTemplateSelector}">
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="HorizontalContentAlignment" Value="Left" />
<Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="behaviours:TreeViewItemBehaviour.IsBroughtIntoViewWhenSelected" Value="true"/>
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}" />
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
<Setter Property="FontWeight" Value="Normal" />
<Setter Property="Visibility" Value="Visible"/>
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="FontWeight" Value="Bold" />
</Trigger>
<DataTrigger Binding="{Binding Path=IsVisible}" Value="false">
<Setter Property="Visibility" Value="Collapsed"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TreeView.ItemContainerStyle>
</TreeView>
</DockPanel>
The code behind with the dependency property looks like that:
public partial class SearchableTreeView : UserControl
{
public SearchableTreeView()
{
InitializeComponent();
}
public static readonly DependencyProperty TreeViewTemplateSelectorProperty = DependencyProperty.Register(
"TreeViewTemplateSelector", typeof (DataTemplateSelector), typeof (SearchableTreeView), new PropertyMetadata(default(DataTemplateSelector)));
public DataTemplateSelector TreeViewTemplateSelector
{
get { return (DataTemplateSelector) GetValue(TreeViewTemplateSelectorProperty); }
set { SetValue(TreeViewTemplateSelectorProperty, value); }
}
}
And the usercontrol is used in a xaml like that:
<searchableTreeView:SearchableTreeView TreeViewTemplateSelector="{StaticResource TreeViewFieldTemplateSelector}"/>
Where the TreeViewFieldTemplateSelector is a class of type datatemplateselector, which allready worked before i startet to create a usercontrol out of the searchable treeview.
Does anybody know what I'm doing wrong? Or is it not possible to bind a datatemplateselector directly to a treeview?
Thanks
Manuel
You are complicating your system by using a DataTemplateSelector. While it is true that these objects were created for this purpose, there is a much easier way to achieve your requirements. Basically, if you declare a DataTemplate for each data type without specifying the x:Key values, then they will be applied implicitly to all objects of the correct type:
<DataTemplate DataType="{x:Type YourPrefix:YourDataType">
...
</DataTemplate>
<DataTemplate DataType="{x:Type YourPrefix:YourOtherDataType">
...
</DataTemplate>
<DataTemplate DataType="{x:Type YourPrefix:SomeOtherDataType">
...
</DataTemplate>
Now, if you put items of these data types into a collection and data bind that to a collection control, then you'll see your various items rendered as expected, but without the complications of the DataTemplateSelector.
UPDATE >>>
Ok, then try this instead... first remove the DataContext="{Binding ElementName=parent}" setting and then add a RelativeSource Binding for the TreeViewTemplateSelector property:
<DockPanel>
<TextBox DockPanel.Dock="Top" Margin="5" Text="{Binding SearchText,
UpdateSourceTrigger=PropertyChanged}"/>
<TreeView DockPanel.Dock="Top" Margin="5" ItemsSource="{Binding TreeViewItems}"
ItemTemplateSelector="{Binding TreeViewTemplateSelector, RelativeSource={
RelativeSource AncestorType={x:Type YourPrefix:SearchableTreeView}}}">
<TreeView.ItemContainerStyle>
...
</TreeView.ItemContainerStyle>
</TreeView>
</DockPanel>

ContextMenu bound to ObservableCollection<string> - show item if count == 0

I've got an observable collection of strings to thats data bound to my XAML contextmenu:
The ViewModel-Property:
public ObservableCollection<string> Indexes
{
get { return _Indexes; }
private set
{
if (value != _Indexes)
{
_Indexes = value;
OnPropertyChanged("Indexes");
}
}
}
The XAML code:
<viewmodel:IndexViewModel x:Key="IndexViewModel" />
<ContextMenu x:Key="ContextMenu_Index" Placement="Mouse" IsOpen="False">
<ContextMenu.ItemsSource>
<CompositeCollection>
<MenuItem Header="No items!" IsEnabled="False" Visibility="Collapsed">
<MenuItem.Style>
<Style TargetType="{x:Type MenuItem}">
<Style.Triggers>
<DataTrigger Binding="{Binding Source={StaticResource IndexViewModel}, Path=Indexes.Count}" Value="0">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
</Style>
</MenuItem.Style>
</MenuItem>
<CollectionContainer Collection="{Binding Path=Indexes, Source={StaticResource IndexViewModel}}" />
</CompositeCollection>
</ContextMenu.ItemsSource>
<ContextMenu.Style>
<Style TargetType="ContextMenu"></Style>
</ContextMenu.Style>
<ContextMenu.ItemTemplate>
<DataTemplate DataType="string">
<TextBlock Text="{Binding}" MouseDown="TextBlock_Index_MouseDown"></TextBlock>
</DataTemplate>
</ContextMenu.ItemTemplate>
</ContextMenu>
Now I want to show the "No items" menu item if the count of Indexes is 0. But unfortunately it doesn't work this way, the "No items!" menu item is not shown. Do you have some hints?
There is a Dependency Property Setting Precedence List and because of that when you manually set Visibility it has priority over style trigger. Bring default value as setter into your Style instead of setting it against MenuItem and then Style.Trigger will be able to change that value:
<MenuItem Header="No items!" IsEnabled="False">
<MenuItem.Style>
<Style TargetType="{x:Type MenuItem}">
<Setter Property="Visibility" Value="Collapsed" />
<Style.Triggers>
<DataTrigger Binding="{Binding Source={StaticResource IndexViewModel}, Path=Indexes.Count}" Value="0">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
</Style>
</MenuItem.Style>
</MenuItem>
In my opinion, displaying a MenuItem to say 'No items!' is unhelpful and incorrect... surely your users can tell when there are no MenuItems without being told that. Even if you feel that you absolutely do have to do that, then why don't you simply add an actual item into your data bound collection?:
Indexes.Add("No items!");
In your AddItem method, you'd just need to check for the existence of this item before adding a new item:
if (Indexes.Contains("No items!")) Indexes.Remove("No items!");
Indexes.Add(newItem);
In your comment, you said that you couldn't Style this item differently... I don't know why you'd want to do that anyway, but you could just use the DataTemplateSelector Class to do that for you. It would easier for you to implement your requirements this way.

Setting ItemTemplate based on CheckBox value

I have a DataTemplate which contains a CheckBox and ListBox. When the CheckBox is checked, I want to change the ItemTemplate property on the ListBox to change the appearance of each item.
Right now, it looks like this:
<DataTemplate DataType={x:Type MyViewModel}>
<DockPanel>
<CheckBox DockPanel.Dock="Bottom"
Content="Show Details"
HorizontalAlignment="Right"
IsChecked="{Binding ShowDetails}"
Margin="0 5 10 5" />
<ListBox ItemsSource="{Binding Items}"
ItemTemplate="{StaticResource SimpleItemTemplate}"
Margin="10 0 10 5">
<ListBox.Triggers>
<DataTrigger Binding="{Binding ShowDetails}" Value="True">
<Setter Property="ItemTemplate"
Value="{StaticResource DetailedItemTemplate}" />
</DataTrigger>
</ListBox.Triggers>
</ListBox>
</DockPanel>
</DataTemplate>
However, when I try to compile, I get the following error messages:
Value 'ItemTemplate' cannot be assigned to property 'Property'. Invalid PropertyDescriptor value.
and
Cannot find the static member 'ItemTemplateProperty' on the type 'ContentPresenter'.
I'm still fairly new to WPF, so perhaps there is something I'm not quite understanding?
You need to do this through the ListBox Style rather than directly through its Triggers collection. A FrameworkElement's Triggers collection can only contain EventTriggers (so I'm surprised your sample got as far as complaining about the properties!). Here's what you need to do:
<ListBox ItemsSource="{Binding Items}">
<ListBox.Style>
<Style TargetType="ListBox">
<Setter Property="ItemTemplate" Value="{StaticResource SimpleItemTemplate}" />
<Style.Triggers>
<DataTrigger Binding="{Binding ShowDetails}" Value="True">
<Setter Property="ItemTemplate"
Value="{StaticResource DetailedItemTemplate}" />
</DataTrigger>
</Style.Triggers>
</Style>
</ListBox.Style>
</ListBox>

Relative binding in style of control inside ListBoxItem template

My problem is with the following code, with binding the IsAvailable property of the MyListBoxItem class. My current solution:
<ListBox ItemTemplate="{StaticResource myTemplate}">
<ListBox.Resources>
<DataTemplate x:Key="myTemplate" DataType="{x:Type local:MyListBoxItem}">
<Label Foreground="Green" Content="{Binding Title}" Tag="{Binding IsAvailable}">
<Label.Style>
<Style TargetType="{x:Type Label}">
<Style.Triggers>
<DataTrigger Binding="{Binding Tag, RelativeSource={RelativeSource Self}}" Value="True">
<Setter Property="FontWeight" Value="Bold"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Label.Style>
</Label>
</DataTemplate>
... (more datatemplates)
</ListBox.Resources>
</ListBox>
My question: In my solution the value of IsAvailable "goes through" two bindings. The first one binds the value to the Tag property of the Label and then in the style triggers, a trigger checks its value and sets a property of the Label. When I used Binding="{Binding IsAvailable, RelativeSource={RelativeSource AncestorType={x:Type local:MyListBoxItem}}}" it didn't work, because the Style can't see any ancestor of the Label (or something similar reason), it resulted binding errors (with code 4 or 40 maybe), for each item added to the ListBox.
So finally: can I make the solution more simple, or there is no another (better) one?
An important thing I've forgot to mention, sorry: I put the DataTemplate in the ListBox's resources because I have more templates (they are basically differ, so I can't style them with triggers), which I have to switch between sometimes...
The ItemTemplate will take the type that the ItemsSource is bound to. Therefore you should be able to simply bind to IsAvailable, as the ListBox's item type is MyListBoxItem. Try this:
<ListBox ItemsSource="...">
<ListBox.ItemTemplate>
<DataTemplate>
<Label Foreground="Green" Content="{Binding Title}" Tag="{Binding IsAvailable}">
<Label.Style>
<Style TargetType="{x:Type Label}">
<Style.Triggers>
<DataTrigger Binding="{Binding Tag, RelativeSource={RelativeSource Self}}" Value="True">
<Setter Property="FontWeight" Value="Bold"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Label.Style>
</Label>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
You'll need to set your ItemsSource property to a Binding to the MyListBoxItem collection.

Categories

Resources