using System.Collections; namespace VariaStudio.Controls { // 定义模板中必须存在的部件,这是良好的自定义控件开发实践 /// /// TreeGrid 控件是一个自定义控件,它结合了树形结构和表格布局的功能。通过此控件,用户可以展示具有层次结构的数据,并且能够以类似于表格的方式组织列。 /// /// /// 该控件支持数据绑定、列定义以及行模板的自定义,允许灵活地展示复杂的数据结构。TreeGrid 内部使用了 ScrollViewer 和 TreeView 作为其主要组成部分来实现滚动和树形视图功能。 /// [TemplatePart(Name = "PART_HeaderScrollViewer", Type = typeof(ScrollViewer))] [TemplatePart(Name = "PART_ContentTreeView", Type = typeof(TreeView))] public class TreeGrid : Control { private ScrollViewer? headerScrollViewer; private ScrollViewer? contentScrollViewer; private TreeView? contentTreeView; #region Dependency Properties // 1. ItemsSource (IEnumerable) - 用于绑定数据源 /// /// 表示 TreeGrid 控件的数据源的依赖属性,用于绑定到一个实现了 IEnumerable 接口的数据集合。 /// 通过设置此属性,可以将具有层次结构或简单列表形式的数据绑定到控件上,从而在 TreeGrid 中展示数据。支持任何类型的数据源,只要它能够被枚举。 /// public static readonly DependencyProperty ItemsSourceProperty = DependencyProperty.Register(nameof(ItemsSource), typeof(IEnumerable), typeof(TreeGrid), new PropertyMetadata(null)); /// /// /// public IEnumerable ItemsSource { get => (IEnumerable)GetValue(ItemsSourceProperty); set => SetValue(ItemsSourceProperty, value); } // 2. Columns (GridViewColumnCollection) - 直接使用WPF内置的列集合 /// /// 表示 TreeGrid 控件中定义的列集合的依赖属性,用于指定显示在控件中的各个列及其属性。 /// 通过此属性可以添加、删除或修改列,例如设置列标题、绑定数据成员等。每个列都由一个 GridViewColumn 对象表示。 /// public static readonly DependencyProperty ColumnsProperty = DependencyProperty.Register(nameof(Columns), typeof(GridViewColumnCollection), typeof(TreeGrid), new PropertyMetadata(new GridViewColumnCollection())); /// /// 表示 TreeGrid 控件中定义的列集合,用于指定显示在控件中的各个列及其属性。 /// 通过此属性可以添加、删除或修改列,例如设置列标题、绑定数据成员等。每个列都由一个 GridViewColumn 对象表示。 /// public GridViewColumnCollection Columns { get => (GridViewColumnCollection)GetValue(ColumnsProperty); set => SetValue(ColumnsProperty, value); } // 3. ItemTemplate (HierarchicalDataTemplate) - 用户自定义行和子项的模板 /// /// 表示用于设置或获取 TreeGrid 控件中每个项的数据模板的依赖属性。 /// 通过此属性,用户可以自定义 TreeViewItem 的数据展示方式,例如通过 HierarchicalDataTemplate 定义复杂的数据结构展示。 /// public static readonly DependencyProperty ItemTemplateProperty = DependencyProperty.Register(nameof(ItemTemplate), typeof(HierarchicalDataTemplate), typeof(TreeGrid), new PropertyMetadata(null)); /// /// /// public HierarchicalDataTemplate ItemTemplate { get => (HierarchicalDataTemplate)GetValue(ItemTemplateProperty); set => SetValue(ItemTemplateProperty, value); } // 4. ItemContainerStyle (Style) - 允许用户自定义 TreeViewItem 的样式 /// /// 表示用于设置或获取 TreeGrid 控件中每个项的容器样式(即 TreeViewItem 的样式)的依赖属性。 /// 通过此属性,用户可以自定义 TreeViewItem 的外观,例如修改其背景色、边框样式等视觉效果。 /// public static readonly DependencyProperty ItemContainerStyleProperty = DependencyProperty.Register(nameof(ItemContainerStyle), typeof(Style), typeof(TreeGrid), new PropertyMetadata(null)); /// /// 获取或设置应用于 TreeGrid 控件中每个项容器(即 TreeViewItem)的样式。 /// 通过此属性,可以自定义 TreeViewItem 的外观,例如背景色、边框样式等视觉效果。 /// public Style ItemContainerStyle { get => (Style)GetValue(ItemContainerStyleProperty); set => SetValue(ItemContainerStyleProperty, value); } // 也可以添加 SelectedItem, CornerRadius 等其他 BUI 库中有的属性... #endregion static TreeGrid() { DefaultStyleKeyProperty.OverrideMetadata(typeof(TreeGrid), new FrameworkPropertyMetadata(typeof(TreeGrid))); } // 当控件的模板应用时,此方法被调用 /// public override void OnApplyTemplate() { base.OnApplyTemplate(); // 解除旧的事件处理器,防止内存泄漏 if (contentScrollViewer != null) contentScrollViewer.ScrollChanged -= OnContentScrollChanged; // 从模板中获取关键部件 headerScrollViewer = GetTemplateChild("PART_HeaderScrollViewer") as ScrollViewer; contentTreeView = GetTemplateChild("PART_ContentTreeView") as TreeView; if (contentTreeView != null) { // TreeView 加载完成后,查找其内部的 ScrollViewer contentTreeView.Loaded += (_, _) => { contentScrollViewer = FindVisualChild(contentTreeView); if (contentScrollViewer != null) { // 【核心】挂载滚动同步事件 contentScrollViewer.ScrollChanged += OnContentScrollChanged; } }; } } // 当内容滚动时,同步表头的水平滚动位置 private void OnContentScrollChanged(object sender, ScrollChangedEventArgs e) { if (headerScrollViewer != null && e.HorizontalChange != 0) { headerScrollViewer.ScrollToHorizontalOffset(e.HorizontalOffset); } } // 辅助方法:在可视化树中查找指定类型的子元素 private static T? FindVisualChild(DependencyObject? obj) where T : DependencyObject { if (obj == null) return null; for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++) { DependencyObject child = VisualTreeHelper.GetChild(obj, i); if (child is T typedChild) { return typedChild; } T? childOfChild = FindVisualChild(child); if (childOfChild != null) { return childOfChild; } } return null; } } }