如何在wpf中使用复选框开发treeview?

vbopmzt1  于 2023-01-27  发布在  其他
关注(0)|答案(7)|浏览(253)

我有一个要求,我需要动态地向TreeView添加节点,并且具有CheckBox的节点。如果选择了一个CheckBox查尔兹则也会选择子节点。
我主要想动态地向TreeView添加数据。

yk9xbfzb

yk9xbfzb1#

一旦你知道了怎么做,这是非常简单的。
为树视图项目数据创建一个视图模型类(我在这里称之为CheckableItem),它需要以下三个东西:

  • 它必须实现INotifyPropertyChanged。
  • 它需要一个ObservableCollection<CheckableItem>类型的Children属性。
  • 它需要一个类型为VisibilityIsChecked属性,该属性在其setter中引发PropertyChanged,还迭代Children中的项并设置它们的IsChecked属性。

在这个类中实现其他属性来向绑定公开条目的数据(我的示例只假设了一个名为Value的东西),或者你可以只实现一个object类型的Item类,并在模板中使用一个ContentPresenter,但我会让你自己去弄清楚。
现在为类创建一个HierarchicalDataTemplate,如下所示:

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

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

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

工作原理:TreeView使用HierarchicalDataTemplate在其ItemsSource中呈现每个项目。HierarchicalDataTemplate是一个模板,用于创建HeaderedItemsControl(在本例中为TreeViewItem),使用其模板呈现头,然后使用其ItemsSource作为控件项的源,因为这些项都是CheckableItem,被HierarchicalDataTemplate转化为TreeViewItem。在那之后,海龟一路向下。
This很好地概述了TreeView实际上是如何工作的,尽管与我找到的大多数示例一样,它有太多的花哨之处,以至于很难看出基本原理有多简单。如果您了解MVVM,那么上一段内容就包含了您需要了解的90%。

gg58donl

gg58donl2#

看看这个:

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

a7qyws3x3#

我在@pr0gg3r的答案上添加了一些内容,使其具有通用性。我不确定这是否是最好的方法,但它更灵活一些。
MainWindow是相同的,但其他类稍有不同。
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);
    }
}
vktxenjb

vktxenjb4#

我发现遵循this 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
    }
}
nszi6y05

nszi6y055#

本指南中包含完整源代码项目的解决方案运行良好:Working with Checkboxes in the WPF TreeView
多亏了Josh Smith
查看结果:

bakd9h0s

bakd9h0s6#

您可以手动执行此操作,但必须自动选中每个复选框。<TreeView><CheckBox><TreeViewItem Header="1"><CheckBox Content="Data 1.1"/<CheckBox Content="Data 1.2/></TreeViewItem></CheckBox></TreeView>Result

zyfwsgd6

zyfwsgd67#

下面是一个非常简单的解决方案(上面的解决方案要好得多,但在我的情况下,我只需要一个小工具的函数,所以其他解决方案对我来说有太多的开销):

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

相关问题