TreeNode.cs 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643
  1. 
  2. using System;
  3. using System.Collections.Generic;
  4. using System.Collections.Specialized;
  5. using System.Diagnostics;
  6. using System.Linq;
  7. using System.Windows;
  8. using Aitex.Core.RT.DataCenter;
  9. using Aitex.Core.RT.Event;
  10. using Aitex.Core.Util;
  11. using MECF.Framework.Common.DataCenter;
  12. using MECF.Framework.UI.Client.ClientBase.EventArgs;
  13. namespace MECF.Framework.UI.Client.ClientBase.Tree
  14. {
  15. public class TreeNode : BindableBase
  16. {
  17. #region Variables
  18. /// <summary>
  19. /// 当末端的节点选择属性发生变化时触发此事件。
  20. /// </summary>
  21. public event EventHandler<TreeNodeSelectionChangedEventArgs> TerminalNodeSelectionChanged;
  22. /// <summary>
  23. /// 当子节点中有元素发生变化时触发事件。
  24. /// </summary>
  25. public event EventHandler<NotifyCollectionChangedEventArgs> ChildNodesChanged;
  26. private bool? _isSelected = false;
  27. private double _averageValue;
  28. private double _minValue;
  29. private double _maxValue;
  30. private bool _isExpanded;
  31. private bool _isMatch = true;
  32. private string _filterKeyword;
  33. private Visibility _visibility = Visibility.Visible;
  34. private int _totalTerminalCount;
  35. private int _selectedTerminalCount;
  36. /// <summary>
  37. /// 挂起更新操作时的变量锁,防止多线程问题。
  38. /// </summary>
  39. private readonly object _suspendUpdateLocker;
  40. /// <summary>
  41. /// 时否挂起更新操作。
  42. /// </summary>
  43. private bool _isUpdateSuspended;
  44. #endregion
  45. #region Constructors
  46. public TreeNode(string name)
  47. {
  48. _suspendUpdateLocker = new object();
  49. _isUpdateSuspended = false;
  50. Name = name;
  51. DisplayName = name;
  52. //RawData = new List<ParameterNodePoint>();
  53. MaxTerminalSelectionAllowed = -1;
  54. ChildNodes = new TreeNodeCollection(this);
  55. ChildNodes.TerminalNodeSelectionChanged += OnTerminalNodeSelectionChanged;
  56. ChildNodes.CollectionChanged += ChildNodesOnCollectionChanged;
  57. }
  58. #endregion
  59. #region Properties
  60. /// <summary>
  61. /// 当前树中被选中的终端节点的数量。
  62. /// </summary>
  63. public int SelectedTerminalCount
  64. {
  65. get => _selectedTerminalCount;
  66. private set => Set(ref _selectedTerminalCount, value);
  67. }
  68. /// <summary>
  69. /// 当前树中终端节点的总数量。
  70. /// </summary>
  71. public int TotalTerminalCount
  72. {
  73. get => _totalTerminalCount;
  74. private set => Set(ref _totalTerminalCount, value);
  75. }
  76. /// <summary>
  77. /// 返回允许的最大终端节点选中数量。
  78. /// <para>-1表示可选中所有终端节点。</para>
  79. /// </summary>
  80. public int MaxTerminalSelectionAllowed { get; set; }
  81. public bool? IsSelected
  82. {
  83. get => _isSelected;
  84. set
  85. {
  86. if (value == _isSelected)
  87. {
  88. OnPropertyChanged();
  89. return;
  90. }
  91. var snapshot = _isSelected;
  92. Set(ref _isSelected, value);
  93. // 仅当该节点未最末端节点时触发事件。
  94. var cancelEventArgs = new TreeNodeSelectionChangedEventArgs(this, snapshot, value);
  95. RaiseTerminalNodeSelectionChangedEvent(cancelEventArgs);
  96. if (cancelEventArgs.Cancel)
  97. {
  98. _isSelected = snapshot;
  99. OnPropertyChanged();
  100. }
  101. // 处理子节点的选择状态
  102. UpdateChildrenSelection();
  103. // 处理我的状态。
  104. UpdateSelectionState(this);
  105. // 处理父节点的选择状态
  106. UpdateParentSelection();
  107. }
  108. }
  109. public string Name { get; }
  110. public string DisplayName { get; set; }
  111. /// <summary>
  112. /// 返回包含完整路劲的节点名称。
  113. /// </summary>
  114. public string FullName => ToString();
  115. public double AverageValue
  116. {
  117. get => _averageValue;
  118. private set => Set(ref _averageValue, value);
  119. }
  120. public double MinValue
  121. {
  122. get => _minValue;
  123. private set => Set(ref _minValue, value);
  124. }
  125. public double MaxValue
  126. {
  127. get => _maxValue;
  128. private set => Set(ref _maxValue, value);
  129. }
  130. /// <summary>
  131. /// 是否展开节点。
  132. /// </summary>
  133. public bool IsExpanded
  134. {
  135. get => _isExpanded;
  136. set => Set(ref _isExpanded, value);
  137. }
  138. /// <summary>
  139. /// 是否包含Filter指定的关键字。
  140. /// </summary>
  141. public bool IsMatch
  142. {
  143. get => _isMatch;
  144. set => Set(ref _isMatch, value);
  145. }
  146. /// <summary>
  147. /// 设置或返回节点是否可见。
  148. /// </summary>
  149. public Visibility Visibility
  150. {
  151. get => _visibility;
  152. set
  153. {
  154. Set(ref _visibility, value);
  155. if (IsTerminal)
  156. UpdateParentVisibility(ParentNode);
  157. }
  158. }
  159. public TreeNode ParentNode { get; set; }
  160. //public List<ParameterNodePoint> RawData { get; }
  161. public Visibility IsVisibilityParentNode { get; set; }
  162. public TreeNodeCollection ChildNodes { get; set; }
  163. /// <summary>
  164. /// 筛选器的关键字。
  165. /// </summary>
  166. public string FilterKeyWord
  167. {
  168. get => _filterKeyword;
  169. set => Set(ref _filterKeyword, value);
  170. }
  171. /// <summary>
  172. /// 返回我是否为最末端节点。
  173. /// </summary>
  174. public bool IsTerminal => (ChildNodes == null || ChildNodes.Count == 0);
  175. /// <summary>
  176. /// 返回我是否属于一级节点。
  177. /// </summary>
  178. public bool IsTopLevel => ParentNode == null;
  179. /// <summary>
  180. /// 返回我是否属于二级节点。
  181. /// </summary>
  182. public bool IsSecondLevel => ParentNode != null && ParentNode.ParentNode == null;
  183. /// <summary>
  184. /// 返回是否有终端节点被选中。
  185. /// </summary>
  186. public bool HasTerminalSelected => Flatten(true).FirstOrDefault(x => x.IsSelected == true) != null;
  187. #endregion
  188. #region Methods
  189. /// <summary>
  190. /// 如果是末端节点,触发事件。
  191. /// </summary>
  192. private void RaiseTerminalNodeSelectionChangedEvent(TreeNodeSelectionChangedEventArgs e)
  193. {
  194. // 仅当该节点未最末端节点时触发事件。
  195. if (IsTerminal)
  196. TerminalNodeSelectionChanged?.Invoke(this, e);
  197. }
  198. /// <summary>
  199. /// 处理子节点和父节点的选择状态。
  200. /// </summary>
  201. private void UpdateChildrenSelection()
  202. {
  203. if (_isSelected.HasValue == false)
  204. return;
  205. // 刷新子节点的选择状态
  206. if (IsTerminal)
  207. return;
  208. //! 遍历子节点时,本节点的_isSelected属性可能被改写。
  209. // 例如:某个终端节点的Selected改变时,会反向设置父节点的Selected属性;则接下来所有子节点赋值Selected属性时会使用新值。
  210. var fixedIsSelected = _isSelected;
  211. Flatten(true).ToList()
  212. .ForEach(x =>
  213. {
  214. if (x.IsMatch)
  215. x.SetSelectionProperty(fixedIsSelected);
  216. });
  217. }
  218. /// <summary>
  219. /// 刷新父节点的选择状态。
  220. /// </summary>
  221. public void UpdateParentSelection()
  222. {
  223. if (ParentNode == null)
  224. return;
  225. // 递归更新IsSelected属性
  226. UpdateSelectionState(ParentNode);
  227. ParentNode.UpdateParentSelection();
  228. }
  229. /// <summary>
  230. /// 刷新指定节点的IsSelected属性。
  231. /// </summary>
  232. /// <param name="node"></param>
  233. private static void UpdateSelectionState(TreeNode node)
  234. {
  235. if (node.IsTerminal)
  236. return;
  237. // 刷新父节点的选择状态
  238. var selectionGroup = node.Flatten(true).GroupBy(x => x.IsSelected);
  239. var enumerable = selectionGroup.ToList();
  240. if (!enumerable.Any())
  241. return;
  242. if (enumerable.Count() > 1)
  243. node.SetSelectionProperty(null);
  244. else if (enumerable.Count() == 1)
  245. {
  246. var ss = enumerable[0].Key;
  247. if (ss.HasValue)
  248. node.SetSelectionProperty(ss.Value);
  249. }
  250. else
  251. {
  252. // 非法情况
  253. Debugger.Break();
  254. }
  255. }
  256. /// <summary>
  257. /// 刷新父级节点的显示属性。
  258. /// </summary>
  259. private static void UpdateParentVisibility(TreeNode node)
  260. {
  261. while (true)
  262. {
  263. // 刷新父节点的选择状态
  264. var selectionGroup = node.Flatten(true).GroupBy(x => x.Visibility);
  265. var enumerable = selectionGroup.ToList();
  266. if (!enumerable.Any())
  267. return;
  268. if (enumerable.Count() > 1)
  269. // 如果终端节点存在多种情况,则一定显示本节点。
  270. node.Visibility = Visibility.Visible;
  271. else if (enumerable.Count == 1)
  272. {
  273. // 如果存在一种情况,则判断是全部为显示还是全部为折叠。
  274. var ss = enumerable[0].Key;
  275. if (ss == Visibility.Visible)
  276. node.Visibility = Visibility.Visible;
  277. else
  278. node.Visibility = Visibility.Collapsed;
  279. }
  280. else
  281. {
  282. // 非法情况
  283. Debugger.Break();
  284. }
  285. // 如果本节点已经是顶层节点,则退出
  286. if (node.ParentNode == null)
  287. break;
  288. node = node.ParentNode;
  289. }
  290. }
  291. /// <summary>
  292. /// 当终端节点被选则或取消选择时触发此事件。
  293. /// </summary>
  294. /// <param name="sender"></param>
  295. /// <param name="e"></param>
  296. private void OnTerminalNodeSelectionChanged(object sender, TreeNodeSelectionChangedEventArgs e)
  297. {
  298. //! 在树的根部检查终端节点选中数是否超过最大限制
  299. if (IsTopLevel)
  300. {
  301. if (MaxTerminalSelectionAllowed > -1)
  302. if (GetSelectedTerminalCount() > MaxTerminalSelectionAllowed)
  303. {
  304. e.Cancel = true;
  305. return;
  306. }
  307. UpdateTerminalNodeCountInfo();
  308. }
  309. TerminalNodeSelectionChanged?.Invoke(sender, e);
  310. }
  311. /// <summary>
  312. /// 当ChildNodes元素发生变化时触发此事件。
  313. /// </summary>
  314. /// <param name="sender"></param>
  315. /// <param name="e"></param>
  316. private void ChildNodesOnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
  317. {
  318. /*
  319. * 如果当前节点是顶层节点,则统计终端节点总数,否则继续向父级节点传递事件。
  320. */
  321. if (IsTopLevel)
  322. UpdateTerminalNodeCountInfo();
  323. ChildNodesChanged?.Invoke(this, e);
  324. }
  325. /// <summary>
  326. /// 更新终端节点数量信息。
  327. /// </summary>
  328. private void UpdateTerminalNodeCountInfo()
  329. {
  330. lock (_suspendUpdateLocker)
  331. {
  332. if (_isUpdateSuspended)
  333. return;
  334. SelectedTerminalCount = GetSelectedTerminalCount();
  335. TotalTerminalCount = ChildNodes.Sum(x => x.Flatten(true).ToList().Count);
  336. }
  337. }
  338. /// <summary>
  339. /// 该方法用于Tree内部设置<see cref="IsSelected"/>属性的状态。
  340. /// </summary>
  341. /// <param name="isSelected"></param>
  342. private void SetSelectionProperty(bool? isSelected)
  343. {
  344. var snapshot = _isSelected;
  345. _isSelected = isSelected;
  346. var args = new TreeNodeSelectionChangedEventArgs(this, snapshot, _isSelected);
  347. RaiseTerminalNodeSelectionChangedEvent(args);
  348. if (args.Cancel)
  349. _isSelected = snapshot;
  350. OnPropertyChanged(nameof(IsSelected));
  351. if (IsTerminal)
  352. UpdateParentSelection();
  353. }
  354. /// <summary>
  355. /// 查找指定完整路径名称的末端节点。
  356. /// </summary>
  357. /// <param name="fullName"></param>
  358. /// <returns></returns>
  359. public TreeNode FindTerminalByFullName(string fullName)
  360. {
  361. return Flatten(true).FirstOrDefault(x => x.FullName == fullName);
  362. }
  363. /// <summary>
  364. /// 获取指定层级的节点。
  365. /// </summary>
  366. /// <param name="level">0-based层级,0表示Tree的根节点。</param>
  367. /// <returns></returns>
  368. public TreeNode GetNodeByLevel(int level)
  369. {
  370. var list = new List<TreeNode>();
  371. list.Add(this);
  372. while (true)
  373. {
  374. if (ParentNode != null)
  375. list.Add(this.ParentNode);
  376. else
  377. break;
  378. }
  379. if (level >= list.Count)
  380. return null;
  381. return list[level];
  382. }
  383. /// <summary>
  384. /// 获取被选中的终端节点的数量。
  385. /// </summary>
  386. /// <returns></returns>
  387. public int GetSelectedTerminalCount()
  388. {
  389. return Flatten(true).Where(x => x.IsSelected == true).ToList().Count;
  390. }
  391. /// <summary>
  392. /// 将数中的节点转换为节点数组。
  393. /// </summary>
  394. /// <param name="terminalOnly">是否仅枚举最末端节点。</param>
  395. /// <para>更新子节点的Selected属性时,需要同时更新中间节点的属性;更新父节点时,只评估终端节点的Selected属性。</para>
  396. /// <returns></returns>
  397. public List<TreeNode> Flatten(bool terminalOnly)
  398. {
  399. if (ChildNodes == null || ChildNodes.Count <= 0)
  400. return new List<TreeNode>(new[] { this });
  401. var lst = ChildNodes.SelectMany(x => x.Flatten(terminalOnly)).ToList();
  402. if (!terminalOnly)
  403. lst.Add(this);
  404. return lst;
  405. }
  406. /// <summary>
  407. /// 清除统计数据。
  408. /// </summary>
  409. public void ClearStatistic()
  410. {
  411. MinValue = double.NaN;
  412. MaxValue = double.NaN;
  413. AverageValue = double.NaN;
  414. }
  415. /// <summary>
  416. /// 设置统计数据。
  417. /// </summary>
  418. /// <param name="min"></param>
  419. /// <param name="max"></param>
  420. /// <param name="average"></param>
  421. public void SetStatistic(double min, double max, double average)
  422. {
  423. MinValue = min;
  424. MaxValue = max;
  425. AverageValue = average;
  426. }
  427. /// <summary>
  428. /// 取消所有节点选择状态。
  429. /// </summary>
  430. public void UnselectAll()
  431. {
  432. SuspendUpdate();
  433. Flatten(true).ForEach(x => x.IsSelected = false);
  434. ResumeUpdate();
  435. }
  436. /// <summary>
  437. /// 选择所有节点。
  438. /// </summary>
  439. public void SelectAll()
  440. {
  441. SuspendUpdate();
  442. Flatten(true).ForEach(x => x.IsSelected = true);
  443. ResumeUpdate();
  444. }
  445. /// <summary>
  446. /// 折叠所有节点。
  447. /// </summary>
  448. public void CollapseAll()
  449. {
  450. Flatten(false).ForEach(x => x.IsExpanded = false);
  451. }
  452. /// <summary>
  453. /// 展开所有节点。
  454. /// </summary>
  455. public void ExpandAll()
  456. {
  457. Flatten(false).ForEach(x => x.IsExpanded = true);
  458. }
  459. /// <summary>
  460. /// 显示所有节点。
  461. /// </summary>
  462. public void ShowAll()
  463. {
  464. Flatten(true).ForEach(x => x.Visibility = Visibility.Visible);
  465. }
  466. /// <summary>
  467. /// 显示选中的项目。
  468. /// </summary>
  469. public void ShowSelectedOnly()
  470. {
  471. Flatten(true).Where(x => x.IsSelected == false).ToList()
  472. .ForEach(x => x.Visibility = Visibility.Collapsed);
  473. }
  474. /// <summary>
  475. /// 应用筛选器。
  476. /// </summary>
  477. /// <param name="keyWord"></param>
  478. public void ApplyFilter(string keyWord)
  479. {
  480. //! 自顶向下遍历,仅需关心子节点状态。
  481. // 如果父节点匹配,则我强制匹配。
  482. IsMatch = (ParentNode != null && ParentNode.IsMatch) || Name.ToLower().Contains(keyWord.ToLower());
  483. FilterKeyWord = keyWord;
  484. // 如果是终端节点,则仅检查是否匹配关键字,然后退出
  485. if (IsTerminal)
  486. return;
  487. // 否则检查我的名称是否匹配关键字,并检查我的子节点中是否有匹配关键字的节点,如果有,则我也要显示。
  488. foreach (var node in ChildNodes)
  489. {
  490. node.ApplyFilter(keyWord);
  491. }
  492. var isMatchedNodes = Flatten(false).Where(x => x.IsMatch).ToList();
  493. if (isMatchedNodes.Any())
  494. IsMatch = true;
  495. }
  496. /// <summary>
  497. /// 清除筛选器。
  498. /// </summary>
  499. public void ClearFilter()
  500. {
  501. IsMatch = true;
  502. FilterKeyWord = "";
  503. if (IsTerminal)
  504. return;
  505. foreach (var node in ChildNodes)
  506. {
  507. node.ClearFilter();
  508. }
  509. }
  510. /// <summary>
  511. /// 挂起TreeView视图更新。
  512. /// <para>通常在后端对节点Selected属性操作时,挂起视图更新,以提高性能。</para>
  513. /// </summary>
  514. public void SuspendUpdate()
  515. {
  516. lock (_suspendUpdateLocker)
  517. {
  518. _isUpdateSuspended = true;
  519. }
  520. }
  521. /// <summary>
  522. /// 恢复TreeView视图更新。
  523. /// <para>通常在后端对节点Selected属性操作时,挂起视图更新,以提高性能。</para>
  524. /// <para>恢复视图更新后,主动更新节点数量统计值。</para>
  525. /// </summary>
  526. public void ResumeUpdate()
  527. {
  528. lock (_suspendUpdateLocker)
  529. {
  530. _isUpdateSuspended = false;
  531. }
  532. UpdateTerminalNodeCountInfo();
  533. }
  534. public override string ToString()
  535. {
  536. // 完整路径中删除根节点的名称,以确保FullName匹配系统中的定义。
  537. return (ParentNode?.ParentNode== null) ? Name : $"{ParentNode}.{Name}";
  538. }
  539. #endregion
  540. }
  541. }