591 lines
21 KiB
C#
591 lines
21 KiB
C#
using UIShare.UIViewModel;
|
||
using UIShare.GlobalVariable;
|
||
using Common.Attributes;
|
||
using Logger;
|
||
using Microsoft.IdentityModel.Logging;
|
||
using Newtonsoft.Json;
|
||
using System;
|
||
using System.Collections.Generic;
|
||
using System.Collections.ObjectModel;
|
||
using System.ComponentModel;
|
||
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.Input;
|
||
using System.Xml;
|
||
using Model;
|
||
using static UIShare.UIViewModel.ParameterModel;
|
||
using UIShare.ViewModelBase;
|
||
using NLog;
|
||
|
||
namespace TestingModule.ViewModels
|
||
{
|
||
public class CommandTreeViewModel:NavigateViewModelBase
|
||
{
|
||
#region 属性
|
||
private string _SearchText;
|
||
|
||
public string SearchText
|
||
{
|
||
get => _SearchText;
|
||
set => SetProperty(ref _SearchText, value);
|
||
}
|
||
private ObservableCollection<InstructionNode> _instructionTree = new();
|
||
public ObservableCollection<InstructionNode> InstructionTree
|
||
{
|
||
get => _instructionTree;
|
||
set => SetProperty(ref _instructionTree, value);
|
||
}
|
||
|
||
public ProgramModel Program
|
||
{
|
||
get => _ScopedContext.Program;
|
||
set
|
||
{
|
||
if (_ScopedContext.Program != value)
|
||
{
|
||
_ScopedContext.Program = value;
|
||
RaisePropertyChanged();
|
||
}
|
||
}
|
||
}
|
||
|
||
public ObservableCollection<Assembly> Assemblies
|
||
{
|
||
get => _ScopedContext.Assemblies;
|
||
set
|
||
{
|
||
if (_ScopedContext.Assemblies != value)
|
||
{
|
||
_ScopedContext.Assemblies = value;
|
||
RaisePropertyChanged();
|
||
}
|
||
}
|
||
}
|
||
|
||
private ObservableCollection<SubProgramItem> _subPrograms = new();
|
||
public ObservableCollection<SubProgramItem> SubPrograms
|
||
{
|
||
get => _subPrograms;
|
||
set => SetProperty(ref _subPrograms, value);
|
||
}
|
||
|
||
private Dictionary<object, InstructionNode> _treeNodeMap = new();
|
||
public Dictionary<object, InstructionNode> TreeNodeMap
|
||
{
|
||
get => _treeNodeMap;
|
||
set => SetProperty(ref _treeNodeMap, value);
|
||
}
|
||
|
||
private Dictionary<string, XmlDocument> _xmlDocumentCache = new();
|
||
|
||
public Dictionary<string, XmlDocument> XmlDocumentCache
|
||
{
|
||
get => _xmlDocumentCache;
|
||
set => SetProperty(ref _xmlDocumentCache, value);
|
||
}
|
||
#endregion
|
||
public ICommand LoadedCommand { get; set; }
|
||
public ICommand SearchEnterCommand { get; set; }
|
||
public ICommand TreeDoubleClickCommand { get; set; }
|
||
public ICommand ReloadCommand { get; set; }
|
||
private ScopedContext _ScopedContext { get; set; }
|
||
private readonly SystemConfig _systemConfig;
|
||
private readonly GlobalInfo _globalInfo;
|
||
|
||
public CommandTreeViewModel(IContainerProvider containerProvider, ScopedContext scopedContext, SystemConfig systemConfig, GlobalInfo globalInfo) : base(containerProvider)
|
||
{
|
||
_ScopedContext = scopedContext;
|
||
_systemConfig = systemConfig;
|
||
_globalInfo = globalInfo;
|
||
LoadedCommand = new DelegateCommand(Loaded);
|
||
SearchEnterCommand = new DelegateCommand(Search);
|
||
TreeDoubleClickCommand = new DelegateCommand<object>(TreeDoubleClick);
|
||
ReloadCommand = new DelegateCommand(Reload);
|
||
}
|
||
|
||
|
||
#region 委托命令
|
||
private void Reload()
|
||
{
|
||
LoadAllAssemblies();
|
||
LoadSubPrograms();
|
||
LoadInstructionsToTreeView();
|
||
}
|
||
private void TreeDoubleClick(object obj)
|
||
{
|
||
if (!_globalInfo.IsAdmin) return;
|
||
if(obj is InstructionNode Node)
|
||
{
|
||
if(Node.Children.Count == 0)
|
||
{
|
||
int index = _ScopedContext.SelectedStep?.Index >= 0 ? _ScopedContext.SelectedStep.Index : -1;
|
||
if (Node.Tag is MethodInfo method)
|
||
{
|
||
AddMethodToProgram(method, index);
|
||
}
|
||
else if(Node.Tag is string tag)
|
||
{
|
||
switch (tag)
|
||
{
|
||
case "循环开始":
|
||
AddLoopStartStep(index);
|
||
break;
|
||
case "循环结束":
|
||
AddLoopEndStep(index);
|
||
break;
|
||
}
|
||
}
|
||
else if(Node.Tag is SubProgramItem subProgram)
|
||
{
|
||
AddSubProgramToProgram(subProgram, index);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
private void Search()
|
||
{
|
||
|
||
}
|
||
|
||
private void Loaded()
|
||
{
|
||
LoadAllAssemblies();
|
||
LoadSubPrograms();
|
||
LoadInstructionsToTreeView();
|
||
}
|
||
#endregion
|
||
#region 加载指令
|
||
/// <summary>
|
||
/// 加载指定目录下的所有DLL
|
||
/// </summary>
|
||
private void LoadAllAssemblies()
|
||
{
|
||
Assemblies.Clear();
|
||
|
||
foreach (var dllPath in Directory.GetFiles(_systemConfig.DLLFilePath, "*.dll"))
|
||
{
|
||
try
|
||
{
|
||
var assembly = Assembly.LoadFile(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)
|
||
// {
|
||
// LoggerHelper.WarnWithNotify($"加载XML注释失败: {Path.GetFileName(xmlPath)} - {xmlEx.Message}");
|
||
// }
|
||
//}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LoggerHelper.WarnWithNotify($"无法加载程序集 {Path.GetFileName(dllPath)}: {ex.Message}");
|
||
}
|
||
}
|
||
}
|
||
// 子程序加载方法
|
||
private void LoadSubPrograms()
|
||
{
|
||
SubPrograms.Clear();
|
||
|
||
if (!Directory.Exists(_systemConfig.SubProgramFilePath))
|
||
{
|
||
Directory.CreateDirectory(_systemConfig.SubProgramFilePath);
|
||
return;
|
||
}
|
||
|
||
foreach (var filePath in Directory.GetFiles(_systemConfig.SubProgramFilePath, "*.adp"))
|
||
{
|
||
try
|
||
{
|
||
SubPrograms.Add(new SubProgramItem
|
||
{
|
||
Name = Path.GetFileNameWithoutExtension(filePath),
|
||
FilePath = filePath
|
||
});
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LoggerHelper.WarnWithNotify($"加载子程序错误: {filePath} - {ex.Message}");
|
||
}
|
||
}
|
||
}
|
||
/// <summary>
|
||
/// 加载指令集到TreeView
|
||
/// </summary>
|
||
private void LoadInstructionsToTreeView()
|
||
{
|
||
InstructionTree.Clear();
|
||
var controlRootNode = new InstructionNode
|
||
{
|
||
Name = "系统指令",
|
||
Tag = "ControlRoot"
|
||
};
|
||
InstructionTree.Add(controlRootNode);
|
||
|
||
// 循环开始
|
||
controlRootNode.Children.Add(new InstructionNode
|
||
{
|
||
Name = "循环开始",
|
||
Tag = "循环开始"
|
||
});
|
||
|
||
// 循环结束
|
||
controlRootNode.Children.Add(new InstructionNode
|
||
{
|
||
Name = "循环结束",
|
||
Tag = "循环结束"
|
||
});
|
||
|
||
// ----------------------
|
||
// 子程序 根节点
|
||
// ----------------------
|
||
var subProgramRoot = new InstructionNode
|
||
{
|
||
Name = "子程序",
|
||
Tag = "SubProgramRoot"
|
||
};
|
||
InstructionTree.Add(subProgramRoot);
|
||
|
||
foreach (var subProgram in SubPrograms)
|
||
{
|
||
subProgramRoot.Children.Add(new InstructionNode
|
||
{
|
||
Name = subProgram.Name,
|
||
Tag = subProgram,
|
||
});
|
||
}
|
||
|
||
// ----------------------
|
||
// 动态 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<ADPCommandAttribute>() != 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)
|
||
{
|
||
LoggerHelper.ErrorWithNotify($"加载类型错误: {assembly.FullName} - {ex.Message}");
|
||
}
|
||
|
||
if (validTypes.Count > 0)
|
||
{
|
||
var assemblyNode = new InstructionNode
|
||
{
|
||
Name = assembly.GetName().Name,
|
||
Tag = assembly
|
||
};
|
||
InstructionTree.Add(assemblyNode);
|
||
TreeNodeMap[assembly] = assemblyNode;
|
||
|
||
foreach (var type in validTypes)
|
||
{
|
||
//拦截没有在设备列表中的设备类型
|
||
//if (SystemConfig.Instance.DeviceList.Where(x=>x.Remark==type.Name).ToList().Count==0 && type.FullName.Contains("DeviceCommand.Device"))
|
||
//{
|
||
// continue;
|
||
//}
|
||
|
||
var typeNode = new InstructionNode
|
||
{
|
||
Name = type.Name,
|
||
Tag = type,
|
||
};
|
||
assemblyNode.Children.Add(typeNode);
|
||
TreeNodeMap[type] = typeNode;
|
||
|
||
var allMethods = new HashSet<MethodInfo>();
|
||
GetPublicMethods(type, allMethods);
|
||
|
||
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}"));
|
||
|
||
var methodNode = new InstructionNode
|
||
{
|
||
Name = $"{method.Name}({paramText})",
|
||
Tag = method,
|
||
};
|
||
|
||
typeNode.Children.Add(methodNode);
|
||
TreeNodeMap[method] = methodNode;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
#endregion
|
||
#region 辅助方法
|
||
/// <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 | BindingFlags.DeclaredOnly)
|
||
.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);
|
||
}
|
||
}
|
||
}
|
||
#endregion
|
||
#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(_ScopedContext.SelectedStepList == "主程序")
|
||
{
|
||
if(insertIndex >= 0 && insertIndex <= Program.StepCollection.Count) Program.StepCollection.Insert(insertIndex, newStep);
|
||
else Program.StepCollection.Add(newStep); }
|
||
else
|
||
{
|
||
if (insertIndex >= 0 && insertIndex <= Program.ErrorStepCollection.Count) Program.ErrorStepCollection.Insert(insertIndex, newStep);
|
||
else Program.ErrorStepCollection.Add(newStep);
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LoggerHelper.ErrorWithNotify($"添加方法失败: {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)
|
||
{
|
||
newStep.SubProgram = tmp;
|
||
}
|
||
// 添加到程序
|
||
if (_ScopedContext.SelectedStepList == "主程序")
|
||
{
|
||
if (insertIndex >= 0 && insertIndex <= Program.StepCollection.Count) Program.StepCollection.Insert(insertIndex, newStep);
|
||
else Program.StepCollection.Add(newStep);
|
||
}
|
||
else
|
||
{
|
||
if (insertIndex >= 0 && insertIndex <= Program.ErrorStepCollection.Count) Program.ErrorStepCollection.Insert(insertIndex, newStep);
|
||
else Program.ErrorStepCollection.Add(newStep);
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LoggerHelper.ErrorWithNotify($"添加子程序失败: {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 (_ScopedContext.SelectedStepList == "主程序")
|
||
{
|
||
if (insertIndex >= 0) Program.StepCollection.Insert(insertIndex, newStep);
|
||
else Program.StepCollection.Add(newStep);
|
||
}
|
||
else
|
||
{
|
||
if (insertIndex >= 0) Program.ErrorStepCollection.Insert(insertIndex, newStep);
|
||
else Program.ErrorStepCollection.Add(newStep);
|
||
}
|
||
}
|
||
|
||
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 (_ScopedContext.SelectedStepList == "主程序")
|
||
{
|
||
if (insertIndex >= 0) Program.StepCollection.Insert(insertIndex, newStep);
|
||
else Program.StepCollection.Add(newStep);
|
||
}
|
||
else
|
||
{
|
||
if (insertIndex >= 0) Program.ErrorStepCollection.Insert(insertIndex, newStep);
|
||
else Program.ErrorStepCollection.Add(newStep);
|
||
}
|
||
}
|
||
|
||
#endregion
|
||
|
||
}
|
||
}
|