How to develop treeview with checkboxes in wpf?

后端 未结 5 1059
小蘑菇
小蘑菇 2020-12-16 21:41

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

相关标签:
5条回答
  • 2020-12-16 22:26

    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);
        }
    }
    
    0 讨论(0)
  • 2020-12-16 22:30

    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.

    0 讨论(0)
  • 2020-12-16 22:37

    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
        }
    }
    
    0 讨论(0)
  • 2020-12-16 22:38

    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 :

    0 讨论(0)
  • 2020-12-16 22:40

    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;
            }
        }
    }
    
    0 讨论(0)
提交回复
热议问题