移植命令树,命令管理,日志管理

This commit is contained in:
hsc 2025-11-05 13:54:04 +08:00
parent a585333636
commit e87c097c4e
19 changed files with 1137 additions and 52 deletions

View File

@ -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>("MainView");
//注册弹窗
containerRegistry.RegisterDialog<MessageBoxView, MessageBoxViewModel>("MessageBox");
//注册全局变量
containerRegistry.RegisterSingleton<GlobalVariables>();
}

View File

@ -34,6 +34,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="gong-wpf-dragdrop" Version="4.0.0" />
<PackageReference Include="MaterialDesignColors" Version="5.3.0" />
<PackageReference Include="MaterialDesignThemes" Version="5.3.0" />
<PackageReference Include="MaterialDesignThemes.MahApps" Version="5.3.0" />

18
BOB/GlobalVariables.cs Normal file
View File

@ -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;
}
}

52
BOB/Models/DeviceModel.cs Normal file
View File

@ -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; } = "";
}
}

View File

@ -4,6 +4,7 @@ using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
namespace BOB.Models
{
@ -11,6 +12,7 @@ namespace BOB.Models
{
public string Name { get; set; }
public ObservableCollection<InstructionNode> Children { get; set; } = new();
public object Tag { get; set; }
}
}

40
BOB/Models/MethodModel.cs Normal file
View File

@ -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<ParameterModel>(
source.Parameters.Select(p => new ParameterModel(p)));
}
#endregion
public string? Name { get; set; }
public string? FullName { get; set; }
public ObservableCollection<ParameterModel> Parameters { get; set; } = [];
}
}

View File

@ -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<Guid, ParameterModel> paraList)
{
HashSet<Guid> visitedIds = new HashSet<Guid>();
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<Guid, ParameterModel> paraList)
{
HashSet<Guid> visitedIds = new HashSet<Guid>();
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;
}
}
}

View File

@ -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<StepModel>(source.StepCollection.Select(p => new StepModel(p)));
Devices = new ObservableCollection<DeviceModel>(source.Devices.Select(p => new DeviceModel(p)));
Parameters = new ObservableCollection<ParameterModel>(source.Parameters.Select(p => new ParameterModel(p)));
}
#endregion
public Guid ID { get; set; } = Guid.NewGuid();
private ObservableCollection<StepModel> _stepCollection = new ObservableCollection<StepModel>();
public ObservableCollection<StepModel> StepCollection
{
get => _stepCollection;
set => SetProperty(ref _stepCollection, value); // 使用 SetProperty 来处理属性更改
}
private ObservableCollection<ParameterModel> _parameters = new ObservableCollection<ParameterModel>();
public ObservableCollection<ParameterModel> Parameters
{
get => _parameters;
set => SetProperty(ref _parameters, value);
}
private ObservableCollection<DeviceModel> _devices = new ObservableCollection<DeviceModel>();
public ObservableCollection<DeviceModel> Devices
{
get => _devices;
set => SetProperty(ref _devices, value);
}
}
}

177
BOB/Models/StepModel.cs Normal file
View File

@ -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);
}
}
}

View File

@ -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}");
}
}
}

View File

@ -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<InstructionNode> _InstructionTree=new();
private ObservableCollection<InstructionNode> _instructionTree = new();
public ObservableCollection<InstructionNode> InstructionTree
{
get => _InstructionTree;
set => SetProperty(ref _InstructionTree, value);
get => _instructionTree;
set => SetProperty(ref _instructionTree, value);
}
public ObservableCollection<Assembly> Assemblies { get; set; } = [];
private ProgramModel _program;
public ProgramModel Program
{
get => _program;
set => SetProperty(ref _program, value);
}
public ObservableCollection<SubProgramItem> SubPrograms { get; set; } = [];
private ObservableCollection<Assembly> _assemblies = new();
public ObservableCollection<Assembly> Assemblies
{
get => _assemblies;
set => SetProperty(ref _assemblies, value);
}
private ObservableCollection<SubProgramItem> _subPrograms = new();
public ObservableCollection<SubProgramItem> SubPrograms
{
get => _subPrograms;
set => SetProperty(ref _subPrograms, value);
}
private Dictionary<object, InstructionNode> _treeViewItemMap = [];
private TreeViewItem _subProgramRootNode;
private readonly Dictionary<string, XmlDocument> _xmlDocumentCache = [];
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);
}
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<object>(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<MethodInfo>();
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
/// <summary>
/// 递归获取类型的所有公共方法(包括继承的方法),但跳过被重写的方法
/// </summary>
@ -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> -> 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<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);
}
}
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
}
}

