kirankumarG
kirankumarG

Reputation: 117

How to develop treeview with checkboxes in wpf?

I have a requirement that , I need to add nodes to a TreeView dynamically and that nodes with CheckBoxes. If one CheckBox is selected childs also selected.

And mainly I want to add data to TreeView dynamically.

Upvotes: 10

Views: 38826

Answers (7)

devbf
devbf

Reputation: 561

Here is a very simple solution (the above solutions are way better, but in my case I only needed the function for a small tool so the other solutions had too much overhead for me):

private void WriteDataToUi(TreeView tree, List<string> TestStrings)
{
    TreeViewItem item = new TreeViewItem();
    item.Header = "Test";

    foreach (string value in TestStrings)
    {
        CheckBox cb = new CheckBox();
        cb.Checked += CheckBoxWasChecked;
        cb.Tag = ("Test", value);
        cb.Content = value;
        item.Items.Add(cb);
    }

    item.IsExpanded = true;
    tree.Items.Add(item);            
}

private void CheckBoxWasChecked(object sender, RoutedEventArgs e)
{
    var cb = sender as CheckBox;
    MessageBox.Show($"Checkbox was checked. {cb.Tag}");
}

Upvotes: 0

Arturo Rodrigo
Arturo Rodrigo

Reputation: 21

You could do it manually, but you would have to check each checkbox automatically. <TreeView><CheckBox><TreeViewItem Header="1"><CheckBox Content="Data 1.1"/<CheckBox Content="Data 1.2/></TreeViewItem></CheckBox></TreeView> Result

Upvotes: 2

user12805184
user12805184

Reputation:

This solution with full source project on this guide is well working : Working with Checkboxes in the WPF TreeView

Thanks to Josh Smith

See the result :

enter image description here

Upvotes: 2

datchung
datchung

Reputation: 4622

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
    }
}

Upvotes: 5

Chris
Chris

Reputation: 3519

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);
    }
}

Upvotes: 4

pr0gg3r
pr0gg3r

Reputation: 4433

Check this out:

enter image description here

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;
        }
    }
}

Upvotes: 16

Robert Rossney
Robert Rossney

Reputation: 96702

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:

  • It must implement INotifyPropertyChanged.
  • It needs a Children property of type ObservableCollection<CheckableItem>.
  • It needs an 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 CheckableItems, are turned into TreeViewItems 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.

Upvotes: 18

Related Questions