I have a requirement that , I need to add nodes to a TreeView
dynamically and that nodes with CheckBox
es. If one CheckBox
is selected childs also selected.
And mainly I want to add data to TreeView
dynamically.
This is remarkably straightforward to do, once you know how.
Create a view model class (I've called it CheckableItem
here) for your tree view item data. It needs these three things:
Children
property of type ObservableCollection<CheckableItem>
.IsChecked
property of type Visibility
that, in its setter, raises PropertyChanged
and also iterates through the items in Children
and sets their IsChecked
property.Implement other properties in this class to expose the items' data to binding (my example just assumes something called Value
). Or you can just implement an Item
class of type object
and use a ContentPresenter
in the template, but I'll leave figuring that out to you.
Now create a HierarchicalDataTemplate
for your class that looks something like this:
<HierarchicalDataTemplate
DataType="{x:Type local:CheckableItem}"
ItemsSource="{Binding Children}">
<StackPanel Orientation="Horizontal">
<CheckBox IsChecked="{Binding IsChecked}"/>
<TextBlock Text="{Binding Value}"/>
</StackPanel>
</HierarchicalDataTemplate>
...and a TreeView
that uses it (I'm assuming you've populated a collection of these objects, of course):
<TreeView ItemsSource="{Binding MyCollectionOfCheckableItems}"/>
How it works: The TreeView
uses the HierarchicalDataTemplate
to render each item in its ItemsSource
. The HierarchicalDataTemplate
is a template that creates a HeaderedItemsControl
(in this case a TreeViewItem
), uses its template to render the header, and then uses its ItemsSource
as the source for the control's items - which, since they're all CheckableItem
s, are turned into TreeViewItem
s by the HierarchicalDataTemplate
. After that, it's turtles all the way down.
This is a pretty good overview of how TreeView
actually works in practice, though as with most examples I've found, it's got so many bells and whistles that it's sort of hard to see how simple the underlying principles are. If you understand MVVM, the previous paragraph is 90% of what you need to know.
Check this out:
DataModel.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
namespace WpfApplication102
{
public class Family : DependencyObject
{
public string Name { get; set; }
public List<Person> Members { get; set; }
}
public class Person : DependencyObject
{
public string Name { get; set; }
}
}
ItemHelper.cs
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
namespace WpfApplication102
{
public class ItemHelper : DependencyObject
{
public static readonly DependencyProperty IsCheckedProperty = DependencyProperty.RegisterAttached("IsChecked", typeof(bool?), typeof(ItemHelper), new PropertyMetadata(false, new PropertyChangedCallback(OnIsCheckedPropertyChanged)));
private static void OnIsCheckedPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is Family && ((bool?)e.NewValue).HasValue)
foreach (Person p in (d as Family).Members)
ItemHelper.SetIsChecked(p, (bool?)e.NewValue);
if (d is Person)
{
int checked = ((d as Person).GetValue(ItemHelper.ParentProperty) as Family).Members.Where(x => ItemHelper.GetIsChecked(x) == true).Count();
int unchecked = ((d as Person).GetValue(ItemHelper.ParentProperty) as Family).Members.Where(x => ItemHelper.GetIsChecked(x) == false).Count();
if (unchecked > 0 && checked > 0)
{
ItemHelper.SetIsChecked((d as Person).GetValue(ItemHelper.ParentProperty) as DependencyObject, null);
return;
}
if (checked > 0)
{
ItemHelper.SetIsChecked((d as Person).GetValue(ItemHelper.ParentProperty) as DependencyObject, true);
return;
}
ItemHelper.SetIsChecked((d as Person).GetValue(ItemHelper.ParentProperty) as DependencyObject, false);
}
}
public static void SetIsChecked(DependencyObject element, bool? IsChecked)
{
element.SetValue(ItemHelper.IsCheckedProperty, IsChecked);
}
public static bool? GetIsChecked(DependencyObject element)
{
return (bool?)element.GetValue(ItemHelper.IsCheckedProperty);
}
public static readonly DependencyProperty ParentProperty = DependencyProperty.RegisterAttached("Parent", typeof(object), typeof(ItemHelper));
public static void SetParent(DependencyObject element, object Parent)
{
element.SetValue(ItemHelper.ParentProperty, Parent);
}
public static object GetParent(DependencyObject element)
{
return (object)element.GetValue(ItemHelper.ParentProperty);
}
}
}
MainWindow.xaml
<Window x:Class="WpfApplication102.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication102"
Title="MainWindow" Height="220" Width="250">
<StackPanel>
<TreeView x:Name="treeView" ItemsSource="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=Families}">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type local:Family}" ItemsSource="{Binding Members}" >
<CheckBox Content="{Binding Name}" IsChecked="{Binding Path=(local:ItemHelper.IsChecked), Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" >
<CheckBox.Style>
<Style TargetType="{x:Type CheckBox}">
<Setter Property="Foreground" Value="Black"/>
<Setter Property="Visibility" Value="Visible"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=(local:ItemHelper.IsChecked)}" Value="False" >
<Setter Property="Foreground" Value="LightGray"/>
</DataTrigger>
</Style.Triggers>
</Style>
</CheckBox.Style>
</CheckBox>
</HierarchicalDataTemplate>
<DataTemplate DataType="{x:Type local:Person}" >
<CheckBox Content="{Binding Name}" IsChecked="{Binding Path=(local:ItemHelper.IsChecked), Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" >
<CheckBox.Style>
<Style TargetType="{x:Type CheckBox}">
<Setter Property="Foreground" Value="Black"/>
<Setter Property="Visibility" Value="Visible"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=(local:ItemHelper.IsChecked)}" Value="False" >
<Setter Property="Foreground" Value="LightGray"/>
</DataTrigger>
</Style.Triggers>
</Style>
</CheckBox.Style>
</CheckBox>
</DataTemplate>
</TreeView.Resources>
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsExpanded" Value="True"/>
</Style>
</TreeView.ItemContainerStyle>
</TreeView>
<Button Content="?" Click="Button_PrintCrew_Click" />
<TextBlock x:Name="textBoxCrew"/>
</StackPanel>
</Window>
MainWindow.xaml.cs
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace WpfApplication102
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public ObservableCollection<Family> Families { get; set; }
public MainWindow()
{
InitializeComponent();
this.Families = new ObservableCollection<Family>();
this.Families.Add(new Family() { Name = "Simpsons", Members = new List<Person>() { new Person() { Name = "Homer" }, new Person() { Name = "Bart" } } });
this.Families.Add(new Family() { Name = "Griffin", Members = new List<Person>() { new Person() { Name = "Peter" }, new Person() { Name = "Stewie" } } });
this.Families.Add(new Family() { Name = "Fry", Members = new List<Person>() { new Person() { Name = "Philip J." } } });
foreach (Family family in this.Families)
foreach (Person person in family.Members)
person.SetValue(ItemHelper.ParentProperty, family);
}
private void Button_PrintCrew_Click(object sender, RoutedEventArgs e)
{
string crew = "";
foreach (Family family in this.Families)
foreach (Person person in family.Members)
if (ItemHelper.GetIsChecked(person) == true)
crew += person.Name + ", ";
crew = crew.TrimEnd(new char[] { ',', ' ' });
this.textBoxCrew.Text = "Your crew: " + crew;
}
}
}
I added onto @pr0gg3r's answer to make it generic. I'm not sure if this is necessarily the best way, but it's a little more flexible.
MainWindow is the same, but the other classes differ slightly.
IParent.cs
interface IParent<T>
{
IEnumerable<T> GetChildren();
}
DataModel.cs
using System;
using System.Collections.Generic;
using System.Windows;
public class Family : DependencyObject, IParent<object>
{
public string Name { get; set; }
public List<Person> Members { get; set; }
IEnumerable<object> IParent<object>.GetChildren()
{
return Members;
}
}
public class Person : DependencyObject
{
public string Name { get; set; }
}
ItemHelper.cs
using System.Linq;
using System.Windows;
public class ItemHelper : DependencyObject
{
public static readonly DependencyProperty IsCheckedProperty =
DependencyProperty.RegisterAttached("IsChecked", typeof(bool?), typeof(ItemHelper),
new PropertyMetadata(false, new PropertyChangedCallback(OnIsCheckedPropertyChanged)));
private static void OnIsCheckedPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
IParent<object> sect = d as IParent<object>;
DependencyObject depObj = d as DependencyObject;
if (sect != null)
{
if (((bool?)e.NewValue).HasValue)
{
foreach (DependencyObject p in sect.GetChildren())
{
SetIsChecked(p, (bool?)e.NewValue);
}
}
}
if (depObj != null)
{
var parentObject = depObj.GetValue(ParentProperty) as IParent<object>;
var parentDO = depObj.GetValue(ParentProperty) as DependencyObject;
int ch = parentObject?.GetChildren()?.Where(
x => GetIsChecked(x as DependencyObject) == true).Count() ?? 0;
int un = parentObject?.GetChildren()?.Where(
x => GetIsChecked(x as DependencyObject) == false).Count() ?? 0;
if (un > 0 && ch > 0)
{
SetIsChecked(parentDO, null);
return;
}
if (ch > 0)
{
SetIsChecked(parentDO, true);
return;
}
SetIsChecked(parentDO, false);
}
}
public static void SetIsChecked(DependencyObject element, bool? IsChecked)
{
element?.SetValue(IsCheckedProperty, IsChecked);
}
public static bool? GetIsChecked(DependencyObject element)
{
return (bool?)element?.GetValue(IsCheckedProperty);
}
public static readonly DependencyProperty ParentProperty =
DependencyProperty.RegisterAttached("Parent", typeof(object), typeof(ItemHelper));
public static void SetParent(DependencyObject element, object Parent)
{
element?.SetValue(ParentProperty, Parent);
}
public static object GetParent(DependencyObject element)
{
return element?.GetValue(ParentProperty);
}
}
I've found success following this guide.
Below is the complete source code from that guide.
MainWindow.xaml:
<Window x:Class="TreeView.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525" Loaded="Window_Loaded">
<Window.Resources>
<ResourceDictionary>
<Style x:Key="TreeViewItemStyle" TargetType="TreeViewItem">
<Setter Property="IsExpanded" Value="True" />
<Setter Property="IsSelected" Value="{Binding IsInitiallySelected, Mode=OneTime}" />
<Setter Property="KeyboardNavigation.AcceptsReturn" Value="True" />
</Style>
<HierarchicalDataTemplate x:Key="CheckBoxItemTemplate" ItemsSource="{Binding Children, Mode=OneTime}">
<StackPanel Orientation="Horizontal">
<CheckBox Focusable="False" IsChecked="{Binding IsChecked}" VerticalAlignment="Center" />
<ContentPresenter Content="{Binding Name, Mode=OneTime}" Margin="2,0" />
</StackPanel>
</HierarchicalDataTemplate>
</ResourceDictionary>
</Window.Resources>
<Grid>
<TreeView Height="287" HorizontalAlignment="Left" Margin="12,12,0,0" x:Name="treeView1" VerticalAlignment="Top" Width="229"
ItemContainerStyle="{StaticResource TreeViewItemStyle}"
ItemTemplate="{StaticResource CheckBoxItemTemplate}" />
</Grid>
</Window>
MainWindow.xaml.cs:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace TreeView
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
treeView1.ItemsSource = TreeViewModel.SetTree("Top Level");
}
}
}
TreeViewModel.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
namespace TreeView
{
public class TreeViewModel : INotifyPropertyChanged
{
TreeViewModel(string name)
{
Name = name;
Children = new List<TreeViewModel>();
}
#region Properties
public string Name { get; private set; }
public List<TreeViewModel> Children { get; private set; }
public bool IsInitiallySelected { get; private set; }
bool? _isChecked = false;
TreeViewModel _parent;
#region IsChecked
public bool? IsChecked
{
get { return _isChecked; }
set { SetIsChecked(value, true, true); }
}
void SetIsChecked(bool? value, bool updateChildren, bool updateParent)
{
if (value == _isChecked) return;
_isChecked = value;
if (updateChildren && _isChecked.HasValue) Children.ForEach(c => c.SetIsChecked(_isChecked, true, false));
if (updateParent && _parent != null) _parent.VerifyCheckedState();
NotifyPropertyChanged("IsChecked");
}
void VerifyCheckedState()
{
bool? state = null;
for (int i = 0; i < Children.Count; ++i)
{
bool? current = Children[i].IsChecked;
if (i == 0)
{
state = current;
}
else if (state != current)
{
state = null;
break;
}
}
SetIsChecked(state, false, true);
}
#endregion
#endregion
void Initialize()
{
foreach (TreeViewModel child in Children)
{
child._parent = this;
child.Initialize();
}
}
public static List<TreeViewModel> SetTree(string topLevelName)
{
List<TreeViewModel> treeView = new List<TreeViewModel>();
TreeViewModel tv = new TreeViewModel(topLevelName);
treeView.Add(tv);
//Perform recursive method to build treeview
#region Test Data
//Doing this below for this example, you should do it dynamically
//***************************************************
TreeViewModel tvChild4 = new TreeViewModel("Child4");
tv.Children.Add(new TreeViewModel("Child1"));
tv.Children.Add(new TreeViewModel("Child2"));
tv.Children.Add(new TreeViewModel("Child3"));
tv.Children.Add(tvChild4);
tv.Children.Add(new TreeViewModel("Child5"));
TreeViewModel grtGrdChild2 = (new TreeViewModel("GrandChild4-2"));
tvChild4.Children.Add(new TreeViewModel("GrandChild4-1"));
tvChild4.Children.Add(grtGrdChild2);
tvChild4.Children.Add(new TreeViewModel("GrandChild4-3"));
grtGrdChild2.Children.Add(new TreeViewModel("GreatGrandChild4-2-1"));
//***************************************************
#endregion
tv.Initialize();
return treeView;
}
public static List<string> GetTree()
{
List<string> selected = new List<string>();
//select = recursive method to check each tree view item for selection (if required)
return selected;
//***********************************************************
//From your window capture selected your treeview control like: TreeViewModel root = (TreeViewModel)TreeViewControl.Items[0];
// List<string> selected = new List<string>(TreeViewModel.GetTree());
//***********************************************************
}
#region INotifyPropertyChanged Members
void NotifyPropertyChanged(string info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
public event PropertyChangedEventHandler PropertyChanged;
#endregion
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With