BDU/ATS/Views/CommandTreeView.xaml.cs

842 lines
31 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using BDU.Logic;
using BDU.Models;
using BDU.Tools;
using BDU.Windows;
using Common.Attributes;
using GongSolutions.Wpf.DragDrop.Utilities;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
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.Xml;
using static BDU.Models.ParameterModel;
using static Common.Attributes.ATSCommandAttribute;
using Path = System.IO.Path;
namespace BDU.Views
{
/// <summary>
/// CommandTreeView.xaml 的交互逻辑
/// </summary>
public partial class CommandTreeView : UserControl
{
#region
public static CommandTreeView? Instance { get; private set; }
public ObservableCollection<Assembly> Assemblies { get; set; } = [];
public ObservableCollection<SubProgramItem> SubPrograms { get; set; } = [];
public ProgramModel Program => MainWindow.Instance.Program ?? new();
private Dictionary<object, TreeViewItem> _treeViewItemMap = [];
private TreeViewItem _subProgramRootNode;
private readonly Dictionary<string, XmlDocument> _xmlDocumentCache = [];
#endregion
public CommandTreeView()
{
InitializeComponent();
Instance = this;
DataContext = this;
}
private void UserControl_Loaded(object sender, RoutedEventArgs e)
{
Directory.CreateDirectory(SystemConfig.Instance.DLLFilePath);
Directory.CreateDirectory(SystemConfig.Instance.SubProgramFilePath);
LoadAllAssemblies();
LoadSubPrograms();
LoadInstructionsToTreeView();
}
#region
/// <summary>
/// 加载指定目录下的所有DLL
/// </summary>
private void LoadAllAssemblies()
{
Assemblies.Clear();
foreach (var dllPath in Directory.GetFiles(SystemConfig.Instance.DLLFilePath, "*.dll"))
{
try
{
var assembly = Assembly.LoadFrom(dllPath);
Assemblies.Add(assembly);
// 加载对应的XML注释文件
string xmlPath = Path.ChangeExtension(dllPath, ".xml");
if (File.Exists(xmlPath))
{
try
{
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.Load(xmlPath);
_xmlDocumentCache[assembly.FullName!] = xmlDoc;
}
catch (Exception xmlEx)
{
Log.Warning($"加载XML注释失败: {Path.GetFileName(xmlPath)} - {xmlEx.Message}");
}
}
}
catch (Exception ex)
{
Log.Warning($"无法加载程序集 {Path.GetFileName(dllPath)}: {ex.Message}");
}
}
}
// 添加获取注释的方法
private string? GetMethodDocumentation(MethodInfo method)
{
if (method.DeclaringType == null) return null;
try
{
string assemblyName = method.DeclaringType.Assembly.FullName!;
if (!_xmlDocumentCache.TryGetValue(assemblyName, out XmlDocument? xmlDoc)) return null;
// 生成XML文档中的成员ID
string memberName = $"M:{method.DeclaringType.FullName}.{method.Name}";
var parameters = method.GetParameters();
if (parameters.Length > 0)
{
memberName += "(" + string.Join(",", parameters.Select(p => p.ParameterType.FullName?.Replace('+', '.'))) + ")";
}
// 查找注释节点
XmlNode memberNode = xmlDoc.SelectSingleNode($"//member[@name='{memberName}']")!;
if (memberNode == null) return null;
// 获取摘要summary
var summaryNode = memberNode.SelectSingleNode("summary");
string documentation = "";
if (summaryNode != null)
{
documentation += CleanXmlContent(summaryNode.InnerXml);
}
// 获取参数注释param
var paramNodes = memberNode.SelectNodes("param");
if (paramNodes != null && paramNodes.Count > 0)
{
documentation += "\n\n参数:";
foreach (XmlNode paramNode in paramNodes)
{
string? paramName = paramNode.Attributes?["name"]?.Value;
if (!string.IsNullOrEmpty(paramName))
{
documentation += $"\n • {paramName}: {CleanXmlContent(paramNode.InnerXml)}";
}
}
}
// 获取返回值注释returns
var returnsNode = memberNode.SelectSingleNode("returns");
if (returnsNode != null)
{
documentation += $"\n\n返回值: {CleanXmlContent(returnsNode.InnerXml)}";
}
return string.IsNullOrWhiteSpace(documentation)
? null
: System.Net.WebUtility.HtmlDecode(documentation.Trim());
}
catch (Exception ex)
{
Log.Warning($"获取注释失败: {method.Name} - {ex.Message}");
return null;
}
}
// 辅助方法清理XML内容
private string CleanXmlContent(string xmlContent)
{
return xmlContent
.Replace("<see cref=\"", "")
.Replace("\"/>", "")
.Replace("<para>", "\n")
.Replace("</para>", "")
.Replace("<seealso", "")
.Replace("/>", "")
.Replace("<c>", "") // 处理代码标签
.Replace("</c>", "")
.Replace("<code>", "")
.Replace("</code>", "")
.Trim();
}
// 子程序加载方法
private void LoadSubPrograms()
{
SubPrograms.Clear();
if (!Directory.Exists(SystemConfig.Instance.SubProgramFilePath))
{
Directory.CreateDirectory(SystemConfig.Instance.SubProgramFilePath);
return;
}
foreach (var filePath in Directory.GetFiles(SystemConfig.Instance.SubProgramFilePath, "*.ats"))
{
try
{
SubPrograms.Add(new SubProgramItem
{
Name = Path.GetFileNameWithoutExtension(filePath),
FilePath = filePath
});
}
catch (Exception ex)
{
Log.Warning($"加载子程序错误: {filePath} - {ex.Message}");
}
}
}
// ATS/Views/CommandTreeView.xaml.cs - 修正 LoadInstructionsToTreeView 方法
/// <summary>
/// 加载指令集到TreeView
/// </summary>
private void LoadInstructionsToTreeView()
{
InstructionTreeView.Items.Clear();
_treeViewItemMap.Clear(); // 清空旧的映射
// 添加控制指令节点
var controlRootNode = new TreeViewItem
{
Header = "系统指令",
Tag = "ControlRoot"
};
InstructionTreeView.Items.Add(controlRootNode);
// 添加循环开始节点
var loopStartNode = new TreeViewItem
{
Header = "循环开始",
Tag = "循环开始"
};
controlRootNode.Items.Add(loopStartNode);
// 添加循环结束节点
var loopEndNode = new TreeViewItem
{
Header = "循环结束",
Tag = "循环结束"
};
controlRootNode.Items.Add(loopEndNode);
// 创建子程序根节点
_subProgramRootNode = new TreeViewItem
{
Header = "子程序",
Tag = "SubProgramRoot"
};
InstructionTreeView.Items.Add(_subProgramRootNode);
// 添加子程序节点
foreach (var subProgram in SubPrograms)
{
var subProgramNode = new TreeViewItem
{
Header = subProgram.Name,
Tag = subProgram,
ToolTip = subProgram.FilePath
};
_subProgramRootNode.Items.Add(subProgramNode);
}
// 添加dll中的方法指令 - 按分类组织
foreach (var assembly in Assemblies)
{
List<Type> validTypes = new List<Type>();
try
{
// 类型过滤条件
var types = assembly.GetTypes().Where(t =>
t.IsPublic &&
!t.IsNested &&
(t.IsClass || t.IsValueType) &&
(!t.IsAbstract || t.IsSealed) &&
t.GetCustomAttribute<ATSCommandAttribute>() != null);
foreach (var type in types)
{
if (type.GetCustomAttribute<BrowsableAttribute>()?.Browsable == false)
continue;
// 检查类型是否有有效方法(包括继承的方法)
var allMethods = new HashSet<MethodInfo>();
GetPublicMethods(type, allMethods);
if (allMethods.Count > 0)
{
validTypes.Add(type);
}
}
}
catch (Exception ex)
{
Log.Error($"加载类型错误: {assembly.FullName} - {ex.Message}");
}
// 只有当程序集包含有效类型时才添加节点
if (validTypes.Count > 0)
{
var assemblyNode = new TreeViewItem
{
Header = assembly.GetName().Name,
Tag = assembly
};
InstructionTreeView.Items.Add(assemblyNode);
_treeViewItemMap[assembly] = assemblyNode;
// 按分类组织类型 - 修改这部分逻辑
foreach (var type in validTypes)
{
// 获取该类型的所有 DeviceCategoryAttribute 实例
var categoryAttributes = type.GetCustomAttributes<DeviceCategoryAttribute>().ToArray();
// 获取所有分类名称,如果没有则归为 "未分类"
string[] categories = categoryAttributes.Length > 0 ?
categoryAttributes.Select(attr => attr.Category).ToArray() :
new[] { "未分类" };
// 获取该类型的所有公共方法,供后续在不同分类下复用
var allMethods = new HashSet<MethodInfo>();
GetPublicMethods(type, allMethods);
// 为该类型的每个分类创建节点
foreach (var category in categories)
{
// 检查该程序集下是否已存在该分类节点
var categoryNode = assemblyNode.Items.OfType<TreeViewItem>()
.FirstOrDefault(item => item.Header?.ToString() == category);
// 如果不存在,则创建新的分类节点
if (categoryNode == null)
{
categoryNode = new TreeViewItem
{
Header = category,
Tag = $"Category_{category}"
};
assemblyNode.Items.Add(categoryNode);
}
// 为当前分类创建一个 typeNode 实例
var typeNode = new TreeViewItem
{
Header = type.Name,
Tag = type, // Tag 仍然指向原始类型,方便后续处理
ToolTip = type.FullName
};
// 添加到当前分类节点
categoryNode.Items.Add(typeNode);
// 重要:将这个 *特定的* TreeViewItem 实例映射到 *原始的* Type 对象
// 这样在双击时可以通过 SelectedItem.Tag 找到原始 Type
// 如果有多个分类,最后添加的那个分类下的 typeNode 会覆盖前面的映射
// 如果需要精确映射到具体哪个分类下的节点,需要更复杂的结构
_treeViewItemMap[type] = typeNode;
// 为当前分类下的 typeNode 创建方法节点
foreach (var method in allMethods)
{
// 跳过特殊名称方法(属性访问器等)
if (method.IsSpecialName) continue;
// 跳过系统对象方法
if (method.DeclaringType == typeof(object)) continue;
// 跳过特定名称的系统方法
string[] ignoreMethods = { "GetType", "ToString", "Equals", "GetHashCode" };
if (ignoreMethods.Contains(method.Name)) continue;
// 跳过不可浏览的方法
if (method.GetCustomAttribute<BrowsableAttribute>()?.Browsable == false) continue;
// 对于静态类,只显示静态方法
if (type.IsAbstract && type.IsSealed && !method.IsStatic) continue;
var parameters = method.GetParameters();
var paramText = string.Join(", ", parameters.Select(p => $"{p.ParameterType.Name} {p.Name}"));
// 获取方法注释
string summary = GetMethodDocumentation(method) ?? "";
string tooltipContent = $"{method.DeclaringType?.FullName}.{method.Name}";
if (!string.IsNullOrEmpty(summary)) tooltipContent = $"{tooltipContent}\n\n{summary}";
// 为当前分类下的 typeNode 创建一个 methodNode 实例
var methodNode = new TreeViewItem
{
Header = $"{method.Name}({paramText})",
Tag = new MethodInfoWrapper(method),
ToolTip = tooltipContent
};
// 添加到当前分类下的 typeNode
typeNode.Items.Add(methodNode);
// 重要:将这个 *特定的* TreeViewItem 实例映射到 *原始的* MethodInfo 对象
// 如果有多个分类,最后添加的那个分类下的 methodNode 会覆盖前面的映射
// 如果需要精确映射到具体哪个分类下的节点,需要更复杂的结构
_treeViewItemMap[method] = methodNode;
}
}
}
}
}
}
/// <summary>
/// 递归获取类型的所有公共方法(包括继承的方法),但跳过被重写的方法
/// </summary>
/// <param name="type">要处理的目标类型</param>
/// <param name="methods">存储方法的集合</param>
private void GetPublicMethods(Type type, HashSet<MethodInfo> methods)
{
// 获取当前类型的所有公共方法(包括继承的)
var allMethods = type.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static)
.Where(m => !m.IsSpecialName &&
m.DeclaringType != typeof(object))
.ToList();
// 按方法签名分组
var groupedMethods = allMethods
.GroupBy(m => new { m.Name, Parameters = string.Join(",", m.GetParameters().Select(p => p.ParameterType.FullName)) });
foreach (var group in groupedMethods)
{
// 从组中选择声明类型最接近当前类型(即继承层次最深)的方法
MethodInfo? selectedMethod = null;
int minDepth = int.MaxValue;
foreach (var method in group)
{
// 计算声明类型的深度
int depth = 0;
Type? current = type;
Type declaringType = method.DeclaringType!;
while (current != null && current != declaringType)
{
depth++;
current = current.BaseType;
}
// 如果找到声明类型且在继承链上
if (current == declaringType)
{
if (selectedMethod == null || depth < minDepth)
{
selectedMethod = method;
minDepth = depth;
}
}
}
if (selectedMethod != null)
{
methods.Add(selectedMethod);
}
}
}
private void ReOrderProgramList()
{
for (int i = 0; i < Program.StepCollection.Count; i++)
{
Program.StepCollection[i].Index = i + 1;
}
}
private void UnselectTreeViewItems(ItemsControl itemsControl)
{
for (int i = 0; i < itemsControl.Items.Count; i++)
{
var item = itemsControl.Items[i];
if (itemsControl.ItemContainerGenerator.ContainerFromItem(item) is TreeViewItem container)
{
container.IsSelected = false;
UnselectTreeViewItems(container);
}
}
}
#region
private void AddMethodToProgram(MethodInfo method, int insertIndex = -1)
{
try
{
var newStep = new StepModel
{
Name = method.Name,
StepType = "方法",
Method = new MethodModel
{
FullName = method.DeclaringType?.FullName,
Name = method.Name
}
};
// 添加输入参数
foreach (var param in method.GetParameters())
{
newStep.Method.Parameters.Add(new ParameterModel
{
Name = param.Name!,
Type = param.ParameterType,
Category = ParameterCategory.Input
});
}
// 添加输出参数(返回值)
Type returnType = method.ReturnType;
if (returnType == typeof(Task))
{
// 不添加输出参数(无返回值)
}
else if (returnType.IsGenericType &&
returnType.GetGenericTypeDefinition() == typeof(Task<>))
{
// 提取实际返回类型(如 Task<bool> -> bool
Type actualType = returnType.GetGenericArguments()[0];
newStep.Method.Parameters.Add(new ParameterModel
{
Name = "Result",
Type = actualType, // 使用实际类型
Category = ParameterCategory.Output
});
}
else if (returnType != typeof(void))
{
// 同步方法正常添加
newStep.Method.Parameters.Add(new ParameterModel
{
Name = "Result",
Type = returnType,
Category = ParameterCategory.Output
});
}
// 添加到程序
if (insertIndex >= 0 && insertIndex <= Program.StepCollection.Count)
{
Program.StepCollection.Insert(insertIndex, newStep);
}
else
{
Program.StepCollection.Add(newStep);
}
ReOrderProgramList();
}
catch (Exception ex)
{
Log.Error($"添加方法失败: {method.Name} - {ex.Message}");
}
}
private void AddSubProgramToProgram(SubProgramItem subProgram, int insertIndex = -1)
{
try
{
var newStep = new StepModel
{
Name = subProgram.Name,
StepType = "子程序"
};
var jsonstr = File.ReadAllText($"{subProgram.FilePath}");
var tmp = JsonConvert.DeserializeObject<ProgramModel>(jsonstr);
if (tmp != null)
{
if (tmp.Devices != null && tmp.Devices.Count > 0)
{
foreach (var device in tmp.Devices)
{
_ = DeviceConnect.InitAndConnectDevice(tmp, device);
}
}
newStep.SubProgram = tmp;
}
// 添加到程序
if (insertIndex >= 0 && insertIndex <= Program.StepCollection.Count)
{
Program.StepCollection.Insert(insertIndex, newStep);
}
else
{
Program.StepCollection.Add(newStep);
}
ReOrderProgramList();
}
catch (Exception ex)
{
Log.Error($"添加子程序失败: {subProgram.Name} - {ex.Message}");
}
}
private void AddLoopStartStep(int insertIndex = -1)
{
var newStep = new StepModel
{
Name = "循环开始",
StepType = "循环开始",
LoopCount = 1,
Method = new() { Parameters = [new() { Name = "循环次数", Type = typeof(int), Category = ParameterCategory.Input }] }
};
if (insertIndex >= 0)
{
Program.StepCollection.Insert(insertIndex, newStep);
}
else
{
Program.StepCollection.Add(newStep);
}
ReOrderProgramList();
}
private void AddLoopEndStep(int insertIndex = -1)
{
// 查找最近的未匹配循环开始
StepModel? lastUnmatchedLoopStart = null;
for (int i = Program.StepCollection.Count - 1; i >= 0; i--)
{
if (Program.StepCollection[i].StepType == "循环开始")
{
bool isMatched = Program.StepCollection.Any(s => s.StepType == "循环结束" && s.LoopStartStepId == Program.StepCollection[i].ID);
if (!isMatched)
{
lastUnmatchedLoopStart = Program.StepCollection[i];
break;
}
}
}
var newStep = new StepModel
{
Name = "循环结束",
StepType = "循环结束",
LoopStartStepId = lastUnmatchedLoopStart?.ID
};
if (insertIndex >= 0)
{
Program.StepCollection.Insert(insertIndex, newStep);
}
else
{
Program.StepCollection.Add(newStep);
}
ReOrderProgramList();
}
#endregion
#endregion
#region
private class MethodInfoWrapper
{
public MethodInfo Method { get; }
public MethodInfoWrapper(MethodInfo method)
{
Method = method;
}
}
#endregion
private void InstructionTreeView_MouseDoubleClick(object sender, MouseButtonEventArgs e)
{
if (MainWindow.Instance.User.Role < 1)
{
MessageBox.Show("当前登录用户无权限");
return;
}
if (InstructionTreeView.SelectedItem is TreeViewItem selectedItem)
{
int insertIndex = -1;
if (StepsManager.Instance!.SelectedIndex >= 0)
{
insertIndex = StepsManager.Instance.SelectedIndex + 1;
}
if (selectedItem.Tag is string tagString)
{
if (tagString == "循环开始")
{
AddLoopStartStep(insertIndex);
}
else if (tagString == "循环结束")
{
AddLoopEndStep(insertIndex);
}
}
else if (selectedItem.Tag is MethodInfoWrapper wrapper)
{
AddMethodToProgram(wrapper.Method, insertIndex);
}
else if (selectedItem.Tag is MethodInfo methodInfo)
{
AddMethodToProgram(methodInfo, insertIndex);
}
else if (selectedItem.Tag is SubProgramItem subProgram)
{
AddSubProgramToProgram(subProgram, insertIndex);
}
StepsManager.Instance.SelectedIndex = insertIndex;
UnselectTreeViewItems(InstructionTreeView);
}
}
private void RelodCommand_Click(object sender, RoutedEventArgs e)
{
LoadAllAssemblies();
LoadSubPrograms();
LoadInstructionsToTreeView();
}
// 在类中添加变量
private Dictionary<TreeViewItem, Brush> _originalBrushes = new();
private HashSet<TreeViewItem> _matchedItems = new();
// 添加搜索方法
private void SearchTreeView(string searchText)
{
// 清除上次搜索结果
ClearSearchHighlighting();
if (string.IsNullOrWhiteSpace(searchText)) ClearSearchButton_Click(null, null);
searchText = searchText.Trim().ToLower();
// 遍历所有节点
foreach (var item in InstructionTreeView.Items.OfType<TreeViewItem>())
{
SearchTreeViewItem(item, searchText);
}
// 展开所有包含匹配项的节点
foreach (var matchedItem in _matchedItems.ToList())
{
ExpandParents(matchedItem);
}
}
private void SearchTreeViewItem(TreeViewItem item, string searchText)
{
// 检查当前节点是否匹配
bool isMatch = false;
if (string.IsNullOrEmpty(searchText))
{
}
else if (item.Header is string headerText && headerText.Split("(")[0].ToLower().Contains(searchText))
{
isMatch = true;
}
if (isMatch)
{
// 保存原始背景色并设置高亮
if (!_originalBrushes.ContainsKey(item))
{
_originalBrushes[item] = item.Background;
}
item.Background = Brushes.Yellow;
_matchedItems.Add(item);
}
// 递归搜索子节点
foreach (var childItem in item.Items.OfType<TreeViewItem>())
{
SearchTreeViewItem(childItem, searchText);
}
}
private void ClearSearchHighlighting()
{
foreach (var item in _matchedItems)
{
if (_originalBrushes.TryGetValue(item, out var originalBrush))
{
item.Background = originalBrush;
}
else
{
item.ClearValue(TreeViewItem.BackgroundProperty);
}
}
_matchedItems.Clear();
_originalBrushes.Clear();
}
private void ExpandParents(TreeViewItem item)
{
var parent = ItemsControl.ItemsControlFromItemContainer(item) as TreeViewItem;
while (parent != null)
{
parent.IsExpanded = true;
parent = ItemsControl.ItemsControlFromItemContainer(parent) as TreeViewItem;
}
}
// 添加事件处理方法
private void SearchTextBox_TextChanged(object sender, TextChangedEventArgs e)
{
if (string.IsNullOrWhiteSpace(SearchTextBox.Text))
{
ClearSearchHighlighting();
}
}
private void SearchTextBox_KeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.Enter)
{
SearchTreeView(SearchTextBox.Text);
}
}
private void ClearSearchButton_Click(object sender, RoutedEventArgs e)
{
SearchTextBox.Clear();
ClearSearchHighlighting();
}
}
}