629 lines
19 KiB
C#
629 lines
19 KiB
C#
using Newtonsoft.Json.Linq;
|
|
using System.Collections.ObjectModel;
|
|
using System.ComponentModel;
|
|
using System.Diagnostics;
|
|
using System.Windows;
|
|
using System.Windows.Controls;
|
|
using System.Windows.Media;
|
|
|
|
namespace Ramitta
|
|
{
|
|
public partial class winTreeList : UserControl, INotifyPropertyChanged
|
|
{
|
|
#region 初始化
|
|
public event PropertyChangedEventHandler? PropertyChanged;
|
|
protected void OnPropertyChanged(string propertyName)
|
|
{
|
|
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
|
}
|
|
public winTreeList()
|
|
{
|
|
InitializeComponent();
|
|
Nodes = new ObservableCollection<TreeNode>();
|
|
}
|
|
#endregion
|
|
#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 label = new Label()
|
|
{
|
|
Content = text,
|
|
Foreground = Brushes.White
|
|
|
|
};
|
|
|
|
var node = new LabelTreeNode
|
|
{
|
|
Text = text,
|
|
obj = label
|
|
};
|
|
|
|
AddNode(node, parent);
|
|
return node;
|
|
}
|
|
public CheckboxTreeNode AddCheckboxNode(string text, string? tag = null, bool isChecked = false, TreeNode? parent = null)
|
|
{
|
|
var checkbox = new CheckBox()
|
|
{
|
|
Visibility = Visibility.Visible,
|
|
|
|
IsChecked = isChecked,
|
|
Content = text,
|
|
Tag = tag ?? text,
|
|
ToolTip = tag ?? text,
|
|
Foreground = Brushes.White,
|
|
VerticalAlignment = VerticalAlignment.Center
|
|
};
|
|
|
|
var node = new CheckboxTreeNode
|
|
{
|
|
Text = text,
|
|
obj = checkbox
|
|
};
|
|
|
|
AddNode(node, parent);
|
|
return node;
|
|
}
|
|
|
|
public RadioButtonTreeNode AddRadioButtonNode(string text, string? tag = null, string? GroupName = null, bool isChecked = false, TreeNode? parent = null)
|
|
{
|
|
|
|
var RadioButton = new RadioButton()
|
|
{
|
|
Visibility = Visibility.Visible,
|
|
|
|
GroupName = GroupName,
|
|
IsChecked = isChecked,
|
|
Content = text,
|
|
Tag = tag ?? text,
|
|
ToolTip = tag ?? text,
|
|
Foreground = Brushes.White,
|
|
VerticalAlignment = VerticalAlignment.Center
|
|
};
|
|
|
|
var node = new RadioButtonTreeNode
|
|
{
|
|
Text = text,
|
|
obj = RadioButton
|
|
};
|
|
|
|
AddNode(node, parent);
|
|
return node;
|
|
}
|
|
public ComboboxTreeNode AddComboboxNode(string text, List<string>? items = null, TreeNode? parent = null)
|
|
{
|
|
var combobox = new ComboBox()
|
|
{
|
|
Visibility = Visibility.Visible,
|
|
|
|
SelectedIndex = 0,
|
|
VerticalAlignment = VerticalAlignment.Center,
|
|
MinWidth = 100
|
|
};
|
|
|
|
var node = new ComboboxTreeNode
|
|
{
|
|
Text = text,
|
|
obj = combobox
|
|
};
|
|
|
|
AddNode(node, parent);
|
|
return node;
|
|
}
|
|
#endregion
|
|
#region 查
|
|
public List<xTreeNode> FindAllTreeNodes<xTreeNode>(
|
|
IEnumerable<TreeNode>? nodes,
|
|
Func<TreeNode, bool> predicate = null)
|
|
where xTreeNode : class
|
|
{
|
|
var result = new List<xTreeNode>();
|
|
|
|
if (nodes == null)
|
|
nodes = Nodes;
|
|
|
|
foreach (var node in nodes)
|
|
{
|
|
// 检查当前节点是否匹配类型和条件
|
|
if (node is xTreeNode targetNode &&
|
|
(predicate == null || predicate(node)))
|
|
{
|
|
result.Add(targetNode);
|
|
}
|
|
|
|
// 递归查找子节点
|
|
if (node.Children.Count > 0)
|
|
{
|
|
var childResults = FindAllTreeNodes<xTreeNode>(node.Children, predicate);
|
|
result.AddRange(childResults);
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
#endregion
|
|
#region 删
|
|
/// <summary>
|
|
/// 从树中删除指定节点
|
|
/// </summary>
|
|
/// <param name="nodeToRemove">要删除的节点</param>
|
|
/// <returns>是否成功删除</returns>
|
|
public bool RemoveNode(TreeNode nodeToRemove)
|
|
{
|
|
if (nodeToRemove == null)
|
|
return false;
|
|
|
|
// 如果节点有父节点,从父节点的 Children 中删除
|
|
if (nodeToRemove.Parent != null)
|
|
{
|
|
return nodeToRemove.Parent.Children.Remove(nodeToRemove);
|
|
}
|
|
// 如果是根节点,从 Nodes 集合中删除
|
|
else
|
|
{
|
|
return Nodes.Remove(nodeToRemove);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 批量删除多个节点
|
|
/// </summary>
|
|
/// <param name="nodesToRemove">要删除的节点列表</param>
|
|
public void RemoveNodes(IEnumerable<TreeNode> nodesToRemove)
|
|
{
|
|
foreach (var node in nodesToRemove.ToList()) // 使用 ToList() 避免修改集合时的枚举错误
|
|
{
|
|
RemoveNode(node);
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
#region 序列化
|
|
public JObject JsonPrint(IEnumerable<TreeNode>? nodes = null, int loop = 0)
|
|
{
|
|
// 如果传入的节点为空,使用默认的 Nodes 集合
|
|
if (nodes == null) nodes = Nodes;
|
|
|
|
JObject resultObject = new JObject();
|
|
|
|
foreach (var node in nodes)
|
|
{
|
|
JObject nodeObject = new JObject();
|
|
|
|
// 为当前节点添加基本信息
|
|
nodeObject["Type"] = node.GetType().Name;
|
|
nodeObject["Text"] = node.Text; // 添加文本内容
|
|
|
|
// 使用模式匹配简化类型检查
|
|
switch (node)
|
|
{
|
|
case CheckboxTreeNode checkboxNode:
|
|
nodeObject["IsChecked"] = checkboxNode.obj.IsChecked?.ToString() ?? "false";
|
|
nodeObject["Tag"] = checkboxNode.obj.Tag?.ToString() ?? string.Empty;
|
|
break;
|
|
|
|
case ComboboxTreeNode comboboxNode:
|
|
nodeObject["SelectedItem"] = comboboxNode.obj.SelectedItem?.ToString() ?? string.Empty;
|
|
nodeObject["SelectedIndex"] = comboboxNode.obj.SelectedIndex;
|
|
// 添加所有选项
|
|
var items = new JArray();
|
|
foreach (var item in comboboxNode.obj.Items)
|
|
{
|
|
items.Add(item.ToString());
|
|
}
|
|
nodeObject["Items"] = items;
|
|
break;
|
|
|
|
case LabelTreeNode labelNode:
|
|
nodeObject["Content"] = labelNode.obj.Content?.ToString() ?? string.Empty;
|
|
break;
|
|
}
|
|
|
|
// 处理子节点 - 使用更合适的属性名
|
|
if (node.Children.Count > 0)
|
|
{
|
|
JObject childrenObject = JsonPrint(node.Children, loop + 1);
|
|
nodeObject["Children"] = childrenObject; // 改为更合适的属性名
|
|
}
|
|
|
|
// 使用唯一键名避免重复键问题
|
|
string key = node.Text;
|
|
int counter = 1;
|
|
while (resultObject.ContainsKey(key))
|
|
{
|
|
key = $"{node.Text}_{counter}";
|
|
counter++;
|
|
}
|
|
|
|
resultObject.Add(key, nodeObject);
|
|
}
|
|
|
|
return resultObject;
|
|
}
|
|
|
|
public void JsonParse(string jsonContent)
|
|
{
|
|
// 清空现有数据
|
|
Clear();
|
|
|
|
if (string.IsNullOrWhiteSpace(jsonContent)) return;
|
|
|
|
try
|
|
{
|
|
JObject rootObject = JObject.Parse(jsonContent);
|
|
ParseNodes(rootObject, null);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
// 处理解析异常,可以根据需要记录日志或抛出异常
|
|
Debug.WriteLine($"JSON解析失败: {ex.Message}");
|
|
throw new ArgumentException("无效的JSON格式", ex);
|
|
}
|
|
}
|
|
|
|
private void ParseNodes(JObject jObject, TreeNode parent)
|
|
{
|
|
foreach (var property in jObject.Properties())
|
|
{
|
|
JObject nodeObject = property.Value as JObject;
|
|
if (nodeObject == null)
|
|
continue;
|
|
|
|
// 获取节点类型
|
|
string nodeType = nodeObject["Type"]?.ToString() ?? "";
|
|
string text = nodeObject["Text"]?.ToString() ?? "";
|
|
|
|
TreeNode newNode = null;
|
|
|
|
// 根据类型创建相应的节点
|
|
switch (nodeType)
|
|
{
|
|
case nameof(LabelTreeNode):
|
|
newNode = CreateLabelTreeNode(nodeObject, text);
|
|
|
|
break;
|
|
|
|
case nameof(CheckboxTreeNode):
|
|
newNode = CreateCheckboxTreeNode(nodeObject, text);
|
|
break;
|
|
|
|
case nameof(ComboboxTreeNode):
|
|
newNode = CreateComboboxTreeNode(nodeObject, text);
|
|
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (newNode != null)
|
|
{
|
|
// 添加到树中
|
|
if (parent == null)
|
|
{
|
|
Nodes.Add(newNode);
|
|
}
|
|
else
|
|
{
|
|
newNode.Parent = parent;
|
|
parent.Children.Add(newNode);
|
|
}
|
|
|
|
// 递归处理子节点
|
|
if (nodeObject["Children"] is JObject childrenObject)
|
|
{
|
|
ParseNodes(childrenObject, newNode);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private LabelTreeNode CreateLabelTreeNode(JObject nodeObject, string text)
|
|
{
|
|
var labelNode = new LabelTreeNode
|
|
{
|
|
Text = text,
|
|
obj = new Label
|
|
{
|
|
Content = nodeObject["Content"]?.ToString() ?? text
|
|
}
|
|
};
|
|
return labelNode;
|
|
}
|
|
|
|
private CheckboxTreeNode CreateCheckboxTreeNode(JObject nodeObject, string text)
|
|
{
|
|
bool isChecked = bool.TryParse(nodeObject["IsChecked"]?.ToString(), out var result) && result;
|
|
string tag = nodeObject["Tag"]?.ToString() ?? text;
|
|
|
|
var checkboxNode = new CheckboxTreeNode
|
|
{
|
|
Text = text,
|
|
obj = new CheckBox
|
|
{
|
|
Content = text,
|
|
IsChecked = isChecked,
|
|
Tag = tag,
|
|
ToolTip = tag,
|
|
Foreground = Brushes.White,
|
|
|
|
VerticalAlignment = VerticalAlignment.Center
|
|
}
|
|
};
|
|
return checkboxNode;
|
|
}
|
|
|
|
private ComboboxTreeNode CreateComboboxTreeNode(JObject nodeObject, string text)
|
|
{
|
|
var comboboxNode = new ComboboxTreeNode
|
|
{
|
|
Text = text,
|
|
obj = new ComboBox
|
|
{
|
|
VerticalAlignment = VerticalAlignment.Center,
|
|
MinWidth = 100
|
|
}
|
|
};
|
|
|
|
// 添加选项
|
|
if (nodeObject["Items"] is JArray itemsArray)
|
|
{
|
|
foreach (var item in itemsArray)
|
|
{
|
|
comboboxNode.obj.Items.Add(item.ToString());
|
|
}
|
|
}
|
|
|
|
// 设置选中项
|
|
int selectedIndex = nodeObject["SelectedIndex"]?.Value<int>() ?? 0;
|
|
if (selectedIndex >= 0 && selectedIndex < comboboxNode.obj.Items.Count)
|
|
{
|
|
comboboxNode.obj.SelectedIndex = selectedIndex;
|
|
}
|
|
|
|
return comboboxNode;
|
|
}
|
|
#endregion
|
|
|
|
#region 动力学
|
|
public void Clear()
|
|
{
|
|
Nodes.Clear();
|
|
}
|
|
public void ExpandAll(object? sender = null, RoutedEventArgs? e = null)
|
|
{
|
|
foreach (var node in Nodes)
|
|
{
|
|
ExpandNode(node);
|
|
}
|
|
}
|
|
public void CollapseAll(object? sender = null, RoutedEventArgs? e = null)
|
|
{
|
|
foreach (var node in Nodes)
|
|
{
|
|
CollapseNode(node);
|
|
}
|
|
}
|
|
|
|
public 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);
|
|
}
|
|
}
|
|
|
|
public 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
|
|
#endregion
|
|
|
|
#region 私有方法
|
|
public void AddNode(TreeNode node, TreeNode parent)
|
|
{
|
|
if (parent == null)
|
|
{
|
|
node.Parent = null;
|
|
Nodes.Add(node);
|
|
}
|
|
else
|
|
{
|
|
node.Parent = parent;
|
|
parent.Children.Add(node);
|
|
}
|
|
}
|
|
private IEnumerable<TreeNode> GetAllNodes(IEnumerable<TreeNode>? nodes = null)
|
|
{
|
|
if (nodes == null) nodes = null;
|
|
foreach (var node in nodes)
|
|
{
|
|
yield return node;
|
|
foreach (var child in GetAllNodes(node.Children))
|
|
{
|
|
yield return child;
|
|
}
|
|
}
|
|
}
|
|
#endregion
|
|
}
|
|
|
|
#region 数据模型
|
|
public abstract class TreeNode : INotifyPropertyChanged
|
|
{
|
|
private string _text;
|
|
private ObservableCollection<TreeNode> _children;
|
|
private TreeNode _parent;
|
|
|
|
#region 初始化结构
|
|
public event PropertyChangedEventHandler? PropertyChanged;
|
|
protected void OnPropertyChanged(string propertyName)
|
|
{
|
|
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
|
}
|
|
public TreeNode()
|
|
{
|
|
_children = new ObservableCollection<TreeNode>();
|
|
}
|
|
#endregion
|
|
public string Text
|
|
{
|
|
get => _text;
|
|
set
|
|
{
|
|
if (_text != value)
|
|
{
|
|
_text = value;
|
|
OnPropertyChanged(nameof(Text));
|
|
}
|
|
}
|
|
}
|
|
public ObservableCollection<TreeNode> Children
|
|
{
|
|
get => _children;
|
|
set
|
|
{
|
|
if (_children != value)
|
|
{
|
|
_children = value;
|
|
OnPropertyChanged(nameof(Children));
|
|
}
|
|
}
|
|
}
|
|
|
|
public TreeNode Parent
|
|
{
|
|
get => _parent;
|
|
set
|
|
{
|
|
if (_parent != value)
|
|
{
|
|
// 防止设置自己为自己的父节点
|
|
if (value == this)
|
|
throw new InvalidOperationException("不能将节点设置为自己的父节点");
|
|
|
|
// 防止循环引用(检查祖先链)
|
|
var current = value;
|
|
while (current != null)
|
|
{
|
|
if (current == this)
|
|
throw new InvalidOperationException("检测到循环引用");
|
|
current = current.Parent;
|
|
}
|
|
|
|
_parent = value;
|
|
OnPropertyChanged(nameof(Parent));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
public class LabelTreeNode : TreeNode
|
|
{
|
|
private Label _obj;
|
|
|
|
public Label obj
|
|
{
|
|
get => _obj;
|
|
set
|
|
{
|
|
if (_obj != value)
|
|
{
|
|
_obj = value;
|
|
OnPropertyChanged(nameof(obj));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
public class ComboboxTreeNode : TreeNode
|
|
{
|
|
private ComboBox _obj;
|
|
|
|
public ComboBox obj
|
|
{
|
|
get => _obj;
|
|
set
|
|
{
|
|
if (_obj != value)
|
|
{
|
|
_obj = value;
|
|
OnPropertyChanged(nameof(obj));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
public class CheckboxTreeNode : TreeNode
|
|
{
|
|
private CheckBox _obj;
|
|
|
|
public CheckBox obj
|
|
{
|
|
get => _obj;
|
|
set
|
|
{
|
|
if (_obj != value)
|
|
{
|
|
_obj = value;
|
|
OnPropertyChanged(nameof(obj));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public class RadioButtonTreeNode : TreeNode
|
|
{
|
|
private RadioButton _obj;
|
|
|
|
public RadioButton obj
|
|
{
|
|
get => _obj;
|
|
set
|
|
{
|
|
if (_obj != value)
|
|
{
|
|
_obj = value;
|
|
OnPropertyChanged(nameof(obj));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endregion
|
|
} |