Hi Guys
I need a tree view with tri state checkboxes to behave recursively against a hierarchical model. I've set up the following xaml:-
<Window x:Class="TestTreeView.MainWindow"
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:sf="http://schemas.syncfusion.com/wpf"
xmlns:local="clr-namespace:TestTreeView"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<sf:SfTreeView Name="MyTreeView"
ItemsSource="{Binding AllPossibleMembers}" ChildPropertyName="Members"
CheckBoxMode="Recursive"
BorderThickness="1" BorderBrush="DarkGray" Grid.ColumnSpan="2" Margin="4" Grid.Row="0"
ItemTemplateDataContextType="Node">
<sf:SfTreeView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<CheckBox x:Name="CheckBox" FocusVisualStyle="{x:Null}" IsChecked="{Binding IsChecked, Mode=TwoWay}" VerticalAlignment="Center"/>
<TextBlock Text="{Binding Content.DisplayName}" Foreground="Black" FontSize="14" VerticalAlignment="Center"/>
</StackPanel>
</DataTemplate>
</sf:SfTreeView.ItemTemplate>
</sf:SfTreeView>
<Button Grid.Row="1" Click="Button_Click">Inspect</Button>
</Grid>
</Window>
.. and code behind...
namespace TestTreeView
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private class ModelObjectView : INotifyPropertyChanged
{
public string DisplayName { get; set; }
private bool mIsChecked;
public bool IsChecked { get { return mIsChecked; } set { mIsChecked = value; NotifyPropertyChanged(nameof(IsChecked)); } }
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
private class MemberType : INotifyPropertyChanged
{
public MemberType() { Members = new List<ModelObjectView>(); }
public string DisplayName { get; set; }
private bool mIsChecked;
public bool IsChecked { get { return mIsChecked; } set { mIsChecked = value; NotifyPropertyChanged(nameof(IsChecked)); } }
public List<ModelObjectView> Members { get; private set; }
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
private ObservableCollection<MemberType> AllPossibleMembers;
public MainWindow()
{
InitializeComponent();
AllPossibleMembers = new ObservableCollection<MemberType>();
MemberType abstractions = new MemberType { DisplayName = "Abstractions", IsChecked = false };
abstractions.Members.Add(new ModelObjectView { DisplayName = "AB1", IsChecked = false });
abstractions.Members.Add(new ModelObjectView { DisplayName = "AB2", IsChecked = false });
abstractions.Members.Add(new ModelObjectView { DisplayName = "AB3", IsChecked = false });
AllPossibleMembers.Add(abstractions);
MemberType reservoirs = new MemberType { DisplayName="Reservoirs", IsChecked = false };
reservoirs.Members.Add(new ModelObjectView { DisplayName = "RV1", IsChecked = false });
reservoirs.Members.Add(new ModelObjectView { DisplayName = "RV2", IsChecked = false });
reservoirs.Members.Add(new ModelObjectView { DisplayName = "RV3", IsChecked = false });
AllPossibleMembers.Add(reservoirs);
MyTreeView.ItemsSource = AllPossibleMembers;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
}
}
}
This successfully populates the tree and the checkboxes behave recursively. However, when I click Inspect and look at the model, none of the IsChecked properties on the underlying model objects are being set. It looks like the binding isn't working.
I tried changing the CheckBox binding to Content.IsChecked and this seems to fix the binding problem. I now see the IsChecked property on the underlying model objects getting updated. However, I now lose the recursive behaviour of the User Interaction.
Any idea what I'm missing?
Hi Declan Hillier,
We have examined the reported scenario. When utilizing a CheckBox in SfTreeView, it is essential to bind the IsChecked property of the TreeViewNode class to the CheckBox.IsChecked property. Since CheckBox is an individual control, binding the IsChecked property of the MemberType class to the CheckBox.IsChecked property will result in independent updates for each checkbox.
To establish a connection between parent and child checkboxes, it is crucial to bind the IsChecked property of the TreeViewNode class.
For further clarification, please refer to the provided User Guide documentation.
UG Link - CheckBox in SfTreeView
We hope you understand the process. If you need any assistance, feel free to reach us out. We are happy to assist you.
Regards,
Chidanand M.
Sorry but I don't understand what you mean. How do I bind the tree view node to the checkbox?
NB. The ItemTemplateDataContextType is already set to Node if that's what you're referring to.
Think I've worked it out. The IsCheckedProperty on the model is completely redundant and has effectively been orphaned by setting the ItemTemplateDataContextType to node. It's all handled through the CheckedItems property of the TreeView. Is that correct?
I had tried that but previously but it didn't work and I realise now that was because I didn't set the CheckedItems in the code behind. instead I relied on setting the CheckedItems attribute in the xaml. I guess this is something to do with the order the bindings are resolved in.
If I've understood that correctly I would say that's a bit counter intuitive in a data bound setup. I wouldn't expect to have to set the CheckedItems property from code behind The same goes for the ItemsSource. I tried implementing INotifyPropertyChanged on both the AllPossibleMembers and CheckItems collections but that didn't fix it, Indeed, I checked the state of MyTreeView.ItemsSource and MyTreeView.CheckedItems in the forms constructor and both are null, so it appears as if these xaml attributes are simply ignored.
I may be misunderstanding but I think this breaks standard WPF bbehaviour. Changes to the model should automatically be reflected into the UI where an appropriate Notify mechanism is present.
Here's some xaml and code behind that works but demonstrates what I mean. I wouldn't expect to have to set the ItemsSource and CheckedItems properties of the TreeView at the end of the constructor when they're present as attributes in the xaml:-
Xaml:-
<Window x:Class="TestTreeView.MainWindow"
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:sf="http://schemas.syncfusion.com/wpf"
xmlns:local="clr-namespace:TestTreeView"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<sf:SfTreeView Name="MyTreeView"
ItemsSource="{Binding AllPossibleMembers}" ChildPropertyName="Members" CheckedItems="{Binding CheckedMembers}"
CheckBoxMode="Recursive"
NodePopulationMode="Instant"
BorderThickness="1" BorderBrush="DarkGray" Grid.ColumnSpan="2" Margin="4" Grid.Row="0"
ItemTemplateDataContextType="Node">
<sf:SfTreeView.ItemTemplate>
<DataTemplate>
<Grid >
<CheckBox x:Name="CheckBox" FocusVisualStyle="{x:Null}" IsChecked="{Binding IsChecked, Mode=TwoWay}" VerticalAlignment="Center"/>
<TextBlock Text="{Binding Content.DisplayName}" Foreground="Black" FontSize="14" VerticalAlignment="Center" Margin="25, 0, 0, 0"/>
</Grid>
</DataTemplate>
</sf:SfTreeView.ItemTemplate>
</sf:SfTreeView>
<Button Grid.Row="1" Click="Button_Click">Inspect</Button>
</Grid>
</Window>
Code Behind:-
namespace TestTreeView
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window, INotifyPropertyChanged
{
private class ModelObjectView
{
public string DisplayName { get; set; }
}
private class MemberType
{
public MemberType() { Members = new ObservableCollection<ModelObjectView>(); }
public string DisplayName { get; set; }
public ObservableCollection<ModelObjectView> Members { get; private set; }
}
private ObservableCollection<MemberType> mAllPossibleMembers;
private ObservableCollection<MemberType> AllPossibleMembers { get { return mAllPossibleMembers; } set { mAllPossibleMembers = value; NotifyPropertyChanged(nameof(AllPossibleMembers)); } }
private ObservableCollection<object> mCheckedMembers;
private ObservableCollection<object> CheckedMembers { get { return mCheckedMembers; } set { mCheckedMembers = value; NotifyPropertyChanged(nameof(CheckedMembers)); } }
public MainWindow()
{
InitializeComponent();
DataContext = this;
AllPossibleMembers = new ObservableCollection<MemberType>();
CheckedMembers = new ObservableCollection<object>();
MemberType abstractions = new MemberType { DisplayName = "Abstractions" };//, IsChecked = false };
ModelObjectView m = new ModelObjectView { DisplayName = "Should be checked" };
CheckedMembers.Add(m);
abstractions.Members.Add(m);
abstractions.Members.Add(new ModelObjectView { DisplayName = "AB1" });//, IsChecked = false });
abstractions.Members.Add(new ModelObjectView { DisplayName = "AB2" });//, IsChecked = false });
abstractions.Members.Add(new ModelObjectView { DisplayName = "AB3" });//, IsChecked = false });
AllPossibleMembers.Add(abstractions);
MemberType reservoirs = new MemberType { DisplayName= "Reservoirs" };//, IsChecked = false };
reservoirs.Members.Add(new ModelObjectView { DisplayName = "RV1" });//, IsChecked = false });
reservoirs.Members.Add(new ModelObjectView { DisplayName = "RV2" });//, IsChecked = false });
reservoirs.Members.Add(new ModelObjectView { DisplayName = "RV3" });//, IsChecked = false });
AllPossibleMembers.Add(reservoirs);
MyTreeView.ItemsSource = AllPossibleMembers;
MyTreeView.CheckedItems = CheckedMembers;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
}
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
}
I tried the various settings for the NotificationSupriptionMode property but none seemed to make a difference.
Not a huge deal as it's workable as is but might be a possible improvement to help other developers to avoid confusion in the future.
Hi Declan Hillier,
We are currently analyzing the reported scenario, we need some time to validate and will provide an update on or before March 05, 2024.
Regards,
Chidanand M.
Hi Declan Hillier,
We have checked your reported scenario "CheckedItems binding not working when used in XAML". After analyzing our source code, we found that the CheckedItems binding needs to be set before the ItemsSource binding. If the ItemsSource is set before the CheckedItems binding, it will break the CheckedItems binding and create a new CheckedItems collection, since CheckedItems are updated based on the ItemsSource.
Kindly refer the below code snippet.
|
<sf:SfTreeView Name="MyTreeView" Grid.ColumnSpan="2" Margin="4" Grid.Row="0" CheckedItems="{Binding CheckedMembers}" ChildPropertyName="Members" NodePopulationMode="Instant" CheckBoxMode="Recursive" BorderThickness="1" BorderBrush="DarkGray" ItemTemplateDataContextType="Node" ItemsSource="{Binding AllPossibleMembers}"> <sf:SfTreeView.ItemTemplate> <DataTemplate> <Grid > <<<Template>>>> </Grid> </DataTemplate> </sf:SfTreeView.ItemTemplate> </sf:SfTreeView> |
Kindly refer the below mentioned UG
Documentation.
UG document: Checkbox in WPF TreeView control | Syncfusion