View File

@ -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<LogItem> _logs = new();
public ObservableCollection<LogItem> 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;
}
}

View File

@ -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<StepModel> tmpCopyList = new List<StepModel>();
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<StepModel> { 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<StepMovedEvent>().Publish(...);
}), System.Windows.Threading.DispatcherPriority.Background);
}
}
#endregion
}
}

View File

@ -55,7 +55,8 @@
<!-- 双击 -->
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseDoubleClick">
<prism:InvokeCommandAction Command="{Binding TreeDoubleClickCommand}" />
<prism:InvokeCommandAction Command="{Binding TreeDoubleClickCommand}"
CommandParameter="{Binding SelectedItem, RelativeSource={RelativeSource AncestorType=TreeView}}" />
</i:EventTrigger>
</i:Interaction.Triggers>

View File

@ -12,9 +12,8 @@
<Grid>
<GroupBox Header="系统运行日志">
<ListView FontWeight="DemiBold"
ItemsSource="{Binding LogEntries}"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
SelectedItem="{Binding SelectedLog, Mode=TwoWay}">
ItemsSource="{Binding Logs}"
ScrollViewer.HorizontalScrollBarVisibility="Disabled">
<ListView.ItemTemplate>
<DataTemplate>

View File

@ -15,12 +15,15 @@
<Grid>
<GroupBox Header="{Binding Title}">
<DataGrid x:Name="ProgramDataGrid"
dd:DragDrop.IsDragSource="True"
dd:DragDrop.IsDropTarget="True"
dd:DragDrop.UseDefaultDragAdorner="True"
AutoGenerateColumns="False"
Background="Transparent"
CanUserAddRows="False"
CanUserSortColumns="False"
ItemsSource="{Binding Program.StepCollection}"
SelectedItem="{Binding SelectedStep, Mode=TwoWay}"
SelectedItem="{Binding SelectedStep, Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
SelectionMode="Extended"
SelectionUnit="FullRow">
@ -97,25 +100,25 @@
<DataGrid.ContextMenu>
<ContextMenu>
<MenuItem Header="编辑"
Command="{Binding DataContext.EditStepCommand, RelativeSource={RelativeSource AncestorType=UserControl}}" />
Command="{Binding EditStepCommand}"/>
<MenuItem Header="复制"
Command="{Binding DataContext.CopyStepCommand, RelativeSource={RelativeSource AncestorType=UserControl}}" />
Command="{Binding CopyStepCommand}"/>
<MenuItem Header="粘贴"
Command="{Binding DataContext.PasteStepCommand, RelativeSource={RelativeSource AncestorType=UserControl}}" />
Command="{Binding PasteStepCommand}"/>
<MenuItem Header="删除"
Foreground="Red"
Command="{Binding DataContext.DeleteStepCommand, RelativeSource={RelativeSource AncestorType=UserControl}}" />
Command="{Binding DeleteStepCommand}" />
</ContextMenu>
</DataGrid.ContextMenu>
<!-- 键盘 删除键绑定 Delete 命令 -->
<i:Interaction.Triggers>
<!--<i:Interaction.Triggers>
<i:EventTrigger EventName="PreviewKeyDown">
<i:InvokeCommandAction Command="{Binding DeleteStepCommand}"
CommandParameter="{Binding SelectedStep}"
PassEventArgsToCommand="True" />
</i:EventTrigger>
</i:Interaction.Triggers>
</i:Interaction.Triggers>-->
</DataGrid>
</GroupBox>

View File

@ -1,4 +1,5 @@
using System;
using Common.PubEvent;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

View File

@ -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
{
}
}

View File

@ -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<string, string>? 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)
{