Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 831
kirankumarG Avatar asked Feb 12 '10 10:02

kirankumarG


4 Answers

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.

like image 192
Robert Rossney Avatar answered Nov 06 '22 01:11

Robert Rossney


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;
        }
    }
}
like image 26
pr0gg3r Avatar answered Nov 06 '22 00:11

pr0gg3r


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);
    }
}
like image 26
Chris Avatar answered Nov 06 '22 00:11

Chris


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
    }
}
like image 3
datchung Avatar answered Nov 06 '22 02:11

datchung