From e87c097c4e606db627029a7a64de8cbdb2ad5eae Mon Sep 17 00:00:00 2001 From: hsc Date: Wed, 5 Nov 2025 13:54:04 +0800 Subject: [PATCH] =?UTF-8?q?=E7=A7=BB=E6=A4=8D=E5=91=BD=E4=BB=A4=E6=A0=91?= =?UTF-8?q?=EF=BC=8C=E5=91=BD=E4=BB=A4=E7=AE=A1=E7=90=86=EF=BC=8C=E6=97=A5?= =?UTF-8?q?=E5=BF=97=E7=AE=A1=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- BOB/App.xaml.cs | 6 +- BOB/BOB.csproj | 1 + BOB/GlobalVariables.cs | 18 ++ BOB/Models/DeviceModel.cs | 52 ++++ BOB/Models/InstructionNode.cs | 4 +- BOB/Models/MethodModel.cs | 40 +++ BOB/Models/ParameterModel.cs | 316 ++++++++++++++++++++++++ BOB/Models/ProgramModel.cs | 50 ++++ BOB/Models/StepModel.cs | 177 +++++++++++++ BOB/SystemConfig.cs | 6 +- BOB/ViewModels/CommandTreeViewModel.cs | 272 ++++++++++++++++++-- BOB/ViewModels/LogAreaViewModel.cs | 35 ++- BOB/ViewModels/StepsManagerViewModel.cs | 130 +++++++++- BOB/Views/CommandTree.xaml | 3 +- BOB/Views/LogArea.xaml | 5 +- BOB/Views/StepsManager.xaml | 17 +- BOB/Views/StepsManager.xaml.cs | 3 +- Common/PubEvent/AfterDragEvent.cs | 12 + Logger/LoggerHelper.cs | 42 ++++ 19 files changed, 1137 insertions(+), 52 deletions(-) create mode 100644 BOB/GlobalVariables.cs create mode 100644 BOB/Models/DeviceModel.cs create mode 100644 BOB/Models/MethodModel.cs create mode 100644 BOB/Models/ParameterModel.cs create mode 100644 BOB/Models/ProgramModel.cs create mode 100644 BOB/Models/StepModel.cs create mode 100644 Common/PubEvent/AfterDragEvent.cs diff --git a/BOB/App.xaml.cs b/BOB/App.xaml.cs index ad9f236..6c7466b 100644 --- a/BOB/App.xaml.cs +++ b/BOB/App.xaml.cs @@ -1,4 +1,5 @@ -using BOB.ViewModels; +using BOB.Models; +using BOB.ViewModels; using BOB.ViewModels.Dialogs; using BOB.Views; using BOB.Views.Dialogs; @@ -33,7 +34,8 @@ namespace BOB containerRegistry.RegisterForNavigation("MainView"); //注册弹窗 containerRegistry.RegisterDialog("MessageBox"); - + //注册全局变量 + containerRegistry.RegisterSingleton(); } diff --git a/BOB/BOB.csproj b/BOB/BOB.csproj index 79445af..8b9be29 100644 --- a/BOB/BOB.csproj +++ b/BOB/BOB.csproj @@ -34,6 +34,7 @@ + diff --git a/BOB/GlobalVariables.cs b/BOB/GlobalVariables.cs new file mode 100644 index 0000000..8149e0e --- /dev/null +++ b/BOB/GlobalVariables.cs @@ -0,0 +1,18 @@ +using BOB.Models; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace BOB +{ + public class GlobalVariables + { + public ProgramModel Program = new(); + public bool IsAdmin=true; + public String UserName="hsc"; + public string CurrentFilePath; + public StepModel SelectedStep; + } +} diff --git a/BOB/Models/DeviceModel.cs b/BOB/Models/DeviceModel.cs new file mode 100644 index 0000000..c231f5a --- /dev/null +++ b/BOB/Models/DeviceModel.cs @@ -0,0 +1,52 @@ +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection.Metadata; +using System.Text; +using System.Threading.Tasks; + +namespace BOB.Models +{ + public class DeviceModel + { + public DeviceModel() + { + + } + + public DeviceModel(DeviceModel source) + { + ID = source.ID; + ParameterID = source.ParameterID; + Name = source.Name; + Connected = source.Connected; + ErrorMessage = source.ErrorMessage; + Type = source.Type; + ConnectString = source.ConnectString; + CommunicationProtocol = source.CommunicationProtocol; + Description = source.Description; + } + + public Guid ID { get; set; } = Guid.NewGuid(); + + public Guid ParameterID { get; set; } + + public string Name { get; set; } = ""; + + [JsonIgnore] + public bool Connected { get; set; } + + [JsonIgnore] + public string? ErrorMessage { get; set; } + + public string Type { get; set; } = "Tcp"; + + public string ConnectString { get; set; } = ""; + + [JsonIgnore] + public object? CommunicationProtocol { get; set; } + + public string Description { get; set; } = ""; + } +} diff --git a/BOB/Models/InstructionNode.cs b/BOB/Models/InstructionNode.cs index 57e421f..e302caa 100644 --- a/BOB/Models/InstructionNode.cs +++ b/BOB/Models/InstructionNode.cs @@ -4,13 +4,15 @@ using System.Collections.ObjectModel; using System.Linq; using System.Text; using System.Threading.Tasks; +using System.Windows; namespace BOB.Models { public class InstructionNode { - public string Name { get; set; } + public string Name { get; set; } public ObservableCollection Children { get; set; } = new(); + public object Tag { get; set; } } } diff --git a/BOB/Models/MethodModel.cs b/BOB/Models/MethodModel.cs new file mode 100644 index 0000000..5954bb6 --- /dev/null +++ b/BOB/Models/MethodModel.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace BOB.Models +{ + public class MethodModel + { + + #region 构造函数 + + public MethodModel() + { + + } + + public MethodModel(MethodModel source) + { + if (source == null) return; + + Name = source.Name; + FullName = source.FullName; + + // 深拷贝参数 + Parameters = new ObservableCollection( + source.Parameters.Select(p => new ParameterModel(p))); + } + + #endregion + + public string? Name { get; set; } + + public string? FullName { get; set; } + + public ObservableCollection Parameters { get; set; } = []; + } +} diff --git a/BOB/Models/ParameterModel.cs b/BOB/Models/ParameterModel.cs new file mode 100644 index 0000000..afc0d2c --- /dev/null +++ b/BOB/Models/ParameterModel.cs @@ -0,0 +1,316 @@ +using Newtonsoft.Json; +using Prism.Mvvm; // 引入 Prism 的 BindableBase +using System; +using System.Collections.Generic; + +namespace BOB.Models +{ + public class ParameterModel : BindableBase + { + #region 构造函数 + + public ParameterModel() + { + + } + + public ParameterModel(ParameterModel source) + { + if (source == null) return; + + ID = source.ID; + Name = source.Name; + Type = source.Type; + Category = source.Category; + IsUseVar = source.IsUseVar; + IsSave = source.IsSave; + VariableName = source.VariableName; + VariableID = source.VariableID; + IsGlobal = source.IsGlobal; + Value = source.Value; + LowerLimit = source.LowerLimit; + UpperLimit = source.UpperLimit; + } + + #endregion + + private Guid _id = Guid.NewGuid(); + public Guid ID + { + get => _id; + set => SetProperty(ref _id, value); + } + + private bool _isVisible = true; + public bool IsVisible + { + get => _isVisible; + set => SetProperty(ref _isVisible, value); + } + + private string _name; + public string Name + { + get => _name; + set => SetProperty(ref _name, value); + } + + private Type _type = typeof(string); + public Type Type + { + get => _type; + set => SetProperty(ref _type, value); + } + + private ParameterCategory _category = ParameterCategory.Temp; + public ParameterCategory Category + { + get => _category; + set => SetProperty(ref _category, value); + } + + private bool _isGlobal; + public bool IsGlobal + { + get => _isGlobal; + set => SetProperty(ref _isGlobal, value); + } + + private object? _value; + public object? Value + { + get => _value; + set => SetProperty(ref _value, value); + } + + private object? _lowerLimit; + public object? LowerLimit + { + get => _lowerLimit; + set => SetProperty(ref _lowerLimit, value); + } + + private object? _upperLimit; + public object? UpperLimit + { + get => _upperLimit; + set => SetProperty(ref _upperLimit, value); + } + + private bool _result = true; + public bool Result + { + get => _result; + set => SetProperty(ref _result, value); + } + + private bool _isUseVar; + public bool IsUseVar + { + get => _isUseVar; + set => SetProperty(ref _isUseVar, value); + } + + private bool _isSave; + public bool IsSave + { + get => _isSave; + set => SetProperty(ref _isSave, value); + } + + private string? _variableName; + public string? VariableName + { + get => _variableName; + set => SetProperty(ref _variableName, value); + } + + private Guid? _variableID; + public Guid? VariableID + { + get => _variableID; + set => SetProperty(ref _variableID, value); + } + + public enum ParameterCategory + { + Input, + Output, + Temp + } + + public object? GetActualValue(Dictionary paraList) + { + HashSet visitedIds = new HashSet(); + ParameterModel current = this; + + while (current != null) + { + if (!current.IsUseVar) + { + return current.Value; + } + + if (visitedIds.Contains(current.ID)) + { + return null; + } + visitedIds.Add(current.ID); + + if (current.VariableID == null) + { + if (Type != null && Value != null) + { + try + { + return Convert.ChangeType(Value, Type); + } + catch + { + return Value; + } + } + } + + ParameterModel? next = paraList[(Guid)current.VariableID!]; + if (next == null) + { + return null; + } + + current = next; + } + + return null; + } + + public ParameterModel? GetCurrentParameter(Dictionary paraList) + { + HashSet visitedIds = new HashSet(); + ParameterModel current = this; + + while (current != null) + { + if (current.VariableID == null) + { + return current; + } + + if (visitedIds.Contains(current.ID)) + { + return null; + } + visitedIds.Add(current.ID); + + if (current.VariableID == null) + { + if (Type != null && Value != null) + { + try + { + return current; + } + catch + { + return null; + } + } + } + + ParameterModel? next = paraList[(Guid)current.VariableID!]; + if (next == null) + { + return null; + } + + current = next; + } + + return null; + } + + public (bool, string?) GetResult() + { + if (Type == typeof(string) && (!string.IsNullOrWhiteSpace(LowerLimit?.ToString()) || !string.IsNullOrWhiteSpace(UpperLimit?.ToString()))) + { + return (true, $"参数 [ {Name}({Type}) ] 不可比较"); + } + + if (Value == null || (LowerLimit == null && UpperLimit == null)) + { + return (true, null); + } + + if (string.IsNullOrWhiteSpace(Value?.ToString()) || (string.IsNullOrWhiteSpace(LowerLimit?.ToString()) && string.IsNullOrWhiteSpace(UpperLimit?.ToString()))) + { + return (true, null); + } + + try + { + object? comparableValue = ConvertToComparable(Value); + object? comparableLower = LowerLimit != null ? ConvertToComparable(LowerLimit) : null; + object? comparableUpper = UpperLimit != null ? ConvertToComparable(UpperLimit) : null; + + if (comparableValue == null) + { + return (true, $"参数 [ {Name}({Type}) ] 不可比较"); + } + + bool lowerValid = true; + bool upperValid = true; + + if (comparableLower != null) + { + lowerValid = CompareValues(comparableValue, comparableLower) >= 0; + } + + if (comparableUpper != null) + { + upperValid = CompareValues(comparableValue, comparableUpper) <= 0; + } + + return (lowerValid && upperValid, null); + } + catch (Exception ex) + { + return (true, $"参数 [ {Name}({Type}) ] 上下限比较失败:{ex.Message}"); + } + } + + private static object? ConvertToComparable(object value) + { + if (value is IConvertible convertible) + { + try + { + return convertible.ToDouble(null); + } + catch { } + + try + { + return convertible.ToDateTime(null); + } + catch { } + } + + return null; + } + + private static int CompareValues(object a, object b) + { + if (a is double aDouble && b is double bDouble) + { + return aDouble.CompareTo(bDouble); + } + + if (a is DateTime aDate && b is DateTime bDate) + { + return aDate.CompareTo(bDate); + } + + return 0; + } + } +} diff --git a/BOB/Models/ProgramModel.cs b/BOB/Models/ProgramModel.cs new file mode 100644 index 0000000..b7df03c --- /dev/null +++ b/BOB/Models/ProgramModel.cs @@ -0,0 +1,50 @@ +using Prism.Mvvm; // 引入 Prism 的 BindableBase +using System; +using System.Collections.ObjectModel; +using System.Linq; + +namespace BOB.Models +{ + public class ProgramModel : BindableBase + { + #region 构造函数 + + public ProgramModel() + { + // 可以进行初始化操作 + } + + public ProgramModel(ProgramModel source) + { + ID = source.ID; + StepCollection = new ObservableCollection(source.StepCollection.Select(p => new StepModel(p))); + Devices = new ObservableCollection(source.Devices.Select(p => new DeviceModel(p))); + Parameters = new ObservableCollection(source.Parameters.Select(p => new ParameterModel(p))); + } + + #endregion + + public Guid ID { get; set; } = Guid.NewGuid(); + + private ObservableCollection _stepCollection = new ObservableCollection(); + public ObservableCollection StepCollection + { + get => _stepCollection; + set => SetProperty(ref _stepCollection, value); // 使用 SetProperty 来处理属性更改 + } + + private ObservableCollection _parameters = new ObservableCollection(); + public ObservableCollection Parameters + { + get => _parameters; + set => SetProperty(ref _parameters, value); + } + + private ObservableCollection _devices = new ObservableCollection(); + public ObservableCollection Devices + { + get => _devices; + set => SetProperty(ref _devices, value); + } + } +} diff --git a/BOB/Models/StepModel.cs b/BOB/Models/StepModel.cs new file mode 100644 index 0000000..886596b --- /dev/null +++ b/BOB/Models/StepModel.cs @@ -0,0 +1,177 @@ +using Newtonsoft.Json; +using Prism.Mvvm; // 引入 Prism 的 BindableBase +using System; + +namespace BOB.Models +{ + public class StepModel : BindableBase + { + #region 构造函数 + + public StepModel() { } + + public StepModel(StepModel source) + { + if (source == null) return; + + ID = source.ID; + Index = source.Index; + Name = source.Name; + StepType = source.StepType; + LoopCount = source.LoopCount; + LoopStartStepId = source.LoopStartStepId; + OKExpression = source.OKExpression; + OKGotoStepID = source.OKGotoStepID; + NGGotoStepID = source.NGGotoStepID; + Description = source.Description; + IsUsed = source.IsUsed; + + if (source.Method != null) + { + Method = new MethodModel(source.Method); + } + if (source.SubProgram != null) + { + SubProgram = new ProgramModel(source.SubProgram); + } + } + + #endregion + + private Guid _id = Guid.NewGuid(); + [JsonIgnore] // 添加 JsonIgnore + public Guid ID + { + get => _id; + set => SetProperty(ref _id, value); + } + + private bool _isUsed = true; + [JsonIgnore] // 添加 JsonIgnore + public bool IsUsed + { + get => _isUsed; + set => SetProperty(ref _isUsed, value); + } + + private int _index; + [JsonIgnore] // 添加 JsonIgnore + public int Index + { + get => _index; + set => SetProperty(ref _index, value); + } + + private string? _name; + [JsonIgnore] // 添加 JsonIgnore + public string? Name + { + get => _name; + set => SetProperty(ref _name, value); + } + + private string? _stepType; + [JsonIgnore] // 添加 JsonIgnore + public string? StepType + { + get => _stepType; + set => SetProperty(ref _stepType, value); + } + + private MethodModel? _method; + [JsonIgnore] // 添加 JsonIgnore + public MethodModel? Method + { + get => _method; + set => SetProperty(ref _method, value); + } + + private ProgramModel? _subProgram; + [JsonIgnore] // 添加 JsonIgnore + public ProgramModel? SubProgram + { + get => _subProgram; + set => SetProperty(ref _subProgram, value); + } + + private int? _loopCount; + [JsonIgnore] // 添加 JsonIgnore + public int? LoopCount + { + get => _loopCount; + set => SetProperty(ref _loopCount, value); + } + + private int? _currentLoopCount; + [JsonIgnore] // 添加 JsonIgnore + public int? CurrentLoopCount + { + get => _currentLoopCount; + set => SetProperty(ref _currentLoopCount, value); + } + + private Guid? _loopStartStepId; + [JsonIgnore] // 添加 JsonIgnore + public Guid? LoopStartStepId + { + get => _loopStartStepId; + set => SetProperty(ref _loopStartStepId, value); + } + + private int _result = -1; + [JsonIgnore] // 添加 JsonIgnore + public int Result + { + get => _result; + set => SetProperty(ref _result, value); + } + + private int? _runTime; + [JsonIgnore] // 添加 JsonIgnore + public int? RunTime + { + get => _runTime; + set => SetProperty(ref _runTime, value); + } + + private string? _okExpression; + [JsonIgnore] // 添加 JsonIgnore + public string? OKExpression + { + get => _okExpression; + set => SetProperty(ref _okExpression, value); + } + + private string _gotoSettingString = ""; + [JsonIgnore] // 添加 JsonIgnore + public string GotoSettingString + { + get => _gotoSettingString; + set => SetProperty(ref _gotoSettingString, value); + } + + private Guid? _okGotoStepID; + [JsonIgnore] // 添加 JsonIgnore + public Guid? OKGotoStepID + { + get => _okGotoStepID; + set => SetProperty(ref _okGotoStepID, value); + } + + private Guid? _ngGotoStepID; + [JsonIgnore] // 添加 JsonIgnore + public Guid? NGGotoStepID + { + get => _ngGotoStepID; + set => SetProperty(ref _ngGotoStepID, value); + } + + private string? _description; + [JsonIgnore] // 添加 JsonIgnore + public string? Description + { + get => _description; + set => SetProperty(ref _description, value); + } + } +} diff --git a/BOB/SystemConfig.cs b/BOB/SystemConfig.cs index c38b855..a49f8b2 100644 --- a/BOB/SystemConfig.cs +++ b/BOB/SystemConfig.cs @@ -67,7 +67,7 @@ namespace BOB } catch (Exception ex) { - LoggerHelper.Logger.Error($"配置加载失败: {ex.Message}"); + LoggerHelper.ErrorWithNotify($"配置加载失败: {ex.Message}",ex.StackTrace); } } public void SaveToFile() @@ -84,11 +84,11 @@ namespace BOB File.WriteAllText(configPath, json); - LoggerHelper.Logger.Info("系统配置已保存。"); + LoggerHelper.InfoWithNotify("系统配置已保存。"); } catch (Exception ex) { - LoggerHelper.Logger.Error($"配置保存失败: {ex.Message}"); + LoggerHelper.ErrorWithNotify($"配置保存失败: {ex.Message}"); } } } diff --git a/BOB/ViewModels/CommandTreeViewModel.cs b/BOB/ViewModels/CommandTreeViewModel.cs index bc0461c..10b8b50 100644 --- a/BOB/ViewModels/CommandTreeViewModel.cs +++ b/BOB/ViewModels/CommandTreeViewModel.cs @@ -2,6 +2,7 @@ using Common.Attributes; using Logger; using Microsoft.IdentityModel.Logging; +using Newtonsoft.Json; using System; using System.Collections.Generic; using System.Collections.ObjectModel; @@ -15,6 +16,7 @@ using System.Windows; using System.Windows.Controls; using System.Windows.Input; using System.Xml; +using static BOB.Models.ParameterModel; namespace BOB.ViewModels { @@ -28,34 +30,60 @@ namespace BOB.ViewModels get => _SearchText; set => SetProperty(ref _SearchText, value); } - public ObservableCollection _InstructionTree=new(); + private ObservableCollection _instructionTree = new(); public ObservableCollection InstructionTree { - get => _InstructionTree; - set => SetProperty(ref _InstructionTree, value); + get => _instructionTree; + set => SetProperty(ref _instructionTree, value); } - public ObservableCollection Assemblies { get; set; } = []; + private ProgramModel _program; + public ProgramModel Program + { + get => _program; + set => SetProperty(ref _program, value); + } - public ObservableCollection SubPrograms { get; set; } = []; + private ObservableCollection _assemblies = new(); + public ObservableCollection Assemblies + { + get => _assemblies; + set => SetProperty(ref _assemblies, value); + } + private ObservableCollection _subPrograms = new(); + public ObservableCollection SubPrograms + { + get => _subPrograms; + set => SetProperty(ref _subPrograms, value); + } - private Dictionary _treeViewItemMap = []; - - private TreeViewItem _subProgramRootNode; - - private readonly Dictionary _xmlDocumentCache = []; + private Dictionary _treeNodeMap = new(); + public Dictionary TreeNodeMap + { + get => _treeNodeMap; + set => SetProperty(ref _treeNodeMap, value); + } + private Dictionary _xmlDocumentCache = new(); + public Dictionary XmlDocumentCache + { + get => _xmlDocumentCache; + set => SetProperty(ref _xmlDocumentCache, value); + } + GlobalVariables GlobalVariables { get; set; } #endregion public ICommand LoadedCommand { get; set; } public ICommand SearchEnterCommand { get; set; } public ICommand TreeDoubleClickCommand { get; set; } public ICommand ReloadCommand { get; set; } - public CommandTreeViewModel() + public CommandTreeViewModel(GlobalVariables _GlobalVariables) { + GlobalVariables= _GlobalVariables; + Program = GlobalVariables.Program; LoadedCommand = new DelegateCommand(Loaded); SearchEnterCommand = new DelegateCommand(Search); - TreeDoubleClickCommand = new DelegateCommand(TreeDoubleClick); + TreeDoubleClickCommand = new DelegateCommand(TreeDoubleClick); ReloadCommand = new DelegateCommand(Reload); } @@ -67,9 +95,31 @@ namespace BOB.ViewModels LoadSubPrograms(); LoadInstructionsToTreeView(); } - private void TreeDoubleClick() + private void TreeDoubleClick(object obj) { - + if(obj is InstructionNode Node) + { + if(Node.Children.Count == 0) + { + int index = GlobalVariables.SelectedStep?.Index >= 0 ? GlobalVariables.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; + } + } + } + } } private void Search() { @@ -113,13 +163,13 @@ namespace BOB.ViewModels } catch (Exception xmlEx) { - LoggerHelper.Logger.Warn($"加载XML注释失败: {Path.GetFileName(xmlPath)} - {xmlEx.Message}"); + LoggerHelper.warnWithNotify($"加载XML注释失败: {Path.GetFileName(xmlPath)} - {xmlEx.Message}"); } } } catch (Exception ex) { - LoggerHelper.Logger.Warn($"无法加载程序集 {Path.GetFileName(dllPath)}: {ex.Message}"); + LoggerHelper.warnWithNotify($"无法加载程序集 {Path.GetFileName(dllPath)}: {ex.Message}"); } } } @@ -146,7 +196,7 @@ namespace BOB.ViewModels } catch (Exception ex) { - LoggerHelper.Logger.Warn($"加载子程序错误: {filePath} - {ex.Message}"); + LoggerHelper.warnWithNotify($"加载子程序错误: {filePath} - {ex.Message}"); } } } @@ -159,6 +209,7 @@ namespace BOB.ViewModels var controlRootNode = new InstructionNode { Name = "系统指令", + Tag = "ControlRoot" }; InstructionTree.Add(controlRootNode); @@ -166,12 +217,14 @@ namespace BOB.ViewModels controlRootNode.Children.Add(new InstructionNode { Name = "循环开始", + Tag = "循环开始" }); // 循环结束 controlRootNode.Children.Add(new InstructionNode { Name = "循环结束", + Tag = "循环结束" }); // ---------------------- @@ -180,6 +233,7 @@ namespace BOB.ViewModels var subProgramRoot = new InstructionNode { Name = "子程序", + Tag = "SubProgramRoot" }; InstructionTree.Add(subProgramRoot); @@ -188,6 +242,7 @@ namespace BOB.ViewModels subProgramRoot.Children.Add(new InstructionNode { Name = subProgram.Name, + Tag = subProgram, }); } @@ -221,7 +276,7 @@ namespace BOB.ViewModels } catch (Exception ex) { - LoggerHelper.Logger.Error($"加载类型错误: {assembly.FullName} - {ex.Message}"); + LoggerHelper.ErrorWithNotify($"加载类型错误: {assembly.FullName} - {ex.Message}"); } if (validTypes.Count > 0) @@ -229,19 +284,20 @@ namespace BOB.ViewModels var assemblyNode = new InstructionNode { Name = assembly.GetName().Name, + Tag = assembly }; InstructionTree.Add(assemblyNode); - _treeViewItemMap[assembly] = assemblyNode; + TreeNodeMap[assembly] = assemblyNode; foreach (var type in validTypes) { var typeNode = new InstructionNode { Name = type.Name, - + Tag = type, }; assemblyNode.Children.Add(typeNode); - _treeViewItemMap[type] = typeNode; + TreeNodeMap[type] = typeNode; var allMethods = new HashSet(); GetPublicMethods(type, allMethods); @@ -263,11 +319,11 @@ namespace BOB.ViewModels var methodNode = new InstructionNode { Name = $"{method.Name}({paramText})", - + Tag = method, }; typeNode.Children.Add(methodNode); - _treeViewItemMap[method] = methodNode; + TreeNodeMap[method] = methodNode; } } } @@ -275,6 +331,7 @@ namespace BOB.ViewModels } #endregion + #region 辅助方法 /// /// 递归获取类型的所有公共方法(包括继承的方法),但跳过被重写的方法 /// @@ -328,6 +385,175 @@ namespace BOB.ViewModels } } } + #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) + 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); + } + } + 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(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); + } + } + + 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); + } + } + + #endregion } } diff --git a/BOB/ViewModels/LogAreaViewModel.cs b/BOB/ViewModels/LogAreaViewModel.cs index d5be75e..c46f11d 100644 --- a/BOB/ViewModels/LogAreaViewModel.cs +++ b/BOB/ViewModels/LogAreaViewModel.cs @@ -1,16 +1,37 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using Logger; +using Prism.Mvvm; +using System.Collections.ObjectModel; +using System.Windows.Media; namespace BOB.ViewModels { - public class LogAreaViewModel + public class LogAreaViewModel : BindableBase { + // 日志集合 + private ObservableCollection _logs = new(); + public ObservableCollection Logs + { + get => _logs; + set => SetProperty(ref _logs, value); + } + public LogAreaViewModel() { - + LoggerHelper.LogAdded += OnLogAdded; + } + + // 方便外部添加日志 + public void OnLogAdded(string message, string color) + { + var brush = (Brush)new BrushConverter().ConvertFromString(color); + Logs.Add(new LogItem { Message = message, Color = brush }); } } + + // 日志条目类 + public class LogItem + { + public string Message { get; set; } + public Brush Color { get; set; } = Brushes.Black; + } } diff --git a/BOB/ViewModels/StepsManagerViewModel.cs b/BOB/ViewModels/StepsManagerViewModel.cs index b5f1bd0..63e8b33 100644 --- a/BOB/ViewModels/StepsManagerViewModel.cs +++ b/BOB/ViewModels/StepsManagerViewModel.cs @@ -1,16 +1,138 @@ -using System; +using BOB.Models; +using Common.PubEvent; +using System; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.Linq; +using System.Reflection; using System.Text; using System.Threading.Tasks; +using System.Windows; +using System.Windows.Input; +using System.Xml; namespace BOB.ViewModels { - public class StepsManagerViewModel + public class StepsManagerViewModel:BindableBase { - public StepsManagerViewModel() + #region 属性 + private string _Title; + public string Title { - + get => _Title; + private set + { + SetProperty(ref _Title, value); + } } + private StepModel _SelectedStep; + public StepModel SelectedStep + { + get => _SelectedStep; + set + { + if (SetProperty(ref _SelectedStep, value)) + { + GlobalVariables.SelectedStep = value; + } + } + } + + private ProgramModel _program = new(); + public ProgramModel Program + { + get => _program; + set => SetProperty(ref _program, value); + } + private bool _IsAdmin = new(); + public bool IsAdmin + { + get => _IsAdmin; + set => SetProperty(ref _IsAdmin, value); + } + GlobalVariables GlobalVariables { get; set; } + private List tmpCopyList = new List(); + private IEventAggregator eventAggregator; + + #endregion + public ICommand EditStepCommand { get;set; } + public ICommand CopyStepCommand { get;set; } + public ICommand PasteStepCommand { get;set; } + public ICommand DeleteStepCommand { get;set; } + public StepsManagerViewModel(GlobalVariables _GlobalVariables,IEventAggregator eventAggregator) + { + eventAggregator = eventAggregator; + GlobalVariables = _GlobalVariables; + Program = GlobalVariables.Program; + IsAdmin = GlobalVariables.IsAdmin; + EditStepCommand = new DelegateCommand(EditStep); + CopyStepCommand = new DelegateCommand(CopyStep); + PasteStepCommand = new DelegateCommand(PasteStep); + DeleteStepCommand = new DelegateCommand(DeleteStep); + Program.StepCollection.CollectionChanged += StepCollection_CollectionChanged; + } + #region 委托命令 + private void EditStep() + { + if (IsAdmin && SelectedStep != null) + { + var stepToEdit = SelectedStep; + } + } + private void CopyStep() + { + if (IsAdmin && SelectedStep != null) + { + tmpCopyList.Clear(); + tmpCopyList.Add(SelectedStep); + } + } + private void PasteStep() + { + if (IsAdmin && tmpCopyList.Any()) + { + int insertIndex = Program.StepCollection.IndexOf(SelectedStep) + 1; + foreach (var item in tmpCopyList) + { + Program.StepCollection.Insert(insertIndex, new StepModel(item) { ID = Guid.NewGuid() }); + insertIndex++; + } + } + } + private void DeleteStep() + { + if (IsAdmin && SelectedStep != null) + { + var tmpList = new List { SelectedStep }; + foreach (var item in tmpList) + { + Program.StepCollection.Remove(item); + } + } + } + #endregion + + #region 辅助方法 + private void StepCollection_CollectionChanged(object? sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) + { + // Add/Move/Remove 都会触发,这里判断具体情形 + if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Add || + e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Move|| + e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Remove) + { + // 如果需要等 UI 更新完再处理,可也用 Dispatcher 延迟一小段时间 + Application.Current?.Dispatcher.BeginInvoke(new Action(() => + { + // after drop: 重新编号或其他处理 + for (int i = 0; i < Program.StepCollection.Count; i++) + Program.StepCollection[i].Index = i + 1; + + // 发布 Prism 事件(如果需要) + // _eventAggregator.GetEvent().Publish(...); + }), System.Windows.Threading.DispatcherPriority.Background); + } + } + + #endregion } } diff --git a/BOB/Views/CommandTree.xaml b/BOB/Views/CommandTree.xaml index a681389..bacef93 100644 --- a/BOB/Views/CommandTree.xaml +++ b/BOB/Views/CommandTree.xaml @@ -55,7 +55,8 @@ - + diff --git a/BOB/Views/LogArea.xaml b/BOB/Views/LogArea.xaml index a8be49f..467fc92 100644 --- a/BOB/Views/LogArea.xaml +++ b/BOB/Views/LogArea.xaml @@ -12,9 +12,8 @@ + ItemsSource="{Binding Logs}" + ScrollViewer.HorizontalScrollBarVisibility="Disabled"> diff --git a/BOB/Views/StepsManager.xaml b/BOB/Views/StepsManager.xaml index dd0212a..189f465 100644 --- a/BOB/Views/StepsManager.xaml +++ b/BOB/Views/StepsManager.xaml @@ -15,12 +15,15 @@ @@ -97,25 +100,25 @@ + Command="{Binding EditStepCommand}"/> + Command="{Binding CopyStepCommand}"/> + Command="{Binding PasteStepCommand}"/> + Command="{Binding DeleteStepCommand}" /> - + diff --git a/BOB/Views/StepsManager.xaml.cs b/BOB/Views/StepsManager.xaml.cs index a1e0653..dff404f 100644 --- a/BOB/Views/StepsManager.xaml.cs +++ b/BOB/Views/StepsManager.xaml.cs @@ -1,4 +1,5 @@ -using System; +using Common.PubEvent; +using System; using System.Collections.Generic; using System.Linq; using System.Text; diff --git a/Common/PubEvent/AfterDragEvent.cs b/Common/PubEvent/AfterDragEvent.cs new file mode 100644 index 0000000..d95f1bb --- /dev/null +++ b/Common/PubEvent/AfterDragEvent.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Common.PubEvent +{ + public class AfterDragEvent:PubSubEvent + { + } +} diff --git a/Logger/LoggerHelper.cs b/Logger/LoggerHelper.cs index 83c360e..0ee1bb3 100644 --- a/Logger/LoggerHelper.cs +++ b/Logger/LoggerHelper.cs @@ -1,6 +1,7 @@ using NLog; using System; using System.Collections.Generic; +using System.Drawing; using System.Linq; using System.Text; using System.Text.RegularExpressions; @@ -12,7 +13,48 @@ namespace Logger { public static readonly ILogger Logger = LogManager.GetLogger("InfoLogger"); public static readonly ILogger sqlLogger = LogManager.GetLogger("SqlLogger"); + // 日志事件,UI可以订阅 + public static event Action? LogAdded; + public static void InfoWithNotify(string message) + { + Logger.Info(message); // 写入 NLog + NotifyUI(message, "lightblue"); // 触发UI显示 + } + + public static void SuccessWithNotify(string message) + { + Logger.Info(message); + NotifyUI(message, "lightgreen"); + } + public static void warnWithNotify(string message, string stackTrace = null) + { + if (!string.IsNullOrEmpty(stackTrace)) + { + string location = GetProjectStackLine(stackTrace); + message = $"{message} ({location})"; + } + + Logger.Warn(message); + NotifyUI(message, "orange"); + } + + public static void ErrorWithNotify(string message, string stackTrace = null) + { + if (!string.IsNullOrEmpty(stackTrace)) + { + string location = GetProjectStackLine(stackTrace); + message = $"{message} ({location})"; + } + + Logger.Error(message); + NotifyUI(message, "red"); + } + + private static void NotifyUI(string message, string color) + { + LogAdded?.Invoke(message,color); + } // 解析堆栈,找到项目文件路径和行号 public static string GetProjectStackLine(string stackTrace) {