如何在 wpf 中使用复选框开发树视图?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/2251260/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-08 21:30:07  来源:igfitidea点击:

How to develop treeview with checkboxes in wpf?

wpfcheckboxtreeview

提问by kirankumarG

I have a requirement that , I need to add nodes to a TreeViewdynamically and that nodes with CheckBoxes. If one CheckBoxis selected childs also selected.

我有一个要求,我需要TreeView动态地将节点添加到 a以及带有CheckBoxes 的节点。如果CheckBox选择了一个孩子也选择了。

And mainly I want to add data to TreeViewdynamically.

主要是我想TreeView动态添加数据。

回答by Robert Rossney

This is remarkably straightforward to do, once you know how.

一旦你知道如何做,这是非常简单的。

Create a view model class (I've called it CheckableItemhere) for your tree view item data. It needs these three things:

CheckableItem为您的树视图项数据创建一个视图模型类(我在这里称之为)。它需要这三样东西:

  • It must implement INotifyPropertyChanged.
  • It needs a Childrenproperty of type ObservableCollection<CheckableItem>.
  • It needs an IsCheckedproperty of type Visibilitythat, in its setter, raises PropertyChangedand also iterates through the items in Childrenand sets their IsCheckedproperty.
  • 它必须实现 INotifyPropertyChanged。
  • 它需要一个Children类型的属性ObservableCollection<CheckableItem>
  • 它需要一个IsChecked类型的属性Visibility,在它的 setter 中,引发PropertyChanged并遍历其中的项目Children并设置它们的IsChecked属性。

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 Itemclass of type objectand use a ContentPresenterin the template, but I'll leave figuring that out to you.

在此类中实现其他属性以将项目的数据公开给绑定(我的示例仅假设称为Value)。或者,您可以只实现一个Item类型的类objectContentPresenter在模板中使用 a ,但我将把它留给您解决。

Now create a HierarchicalDataTemplatefor your class that looks something like this:

现在HierarchicalDataTemplate为你的类创建一个看起来像这样的:

<HierarchicalDataTemplate
    DataType="{x:Type local:CheckableItem}" 
    ItemsSource="{Binding Children}">
    <StackPanel Orientation="Horizontal">
        <CheckBox IsChecked="{Binding IsChecked}"/>
        <TextBlock Text="{Binding Value}"/>
    </StackPanel>
</HierarchicalDataTemplate>

...and a TreeViewthat uses it (I'm assuming you've populated a collection of these objects, of course):

...以及TreeView使用它的一个(当然,我假设您已经填充了这些对象的集合):

<TreeView ItemsSource="{Binding MyCollectionOfCheckableItems}"/>

How it works: The TreeViewuses the HierarchicalDataTemplateto render each item in its ItemsSource. The HierarchicalDataTemplateis a template that creates a HeaderedItemsControl(in this case a TreeViewItem), uses its template to render the header, and then uses its ItemsSourceas 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.

工作原理:TreeView使用HierarchicalDataTemplate来呈现其ItemsSource. 该HierarchicalDataTemplate是创建一个模板HeaderedItemsControl(在这种情况下TreeViewItem),使用它的模板来呈现标题,然后利用其ItemsSource为源控件的项目-这,因为他们都是CheckableItemS,都变成了TreeViewItem由S HierarchicalDataTemplate。在那之后,它一直是乌龟。

Thisis a pretty good overview of how TreeViewactually 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.

TreeView对实践中实际工作方式的一个很好的概述,尽管与我发现的大多数示例一样,它有很多花里胡哨的东西,以至于很难看出基本原理是多么简单。如果你了解MVVM,上一段是你需要知道的90%。

回答by pr0gg3r

Check this out:

看一下这个:

enter image description here

在此处输入图片说明

DataModel.cs

数据模型.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

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

主窗口.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

主窗口.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;
        }
    }
}

回答by Chris

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.

我添加到@pr0gg3r 的答案中以使其通用。我不确定这是否一定是最好的方法,但它更灵活一些。

MainWindow is the same, but the other classes differ slightly.

MainWindow 是相同的,但其他类略有不同。

IParent.cs

父母.cs

interface IParent<T>
{
    IEnumerable<T> GetChildren();
}

DataModel.cs

数据模型.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

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

回答by sodjsn26fr

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

本指南中包含完整源项目的解决方案运行良好:使用 WPF TreeView 中的复选框

Thanks to Josh Smith

谢乔什·史密斯

See the result :

查看结果:

enter image description here

在此处输入图片说明

回答by datchung

I've found success following this guide.

我发现按照本指南取得了成功。

Below is the complete source code from that guide.

以下是该指南的完整源代码。

MainWindow.xaml:

主窗口.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:

主窗口.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

树视图模型.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
    }
}