添加项目文件。

This commit is contained in:
“hsc”
2026-07-29 13:12:27 +08:00
parent d6da766e2d
commit 8cf45d36b9
297 changed files with 35814 additions and 0 deletions

View File

@@ -0,0 +1,610 @@
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.ParameterVM;
using UIShare.ViewModelBase;
using NLog;
namespace TestingModule.ViewModels
{
public class CommandTreeViewModel:NavigateViewModelBase,IDisposable
{
#region
private string _SearchText;
public string SearchText
{
get => _SearchText;
set => SetProperty(ref _SearchText, value);
}
private ObservableCollection<InstructionNodeVM> _instructionTree = new();
public ObservableCollection<InstructionNodeVM> InstructionTree
{
get => _instructionTree;
set => SetProperty(ref _instructionTree, value);
}
public ProgramVM 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<SubProgramItemVM> _subPrograms = new();
public ObservableCollection<SubProgramItemVM> SubPrograms
{
get => _subPrograms;
set => SetProperty(ref _subPrograms, value);
}
private Dictionary<object, InstructionNodeVM> _treeNodeMap = new();
public Dictionary<object, InstructionNodeVM> 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);
}
public void Dispose()
{
try
{
_ScopedContext = null!;
InstructionTree?.Clear();
SubPrograms?.Clear();
TreeNodeMap?.Clear();
XmlDocumentCache?.Clear();
InstructionTree = null!;
SubPrograms = null!;
TreeNodeMap = null!;
XmlDocumentCache = null!;
}
catch (Exception ex)
{
LoggerHelper.Error($"清理指令树缓存失败: {ex.Message}");
}
}
#region
private void Reload()
{
LoadAllAssemblies();
LoadSubPrograms();
LoadInstructionsToTreeView();
}
private void TreeDoubleClick(object obj)
{
if (!_globalInfo.IsAdmin) return;
if(obj is InstructionNodeVM 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 SubProgramItemVM 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.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)
// {
// LoggerHelper.WarnWithNotify($"加载XML注释失败: {Path.GetFileName(xmlPath)} - {xmlEx.Message}");
// }
//}
}
catch (Exception ex)
{
LoggerHelper.WarnWithNotify(_globalInfo.CurrentScope, $"无法加载程序集 {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, "*.ACP"))
{
try
{
SubPrograms.Add(new SubProgramItemVM
{
Name = Path.GetFileNameWithoutExtension(filePath),
FilePath = filePath
});
}
catch (Exception ex)
{
LoggerHelper.WarnWithNotify(_globalInfo.CurrentScope, $"加载子程序错误: {filePath} - {ex.Message}");
}
}
}
/// <summary>
/// 加载指令集到TreeView
/// </summary>
private void LoadInstructionsToTreeView()
{
InstructionTree.Clear();
var controlRootNode = new InstructionNodeVM
{
Name = "系统指令",
Tag = "ControlRoot"
};
InstructionTree.Add(controlRootNode);
// 循环开始
controlRootNode.Children.Add(new InstructionNodeVM
{
Name = "循环开始",
Tag = "循环开始"
});
// 循环结束
controlRootNode.Children.Add(new InstructionNodeVM
{
Name = "循环结束",
Tag = "循环结束"
});
// ----------------------
// 子程序 根节点
// ----------------------
var subProgramRoot = new InstructionNodeVM
{
Name = "子程序",
Tag = "SubProgramRoot"
};
InstructionTree.Add(subProgramRoot);
foreach (var subProgram in SubPrograms)
{
subProgramRoot.Children.Add(new InstructionNodeVM
{
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<ACPCommandAttribute>() != 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(_globalInfo.CurrentScope, $"加载类型错误: {assembly.FullName} - {ex.Message}");
}
if (validTypes.Count > 0)
{
var assemblyNode = new InstructionNodeVM
{
Name = assembly.GetName().Name,
Tag = assembly
};
InstructionTree.Add(assemblyNode);
TreeNodeMap[assembly] = assemblyNode;
foreach (var type in validTypes)
{
//拦截没有在设备列表中的设备类型
//if (_systemConfig.DeviceList.Where(x=>x.Remark==type.Name).ToList().Count==0 && type.FullName.Contains("DeviceCommand.Devices"))
//{
// continue;
//}
var typeNode = new InstructionNodeVM
{
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 InstructionNodeVM
{
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 StepVM
{
Name = method.Name,
StepType = "方法",
Method = new MethodVM
{
FullName = method.DeclaringType?.FullName,
Name = method.Name
}
};
// 添加输入参数
foreach (var param in method.GetParameters())
{
newStep.Method.Parameters.Add(new ParameterVM
{
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 ParameterVM
{
Name = "Result",
Type = actualType, // 使用实际类型
Category = ParameterCategory.Output
});
}
else if (returnType != typeof(void))
{
// 同步方法正常添加
newStep.Method.Parameters.Add(new ParameterVM
{
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(_globalInfo.CurrentScope, $"添加方法失败: {method.Name} - {ex.Message}");
}
}
private void AddSubProgramToProgram(SubProgramItemVM subProgram, int insertIndex = -1)
{
try
{
var newStep = new StepVM
{
Name = subProgram.Name,
StepType = "子程序"
};
var jsonstr = File.ReadAllText($"{subProgram.FilePath}");
var tmp = JsonConvert.DeserializeObject<ProgramVM>(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(_globalInfo.CurrentScope, $"添加子程序失败: {subProgram.Name} - {ex.Message}"); ;
}
}
private void AddLoopStartStep(int insertIndex = -1)
{
var newStep = new StepVM
{
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)
{
// 查找最近的未匹配循环开始
StepVM? 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 StepVM
{
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
}
}

View File

@@ -0,0 +1,148 @@
using UIShare.UIViewModel;
using UIShare.PubEvent;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
using UIShare.GlobalVariable;
using static UIShare.UIViewModel.ParameterVM;
using UIShare.ViewModelBase;
using Prism.Ioc;
using Prism.Navigation.Regions;
namespace TestingModule.ViewModels.Dialogs
{
public class ParameterSettingViewModel : DialogViewModelBase
{
#region
private string _title = "参数设置界面";
public string Title
{
get => _title;
set => SetProperty(ref _title, value);
}
private Array _EnumValues;
public Array EnumValues
{
get => _EnumValues;
set => SetProperty(ref _EnumValues, value);
}
private ObservableCollection<Type> _types =
[
typeof(string), typeof(bool),
typeof(short), typeof(int), typeof(long), typeof(float), typeof(double),
typeof(byte[]), typeof(short[]), typeof(ushort[]), typeof(int[]), typeof(long[]), typeof(float[]), typeof(double[]),
typeof(object)
];
public ObservableCollection<Type> Types
{
get => _types;
set => SetProperty(ref _types, value);
}
private ObservableCollection<string> _categories = new ObservableCollection<string>(
Enum.GetNames(typeof(ParameterCategory))
);
public ObservableCollection<string> Categories
{
get => _categories;
set => SetProperty(ref _categories, value);
}
private string _Mode;
public string Mode
{
get => _Mode;
set => SetProperty(ref _Mode, value);
}
private ProgramVM _program;
public ProgramVM Program
{
get => _program;
set => SetProperty(ref _program, value);
}
private ParameterVM _Parameter;
public ParameterVM Parameter
{
get => _Parameter;
set => SetProperty(ref _Parameter, value);
}
#endregion
public DialogCloseListener RequestClose{get;set;}
private ScopedContext _ScopedContext;
public ICommand CancelCommand { get; set; }
public ICommand SaveCommand { get; set; }
public ParameterSettingViewModel(IContainerProvider containerProvider) : base(containerProvider)
{
CancelCommand = new DelegateCommand(Cancel);
SaveCommand = new DelegateCommand(Save);
}
private void Save()
{
if (Mode == "ADD")
{
Program.Parameters.Add(Parameter);
_ScopedContext.SelectedParameter = Parameter;
}
else
{
var index = Program.Parameters
.Select((x, i) => new { x, i })
.FirstOrDefault(p => p.x.ID == _ScopedContext.SelectedParameter.ID)?.i;
if (!_ScopedContext.SelectedParameter.IsEditable)
{
//公共变量只允许更改ParameterCategory为Input,Temp
if (Program.Parameters[index.Value].Value != Parameter.Value
|| Program.Parameters[index.Value].UpperLimit != Parameter.UpperLimit
|| Program.Parameters[index.Value].LowerLimit != Parameter.LowerLimit
|| Program.Parameters[index.Value].Name != Parameter.Name
|| Parameter.Category == ParameterCategory.Output
|| Program.Parameters[index.Value].Type != Parameter.Type)
{
RequestClose.Invoke(ButtonResult.Yes);
return;
}
}
if (index.HasValue)
{
Program.Parameters[index.Value] = Parameter;
}
}
RequestClose.Invoke(ButtonResult.OK);
}
private void Cancel()
{
RequestClose.Invoke(ButtonResult.No);
}
#region Prism Dialog
public override void OnDialogClosed()
{
_eventAggregator.GetEvent<OverlayEvent>().Publish(false);
}
public override void OnDialogOpened(IDialogParameters parameters)
{
if (parameters.ContainsKey("ScopedContext"))
_ScopedContext = parameters.GetValue<ScopedContext>("ScopedContext");
_eventAggregator.GetEvent<OverlayEvent>().Publish(true);
Program =_ScopedContext.Program;
Mode = parameters.GetValue<string>("Mode");
if (Mode == "ADD")
{
Parameter = new();
}
else
{
Parameter = new ParameterVM(_ScopedContext.SelectedParameter);
}
}
#endregion
}
}

View File

@@ -0,0 +1,119 @@
using UIShare.GlobalVariable;
using Logger;
using Prism.Ioc;
using Prism.Mvvm;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Threading;
using UIShare.ViewModelBase;
namespace TestingModule.ViewModels
{
public class LogAreaViewModel : NavigateViewModelBase, IDisposable
{
// 日志集合
private ObservableCollection<LogItem> _logs = new();
public ObservableCollection<LogItem> Logs
{
get => _logs;
set => SetProperty(ref _logs, value);
}
public ICommand ClearLogCommand { get; set; }
private readonly ScopedContext _scopedContext;
private readonly GlobalInfo _globalInfo;
/// <summary>UI 线程定时器,每 100ms 批量从 LogBuffer 出队并刷新</summary>
private readonly DispatcherTimer _flushTimer;
public LogAreaViewModel(IContainerProvider containerProvider) : base(containerProvider)
{
ClearLogCommand = new DelegateCommand(ClearLog);
_scopedContext = containerProvider.Resolve<ScopedContext>();
_globalInfo = containerProvider.Resolve<GlobalInfo>();
// UI 线程定时器:每 100ms 从 ScopedContext.LogBuffer 批量出队并 Add
_flushTimer = new DispatcherTimer(DispatcherPriority.Normal, System.Windows.Application.Current.Dispatcher)
{
Interval = TimeSpan.FromMilliseconds(100)
};
_flushTimer.Tick += FlushLogBuffer;
_flushTimer.Start();
}
/// <summary>
/// 定时批量刷新日志:一次性出队并 AddWPF 对连续 Add 做合并渲染
/// </summary>
private void FlushLogBuffer(object? sender, EventArgs e)
{
if (_scopedContext.LogBuffer.IsEmpty || Logs == null) return;
var batch = new List<(string Message, Brush Color, int Depth)>();
while (_scopedContext.LogBuffer.TryDequeue(out var item))
{
batch.Add(item);
}
// 批量 AddWPF 会对连续 Add 做合并渲染,只触发一次 layout/render pass
foreach (var item in batch)
{
Logs.Add(new LogItem(item.Message, item.Color, item.Depth));
}
}
private void ClearLog()
{
// 清空缓冲区,防止定时器下次还把旧数据刷出来
while (_scopedContext.LogBuffer.TryDequeue(out _)) { }
Logs?.Clear();
}
/// <summary>
/// 完善后的资源释放方法
/// </summary>
public void Dispose()
{
try
{
// 停止定时器
_flushTimer.Stop();
_flushTimer.Tick -= FlushLogBuffer;
// 最后一次刷新缓冲区中的剩余日志
FlushLogBuffer(null, EventArgs.Empty);
if (Logs != null)
{
Logs.Clear();
Logs = null!;
}
}
catch (Exception ex)
{
Logger.LoggerHelper.ErrorWithNotify(
_scopedContext != null ? _globalInfo.CurrentScope : "",
$"释放日志组件LogAreaViewModel资源失败: {ex.Message}");
}
}
}
// 日志条目类
public class LogItem
{
public string Message { get; set; }
public Brush Color { get; set; } = Brushes.Black;
public int Depth { get; set; }
public LogItem(string message, Brush color, int depth = 0)
{
Message = new string(' ', depth * 20) + message;
Color = color;
Depth = depth;
}
}
}

View File

@@ -0,0 +1,264 @@
using UIShare.UIViewModel;
using UIShare.GlobalVariable;
using UIShare.PubEvent;
using Logger;
using MaterialDesignThemes.Wpf;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Reflection;
using System.Windows.Input;
using System.Xml;
using UIShare.ViewModelBase;
using Prism.Events;
namespace TestingModule.ViewModels
{
public class ParametersManagerViewModel:NavigateViewModelBase, IDisposable
{
#region
private ObservableCollection<DeviceInfoVM> _DeviceInfoModel;
public ObservableCollection<DeviceInfoVM> DeviceInfoVM
{
get { return _DeviceInfoModel; }
set { SetProperty(ref _DeviceInfoModel, value); }
}
public ProgramVM Program
{
get => _ScopedContext.Program;
set
{
if (_ScopedContext.Program != value)
{
_ScopedContext.Program = value;
RaisePropertyChanged();
}
}
}
private ParameterVM _SelectedParameter;
public ParameterVM SelectedParameter
{
get => _SelectedParameter;
set
{
if (SetProperty(ref _SelectedParameter, value))
{
_ScopedContext.SelectedParameter = value;
}
}
}
private DeviceInfoVM _SelectedDevice;
public DeviceInfoVM SelectedDevice
{
get { return _SelectedDevice; }
set { SetProperty(ref _SelectedDevice, value); }
}
#endregion
private ScopedContext _ScopedContext { get; set; }
private readonly SystemConfig _systemConfig;
private readonly GlobalInfo _globalInfo;
private readonly DeviceManager _deviceManager;
private readonly IContainerProvider _containerProvider;
#region
public ICommand ParameterAddCommand { get; set; }
public ICommand ParameterEditCommand { get; set; }
public ICommand ParameterDeleteCommand { get; set; }
public ICommand DeviceEditCommand { get; set; }
public ICommand ReConnectCommand { get; set; }
public ICommand CloseCommand { get; set; }
public ICommand LoadedCommand { get; set; }
#endregion
public ParametersManagerViewModel(IContainerProvider containerProvider) : base(containerProvider)
{
_ScopedContext = containerProvider.Resolve<ScopedContext>();
_systemConfig = containerProvider.Resolve<SystemConfig>();
_deviceManager = containerProvider.Resolve<DeviceManager>();
_globalInfo = containerProvider.Resolve<GlobalInfo>();
_containerProvider = containerProvider;
Program = _ScopedContext.Program;
ParameterAddCommand = new DelegateCommand(ParameterAdd);
ParameterEditCommand = new DelegateCommand(ParameterEdit);
ParameterDeleteCommand = new DelegateCommand(ParameterDelete);
DeviceEditCommand = new DelegateCommand(DeviceEdit);
ReConnectCommand = new AsyncDelegateCommand(OnReConnect);
CloseCommand = new AsyncDelegateCommand(OnClose);
LoadedCommand = new DelegateCommand(OnLoad);
InitParameters();
}
#region
private void InitParameters()
{
foreach(var item in _systemConfig.ParameterList)
{
var copy=new ParameterVM(item);
var param = _systemConfig.SharedParameterList.FirstOrDefault(x => x.ParameterName == copy.Name);
if (param != null)
{
copy.Value = param.Value;
}
Program.Parameters.Add(copy);
}
}
private void OnLoad()
{
DeviceInfoVM = _systemConfig.DeviceList;
} private async Task OnReConnect()
{
await _deviceManager.ConnectSpecifiedDevice(SelectedDevice.DeviceName);
} private async Task OnClose()
{
await _deviceManager.CloseDeviceAsync(SelectedDevice.DeviceName);
}
/// <summary>
/// 跟踪已打开的弹窗管理器窗口实例,避免重复打开多个 DialogMangerView 窗口。
/// </summary>
private static System.Windows.Window? _dialogWindow;
private void DeviceEdit()
{
if (!_globalInfo.IsAdmin) return;
if (SelectedDevice == null) return;
var type = SelectedDevice.DeviceType.Split('.').Last();
var viewName = type + "View";
var vmName = type + "ViewModel"; // 按照命名规范拼接出对应的 VM 类型名
try
{
// 1. 先确保弹窗管理器窗口已打开
if (_dialogWindow == null || !_dialogWindow.IsVisible)
{
_dialogService.Show("DialogMangerView");
_dialogWindow = System.Windows.Application.Current.Windows
.OfType<System.Windows.Window>()
.FirstOrDefault(w => w.DataContext?.GetType().Name == "DialogMangerViewModel");
}
// 2. 从当前专属容器解析设备编辑 View 实例
var view = _containerProvider.Resolve<object>(viewName) as System.Windows.FrameworkElement;
if (view == null) return;
// 3. 关键:从同一个台架的专属容器中解析出该设备对应的 ViewModel 实例
// 从而实现:不同的台架(不同的专属 _containerProvider解析出各自独立的 VM 实例
var vmType = Assembly.Load("DeviceEditModule").GetType($"DeviceEditModule.ViewModels.{vmName}");
if (vmType != null)
{
var scopedViewModel = _containerProvider.Resolve(vmType);
if (scopedViewModel != null)
{
// 手动绑定 DataContext将其锁死在当前作用域内
view.DataContext = scopedViewModel;
}
}
// 4. 调用 ViewModel 上的 Initialize 方法
var vm = view.DataContext;
if (vm != null)
{
var initMethod = vm.GetType().GetMethod("Initialize", new[] { typeof(string) });
initMethod?.Invoke(vm, new object[] { SelectedDevice.DeviceName });
}
// 5. 发布事件,将绑定好独立 VM 的 View 实例作为 Tab 载入
var fingerprint = DeviceManager.ExtractHardwareFingerprint(SelectedDevice);
_eventAggregator.GetEvent<AddDialogTabEvent>().Publish(new DialogTabInfo
{
Title = $"{_globalInfo.CurrentScope} - {SelectedDevice.DeviceName}",
Fingerprint = fingerprint,
Content = view
});
// 6. 窗口置顶
if (_dialogWindow != null && _dialogWindow.IsVisible)
{
if (_dialogWindow.WindowState == System.Windows.WindowState.Minimized)
_dialogWindow.WindowState = System.Windows.WindowState.Normal;
_dialogWindow.Activate();
}
}
catch (Exception ex)
{
LoggerHelper.ErrorWithNotify(_globalInfo.CurrentScope, $"打开设备编辑窗口 [{type}] 失败:{ex.Message}");
}
}
private void ParameterDelete()
{
if (!_globalInfo.IsAdmin || !SelectedParameter.IsEditable) return;
Program.Parameters.Remove(SelectedParameter);
}
private void ParameterEdit()
{
if (!_globalInfo.IsAdmin) return;
var param = new DialogParameters
{
{ "Mode",SelectedParameter==null?"ADD":"Edit" },
{ "ScopedContext",_ScopedContext }
};
_dialogService.ShowDialog("ParameterSetting", param, (r) =>
{
if (r.Result == ButtonResult.OK)
{
_eventAggregator.GetEvent<ParamsChangedEvent>().Publish();
ShowInfoMessageBox("保存成功", () => { });
}
else if (r.Result == ButtonResult.Yes)
{
ShowErrorMessageBox("公共变量只能设置变量类型Input,Temp", ()=>{ });
}
});
}
private void ParameterAdd()
{
if (!_globalInfo.IsAdmin) return;
var param = new DialogParameters
{
{ "Mode", "ADD" },
{ "ScopedContext",_ScopedContext }
};
_dialogService.ShowDialog("ParameterSetting", param, (r) =>
{
if (r.Result == ButtonResult.OK)
{
_eventAggregator.GetEvent<ParamsChangedEvent>().Publish();
}
else
{
}
});
}
#endregion
public void Dispose()
{
try
{
DeviceInfoVM?.Clear();
DeviceInfoVM = null!;
SelectedParameter = null!;
SelectedDevice = null!;
if (_ScopedContext != null)
{
_ScopedContext.SelectedParameter = null;
_ScopedContext = null!;
}
}
catch (Exception ex)
{
Logger.LoggerHelper.Error($"释放参数管理组件ParametersManagerViewModel资源失败: {ex.Message}");
}
}
}
}

View File

@@ -0,0 +1,223 @@
using UIShare.UIViewModel;
using UIShare.PubEvent;
using Logger;
using Microsoft.IdentityModel.Logging;
using SqlSugar.Extensions;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Input;
using UIShare.ViewModelBase;
using Prism.Dialogs;
using Prism.Events;
using UIShare.GlobalVariable;
namespace TestingModule.ViewModels
{
public class SingleStepEditViewModel:NavigateViewModelBase,IDisposable
{
#region
private Guid _ID;
public Guid ID
{
get => _ID;
set => SetProperty(ref _ID, value);
}
private StepVM _SelectedStep;
public StepVM SelectedStep
{
get => _SelectedStep;
set => SetProperty(ref _SelectedStep, value);
}
public ProgramVM Program
{
get => _ScopedContext.Program;
set
{
if (_ScopedContext.Program != value)
{
_ScopedContext.Program = value;
RaisePropertyChanged();
}
}
}
#endregion
private ScopedContext _ScopedContext;
private GlobalInfo _globalInfo;
#region
public ICommand CancelEditCommand { get; set; }
public ICommand SaveStepCommand { get; set; }
#endregion
public SingleStepEditViewModel(IContainerProvider containerProvider, ScopedContext scopedContext) : base(containerProvider)
{
_ScopedContext = scopedContext;
_globalInfo=containerProvider.Resolve<GlobalInfo>();
_eventAggregator.GetEvent<EditSetpEvent>().Subscribe(EditSingleStep);
CancelEditCommand = new DelegateCommand(CancelEdit);
SaveStepCommand = new DelegateCommand(SaveStep);
_eventAggregator.GetEvent<DeletedStepEvent>().Subscribe(DisposeSelectedStep);
_eventAggregator.GetEvent<ParamsChangedEvent>().Subscribe(ParamsChanged);
}
#region
private void ParamsChanged()
{
CancelEdit();
}
private void DisposeSelectedStep(Guid id)
{
if (SelectedStep == null) return;
if(id== SelectedStep.ID)
SelectedStep = null;
}
private void CancelEdit()
{
SelectedStep = null;
}
private void SaveStep()
{
if (SelectedStep == null || (SelectedStep.Method == null && SelectedStep.SubProgram == null))
{
return;
}
var steps = _ScopedContext.SelectedStepList=="主程序"?_ScopedContext.Program.StepCollection: _ScopedContext.Program.ErrorStepCollection;
int index = steps.ToList().FindIndex(x => x.ID == ID);
if (index >= 0)
{
steps[index] = SelectedStep;
if (steps[index].Method != null)
{
if (steps[index].StepType == "循环开始")
{
try
{
steps[index].LoopCount = Convert.ToInt32(SelectedStep.Method!.Parameters[0].Value);
}
catch
{
LoggerHelper.ErrorWithNotify(_globalInfo.CurrentScope,"循环指令参数设置错误:类型转换失败");
}
}
else
{ //设置步骤参数
(steps[index].OKGotoStepID, steps[index].NGGotoStepID) = GetOKNGGotoStepID(SelectedStep.GotoSettingString);
for (int i = 0; i < steps[index].Method.Parameters.Count; i++)
{
var editedParam = SelectedStep.Method!.Parameters[i];
var originalParam = steps[index].Method.Parameters[i];
if (editedParam.IsUseVar)
{
originalParam.VariableName = editedParam.VariableName;
originalParam.VariableID = _ScopedContext.Program.Parameters.FirstOrDefault(x => x.Name == editedParam.VariableName)!.ID;
}
originalParam.Value = editedParam.Value;
originalParam.IsUseVar = editedParam.IsUseVar;
originalParam.LowerLimit = editedParam.LowerLimit;
originalParam.UpperLimit = editedParam.UpperLimit;
}
var parameters = new DialogParameters
{
{ "Title", "提示" },
{ "Message", "保存成功!" },
{ "Icon", "info" },
{ "ShowOk", true }
};
_dialogService.ShowDialog("MessageBox", parameters);
}
}
else if (steps[index].SubProgram != null)
{
if (SelectedStep.SubProgram.Parameters.Where(x => x.VariableName == null && x.IsUseVar == true).FirstOrDefault() != null)
{
var parameters1 = new DialogParameters
{
{ "Title", "警告" },
{ "Message", "选中变量不得为空!" },
{ "Icon", "warn" },
{ "ShowOk", true },
};
_dialogService.ShowDialog("MessageBox",parameters1);
return;
}
(steps[index].OKGotoStepID, steps[index].NGGotoStepID) = GetOKNGGotoStepID(SelectedStep.GotoSettingString);
for (int i = 0; i < steps[index].SubProgram.Parameters.Count; i++)
{
var editedParam = SelectedStep.SubProgram!.Parameters[i];
var originalParam = steps[index].SubProgram.Parameters[i];
if (editedParam.IsUseVar)
{
originalParam.VariableName = editedParam.VariableName;
originalParam.VariableID = _ScopedContext.Program.Parameters.FirstOrDefault(x => x.Name == editedParam.VariableName)!.ID;
}
originalParam.Value = editedParam.Value;
originalParam.IsUseVar = editedParam.IsUseVar;
originalParam.LowerLimit = editedParam.LowerLimit;
originalParam.UpperLimit = editedParam.UpperLimit;
}
var parameters = new DialogParameters
{
{ "Title", "提示" },
{ "Message", "保存成功!" },
{ "Icon", "info" },
{ "ShowOk", true }
};
_dialogService.ShowDialog("MessageBox", parameters);
}
}
}
private (Guid,Guid) GetOKNGGotoStepID(string GotoSettingString)
{
if (string.IsNullOrWhiteSpace(GotoSettingString))
return (Guid.Empty, Guid.Empty);
var match = Regex.Match(GotoSettingString, @"^(\d+)\s*/\s*(\d+)$");
if (match.Success)
{
int ok = int.Parse(match.Groups[1].Value);
int ng = int.Parse(match.Groups[2].Value);
Guid okGuid = _ScopedContext.Program.StepCollection.ElementAtOrDefault(ok-1)?.ID ?? Guid.Empty;
Guid ngGuid = _ScopedContext.Program.StepCollection.ElementAtOrDefault(ng-1)?.ID ?? Guid.Empty;
return (okGuid, ngGuid);
}
return (Guid.Empty, Guid.Empty);
}
private void EditSingleStep()
{
if (_ScopedContext.SelectedStep == null) return;
ID = _ScopedContext.SelectedStep.ID;
SelectedStep = new StepVM(_ScopedContext.SelectedStep);
}
#endregion
public void Dispose()
{
try
{
// 1. 【核心修复】必须严格退订所有全局 Prism 事件
_eventAggregator?.GetEvent<EditSetpEvent>()?.Unsubscribe(EditSingleStep);
_eventAggregator?.GetEvent<DeletedStepEvent>()?.Unsubscribe(DisposeSelectedStep);
_eventAggregator?.GetEvent<ParamsChangedEvent>()?.Unsubscribe(ParamsChanged);
// 2. 清空当前正在编辑的步骤副本,断开前台绑定,防止 UI 视图树悬挂
SelectedStep = null!;
// 3. 断开对工位隔离上下文的强引用
_ScopedContext = null!;
}
catch (Exception ex)
{
LoggerHelper.Error($"释放单步编辑组件SingleStepEditViewModel资源失败: {ex.Message}");
}
}
}
}

View File

@@ -0,0 +1,333 @@
using UIShare.PubEvent;
using UIShare.UIViewModel;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Input;
using System.Xml;
using UIShare.GlobalVariable;
using UIShare.ViewModelBase;
using Prism.Events;
namespace TestingModule.ViewModels
{
public class StepsManagerViewModel:NavigateViewModelBase, IDisposable
{
#region
private string _Title;
public string Title
{
get => _Title;
set => SetProperty(ref _Title, value);
}
private bool _Admin;
public bool Admin
{
get => _Admin;
set => SetProperty(ref _Admin, value);
}
private int _SelectedTabIndex;
public int SelectedTabIndex
{
get => _SelectedTabIndex;
set => SetProperty(ref _SelectedTabIndex, value);
}
private string _selectedTabHeader;
public string SelectedTabHeader
{
get { return _selectedTabHeader; }
set { SetProperty(ref _selectedTabHeader, value); }
}
private List<StepVM> _SelectedItems;
public List<StepVM> SelectedItems
{
get { return _SelectedItems; }
set { SetProperty(ref _SelectedItems, value); }
}
private StepVM _SelectedStep;
public StepVM SelectedStep
{
get => _SelectedStep;
set
{
if (SetProperty(ref _SelectedStep, value))
{
_ScopedContext.SelectedStep = value;
}
}
}
public ProgramVM Program
{
get => _ScopedContext.Program;
set
{
if (_ScopedContext.Program != value)
{
_ScopedContext.Program = value;
RaisePropertyChanged();
}
}
}
ScopedContext _ScopedContext { get; set; }
private readonly SystemConfig _systemConfig;
private readonly GlobalInfo _globalInfo;
private List<StepVM> tmpCopyList = new List<StepVM>();
// 追踪当前订阅的集合实例,用于在集合被整体替换时重新订阅
private ObservableCollection<StepVM> _trackedStepCollection;
private ObservableCollection<StepVM> _trackedErrorStepCollection;
#endregion
#region
public ICommand EditStepCommand { get;set; }
public ICommand CopyStepCommand { get;set; }
public ICommand PasteStepCommand { get;set; }
public ICommand DeleteStepCommand { get;set; }
public ICommand TabSelectionChangedCommand { get;set; }
public ICommand SelectionChangedCommand { get;set; }
#endregion
public StepsManagerViewModel(IContainerProvider containerProvider, ScopedContext scopedContext, SystemConfig systemConfig, GlobalInfo globalInfo) : base(containerProvider)
{
_ScopedContext = scopedContext;
_systemConfig = systemConfig;
_globalInfo = globalInfo;
Title = _systemConfig.Title;
EditStepCommand = new DelegateCommand(EditStep);
CopyStepCommand = new DelegateCommand(CopyStep);
PasteStepCommand = new DelegateCommand(PasteStep);
DeleteStepCommand = new DelegateCommand(DeleteStep);
TabSelectionChangedCommand = new DelegateCommand<string>(TabSelectionChanged);
SelectionChangedCommand = new DelegateCommand<object>(SelectionChanged);
SubscribeStepCollections();
Program.PropertyChanged += Program_PropertyChanged;
Admin = _globalInfo.IsAdmin;
}
private void SelectionChanged(object parameter)
{
var selectedList = parameter as IList;
if (selectedList != null)
{
SelectedItems = selectedList.Cast<StepVM>().ToList();
}
}
private void TabSelectionChanged(string SelectedTabHeader)
{
_ScopedContext.SelectedStepList = SelectedTabHeader;
}
#region
private void EditStep()
{
if (_globalInfo.IsAdmin && SelectedStep != null&& _ScopedContext.SelectedStep!=null)
{
_eventAggregator.GetEvent<EditSetpEvent>().Publish() ;
}
}
private void CopyStep()
{
if (_globalInfo.IsAdmin && SelectedItems.Any())
{
tmpCopyList.Clear();
foreach (var item in SelectedItems)
{
tmpCopyList.Add(item);
}
}
}
private void PasteStep()
{
// 权限校验并确保有可粘贴的内容
if (_globalInfo.IsAdmin && tmpCopyList.Any())
{
int insertIndex;
if (_ScopedContext.SelectedStepList == "主程序")
{
insertIndex = SelectedStep != null ? Program.StepCollection.IndexOf(SelectedStep) + 1 : Program.StepCollection.Count;
foreach (var item in tmpCopyList)
{
// 创建新副本,避免引用同一个对象,并赋予新 ID
var newStep = new StepVM(item) { ID = Guid.NewGuid() };
Program.StepCollection.Insert(insertIndex, newStep);
insertIndex++; // 递增索引,保证粘贴的多项顺序一致
}
}
else if (_ScopedContext.SelectedStepList == "错误程序")
{
insertIndex = SelectedStep != null ? Program.ErrorStepCollection.IndexOf(SelectedStep) + 1 : Program.ErrorStepCollection.Count;
foreach (var item in tmpCopyList)
{
var newStep = new StepVM(item) { ID = Guid.NewGuid() };
Program.ErrorStepCollection.Insert(insertIndex, newStep);
insertIndex++;
}
}
}
}
private void DeleteStep()
{
// 确保有选中的项
if (_globalInfo.IsAdmin && SelectedItems != null && SelectedItems.Any())
{
// 创建一个副本进行循环,防止在 Remove 过程中集合变化导致的问题
var toDelete = SelectedItems.ToList();
foreach (var item in toDelete)
{
_eventAggregator.GetEvent<DeletedStepEvent>().Publish(item.ID);
if (_ScopedContext.SelectedStepList == "主程序")
{
Program.StepCollection.Remove(item);
}
else if (_ScopedContext.SelectedStepList == "错误程序")
{
Program.ErrorStepCollection.Remove(item);
}
}
// 3. 清空 ViewModel 的选中状态,避免悬挂引用
SelectedStep = null;
SelectedItems.Clear();
_ScopedContext.SelectedStep = null;
}
}
#endregion
#region
/// <summary>
/// 订阅当前 Program 的 StepCollection 和 ErrorStepCollection 的 CollectionChanged 事件。
/// </summary>
private void SubscribeStepCollections()
{
_trackedStepCollection = Program.StepCollection;
_trackedErrorStepCollection = Program.ErrorStepCollection;
if (_trackedStepCollection != null)
_trackedStepCollection.CollectionChanged += StepCollection_CollectionChanged;
if (_trackedErrorStepCollection != null)
_trackedErrorStepCollection.CollectionChanged += StepCollection_CollectionChanged;
}
/// <summary>
/// 取消订阅当前追踪的集合事件。
/// </summary>
private void UnsubscribeStepCollections()
{
if (_trackedStepCollection != null)
{
_trackedStepCollection.CollectionChanged -= StepCollection_CollectionChanged;
_trackedStepCollection = null;
}
if (_trackedErrorStepCollection != null)
{
_trackedErrorStepCollection.CollectionChanged -= StepCollection_CollectionChanged;
_trackedErrorStepCollection = null;
}
}
/// <summary>
/// 当 Program 的 StepCollection / ErrorStepCollection 被整体替换时,自动重新订阅并刷新序号。
/// </summary>
private void Program_PropertyChanged(object? sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(ProgramVM.StepCollection) ||
e.PropertyName == nameof(ProgramVM.ErrorStepCollection))
{
UnsubscribeStepCollections();
SubscribeStepCollections();
// 集合被整体替换后,对新集合重新编号
Application.Current?.Dispatcher.BeginInvoke(new Action(() =>
{
for (int i = 0; i < Program.StepCollection.Count; i++)
Program.StepCollection[i].Index = i + 1;
for (int i = 0; i < Program.ErrorStepCollection.Count; i++)
Program.ErrorStepCollection[i].Index = i + 1;
}), System.Windows.Threading.DispatcherPriority.Background);
}
}
private void StepCollection_CollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
{
var collection = sender as ObservableCollection<StepVM>;
// Add/Move/Remove 都会触发,这里判断具体情形
if (e.Action == NotifyCollectionChangedAction.Add ||
e.Action == NotifyCollectionChangedAction.Move||
e.Action == NotifyCollectionChangedAction.Remove)
{
// 如果需要等 UI 更新完再处理,可也用 Dispatcher 延迟一小段时间
Application.Current?.Dispatcher.BeginInvoke(new Action(() =>
{
if(_ScopedContext.SelectedStepList=="主程序")
{
for (int i = 0; i < Program.StepCollection.Count; i++)
Program.StepCollection[i].Index = i + 1;
}
else if (_ScopedContext.SelectedStepList == "错误程序")
for (int i = 0; i < Program.ErrorStepCollection.Count; i++)
Program.ErrorStepCollection[i].Index = i + 1;
}), System.Windows.Threading.DispatcherPriority.Background);
}
}
#endregion
public void Dispose()
{
try
{
// 1. 取消订阅 CollectionChanged 事件(从追踪的实例上取消,而非可能已被替换的当前实例)
UnsubscribeStepCollections();
// 2. 取消订阅 Program.PropertyChanged
if (Program != null)
{
Program.PropertyChanged -= Program_PropertyChanged;
}
// 3. 【核心修复】必须显式退订 Prism 全局事件AlarmEvent
// 注意:因为订阅时使用的是匿名 Lambda最安全稳妥的退订方式是把整个事件上的当前 VM 订阅者全部注销
_eventAggregator?.GetEvent<AlarmEvent>()?.Unsubscribe(null);
// 4. 清空临时缓存集合与 UI 绑定列表,避免悬挂指针
tmpCopyList?.Clear();
tmpCopyList = null!;
SelectedItems?.Clear();
SelectedItems = null!;
// 5. 清除选中项状态引用
SelectedStep = null;
if (_ScopedContext != null)
{
_ScopedContext.SelectedStep = null;
_ScopedContext = null!; // 断开上下文引用
}
}
catch (Exception ex)
{
Logger.LoggerHelper.Error($"释放步骤管理组件StepsManagerViewModel资源失败: {ex.Message}");
}
}
}
}