Avalonia treedatagrid使用杂记

这里只对最近使用到的分层树做一些记录,有复选框示例,支持父级选中状态改变子集同步变化

废话不多说,直接上源码

View布局

复制代码
                <TreeDataGrid
                    Height="710"
                    BorderBrush="Gray"
                    CanUserResizeColumns="False"
                    FontSize="16"
                    Source="{Binding TreeDataGridSource}">
                    <TreeDataGrid.Resources>
                        <DataTemplate x:Key="CheckBoxCellTemplate" x:DataType="vm:TreeDataModel">
                            <CheckBox IsChecked="{Binding IsSelected, Mode=TwoWay}" />
                        </DataTemplate>
                    </TreeDataGrid.Resources>
                </TreeDataGrid>

model对象

复制代码
    public class TreeDataModel : INotifyPropertyChanged
    {
        private string _name;

        public string Name
        {
            get { return _name; }
            set
            {
                _name = value;
                OnPropertyChanged(nameof(Name));
            }
        }

        private int? _age;


        public int? Age
        {
            get { return _age; }
            set
            {
                _age = value;
                OnPropertyChanged(nameof(Age));
            }
        }

        public bool _isSelected;

        public bool IsSelected
        {
            get { return _isSelected; }
            set
            {
                _isSelected = value;
                OnPropertyChanged(nameof(IsSelected));

                // 如果是分类节点,更新所有子项的状态
                if (IsCategory)
                {
                    foreach (var child in Children)
                    {
                        child.IsSelected = value;
                    }
                }
            }
        }

        private string _department;

        public string Department
        {
            get => _department;
            set
            {
                if (_department != value)
                {
                    _department = value;
                    OnPropertyChanged(nameof(Department));
                }
            }
        }

        private string _category;

        public bool IsCategory => !string.IsNullOrEmpty(Category);

        public bool IsExpanded { get; set; } = true;


        public string Category
        {
            get { return _category; }
            set
            {
                _category = value;
                OnPropertyChanged(nameof(Category));
            }
        }


        private ObservableCollection<TreeDataModel> _children = new ObservableCollection<TreeDataModel>();

        public ObservableCollection<TreeDataModel> Children
        {
            get { return _children; }
            set
            {
                _children = value;
                OnPropertyChanged(nameof(Children));
            }
        }

        public event PropertyChangedEventHandler? PropertyChanged;

        protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }

        protected bool SetField<T>(ref T field, T value, [CallerMemberName] string? propertyName = null)
        {
            if (EqualityComparer<T>.Default.Equals(field, value)) return false;
            field = value;
            OnPropertyChanged(propertyName);
            return true;
        }
    }

vm 数据刷新逻辑

复制代码
    private HierarchicalTreeDataGridSource<TreeDataModel> _treeDataGridSource =
        new HierarchicalTreeDataGridSource<TreeDataModel>(new List<TreeDataModel>());

    public HierarchicalTreeDataGridSource<TreeDataModel> TreeDataGridSource
    {
        get { return _treeDataGridSource; }
        set
        {
            _treeDataGridSource = value;
            OnPropertyChanged(nameof(TreeDataGridSource));
        }
    }

    private TreeDataModel selectedItem;

    public TreeDataModel SelectedItem
    {
        get => selectedItem;
        set
        {
            selectedItem = value;
            OnPropertyChanged(nameof(SelectedItem));
        }
    }

    public async void InitTreeData()
    {
        List<TreeDataModel> data = new List<TreeDataModel>();
        for (int i = 0; i < 10; i++)
        {
            int value = 10 + i;
            data.Add(new TreeDataModel()
            {
                Age = value,
                Name = $"示例{value}",
                Department = "测试部",
            });
        }

        for (int i = 0; i < 7; i++)
        {
            int value = 10 + i;
            data.Add(new TreeDataModel()
            {
                Age = value,
                Name = $"张三{value}",
                Department = "研发",
            });
        }

        for (int i = 23; i < 50; i++)
        {
            int value = 10 + i;
            data.Add(new TreeDataModel()
            {
                Age = value,
                Name = $"酒香也怕巷子深{value}",
                Department = "心如止水",
            });
        }

        //聚合形成树形数据集
        var categoryGroups = data
            .GroupBy(i =>
            {
                // 按照"-"前面的部分分类
                var dashIndex = i.Department.IndexOf('-');
                return dashIndex > 0 ? i.Department.Substring(0, dashIndex) : i.Department;
            });
        List<TreeDataModel> listData = new List<TreeDataModel>();
        foreach (var group in categoryGroups)
        {
            var category = new TreeDataModel { Category = group.Key };

            foreach (var item in group)
            {
                category.Children.Add(item);
            }

            listData.Add(category);
        }

        TreeDataGridSource =
            new HierarchicalTreeDataGridSource<TreeDataModel>(listData)
            {
                Columns =
                {
                    new HierarchicalExpanderColumn<TreeDataModel>(
                        new TextColumn<TreeDataModel, string>(
                            "人员",
                            x => x.IsCategory ? $"{x.Category}({x.Children.Count}成员)" : x.Name,
                            GridLength.Auto)
                        ,
                        x => x.Children,
                        isExpandedSelector: x => x.IsExpanded),
                    new TextColumn<TreeDataModel, int?>(
                        "年龄",
                        x => x.Age,
                        GridLength.Auto),
                    new TemplateColumn<TreeDataModel>(
                        "",
                        "CheckBoxCellTemplate", // 使用自定义模板
                        width: GridLength.Auto),
                },
            };

        TreeDataGridSource.RowSelection.SelectionChanged += (s, e) =>
        {
            if (s is TreeDataGridRowSelectionModel<TreeDataModel> selec)
            {
                if (selec.SelectedItem is TreeDataModel selectModel)
                {
                    SelectedItem = selectModel;
                }
            }
        };
    }

关键点,

1.数据格式绑定基本都在vm完成treedatagrid数据源集合需要绑定HierarchicalTreeDataGridSource,而不是传统的ObservableCollection

2.行选中时间SelectionChanged,控件不支持直接使用SelectionChanged,需要vm业务层订阅RowSelection来引出SelectionChanged,数据集每次变化都需要重新订阅

3.复选框:

数据集Columns绑定时其实有CheckBoxColumn列,不过这里演示通过绑定资源键的形式 x:Key="CheckBoxCellTemplate"实现复选框

父级状态改变影响子集,Avalonia原本的TreeDataGrid并没有对这个实现,但是自己可以通过通过对IsSelected属性变化时,验证IsCategory是否是分类节点 从而更改子集选中状态