/************************************************************************ *@file FrameworkLocal\UIClient\CenterViews\DataLogs\Core\TreeNode.cs * @author Su Liang * @Date 2022-08-01 * * @copyright © Sicentury Inc. * * @brief Reconstructed to support rich functions. * * @details * All functions to operate the TreeView are integrated into one class, which * also makes it meet the MVVM coding pattern. * *****************************************************************************/ using System; using System.Collections.Generic; using System.Collections.Specialized; using System.Diagnostics; using System.Linq; using System.Windows; using Sicentury.Core.EventArgs; namespace Sicentury.Core.Tree { public class TreeNode : BindableBase { #region Variables /// /// 当末端的节点选择属性发生变化时触发此事件。 /// public event EventHandler TerminalNodeSelectionChanged; /// /// 当子节点中有元素发生变化时触发事件。 /// public event EventHandler ChildNodesChanged; private bool? _isSelected = false; private double _averageValue; private double _minValue; private double _maxValue; private bool _isExpanded; private bool _isMatch = true; private string _filterKeyword; private Visibility _visibility = Visibility.Visible; private int _totalTerminalCount; private int _selectedTerminalCount; /// /// 挂起更新操作时的变量锁,防止多线程问题。 /// private readonly object _suspendUpdateLocker; /// /// 时否挂起更新操作。 /// private bool _isUpdateSuspended; #endregion #region Constructors public TreeNode(string name) { _suspendUpdateLocker = new object(); _isUpdateSuspended = false; Name = name; //RawData = new List(); MaxTerminalSelectionAllowed = -1; ChildNodes = new TreeNodeCollection(this); ChildNodes.TerminalNodeSelectionChanged += OnTerminalNodeSelectionChanged; ChildNodes.CollectionChanged += ChildNodesOnCollectionChanged; } #endregion #region Properties /// /// 当前树中被选中的终端节点的数量。 /// public int SelectedTerminalCount { get => _selectedTerminalCount; private set => Set(ref _selectedTerminalCount, value); } /// /// 当前树中终端节点的总数量。 /// public int TotalTerminalCount { get => _totalTerminalCount; private set => Set(ref _totalTerminalCount, value); } /// /// 返回允许的最大终端节点选中数量。 /// -1表示可选中所有终端节点。 /// public int MaxTerminalSelectionAllowed { get; set; } public bool? IsSelected { get => _isSelected; set { if (value == _isSelected) { OnPropertyChanged(); return; } var snapshot = _isSelected; Set(ref _isSelected, value); // 仅当该节点未最末端节点时触发事件。 var cancelEventArgs = new TreeNodeSelectionChangedEventArgs(this, snapshot, value); RaiseTerminalNodeSelectionChangedEvent(cancelEventArgs); if (cancelEventArgs.Cancel) { _isSelected = snapshot; OnPropertyChanged(); } // 处理子节点的选择状态 UpdateChildrenSelection(); // 处理我的状态。 UpdateSelectionState(this); // 处理父节点的选择状态 UpdateParentSelection(); } } public string Name { get; } /// /// 返回包含完整路劲的节点名称。 /// public string FullName => ToString(); public double AverageValue { get => _averageValue; private set => Set(ref _averageValue, value); } public double MinValue { get => _minValue; private set => Set(ref _minValue, value); } public double MaxValue { get => _maxValue; private set => Set(ref _maxValue, value); } /// /// 是否展开节点。 /// public bool IsExpanded { get => _isExpanded; set => Set(ref _isExpanded, value); } /// /// 是否包含Filter指定的关键字。 /// public bool IsMatch { get => _isMatch; set => Set(ref _isMatch, value); } /// /// 设置或返回节点是否可见。 /// public Visibility Visibility { get => _visibility; set { Set(ref _visibility, value); if (IsTerminal) UpdateParentVisibility(ParentNode); } } public TreeNode ParentNode { get; set; } //public List RawData { get; } public Visibility IsVisibilityParentNode { get; set; } public TreeNodeCollection ChildNodes { get; set; } /// /// 筛选器的关键字。 /// public string FilterKeyWord { get => _filterKeyword; set => Set(ref _filterKeyword, value); } /// /// 返回我是否为最末端节点。 /// public bool IsTerminal => (ChildNodes == null || ChildNodes.Count == 0); /// /// 返回我是否属于一级节点。 /// public bool IsTopLevel => ParentNode == null; /// /// 返回我是否属于二级节点。 /// public bool IsSecondLevel => ParentNode != null && ParentNode.ParentNode == null; /// /// 返回是否有终端节点被选中。 /// public bool HasTerminalSelected => Flatten(true).FirstOrDefault(x => x.IsSelected == true) != null; #endregion #region Methods /// /// 如果是末端节点,触发事件。 /// private void RaiseTerminalNodeSelectionChangedEvent(TreeNodeSelectionChangedEventArgs e) { // 仅当该节点未最末端节点时触发事件。 if (IsTerminal) TerminalNodeSelectionChanged?.Invoke(this, e); } /// /// 处理子节点和父节点的选择状态。 /// private void UpdateChildrenSelection() { if (_isSelected.HasValue == false) return; // 刷新子节点的选择状态 if (IsTerminal) return; //! 遍历子节点时,本节点的_isSelected属性可能被改写。 // 例如:某个终端节点的Selected改变时,会反向设置父节点的Selected属性;则接下来所有子节点赋值Selected属性时会使用新值。 var fixedIsSelected = _isSelected; Flatten(true).ToList() .ForEach(x => { if(x.IsMatch) x.SetSelectionProperty(fixedIsSelected); }); } /// /// 刷新父节点的选择状态。 /// public void UpdateParentSelection() { if (ParentNode == null) return; // 递归更新IsSelected属性 UpdateSelectionState(ParentNode); ParentNode.UpdateParentSelection(); } /// /// 刷新指定节点的IsSelected属性。 /// /// private static void UpdateSelectionState(TreeNode node) { if (node.IsTerminal) return; // 刷新父节点的选择状态 var selectionGroup = node.Flatten(true).GroupBy(x => x.IsSelected); var enumerable = selectionGroup.ToList(); if (!enumerable.Any()) return; if (enumerable.Count() > 1) node.SetSelectionProperty(null); else if (enumerable.Count() == 1) { var ss = enumerable[0].Key; if (ss.HasValue) node.SetSelectionProperty(ss.Value); } else { // 非法情况 Debugger.Break(); } } /// /// 刷新父级节点的显示属性。 /// private static void UpdateParentVisibility(TreeNode node) { while (true) { // 刷新父节点的选择状态 var selectionGroup = node.Flatten(true).GroupBy(x => x.Visibility); var enumerable = selectionGroup.ToList(); if (!enumerable.Any()) return; if (enumerable.Count() > 1) // 如果终端节点存在多种情况,则一定显示本节点。 node.Visibility = Visibility.Visible; else if (enumerable.Count == 1) { // 如果存在一种情况,则判断是全部为显示还是全部为折叠。 var ss = enumerable[0].Key; if (ss == Visibility.Visible) node.Visibility = Visibility.Visible; else node.Visibility = Visibility.Collapsed; } else { // 非法情况 Debugger.Break(); } // 如果本节点已经是顶层节点,则退出 if (node.ParentNode == null) break; node = node.ParentNode; } } /// /// 当终端节点被选则或取消选择时触发此事件。 /// /// /// private void OnTerminalNodeSelectionChanged(object sender, TreeNodeSelectionChangedEventArgs e) { //! 在树的根部检查终端节点选中数是否超过最大限制 if (IsTopLevel) { if (MaxTerminalSelectionAllowed > -1) if (GetSelectedTerminalCount() > MaxTerminalSelectionAllowed) { e.Cancel = true; return; } UpdateTerminalNodeCountInfo(); } TerminalNodeSelectionChanged?.Invoke(sender, e); } /// /// 当ChildNodes元素发生变化时触发此事件。 /// /// /// private void ChildNodesOnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) { /* * 如果当前节点是顶层节点,则统计终端节点总数,否则继续向父级节点传递事件。 */ if (IsTopLevel) UpdateTerminalNodeCountInfo(); ChildNodesChanged?.Invoke(this, e); } /// /// 更新终端节点数量信息。 /// private void UpdateTerminalNodeCountInfo() { lock (_suspendUpdateLocker) { if (_isUpdateSuspended) return; SelectedTerminalCount = GetSelectedTerminalCount(); TotalTerminalCount = ChildNodes.Sum(x => x.Flatten(true).ToList().Count); } } /// /// 该方法用于Tree内部设置属性的状态。 /// /// private void SetSelectionProperty(bool? isSelected) { var snapshot = _isSelected; _isSelected = isSelected; var args = new TreeNodeSelectionChangedEventArgs(this, snapshot, _isSelected); RaiseTerminalNodeSelectionChangedEvent(args); if (args.Cancel) _isSelected = snapshot; OnPropertyChanged(nameof(IsSelected)); if (IsTerminal) UpdateParentSelection(); } /// /// 查找指定完整路径名称的末端节点。 /// /// /// public TreeNode FindTerminalByFullName(string fullName) { return Flatten(true).FirstOrDefault(x => x.FullName == fullName); } /// /// 获取指定层级的节点。 /// /// 0-based层级,0表示Tree的根节点。 /// public TreeNode GetNodeByLevel(int level) { var list = new List(); list.Add(this); while (true) { if (ParentNode != null) list.Add(this.ParentNode); else break; } if (level >= list.Count) return null; return list[level]; } /// /// 获取被选中的终端节点的数量。 /// /// public int GetSelectedTerminalCount() { return Flatten(true).Where(x => x.IsSelected == true).ToList().Count; } /// /// 将数中的节点转换为节点数组。 /// /// 是否仅枚举最末端节点。 /// 更新子节点的Selected属性时,需要同时更新中间节点的属性;更新父节点时,只评估终端节点的Selected属性。 /// public List Flatten(bool terminalOnly) { if (ChildNodes == null || ChildNodes.Count <= 0) return new List(new[] { this }); var lst = ChildNodes.SelectMany(x => x.Flatten(terminalOnly)).ToList(); if(!terminalOnly) lst.Add(this); return lst; } /// /// 清除统计数据。 /// public void ClearStatistic() { MinValue = double.NaN; MaxValue = double.NaN; AverageValue = double.NaN; } /// /// 设置统计数据。 /// /// /// /// public void SetStatistic(double min, double max, double average) { MinValue = min; MaxValue = max; AverageValue = average; } /// /// 取消所有节点选择状态。 /// public void UnselectAll() { SuspendUpdate(); Flatten(true).ForEach(x => x.IsSelected = false); ResumeUpdate(); } /// /// 选择所有节点。 /// public void SelectAll() { SuspendUpdate(); Flatten(true).ForEach(x => x.IsSelected = true); ResumeUpdate(); } /// /// 折叠所有节点。 /// public void CollapseAll() { Flatten(false).ForEach(x => x.IsExpanded = false); } /// /// 展开所有节点。 /// public void ExpandAll() { Flatten(false).ForEach(x => x.IsExpanded = true); } /// /// 显示所有节点。 /// public void ShowAll() { Flatten(true).ForEach(x => x.Visibility = Visibility.Visible); } /// /// 显示选中的项目。 /// public void ShowSelectedOnly() { Flatten(true).Where(x => x.IsSelected == false).ToList() .ForEach(x => x.Visibility = Visibility.Collapsed); } /// /// 应用筛选器。 /// /// public void ApplyFilter(string keyWord) { //! 自顶向下遍历,仅需关心子节点状态。 // 如果父节点匹配,则我强制匹配。 IsMatch = (ParentNode != null && ParentNode.IsMatch) || Name.ToLower().Contains(keyWord.ToLower()); FilterKeyWord = keyWord; // 如果是终端节点,则仅检查是否匹配关键字,然后退出 if (IsTerminal) return; // 否则检查我的名称是否匹配关键字,并检查我的子节点中是否有匹配关键字的节点,如果有,则我也要显示。 foreach (var node in ChildNodes) { node.ApplyFilter(keyWord); } var isMatchedNodes = Flatten(false).Where(x => x.IsMatch).ToList(); if (isMatchedNodes.Any()) IsMatch = true; } /// /// 清除筛选器。 /// public void ClearFilter() { IsMatch = true; FilterKeyWord = ""; if (IsTerminal) return; foreach (var node in ChildNodes) { node.ClearFilter(); } } /// /// 挂起TreeView视图更新。 /// 通常在后端对节点Selected属性操作时,挂起视图更新,以提高性能。 /// public void SuspendUpdate() { lock (_suspendUpdateLocker) { _isUpdateSuspended = true; } } /// /// 恢复TreeView视图更新。 /// 通常在后端对节点Selected属性操作时,挂起视图更新,以提高性能。 /// 恢复视图更新后,主动更新节点数量统计值。 /// public void ResumeUpdate() { lock (_suspendUpdateLocker) { _isUpdateSuspended = false; } UpdateTerminalNodeCountInfo(); } public override string ToString() { // 完整路径中删除根节点的名称,以确保FullName匹配系统中的定义。 return (ParentNode?.ParentNode == null) ? Name : $"{ParentNode}.{Name}"; } #endregion } }