2025-08-29 14:57:55 +08:00
|
|
|
|
using System;
|
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
|
using System.Collections.ObjectModel;
|
|
|
|
|
|
using System.Diagnostics;
|
|
|
|
|
|
using System.Linq;
|
|
|
|
|
|
using System.Text;
|
|
|
|
|
|
using System.Threading.Channels;
|
|
|
|
|
|
using System.Threading.Tasks;
|
|
|
|
|
|
using System.Windows;
|
|
|
|
|
|
using System.Windows.Controls;
|
|
|
|
|
|
using System.Windows.Data;
|
|
|
|
|
|
using System.Windows.Documents;
|
|
|
|
|
|
using System.Windows.Input;
|
|
|
|
|
|
using System.Windows.Media;
|
|
|
|
|
|
using System.Windows.Media.Imaging;
|
|
|
|
|
|
using System.Windows.Navigation;
|
|
|
|
|
|
using System.Windows.Shapes;
|
|
|
|
|
|
using System.Windows.Threading;
|
|
|
|
|
|
using System.Xml.Linq;
|
|
|
|
|
|
|
|
|
|
|
|
namespace Ramitta
|
|
|
|
|
|
{
|
|
|
|
|
|
public partial class winTreeList : UserControl
|
|
|
|
|
|
{
|
|
|
|
|
|
public winTreeList()
|
|
|
|
|
|
{
|
|
|
|
|
|
InitializeComponent();
|
|
|
|
|
|
Nodes = new ObservableCollection<TreeNode>();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#region 公共属性
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 获取或设置树形列表的节点集合
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public ObservableCollection<TreeNode> Nodes
|
|
|
|
|
|
{
|
|
|
|
|
|
get { return (ObservableCollection<TreeNode>)GetValue(NodesProperty); }
|
|
|
|
|
|
set { SetValue(NodesProperty, value); }
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public static readonly DependencyProperty NodesProperty =
|
|
|
|
|
|
DependencyProperty.Register("Nodes", typeof(ObservableCollection<TreeNode>),
|
|
|
|
|
|
typeof(winTreeList), new PropertyMetadata(null, OnNodesChanged));
|
|
|
|
|
|
|
|
|
|
|
|
private static void OnNodesChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
|
|
|
|
|
{
|
|
|
|
|
|
var control = (winTreeList)d;
|
|
|
|
|
|
control.treeView.ItemsSource = control.Nodes;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
|
|
#region 公共方法
|
|
|
|
|
|
|
|
|
|
|
|
#region 增
|
|
|
|
|
|
public LabelTreeNode AddLabelNode(string text, TreeNode parent = null)
|
|
|
|
|
|
{
|
|
|
|
|
|
var node = new LabelTreeNode { Text = text };
|
|
|
|
|
|
AddNode(node, parent);
|
|
|
|
|
|
return node;
|
|
|
|
|
|
}
|
|
|
|
|
|
public CheckboxTreeNode AddCheckboxNode(string text, string? tag=null, bool isChecked = false, TreeNode parent = null)
|
|
|
|
|
|
{
|
|
|
|
|
|
var node = new CheckboxTreeNode {
|
|
|
|
|
|
Text = text,
|
|
|
|
|
|
IsChecked = isChecked,
|
|
|
|
|
|
Tag = tag ?? text };
|
|
|
|
|
|
AddNode(node, parent);
|
|
|
|
|
|
return node;
|
|
|
|
|
|
}
|
|
|
|
|
|
public ComboboxTreeNode AddComboboxNode(string text, string[]? item=null, TreeNode parent = null)
|
|
|
|
|
|
{
|
|
|
|
|
|
var node = new ComboboxTreeNode {
|
|
|
|
|
|
Text = text,
|
|
|
|
|
|
ComboBoxItems = new ObservableCollection<string>(item ?? []),
|
|
|
|
|
|
};
|
|
|
|
|
|
AddNode(node, parent);
|
|
|
|
|
|
return node;
|
|
|
|
|
|
}
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
2025-08-30 21:14:02 +08:00
|
|
|
|
#region 查(这里面的函数下一个版本全被杀)
|
2025-08-29 14:57:55 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 获取所有被勾选的Checkbox节点的Tag
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="nodes">当前节点集合</param>
|
|
|
|
|
|
/// <returns>被勾选Checkbox节点的Tag列表</returns>
|
|
|
|
|
|
public List<string> GetCheckedCheckboxTags(IEnumerable<TreeNode> nodes)
|
|
|
|
|
|
{
|
|
|
|
|
|
var tags = new List<string>();
|
|
|
|
|
|
foreach (var node in nodes)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (node is CheckboxTreeNode checkboxNode && checkboxNode.IsChecked)
|
|
|
|
|
|
{
|
|
|
|
|
|
tags.Add(checkboxNode.Tag);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 递归查找子节点
|
|
|
|
|
|
tags.AddRange(GetCheckedCheckboxTags(node.Children));
|
|
|
|
|
|
}
|
|
|
|
|
|
return tags;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 获取所有被勾选的Checkbox节点的Tag
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="nodes">当前节点集合</param>
|
|
|
|
|
|
/// <returns>被勾选Checkbox节点的Tag列表</returns>
|
|
|
|
|
|
public List<string> GetNoCheckedCheckboxTags(IEnumerable<TreeNode> nodes)
|
|
|
|
|
|
{
|
|
|
|
|
|
var tags = new List<string>();
|
|
|
|
|
|
foreach (var node in nodes)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (node is CheckboxTreeNode checkboxNode && checkboxNode.IsChecked != true)
|
|
|
|
|
|
{
|
|
|
|
|
|
tags.Add(checkboxNode.Tag);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 递归查找子节点
|
|
|
|
|
|
tags.AddRange(GetCheckedCheckboxTags(node.Children));
|
|
|
|
|
|
}
|
|
|
|
|
|
return tags;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public List<CheckboxTreeNode> GetNoCheckBoxNodes(bool check)
|
|
|
|
|
|
{
|
|
|
|
|
|
return GetAllNodes().OfType<CheckboxTreeNode>().
|
|
|
|
|
|
Where(n =>
|
|
|
|
|
|
(check ? n.IsChecked : !n.IsChecked)
|
|
|
|
|
|
).ToList();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 获取指定Label节点下所有被勾选的Checkbox节点的Tag列表
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="labelNodeName">Label节点的名称</param>
|
|
|
|
|
|
/// <returns>被勾选Checkbox节点的Tag列表</returns>
|
|
|
|
|
|
public List<string> GetCheckboxTagsUnderLabelNode(string labelNodeName)
|
|
|
|
|
|
{
|
|
|
|
|
|
var tags = new List<string>();
|
|
|
|
|
|
|
2025-08-30 21:14:02 +08:00
|
|
|
|
List<LabelTreeNode> getTreeNode = FindTreeNodes<LabelTreeNode>(Nodes, labelNodeName);
|
|
|
|
|
|
|
2025-08-29 14:57:55 +08:00
|
|
|
|
var labelNode = getTreeNode.Count != 0 ? getTreeNode[0] : null;
|
|
|
|
|
|
|
|
|
|
|
|
if (labelNode != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
tags.AddRange(GetCheckedCheckboxTags(labelNode.Children));
|
|
|
|
|
|
}
|
|
|
|
|
|
return tags;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-30 21:14:02 +08:00
|
|
|
|
#endregion
|
2025-08-29 14:57:55 +08:00
|
|
|
|
|
2025-08-30 21:14:02 +08:00
|
|
|
|
#region 查
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 按照指定key路径查找元素(返回一个)
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public TreeNode? FindArgvNode(IEnumerable<TreeNode> nodes, List<string> key) {
|
|
|
|
|
|
if (nodes == null) nodes = Nodes;
|
|
|
|
|
|
if(key.Count == 0) return null;
|
|
|
|
|
|
foreach (var node in nodes)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (node is TreeNode obj &&
|
|
|
|
|
|
obj.Text == key[0] &&
|
|
|
|
|
|
key.Count==1)
|
|
|
|
|
|
{
|
|
|
|
|
|
// 最后的层级,且符合指标的第一个值
|
|
|
|
|
|
return obj;
|
|
|
|
|
|
}
|
2025-08-29 14:57:55 +08:00
|
|
|
|
|
2025-08-30 21:14:02 +08:00
|
|
|
|
var foundNode = FindArgvNode(node.Children, key.Skip(1).ToList());
|
|
|
|
|
|
|
|
|
|
|
|
if (foundNode != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
return foundNode;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
public List<xTreeNode>? FindTreeNodes<xTreeNode>(IEnumerable<TreeNode>? nodes, string key)
|
2025-08-29 14:57:55 +08:00
|
|
|
|
where xTreeNode : class
|
|
|
|
|
|
{
|
2025-08-30 21:14:02 +08:00
|
|
|
|
List<xTreeNode>? result = null;
|
|
|
|
|
|
|
2025-08-29 14:57:55 +08:00
|
|
|
|
if (nodes == null) nodes = Nodes;
|
|
|
|
|
|
|
|
|
|
|
|
foreach (var node in nodes)
|
|
|
|
|
|
{
|
|
|
|
|
|
// 检查当前节点是否匹配
|
2025-08-30 21:14:02 +08:00
|
|
|
|
if (node.Text == key && node is xTreeNode)
|
2025-08-29 14:57:55 +08:00
|
|
|
|
{
|
2025-08-30 21:14:02 +08:00
|
|
|
|
if(result == null) result = new List<xTreeNode>();
|
|
|
|
|
|
result.Add(node as xTreeNode);
|
2025-08-29 14:57:55 +08:00
|
|
|
|
}
|
2025-08-30 21:14:02 +08:00
|
|
|
|
// 跳过空子
|
|
|
|
|
|
if (node.Children.Count == 0)
|
|
|
|
|
|
continue;
|
|
|
|
|
|
// 迭代下层
|
|
|
|
|
|
List<xTreeNode>? res = FindTreeNodes<xTreeNode>(node.Children, key);
|
2025-08-29 14:57:55 +08:00
|
|
|
|
|
2025-08-30 21:14:02 +08:00
|
|
|
|
if (res == null)continue;
|
2025-08-29 14:57:55 +08:00
|
|
|
|
|
2025-08-30 21:14:02 +08:00
|
|
|
|
if (res.Count != 0) {
|
|
|
|
|
|
if (result == null) result = new List<xTreeNode>();
|
|
|
|
|
|
result.AddRange(res);
|
|
|
|
|
|
}
|
2025-08-29 14:57:55 +08:00
|
|
|
|
|
2025-08-30 21:14:02 +08:00
|
|
|
|
}
|
|
|
|
|
|
return result;
|
|
|
|
|
|
}
|
|
|
|
|
|
// 改这个玩意
|
2025-08-29 14:57:55 +08:00
|
|
|
|
public xControl? Find<xTreeNode, xControl>(IEnumerable<TreeNode>? nodes, string key)
|
|
|
|
|
|
where xTreeNode : class
|
|
|
|
|
|
where xControl : DependencyObject
|
|
|
|
|
|
{
|
|
|
|
|
|
if (nodes == null) nodes = Nodes;
|
2025-08-30 21:14:02 +08:00
|
|
|
|
|
|
|
|
|
|
xTreeNode? targetNode = FindTreeNodes<xTreeNode>(nodes, key)[0];
|
|
|
|
|
|
|
|
|
|
|
|
if (targetNode == null)
|
|
|
|
|
|
return null;
|
|
|
|
|
|
else
|
|
|
|
|
|
return To<xTreeNode, xControl>(targetNode);
|
2025-08-29 14:57:55 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-30 21:14:02 +08:00
|
|
|
|
|
2025-08-29 14:57:55 +08:00
|
|
|
|
public xControl? To<xTreeNode, xControl>(xTreeNode node)
|
|
|
|
|
|
where xTreeNode : class
|
|
|
|
|
|
where xControl : DependencyObject
|
|
|
|
|
|
{
|
|
|
|
|
|
var treeViewItem = FindTreeViewItem<xTreeNode>(treeView, node);
|
|
|
|
|
|
|
|
|
|
|
|
if (treeViewItem != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
xControl xCtrl = FindVisualChild<xControl>(treeViewItem);
|
|
|
|
|
|
if (xCtrl != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
return xCtrl;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-30 21:14:02 +08:00
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
private TreeViewItem? FindTreeViewItem<T>(ItemsControl container, T xTreeNode) where T : class
|
2025-08-29 14:57:55 +08:00
|
|
|
|
{
|
|
|
|
|
|
for (int i = 0; i < container.Items.Count; i++)
|
|
|
|
|
|
{
|
2025-08-30 21:14:02 +08:00
|
|
|
|
TreeViewItem? item = container.ItemContainerGenerator.ContainerFromIndex(i) as TreeViewItem;
|
2025-08-29 14:57:55 +08:00
|
|
|
|
if (item != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (item.DataContext == xTreeNode)
|
|
|
|
|
|
{
|
|
|
|
|
|
return item;
|
|
|
|
|
|
}
|
2025-08-30 21:14:02 +08:00
|
|
|
|
TreeViewItem? childItem = FindTreeViewItem<T>(item, xTreeNode);
|
2025-08-29 14:57:55 +08:00
|
|
|
|
if (childItem != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
return childItem;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
private T FindVisualChild<T>(DependencyObject parent) where T : DependencyObject
|
|
|
|
|
|
{
|
|
|
|
|
|
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(parent); i++)
|
|
|
|
|
|
{
|
|
|
|
|
|
DependencyObject child = VisualTreeHelper.GetChild(parent, i);
|
|
|
|
|
|
if (child is T)
|
|
|
|
|
|
{
|
|
|
|
|
|
return (T)child;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
T childOfChild = FindVisualChild<T>(child);
|
|
|
|
|
|
if (childOfChild != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
return childOfChild;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
|
|
#region 删
|
|
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
|
|
#region 动力学
|
|
|
|
|
|
public void Clear()
|
|
|
|
|
|
{
|
|
|
|
|
|
Nodes.Clear();
|
|
|
|
|
|
}
|
|
|
|
|
|
public void ExpandAll()
|
|
|
|
|
|
{
|
|
|
|
|
|
foreach (var node in Nodes)
|
|
|
|
|
|
{
|
|
|
|
|
|
ExpandNode(node);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
public void CollapseAll()
|
|
|
|
|
|
{
|
|
|
|
|
|
foreach (var node in Nodes)
|
|
|
|
|
|
{
|
|
|
|
|
|
CollapseNode(node);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
|
|
#region 私有方法
|
|
|
|
|
|
|
|
|
|
|
|
private void AddNode(TreeNode node, TreeNode parent)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (parent == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
Nodes.Add(node);
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
parent.Children.Add(node);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private IEnumerable<TreeNode> GetAllNodes()
|
|
|
|
|
|
{
|
|
|
|
|
|
return GetAllNodes(Nodes);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private IEnumerable<TreeNode> GetAllNodes(IEnumerable<TreeNode> nodes)
|
|
|
|
|
|
{
|
|
|
|
|
|
foreach (var node in nodes)
|
|
|
|
|
|
{
|
|
|
|
|
|
yield return node;
|
|
|
|
|
|
foreach (var child in GetAllNodes(node.Children))
|
|
|
|
|
|
{
|
|
|
|
|
|
yield return child;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void ExpandNode(TreeNode node)
|
|
|
|
|
|
{
|
|
|
|
|
|
var container = treeView.ItemContainerGenerator.ContainerFromItem(node) as TreeViewItem;
|
|
|
|
|
|
if (container != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
container.IsExpanded = true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
foreach (var child in node.Children)
|
|
|
|
|
|
{
|
|
|
|
|
|
ExpandNode(child);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void CollapseNode(TreeNode node)
|
|
|
|
|
|
{
|
|
|
|
|
|
var container = treeView.ItemContainerGenerator.ContainerFromItem(node) as TreeViewItem;
|
|
|
|
|
|
if (container != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
container.IsExpanded = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
foreach (var child in node.Children)
|
|
|
|
|
|
{
|
|
|
|
|
|
CollapseNode(child);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#region 数据模型
|
|
|
|
|
|
public abstract class TreeNode
|
|
|
|
|
|
{
|
|
|
|
|
|
public TreeNode()
|
|
|
|
|
|
{
|
|
|
|
|
|
Children = new ObservableCollection<TreeNode>();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public string Text { get; set; }
|
|
|
|
|
|
public ObservableCollection<TreeNode> Children { get; set; }
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Label类型的树节点
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public class LabelTreeNode : TreeNode
|
|
|
|
|
|
{
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Label类型的树节点
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public class ComboboxTreeNode : TreeNode
|
|
|
|
|
|
{
|
|
|
|
|
|
public string SelectedItem { get; set; }
|
|
|
|
|
|
public ObservableCollection<string> ComboBoxItems { get; set; } = new ObservableCollection<string>();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Checkbox类型的树节点
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public class CheckboxTreeNode : TreeNode
|
|
|
|
|
|
{
|
|
|
|
|
|
public string Tag { get; set; }
|
|
|
|
|
|
private bool _isChecked;
|
|
|
|
|
|
public bool IsChecked
|
|
|
|
|
|
{
|
|
|
|
|
|
get { return _isChecked; }
|
|
|
|
|
|
set
|
|
|
|
|
|
{
|
|
|
|
|
|
if (_isChecked != value)
|
|
|
|
|
|
{
|
|
|
|
|
|
_isChecked = value;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
}
|