WPF随笔(二)--带CheckBox的TreeView

在上一篇的文章中记录了如何使用MahApps.Metro界面库,本人还是比较喜欢它简约现代的风格,但是却发现MahApps.Metro中没有带CheckBox的TreeView,于是就参考资料做了一个带复选框的树形图。


树形图TreeView的特点是可以不断循环嵌套下去,如果只有固定的两级或三级并没有太大意义,因此其对应的数据结构应该也是要可循环嵌套的。同时一个通用的树形图的每个节点应该能够支持多种数据类型。此外对于带选择框CheckBox的树形图TreeView应该能够根据父节点状态改变子节点状态,也能根据子节点状态改变父节点状态。

数据结构

考虑到树形图的循环嵌套结构,首先创建循环嵌套的数据结构。

    public class TreeNode : INotifyPropertyChanged
    {
        #region 析构函数
        public TreeNode()
        {

        }
        public TreeNode(string name,object content)
        {
            _name = name;
            _content = content;
        }
        #endregion

        #region 属性
        private TreeNode _parent;

        public TreeNode Parent
        {
            get { return _parent; }
            set
            {
                if (_parent!=value)
                {
                    _parent = value;
                    OnPropertyChanged("Parent");
                }
            }
        }

        private List<TreeNode> _children;

        public List<TreeNode> Children
        {
            get { return _children; }
            set
            {
                if (_children!=value)
                {
                    _children = value;
                    OnPropertyChanged("Children");
                }
            }
        }

        private string _name;

        public string Name
        {
            get { return _name; }
            set
            {
                if (_name!=value)
                {
                    _name = value;
                    OnPropertyChanged("Name");
                }
            }
        }

        private bool? _isChecked=false;

        public bool? IsChecked
        {
            get { return _isChecked; }
            set
            {
                OnPropertyChanged("IsChecked");
            }
        }

        private object _content;

        public object Content
        {
            get { return _content; }
            set
            {
                if (_content!=value)
                {
                    _content = value;
                    OnPropertyChanged("Content");
                }
            }
        }


        #endregion

        public event PropertyChangedEventHandler PropertyChanged;
        protected virtual void OnPropertyChanged(string propertyName = null)
        {
            var handler = PropertyChanged;
            if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
        }
    }

可以通过TreeNode的Children属性实现数据结构的嵌套。

注意:为了便于进行数据绑定,TreeNode类实现了接口INotifyPropertyChanged

父子节点的选择关联

简单来讲,就是选中父节点时,所有子节点都应选中;取消选中父节点时,所有子节点都应未选中 ;反之依然。这就需要在任一节点的选中状态变化时,分别检查其父节点的状态(如果其有父节点)、子节点的状态(如果其有子节点)。具体方法如下。

        #region 方法
        private void SetChecked(bool? value,bool checkChild,bool checkParent)
        {
            if (_isChecked==value)
            {
                return;
            }
            _isChecked = value;
			//如果有子节点,检查子节点状态
            if (checkChild&&value.HasValue&&_children!=null)
            {
                _children.ForEach(c => c.SetChecked(value,true,false));
            }
            //如果有父节点,检查父节点状态
            if (checkParent&&_parent!=null)
            {
                _parent.CheckParent();
            }
            OnPropertyChanged("IsChecked");
        }

        private void CheckParent()
        {
            string checkedNames = string.Empty;
            bool? _currentState = this.IsChecked;
            bool? _firstState = null;
            for (int i = 0; i < this.Children.Count(); i++)
            {
                bool? childrenState = this.Children[i].IsChecked;
                if (i == 0)
                {
                    _firstState = childrenState;
                }
                else if (_firstState != childrenState)
                {
                    _firstState = null;
                }
            }
            if (_firstState != null) _currentState = _firstState;
            SetChecked(_firstState, false, true);
        }
        #endregion

然后将TreeNode的IsChecked属性稍作修改,如下:

        private bool? _isChecked=false;

        public bool? IsChecked
        {
            get { return _isChecked; }
            set
            {
                SetChecked(value, true, true);
            }
        }

界面展示

与数据结构相对应,需要有支持元素嵌套的页面结构,使用TreeView的模板特性来解决这个问题。

        <TreeView x:Name="treeElement">
            <TreeView.ItemTemplate>
                <HierarchicalDataTemplate DataType="{x:Type model:TreeNode}" ItemsSource="{Binding Path=Children,Mode=TwoWay}">
                    <StackPanel Orientation="Horizontal">
                        <CheckBox IsChecked="{Binding IsChecked}"></CheckBox>
                        <ContentPresenter Content="{Binding Name}"></ContentPresenter>
                    </StackPanel>
                </HierarchicalDataTemplate>
            </TreeView.ItemTemplate>
        </TreeView>

测试示例

        private void BindTreeView()
        {
            TreeNode node1 = new TreeNode("1", "");
            TreeNode node2 = new TreeNode("2", "");
            TreeNode node1_1 = new TreeNode("1.1", "");
            TreeNode node1_2 = new TreeNode("1.2", "");
            TreeNode node1_3 = new TreeNode("1.3", "");
            TreeNode node1_2_1 = new TreeNode("1.2.1", "");
            TreeNode node1_2_2 = new TreeNode("1.2.2", "");
            node1_2.Children = new List<TreeNode>() { node1_2_1, node1_2_2 };
            node1.Children = new List<TreeNode>() { node1_1, node1_2, node1_3 };
            List<TreeNode> nodes = new List<TreeNode>() { node1, node2 };
            this.treeElement.ItemsSource = nodes;

        }

编写测试数据,并将数据绑定到前台界面上。运行后效果如下:
在这里插入图片描述

  • 3
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值