123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643 |
-
- using System;
- using System.Collections.Generic;
- using System.Collections.Specialized;
- using System.Diagnostics;
- using System.Linq;
- using System.Windows;
- using Aitex.Core.RT.DataCenter;
- using Aitex.Core.RT.Event;
- using Aitex.Core.Util;
- using MECF.Framework.Common.DataCenter;
- using MECF.Framework.UI.Client.ClientBase.EventArgs;
- namespace MECF.Framework.UI.Client.ClientBase.Tree
- {
- public class TreeNode : BindableBase
- {
- #region Variables
- /// <summary>
- /// 当末端的节点选择属性发生变化时触发此事件。
- /// </summary>
- public event EventHandler<TreeNodeSelectionChangedEventArgs> TerminalNodeSelectionChanged;
- /// <summary>
- /// 当子节点中有元素发生变化时触发事件。
- /// </summary>
- public event EventHandler<NotifyCollectionChangedEventArgs> 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;
- /// <summary>
- /// 挂起更新操作时的变量锁,防止多线程问题。
- /// </summary>
- private readonly object _suspendUpdateLocker;
- /// <summary>
- /// 时否挂起更新操作。
- /// </summary>
- private bool _isUpdateSuspended;
- #endregion
- #region Constructors
- public TreeNode(string name)
- {
- _suspendUpdateLocker = new object();
- _isUpdateSuspended = false;
- Name = name;
- DisplayName = name;
-
-
- //RawData = new List<ParameterNodePoint>();
- MaxTerminalSelectionAllowed = -1;
- ChildNodes = new TreeNodeCollection(this);
- ChildNodes.TerminalNodeSelectionChanged += OnTerminalNodeSelectionChanged;
- ChildNodes.CollectionChanged += ChildNodesOnCollectionChanged;
- }
- #endregion
- #region Properties
- /// <summary>
- /// 当前树中被选中的终端节点的数量。
- /// </summary>
- public int SelectedTerminalCount
- {
- get => _selectedTerminalCount;
- private set => Set(ref _selectedTerminalCount, value);
- }
- /// <summary>
- /// 当前树中终端节点的总数量。
- /// </summary>
- public int TotalTerminalCount
- {
- get => _totalTerminalCount;
- private set => Set(ref _totalTerminalCount, value);
- }
- /// <summary>
- /// 返回允许的最大终端节点选中数量。
- /// <para>-1表示可选中所有终端节点。</para>
- /// </summary>
- 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 DisplayName { get; set; }
- /// <summary>
- /// 返回包含完整路劲的节点名称。
- /// </summary>
- 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);
- }
- /// <summary>
- /// 是否展开节点。
- /// </summary>
- public bool IsExpanded
- {
- get => _isExpanded;
- set => Set(ref _isExpanded, value);
- }
- /// <summary>
- /// 是否包含Filter指定的关键字。
- /// </summary>
- public bool IsMatch
- {
- get => _isMatch;
- set => Set(ref _isMatch, value);
- }
- /// <summary>
- /// 设置或返回节点是否可见。
- /// </summary>
- public Visibility Visibility
- {
- get => _visibility;
- set
- {
- Set(ref _visibility, value);
- if (IsTerminal)
- UpdateParentVisibility(ParentNode);
- }
- }
- public TreeNode ParentNode { get; set; }
- //public List<ParameterNodePoint> RawData { get; }
- public Visibility IsVisibilityParentNode { get; set; }
- public TreeNodeCollection ChildNodes { get; set; }
- /// <summary>
- /// 筛选器的关键字。
- /// </summary>
- public string FilterKeyWord
- {
- get => _filterKeyword;
- set => Set(ref _filterKeyword, value);
- }
- /// <summary>
- /// 返回我是否为最末端节点。
- /// </summary>
- public bool IsTerminal => (ChildNodes == null || ChildNodes.Count == 0);
- /// <summary>
- /// 返回我是否属于一级节点。
- /// </summary>
- public bool IsTopLevel => ParentNode == null;
- /// <summary>
- /// 返回我是否属于二级节点。
- /// </summary>
- public bool IsSecondLevel => ParentNode != null && ParentNode.ParentNode == null;
- /// <summary>
- /// 返回是否有终端节点被选中。
- /// </summary>
- public bool HasTerminalSelected => Flatten(true).FirstOrDefault(x => x.IsSelected == true) != null;
- #endregion
- #region Methods
- /// <summary>
- /// 如果是末端节点,触发事件。
- /// </summary>
- private void RaiseTerminalNodeSelectionChangedEvent(TreeNodeSelectionChangedEventArgs e)
- {
- // 仅当该节点未最末端节点时触发事件。
- if (IsTerminal)
- TerminalNodeSelectionChanged?.Invoke(this, e);
- }
- /// <summary>
- /// 处理子节点和父节点的选择状态。
- /// </summary>
- 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);
- });
- }
- /// <summary>
- /// 刷新父节点的选择状态。
- /// </summary>
- public void UpdateParentSelection()
- {
- if (ParentNode == null)
- return;
- // 递归更新IsSelected属性
- UpdateSelectionState(ParentNode);
- ParentNode.UpdateParentSelection();
- }
- /// <summary>
- /// 刷新指定节点的IsSelected属性。
- /// </summary>
- /// <param name="node"></param>
- 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();
- }
- }
- /// <summary>
- /// 刷新父级节点的显示属性。
- /// </summary>
- 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;
- }
- }
- /// <summary>
- /// 当终端节点被选则或取消选择时触发此事件。
- /// </summary>
- /// <param name="sender"></param>
- /// <param name="e"></param>
- private void OnTerminalNodeSelectionChanged(object sender, TreeNodeSelectionChangedEventArgs e)
- {
- //! 在树的根部检查终端节点选中数是否超过最大限制
- if (IsTopLevel)
- {
- if (MaxTerminalSelectionAllowed > -1)
- if (GetSelectedTerminalCount() > MaxTerminalSelectionAllowed)
- {
- e.Cancel = true;
- return;
- }
- UpdateTerminalNodeCountInfo();
- }
- TerminalNodeSelectionChanged?.Invoke(sender, e);
- }
- /// <summary>
- /// 当ChildNodes元素发生变化时触发此事件。
- /// </summary>
- /// <param name="sender"></param>
- /// <param name="e"></param>
- private void ChildNodesOnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
- {
- /*
- * 如果当前节点是顶层节点,则统计终端节点总数,否则继续向父级节点传递事件。
- */
- if (IsTopLevel)
- UpdateTerminalNodeCountInfo();
- ChildNodesChanged?.Invoke(this, e);
- }
- /// <summary>
- /// 更新终端节点数量信息。
- /// </summary>
- private void UpdateTerminalNodeCountInfo()
- {
- lock (_suspendUpdateLocker)
- {
- if (_isUpdateSuspended)
- return;
- SelectedTerminalCount = GetSelectedTerminalCount();
- TotalTerminalCount = ChildNodes.Sum(x => x.Flatten(true).ToList().Count);
- }
- }
- /// <summary>
- /// 该方法用于Tree内部设置<see cref="IsSelected"/>属性的状态。
- /// </summary>
- /// <param name="isSelected"></param>
- 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();
- }
- /// <summary>
- /// 查找指定完整路径名称的末端节点。
- /// </summary>
- /// <param name="fullName"></param>
- /// <returns></returns>
- public TreeNode FindTerminalByFullName(string fullName)
- {
- return Flatten(true).FirstOrDefault(x => x.FullName == fullName);
- }
- /// <summary>
- /// 获取指定层级的节点。
- /// </summary>
- /// <param name="level">0-based层级,0表示Tree的根节点。</param>
- /// <returns></returns>
- public TreeNode GetNodeByLevel(int level)
- {
- var list = new List<TreeNode>();
- list.Add(this);
- while (true)
- {
- if (ParentNode != null)
- list.Add(this.ParentNode);
- else
- break;
- }
- if (level >= list.Count)
- return null;
- return list[level];
- }
- /// <summary>
- /// 获取被选中的终端节点的数量。
- /// </summary>
- /// <returns></returns>
- public int GetSelectedTerminalCount()
- {
- return Flatten(true).Where(x => x.IsSelected == true).ToList().Count;
- }
- /// <summary>
- /// 将数中的节点转换为节点数组。
- /// </summary>
- /// <param name="terminalOnly">是否仅枚举最末端节点。</param>
- /// <para>更新子节点的Selected属性时,需要同时更新中间节点的属性;更新父节点时,只评估终端节点的Selected属性。</para>
- /// <returns></returns>
- public List<TreeNode> Flatten(bool terminalOnly)
- {
- if (ChildNodes == null || ChildNodes.Count <= 0)
- return new List<TreeNode>(new[] { this });
- var lst = ChildNodes.SelectMany(x => x.Flatten(terminalOnly)).ToList();
- if (!terminalOnly)
- lst.Add(this);
- return lst;
- }
- /// <summary>
- /// 清除统计数据。
- /// </summary>
- public void ClearStatistic()
- {
- MinValue = double.NaN;
- MaxValue = double.NaN;
- AverageValue = double.NaN;
- }
- /// <summary>
- /// 设置统计数据。
- /// </summary>
- /// <param name="min"></param>
- /// <param name="max"></param>
- /// <param name="average"></param>
- public void SetStatistic(double min, double max, double average)
- {
- MinValue = min;
- MaxValue = max;
- AverageValue = average;
- }
- /// <summary>
- /// 取消所有节点选择状态。
- /// </summary>
- public void UnselectAll()
- {
- SuspendUpdate();
- Flatten(true).ForEach(x => x.IsSelected = false);
- ResumeUpdate();
- }
- /// <summary>
- /// 选择所有节点。
- /// </summary>
- public void SelectAll()
- {
- SuspendUpdate();
- Flatten(true).ForEach(x => x.IsSelected = true);
- ResumeUpdate();
- }
- /// <summary>
- /// 折叠所有节点。
- /// </summary>
- public void CollapseAll()
- {
- Flatten(false).ForEach(x => x.IsExpanded = false);
- }
- /// <summary>
- /// 展开所有节点。
- /// </summary>
- public void ExpandAll()
- {
- Flatten(false).ForEach(x => x.IsExpanded = true);
- }
- /// <summary>
- /// 显示所有节点。
- /// </summary>
- public void ShowAll()
- {
- Flatten(true).ForEach(x => x.Visibility = Visibility.Visible);
- }
- /// <summary>
- /// 显示选中的项目。
- /// </summary>
- public void ShowSelectedOnly()
- {
- Flatten(true).Where(x => x.IsSelected == false).ToList()
- .ForEach(x => x.Visibility = Visibility.Collapsed);
- }
- /// <summary>
- /// 应用筛选器。
- /// </summary>
- /// <param name="keyWord"></param>
- 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;
- }
- /// <summary>
- /// 清除筛选器。
- /// </summary>
- public void ClearFilter()
- {
- IsMatch = true;
- FilterKeyWord = "";
- if (IsTerminal)
- return;
- foreach (var node in ChildNodes)
- {
- node.ClearFilter();
- }
- }
- /// <summary>
- /// 挂起TreeView视图更新。
- /// <para>通常在后端对节点Selected属性操作时,挂起视图更新,以提高性能。</para>
- /// </summary>
- public void SuspendUpdate()
- {
- lock (_suspendUpdateLocker)
- {
- _isUpdateSuspended = true;
- }
- }
- /// <summary>
- /// 恢复TreeView视图更新。
- /// <para>通常在后端对节点Selected属性操作时,挂起视图更新,以提高性能。</para>
- /// <para>恢复视图更新后,主动更新节点数量统计值。</para>
- /// </summary>
- public void ResumeUpdate()
- {
- lock (_suspendUpdateLocker)
- {
- _isUpdateSuspended = false;
- }
- UpdateTerminalNodeCountInfo();
- }
- public override string ToString()
- {
- // 完整路径中删除根节点的名称,以确保FullName匹配系统中的定义。
- return (ParentNode?.ParentNode== null) ? Name : $"{ParentNode}.{Name}";
- }
- #endregion
- }
- }
|