842 lines
31 KiB
C#
842 lines
31 KiB
C#
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();
|
||
}
|
||
}
|
||
}
|