From b69a469c89c4d953cd1a45591829928a149c8dc1 Mon Sep 17 00:00:00 2001 From: hsc Date: Tue, 11 Nov 2025 13:29:56 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E5=96=84=E6=89=A7=E8=A1=8C=E9=80=BB?= =?UTF-8?q?=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- BOB/App.xaml.cs | 5 +- .../ParameterToGotoSettingStringConverter.cs | 123 ++++ BOB/GlobalVariables.cs | 8 + BOB/Models/StepModel.cs | 1 + BOB/StepRunning.cs | 531 ++++++++++++++++++ BOB/ViewModels/CommandTreeViewModel.cs | 31 +- BOB/ViewModels/LogAreaViewModel.cs | 5 +- BOB/ViewModels/ParametersManagerViewModel.cs | 12 +- BOB/ViewModels/ShellViewModel.cs | 186 ++++-- BOB/ViewModels/SingleStepEditViewModel.cs | 28 +- BOB/ViewModels/StepsManagerViewModel.cs | 13 +- BOB/Views/ShellView.xaml | 8 +- BOB/Views/SingleStepEdit.xaml | 4 +- Common/Common.csproj | 1 + Common/Tool/ExpressionEvaluator.cs | 104 ++++ Logger/LoggerHelper.cs | 26 + 16 files changed, 1022 insertions(+), 64 deletions(-) create mode 100644 BOB/Converters/ParameterToGotoSettingStringConverter.cs create mode 100644 BOB/StepRunning.cs create mode 100644 Common/Tool/ExpressionEvaluator.cs diff --git a/BOB/App.xaml.cs b/BOB/App.xaml.cs index a132772..89a7314 100644 --- a/BOB/App.xaml.cs +++ b/BOB/App.xaml.cs @@ -1,4 +1,5 @@ -using BOB.Models; +using BOB.Converters; +using BOB.Models; using BOB.ViewModels; using BOB.ViewModels.Dialogs; using BOB.Views; @@ -38,7 +39,7 @@ namespace BOB containerRegistry.RegisterDialog("DeviceSetting"); //注册全局变量 containerRegistry.RegisterSingleton(); - + containerRegistry.RegisterSingleton(); } } diff --git a/BOB/Converters/ParameterToGotoSettingStringConverter.cs b/BOB/Converters/ParameterToGotoSettingStringConverter.cs new file mode 100644 index 0000000..531c03d --- /dev/null +++ b/BOB/Converters/ParameterToGotoSettingStringConverter.cs @@ -0,0 +1,123 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Data; + +namespace BOB.Converters +{ + public class ParameterToGotoSettingStringConverter : IValueConverter + { + private GlobalVariables _globalVariables; + public ParameterToGotoSettingStringConverter(GlobalVariables globalVariables) + { + _globalVariables= globalVariables; + } + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + if (value is Guid stepID && stepID == _globalVariables.SelectedStep!.ID) + { + if (_globalVariables.SelectedStep == null) + { + return ""; + } + if (_globalVariables.SelectedStep!.OKGotoStepID == null && _globalVariables.SelectedStep!.NGGotoStepID == null) + { + return "0/0"; + } + else + { + string gotoString = ""; + if (_globalVariables.SelectedStep!.OKGotoStepID != null) + { + var OKGotoStep = _globalVariables.Program.StepCollection.FirstOrDefault(x => x.ID == _globalVariables.SelectedStep!.OKGotoStepID); + if (OKGotoStep != null) + { + gotoString = OKGotoStep.Index.ToString() + "/"; + } + else + { + gotoString = "0/"; + } + } + else + { + gotoString = "0/"; + } + if (_globalVariables.SelectedStep!.NGGotoStepID != null) + { + var NGGotoStep = _globalVariables.Program.StepCollection.FirstOrDefault(x => x.ID == _globalVariables.SelectedStep!.NGGotoStepID); + if (NGGotoStep != null) + { + gotoString += NGGotoStep.Index.ToString(); + } + else + { + gotoString += "0"; + } + } + else + { + gotoString += "0"; + } + return gotoString; + } + } + return null; + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + if (value is string gotoSettingstring) + { + gotoSettingstring = gotoSettingstring.Replace(" ", "").Replace("/n", "").Replace("/r", ""); + if (gotoSettingstring == "0/0") + { + _globalVariables.SelectedStep!.OKGotoStepID = null; + _globalVariables.SelectedStep!.NGGotoStepID = null; + } + else + { + try + { + var list = gotoSettingstring.Split("/"); + var okindex = System.Convert.ToInt32(list[0]); + var ngindex = System.Convert.ToInt32(list[1]); + if (okindex == 0) + { + _globalVariables.SelectedStep!.OKGotoStepID = null; + } + else + { + if (okindex > _globalVariables.Program.StepCollection.Count) + { + throw new Exception("步骤序号超出最大值"); + } + _globalVariables.SelectedStep!.OKGotoStepID = _globalVariables.Program.StepCollection.FirstOrDefault(x => x.Index == okindex)?.ID; + } + if (ngindex == 0) + { + _globalVariables.SelectedStep!.NGGotoStepID = null; + } + else + { + if (ngindex > _globalVariables.Program.StepCollection.Count) + { + throw new Exception("步骤序号超出最大值"); + } + _globalVariables.SelectedStep!.NGGotoStepID = _globalVariables.Program.StepCollection.FirstOrDefault(x => x.Index == ngindex)?.ID; + } + } + catch (Exception ex) + { + MessageBox.Show($"跳转表达式错误:{ex.Message}"); + } + } + } + return _globalVariables.SelectedStep!.ID; + } + } +} diff --git a/BOB/GlobalVariables.cs b/BOB/GlobalVariables.cs index 9f54bb4..b2a310f 100644 --- a/BOB/GlobalVariables.cs +++ b/BOB/GlobalVariables.cs @@ -1,7 +1,10 @@ using BOB.Models; +using MaterialDesignThemes.Wpf; using System; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.Linq; +using System.Reflection; using System.Text; using System.Threading.Tasks; @@ -14,6 +17,11 @@ namespace BOB public String UserName { get; set; } = "hsc"; public String Title { get; set; } = "主程序"; public string CurrentFilePath { get; set; } + public bool? IsStop { get; set; } + public bool SingleStep { get; set; } + public string RunState { get; set; } = "运行"; + public ObservableCollection Assemblies { get; set; } = new(); + public PackIconKind RunIcon { get; set; } = PackIconKind.Play; public StepModel SelectedStep { get; set; } public ParameterModel SelectedParameter { get; set; } public DeviceModel SelectedDevice { get; set; } diff --git a/BOB/Models/StepModel.cs b/BOB/Models/StepModel.cs index 6397e9c..8c26444 100644 --- a/BOB/Models/StepModel.cs +++ b/BOB/Models/StepModel.cs @@ -22,6 +22,7 @@ namespace BOB.Models LoopStartStepId = source.LoopStartStepId; OKExpression = source.OKExpression; OKGotoStepID = source.OKGotoStepID; + GotoSettingString = source.GotoSettingString; NGGotoStepID = source.NGGotoStepID; Description = source.Description; IsUsed = source.IsUsed; diff --git a/BOB/StepRunning.cs b/BOB/StepRunning.cs new file mode 100644 index 0000000..aae0b65 --- /dev/null +++ b/BOB/StepRunning.cs @@ -0,0 +1,531 @@ +using BOB.Models; +using Common.Tools; +using Logger; +using MaterialDesignThemes.Wpf; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; +using static BOB.Models.ParameterModel; + + +namespace BOB +{ + public class StepRunning + { + private GlobalVariables _globalVariables; + + private readonly Dictionary tmpParameters = []; + + private readonly Stopwatch stepStopwatch = new(); + + private readonly Stack loopStopwatchStack = new(); + + private readonly Stack loopStack = new(); + + public CancellationTokenSource stepCTS = new(); + private bool SubSingleStep = false; + + private Guid TestRoundID; + public StepRunning(GlobalVariables globalVariables) + { + _globalVariables = globalVariables; + } + public async Task ExecuteSteps(ProgramModel program, int depth = 0, CancellationToken cancellationToken = default) + { + int index = 0; + bool stepSuccess = false; + if (depth == 0) + { + loopStack.Clear(); + loopStopwatchStack.Clear(); + ResetAllStepStatus(program); + tmpParameters.Clear(); + TestRoundID = Guid.NewGuid(); + } + foreach (var item in program.Parameters) + { + tmpParameters.TryAdd(item.ID, item); + } + + while (index < program.StepCollection.Count) + { + while (_globalVariables.IsStop == true) + { + await Task.Delay(50); + } + if (cancellationToken.IsCancellationRequested) + { + break; + } + var step = program.StepCollection[index]; + if (!step.IsUsed) + { + index++; + continue; + } + + step.Result = 0; + if (step.StepType == "循环开始") + { + var endStep = program.StepCollection.FirstOrDefault(x => x.LoopStartStepId == step.ID); + if (endStep != null) + { + endStep.Result = 0; + } + else + { + LoggerHelper.ErrorWithNotify("程序循环指令未闭合,请检查后重试"); + break; + } + } + + // 处理循环开始 + if (step.StepType == "循环开始") + { + Stopwatch loopStopwatch = new(); + loopStopwatch.Start(); + loopStopwatchStack.Push(loopStopwatch); + var context = new LoopContext + { + LoopCount = step.LoopCount ?? 1, + CurrentLoop = 0, + StartIndex = index, + LoopStartStep = step + }; + loopStack.Push(context); + step.CurrentLoopCount = context.LoopCount; + LoggerHelper.InfoWithNotify($"循环开始,共{context.LoopCount}次", depth); + index++; + } + + // 处理循环结束 + else if (step.StepType == "循环结束") + { + if (loopStack.Count == 0) + { + LoggerHelper.ErrorWithNotify("未匹配的循环结束指令", depth); + step.Result = 2; + index++; + continue; + } + + var context = loopStack.Peek(); + context.CurrentLoop++; + + // 更新循环开始步骤的显示 + context.LoopStartStep!.CurrentLoopCount = context.LoopCount - context.CurrentLoop; + + if (context.CurrentLoop < context.LoopCount) + { + // 继续循环:跳转到循环开始后的第一条指令 + index = context.StartIndex + 1; + LoggerHelper.InfoWithNotify($"循环第{context.CurrentLoop}次结束,跳回开始,剩余{context.LoopCount - context.CurrentLoop}次", depth); + } + else + { + // 循环结束 + loopStack.Pop(); + var loopStopwatch = loopStopwatchStack.Peek(); + index++; + LoggerHelper.InfoWithNotify($"循环结束,共执行{context.LoopCount}次", depth); + if (depth == 0 && loopStopwatch.IsRunning) + { + loopStopwatch.Stop(); + step.RunTime = (int)loopStopwatch.ElapsedMilliseconds; + step.Result = 1; + program.StepCollection.First(x => x.ID == step.LoopStartStepId).Result = 1; + loopStopwatchStack.Pop(); + } + } + } + + // 处理普通步骤 + else + { + if (depth == 0) + { + stepStopwatch.Restart(); + } + + if (step.SubProgram != null) + { + if (_globalVariables.SingleStep)//子程序的单步执行将执行完保存下的所有Method + { + SubSingleStep = true; + _globalVariables.SingleStep = false; + } + LoggerHelper.InfoWithNotify($"开始执行子程序 [ {step.Index} ] [ {step.Name} ] ", depth); + stepSuccess = await ExecuteSteps(step.SubProgram, depth + 1, cancellationToken); + UpdateCurrentStepResult(step, true, stepSuccess, depth); + if (SubSingleStep) + { + SubSingleStep = false; + _globalVariables.SingleStep = true; + } + } + else if (step.Method != null) + { + LoggerHelper.InfoWithNotify($"开始执行指令 [ {step.Index} ] [ {step.Method!.FullName}.{step.Method.Name} ] ", depth); + await ExecuteMethodStep(step, tmpParameters, depth, cancellationToken); + stepSuccess = step.Result == 1; + if (step.NGGotoStepID != null && !stepSuccess) + { + var tmp = program.StepCollection.FirstOrDefault(x => x.ID == step.NGGotoStepID); + if (tmp != null) + { + index = tmp.Index - 2; + LoggerHelper.InfoWithNotify($"指令跳转 [ {tmp.Index} ] [ {tmp.Name} ]", depth); + } + } + if (step.OKGotoStepID != null && stepSuccess) + { + var tmp = program.StepCollection.FirstOrDefault(x => x.ID == step.OKGotoStepID); + if (tmp != null) + { + index = tmp.Index - 2; + LoggerHelper.InfoWithNotify($"指令跳转 [ {tmp.Index} ] [ {tmp.Name} ]", depth); + } + } + } + index++; + + if (depth == 0 && stepStopwatch.IsRunning) + { + stepStopwatch.Stop(); + step.RunTime = (int)stepStopwatch.ElapsedMilliseconds; + } + if (_globalVariables.SingleStep) + { + _globalVariables.IsStop = true; + _globalVariables.RunState = "运行"; + _globalVariables.RunIcon = PackIconKind.Play; + } + } + } + + return loopStack.Count == 0 && stepSuccess; + } + + public async Task ExecuteMethodStep(StepModel step, Dictionary parameters, int depth, CancellationToken cancellationToken = default) + { + try + { + _globalVariables.SelectedStep = null; + await Task.Delay(SystemConfig.Instance.PerformanceLevel); + + // 1. 查找类型 + Type? targetType = null; + foreach (var assembly in _globalVariables.Assemblies) + { + targetType = assembly.GetType(step.Method!.FullName!); + if (targetType != null) break; + } + if (targetType == null) + { + LoggerHelper.ErrorWithNotify($"指令 [ {step.Index} ] 执行错误:未找到类型 {step.Method!.FullName}", depth); + step.Result = 2; + } + + // 2. 创建实例(仅当方法不是静态时才需要) + object? instance = null; + bool isMethod = false; + + // 3. 准备参数 + var inputParams = new List(); + var paramTypes = new List(); + ParameterModel? outputParam = null; + foreach (var param in step.Method!.Parameters) + { + if (param.Category == ParameterCategory.Input) + { + if (param.Type == typeof(CancellationToken)) + { + inputParams.Add(stepCTS.Token); + paramTypes.Add(param.Type!); + continue; + } + var actualValue = param.GetActualValue(tmpParameters); + // 类型转换处理 + if (actualValue != null) + { + if (string.IsNullOrEmpty(actualValue.ToString())) + { + actualValue = null; + } + if (actualValue != null && param.Type != null && actualValue.GetType() != param.Type) + { + try + { + if (param.Type.IsArray) + { + // 获取数组元素类型 + Type elementType = param.Type.GetElementType()!; + + // 解析字符串为字符串数组 + string[] stringArray = actualValue.ToString()! + .Trim('[', ']') + .Split(',', StringSplitOptions.RemoveEmptyEntries) + .Select(s => s.Trim()) + .ToArray(); + + // 创建目标类型数组 + Array array = Array.CreateInstance(elementType, stringArray.Length); + + // 转换每个元素 + for (int i = 0; i < stringArray.Length; i++) + { + try + { + // 特殊处理字符串类型 + if (elementType == typeof(string)) + { + array.SetValue(stringArray[i], i); + } + // 特殊处理枚举类型 + else if (elementType.IsEnum) + { + array.SetValue(Enum.Parse(elementType, stringArray[i]), i); + } + // 常规类型转换 + else + { + array.SetValue(Convert.ChangeType(stringArray[i], elementType), i); + } + } + catch + { + throw new InvalidCastException($"指令 [ {step.Index} ] 执行错误:元素 '{stringArray[i]}' 无法转换为 {elementType.Name}[]"); + } + } + actualValue = array; + } + else + { + if (param.Type.BaseType == typeof(Enum)) + { + actualValue = Enum.Parse(param.Type, param.Value!.ToString()!); + } + else + { + actualValue = Convert.ChangeType(actualValue, param.Type); + } + } + } + catch (Exception ex) + { + LoggerHelper.WarnWithNotify($"指令 [ {step.Index} ] 执行错误:参数 {param.Name} 类型转换失败: {ex.Message}", depth); + } + } + } + inputParams.Add(actualValue); + paramTypes.Add(param.Type!); + } + else if (param.Category == ParameterCategory.Output) + { + outputParam = param; + } + } + + // 4. 获取方法 + var method = targetType!.GetMethod( + step.Method.Name!, + BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance, + null, + paramTypes.ToArray(), + null + ); + + if (method == null) + { + LoggerHelper.ErrorWithNotify($"指令 [ {step.Index} ] 执行错误:未找到方法{step.Method.Name}", depth); + step.Result = 2; + } + + // 检查是否是静态方法 + bool isStaticMethod = method!.IsStatic; + + // 如果是实例方法,需要创建实例 + if (!isMethod) + { + try + { + instance = Activator.CreateInstance(targetType); + } + catch (Exception ex) + { + LoggerHelper.ErrorWithNotify($"指令 [ {step.Index} ] 执行错误:创建实例失败 - {ex.Message}", depth); + step.Result = 2; + } + } + + // 5. 执行方法 + + object? returnValue = method.Invoke(instance, inputParams.ToArray()); + try + { + // 处理异步方法 + if (returnValue is Task task) + { + await task.ConfigureAwait(false); + // 获取结果(如果是Task) + if (task.GetType().IsGenericType) + { + var returnValueProperty = task.GetType().GetProperty("Result"); + returnValue = returnValueProperty?.GetValue(task); + } + else + { + returnValue = null; + } + } + + // 处理VoidTaskreturnValue类型 + if (returnValue != null && returnValue.GetType().FullName == "System.Threading.Tasks.VoidTaskreturnValue") + { + returnValue = null; + } + } + catch (OperationCanceledException) + { + return; + } + catch (Exception ex) + { + LoggerHelper.ErrorWithNotify($"指令 [ {step.Index} ] 执行错误: {ex.InnerException?.Message ?? ex.Message}", depth); + step.Result = 2; + return; + } + + // 6. 处理输出 + bool paraResult = true; //记录参数上下限是否NG + if (outputParam != null) + { + outputParam.Value = returnValue; + var currentPara = outputParam.GetCurrentParameter(tmpParameters); + if (currentPara != null) + { + currentPara.Value = returnValue; + var tmp = currentPara.GetResult(); + currentPara.Result = tmp.Item1; + paraResult = tmp.Item1; + if (tmp.Item2 != null) + { + LoggerHelper.WarnWithNotify(tmp.Item2); + } + if (currentPara.IsSave && _globalVariables.Program.Parameters.FirstOrDefault(x => x.ID == currentPara.ID) != null) + { + _ = SaveDataToDatabase(_globalVariables.Program.ID, currentPara); + } + } + var returnType = returnValue?.GetType(); + if (returnType != null) + { + if (!returnType.IsArray) + { + LoggerHelper.SuccessWithNotify($"输出 [ {outputParam.Name} ] = {returnValue} ({returnType.Name})", depth); + } + else + { + if (returnValue is IEnumerable enumerable) + { + var elements = enumerable.Cast().Select(item => item?.ToString() ?? "null"); + LoggerHelper.SuccessWithNotify($"输出 [ {outputParam.Name} ] = [ {string.Join(", ", elements)} ] ({returnType.Name})", depth); + } + } + } + } + LoggerHelper.SuccessWithNotify($"指令 [ {step.Index} ] 执行成功", depth); + UpdateCurrentStepResult(step, paraResult: paraResult, depth: depth); + } + catch (OperationCanceledException) + { + return; + } + catch (Exception ex) + { + LoggerHelper.ErrorWithNotify($"指令 [ {step.Index} ] 执行错误: {ex.InnerException?.Message ?? ex.Message}", depth); + step.Result = 2; + return; + } + } + + public void ResetAllStepStatus(ProgramModel program) + { + foreach (var step in program.StepCollection) + { + step.Result = -1; + step.RunTime = null; + } + } + + private void UpdateCurrentStepResult(StepModel step, bool paraResult = true, bool stepResult = true, int depth = 0) + { + if (stepResult && paraResult) + { + if (string.IsNullOrEmpty(step.OKExpression)) + { + step.Result = 1; + } + else + { + Dictionary paraDic = []; + foreach (var item in tmpParameters) + { + paraDic.TryAdd(item.Value.Name, item.Value.Value!); + } + if (step.SubProgram != null) + { + foreach (var item in step.SubProgram.Parameters.Where(x => x.Category == ParameterCategory.Output)) + { + paraDic.TryAdd(item.Name, item.Value!); + } + } + else if (step.Method != null) + { + foreach (var item in step.Method.Parameters.Where(x => x.Category == ParameterCategory.Output)) + { + paraDic.TryAdd(item.Name, item.Value!); + } + } + bool re = ExpressionEvaluator.EvaluateExpression(step.OKExpression, paraDic); + step.Result = re ? 1 : 2; + if (step.Result == 2) + { + LoggerHelper.WarnWithNotify($"指令 [ {step.Index} ] NG:条件表达式验证失败", depth); + } + } + } + else + { + if (!paraResult) + { + LoggerHelper.WarnWithNotify("参数限值校验失败", depth); + } + step.Result = 2; + } + } + + private async Task SaveDataToDatabase(Guid programID, ParameterModel currentPara) + { + + } + + #region 私有类 + + private class LoopContext + { + public int LoopCount { get; set; } + public int CurrentLoop { get; set; } + public int StartIndex { get; set; } + public StepModel? LoopStartStep { get; set; } + } + + #endregion + + } +} diff --git a/BOB/ViewModels/CommandTreeViewModel.cs b/BOB/ViewModels/CommandTreeViewModel.cs index abc2635..2505f17 100644 --- a/BOB/ViewModels/CommandTreeViewModel.cs +++ b/BOB/ViewModels/CommandTreeViewModel.cs @@ -37,18 +37,30 @@ namespace BOB.ViewModels set => SetProperty(ref _instructionTree, value); } - private ProgramModel _program; public ProgramModel Program { - get => _program; - set => SetProperty(ref _program, value); + get => _globalVariables.Program; + set + { + if (_globalVariables.Program != value) + { + _globalVariables.Program = value; + RaisePropertyChanged(); + } + } } - private ObservableCollection _assemblies = new(); public ObservableCollection Assemblies { - get => _assemblies; - set => SetProperty(ref _assemblies, value); + get => _globalVariables.Assemblies; + set + { + if (_globalVariables.Assemblies != value) + { + _globalVariables.Assemblies = value; + RaisePropertyChanged(); + } + } } private ObservableCollection _subPrograms = new(); @@ -71,7 +83,7 @@ namespace BOB.ViewModels get => _xmlDocumentCache; set => SetProperty(ref _xmlDocumentCache, value); } - GlobalVariables GlobalVariables { get; set; } + GlobalVariables _globalVariables { get; set; } #endregion public ICommand LoadedCommand { get; set; } public ICommand SearchEnterCommand { get; set; } @@ -79,8 +91,7 @@ namespace BOB.ViewModels public ICommand ReloadCommand { get; set; } public CommandTreeViewModel(GlobalVariables _GlobalVariables) { - GlobalVariables= _GlobalVariables; - Program = GlobalVariables.Program; + _globalVariables= _GlobalVariables; LoadedCommand = new DelegateCommand(Loaded); SearchEnterCommand = new DelegateCommand(Search); TreeDoubleClickCommand = new DelegateCommand(TreeDoubleClick); @@ -101,7 +112,7 @@ namespace BOB.ViewModels { if(Node.Children.Count == 0) { - int index = GlobalVariables.SelectedStep?.Index >= 0 ? GlobalVariables.SelectedStep.Index : -1; + int index = _globalVariables.SelectedStep?.Index >= 0 ? _globalVariables.SelectedStep.Index : -1; if (Node.Tag is MethodInfo method) { AddMethodToProgram(method, index); diff --git a/BOB/ViewModels/LogAreaViewModel.cs b/BOB/ViewModels/LogAreaViewModel.cs index c46f11d..015171c 100644 --- a/BOB/ViewModels/LogAreaViewModel.cs +++ b/BOB/ViewModels/LogAreaViewModel.cs @@ -1,7 +1,9 @@ using Logger; using Prism.Mvvm; using System.Collections.ObjectModel; +using System.Windows; using System.Windows.Media; +using System.Windows.Threading; namespace BOB.ViewModels { @@ -24,7 +26,8 @@ namespace BOB.ViewModels public void OnLogAdded(string message, string color) { var brush = (Brush)new BrushConverter().ConvertFromString(color); - Logs.Add(new LogItem { Message = message, Color = brush }); + Application.Current.Dispatcher.Invoke(() => Logs.Add(new LogItem { Message = message, Color = brush })); + } } diff --git a/BOB/ViewModels/ParametersManagerViewModel.cs b/BOB/ViewModels/ParametersManagerViewModel.cs index 1115bad..3f3b93e 100644 --- a/BOB/ViewModels/ParametersManagerViewModel.cs +++ b/BOB/ViewModels/ParametersManagerViewModel.cs @@ -16,11 +16,17 @@ namespace BOB.ViewModels public class ParametersManagerViewModel:BindableBase { #region 属性 - private ProgramModel _program; public ProgramModel Program { - get => _program; - set => SetProperty(ref _program, value); + get => _globalVariables.Program; + set + { + if (_globalVariables.Program != value) + { + _globalVariables.Program = value; + RaisePropertyChanged(); + } + } } private ParameterModel _SelectedParameter; public ParameterModel SelectedParameter diff --git a/BOB/ViewModels/ShellViewModel.cs b/BOB/ViewModels/ShellViewModel.cs index fa049cf..8920691 100644 --- a/BOB/ViewModels/ShellViewModel.cs +++ b/BOB/ViewModels/ShellViewModel.cs @@ -29,6 +29,45 @@ namespace BOB.ViewModels set => SetProperty(ref _IsLeftDrawerOpen, value); } + public String RunState + { + get => _globalVariables.RunState; + set + { + if (_globalVariables.RunState != value) + { + _globalVariables.RunState = value; + RaisePropertyChanged(); + } + } + } + + public bool SingleStep + { + get => _globalVariables.SingleStep; + set + { + if (_globalVariables.SingleStep != value) + { + _globalVariables.SingleStep = value; + RaisePropertyChanged(); + } + } + } + + public PackIconKind RunIcon + { + get => _globalVariables.RunIcon; + set + { + if (_globalVariables.RunIcon != value) + { + _globalVariables.RunIcon = value; + RaisePropertyChanged(); + } + } + } + #endregion #region 命令 public ICommand LeftDrawerOpenCommand { get; set; } @@ -44,14 +83,18 @@ namespace BOB.ViewModels public ICommand OpenCommand { get; set; } public ICommand NewCommand { get; set; } public ICommand SetDefaultCommand { get; set; } + public ICommand LoadCommand { get; set; } #endregion private IEventAggregator _eventAggregator; private GlobalVariables _globalVariables; - public ShellViewModel(IEventAggregator eventAggregator, IContainerProvider containerProvider,GlobalVariables globalVariables) + private StepRunning _stepRunning; + private Task? currentExecutionTask; + public ShellViewModel(IEventAggregator eventAggregator, IContainerProvider containerProvider,GlobalVariables globalVariables, StepRunning stepRunning) { - _eventAggregator= eventAggregator; - _globalVariables= globalVariables; + _eventAggregator = eventAggregator; + _globalVariables = globalVariables; + _stepRunning=stepRunning; LeftDrawerOpenCommand = new DelegateCommand(LeftDrawerOpen); MinimizeCommand = new DelegateCommand(MinimizeWindow); MaximizeCommand = new DelegateCommand(MaximizeWindow); @@ -61,66 +104,89 @@ namespace BOB.ViewModels ResotrationCommand = new DelegateCommand(Resotration); StopCommand = new DelegateCommand(Stop); NewCommand = new DelegateCommand(New); - OpenCommand = new DelegateCommand(Open); + OpenCommand = new DelegateCommand(Open); SaveAsCommand = new DelegateCommand(SaveAs); SaveCommand = new DelegateCommand(Save); SetDefaultCommand = new DelegateCommand(SetDefault); + LoadCommand = new DelegateCommand(Load); } #region ToolBar命令 + private void Load() + { + if (SystemConfig.Instance.DefaultSubProgramFilePath != null) + { + if (File.Exists(SystemConfig.Instance.DefaultSubProgramFilePath)) + { + Open(SystemConfig.Instance.DefaultSubProgramFilePath); + } + } + } private void SetDefault() { if(_globalVariables.CurrentFilePath!=null) { SystemConfig.Instance.DefaultSubProgramFilePath = _globalVariables.CurrentFilePath; + SystemConfig.Instance.SaveToFile(); } } private void New() { _globalVariables.CurrentFilePath = null; - _globalVariables.Program = new(); + _globalVariables.Program.Parameters.Clear(); + _globalVariables.Program.Devices.Clear(); + _globalVariables.Program.StepCollection.Clear(); } - private void Open() + private void Open(string filePath = null) { try { - var openFileDialog = new OpenFileDialog + // 如果没有传路径,弹出文件选择对话框 + if (string.IsNullOrEmpty(filePath)) { - Filter = "BOB程序文件|*.bob|所有文件|*.*", - Title = "打开程序", - InitialDirectory = @"D:\BOB\子程序" - }; - - if (openFileDialog.ShowDialog() == true) - { - string filePath = openFileDialog.FileName; - - // 读取 JSON 文件内容 - string json = File.ReadAllText(filePath); - - // 反序列化为 ProgramModel - var program = JsonConvert.DeserializeObject(json); - - if (program != null) + var openFileDialog = new OpenFileDialog { - _globalVariables.Program = program; - _globalVariables.CurrentFilePath = filePath; + Filter = "BOB程序文件|*.bob|所有文件|*.*", + Title = "打开程序", + InitialDirectory = @"D:\BOB\子程序" + }; - LoggerHelper.SuccessWithNotify($"成功打开文件: {filePath}"); - } - else - { - LoggerHelper.WarnWithNotify($"文件内容格式不正确: {filePath}"); - } + if (openFileDialog.ShowDialog() != true) + return; // 用户取消选择 + + filePath = openFileDialog.FileName; } + + // 确认文件存在 + if (!File.Exists(filePath)) + { + LoggerHelper.ErrorWithNotify($"文件不存在: {filePath}"); + return; + } + + // 读取 JSON 文件 + string json = File.ReadAllText(filePath); + + // 反序列化为 ProgramModel + var program = JsonConvert.DeserializeObject(json); + + if (program == null) + { + LoggerHelper.WarnWithNotify($"文件格式不正确或为空: {filePath}"); + return; + } + + _globalVariables.Program.Parameters = program.Parameters; + _globalVariables.Program.Devices = program.Devices; + _globalVariables.Program.StepCollection = program.StepCollection; + _globalVariables.CurrentFilePath = filePath; + LoggerHelper.SuccessWithNotify($"成功打开文件: {filePath}"); } catch (Exception ex) { LoggerHelper.ErrorWithNotify($"打开文件失败: {ex.Message}"); } } - - private void SaveAs() { string defaultPath = @"D:\BOB\子程序"; @@ -197,14 +263,60 @@ namespace BOB.ViewModels LoggerHelper.InfoWithNotify(_globalVariables.UserName + "执行复位命令"); } - private void RunSingle() + private async void RunSingle() { - LoggerHelper.InfoWithNotify(_globalVariables.UserName + "执行单步执行命令"); + if (RunState == "运行") + { + SingleStep = true; + LoggerHelper.InfoWithNotify(_globalVariables.UserName + "执行单步执行命令"); + RunState = "暂停"; + RunIcon = PackIconKind.Pause; + if (_globalVariables.IsStop == null) + { + _globalVariables.IsStop = false; + currentExecutionTask = _stepRunning.ExecuteSteps(_globalVariables.Program, cancellationToken: _stepRunning.stepCTS.Token); + await currentExecutionTask; + RunState = "运行"; + RunIcon = PackIconKind.Play; + _globalVariables.IsStop = null; + } + else if (_globalVariables.IsStop == true) + { + _globalVariables.IsStop = false; + } + } } - private void Running() + private async void Running() { - LoggerHelper.InfoWithNotify(_globalVariables.UserName + "执行运行命令"); + + if (RunState == "运行") + { + SingleStep = false; + LoggerHelper.InfoWithNotify(_globalVariables.UserName + "执行运行命令"); + RunState = "暂停"; + RunIcon = PackIconKind.Pause; + if (_globalVariables.IsStop == null) + { + _globalVariables.IsStop = false; + currentExecutionTask = _stepRunning.ExecuteSteps(_globalVariables.Program, cancellationToken: _stepRunning.stepCTS.Token); + await currentExecutionTask; + RunState = "运行"; + RunIcon = PackIconKind.Play; + _globalVariables.IsStop = null; + } + else if (_globalVariables.IsStop == true) + { + _globalVariables.IsStop = false; + } + } + else + { + LoggerHelper.InfoWithNotify(_globalVariables.UserName + "点击暂停命令"); + _globalVariables.IsStop = true; + RunState = "运行"; + RunIcon = PackIconKind.Play; + } } private void LeftDrawerOpen() diff --git a/BOB/ViewModels/SingleStepEditViewModel.cs b/BOB/ViewModels/SingleStepEditViewModel.cs index f560730..fd2b4ec 100644 --- a/BOB/ViewModels/SingleStepEditViewModel.cs +++ b/BOB/ViewModels/SingleStepEditViewModel.cs @@ -12,17 +12,29 @@ namespace BOB.ViewModels public class SingleStepEditViewModel:BindableBase { #region 属性 + private Guid _ID; + public Guid ID + { + get => _ID; + set => SetProperty(ref _ID, value); + } private StepModel _SelectedStep; public StepModel SelectedStep { get => _SelectedStep; set => SetProperty(ref _SelectedStep, value); } - private ProgramModel _Program; public ProgramModel Program { - get => _Program; - set => SetProperty(ref _Program, value); + get => _globalVariables.Program; + set + { + if (_globalVariables.Program != value) + { + _globalVariables.Program = value; + RaisePropertyChanged(); + } + } } #endregion private GlobalVariables _globalVariables; @@ -37,7 +49,6 @@ namespace BOB.ViewModels CancelEditCommand = new DelegateCommand(CancelEdit); SaveStepCommand = new DelegateCommand(SaveStep); _eventAggregator.GetEvent().Subscribe(DisposeSelectedStep); - Program = _globalVariables.Program; } private void DisposeSelectedStep(Guid id) @@ -58,11 +69,18 @@ namespace BOB.ViewModels { return; } - + var steps = _globalVariables.Program.StepCollection; + int index = steps.ToList().FindIndex(x => x.ID == ID); + if (index >= 0) + { + steps[index] = SelectedStep; + } + } private void EditSingleSetp() { + ID = _globalVariables.SelectedStep.ID; SelectedStep = new StepModel(_globalVariables.SelectedStep); } } diff --git a/BOB/ViewModels/StepsManagerViewModel.cs b/BOB/ViewModels/StepsManagerViewModel.cs index 8303662..59575cf 100644 --- a/BOB/ViewModels/StepsManagerViewModel.cs +++ b/BOB/ViewModels/StepsManagerViewModel.cs @@ -34,11 +34,17 @@ namespace BOB.ViewModels } } } - private ProgramModel _Program; public ProgramModel Program { - get => _Program; - set => SetProperty(ref _Program, value); + get => _globalVariables.Program; + set + { + if (_globalVariables.Program != value) + { + _globalVariables.Program = value; + RaisePropertyChanged(); + } + } } private bool _IsAdmin; public bool IsAdmin @@ -60,7 +66,6 @@ namespace BOB.ViewModels _eventAggregator = eventAggregator; _globalVariables = _GlobalVariables; IsAdmin = _globalVariables.IsAdmin; - Program = _globalVariables.Program; EditStepCommand = new DelegateCommand(EditStep); CopyStepCommand = new DelegateCommand(CopyStep); PasteStepCommand = new DelegateCommand(PasteStep); diff --git a/BOB/Views/ShellView.xaml b/BOB/Views/ShellView.xaml index db79983..7683515 100644 --- a/BOB/Views/ShellView.xaml +++ b/BOB/Views/ShellView.xaml @@ -18,6 +18,12 @@ + + + + + + @@ -143,7 +149,7 @@ Command="{Binding RunningCommand}" Foreground="White"> - diff --git a/BOB/Views/SingleStepEdit.xaml b/BOB/Views/SingleStepEdit.xaml index c8078be..ab489d8 100644 --- a/BOB/Views/SingleStepEdit.xaml +++ b/BOB/Views/SingleStepEdit.xaml @@ -21,6 +21,7 @@ + @@ -66,7 +67,8 @@ Content="跳转" ToolTip="格式:OK跳转序号/NG跳转序号(默认为0/0)" /> + Text="{Binding SelectedStep.GotoSettingString}" + materialDesign:HintAssist.Hint="格式:OK跳转序号/NG跳转序号(默认为0/0)" /> diff --git a/Common/Common.csproj b/Common/Common.csproj index 4457c23..d6def2b 100644 --- a/Common/Common.csproj +++ b/Common/Common.csproj @@ -8,6 +8,7 @@ + diff --git a/Common/Tool/ExpressionEvaluator.cs b/Common/Tool/ExpressionEvaluator.cs new file mode 100644 index 0000000..7b0bf17 --- /dev/null +++ b/Common/Tool/ExpressionEvaluator.cs @@ -0,0 +1,104 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using NCalc; + +namespace Common.Tools +{ + public class ExpressionEvaluator + { + public static bool EvaluateExpression(string expression, Dictionary? variables = null) + { + try + { + // 预处理:替换中文变量名为英文别名 + var (processedExpression, processedVariables) = PreprocessExpression(expression, variables); + + var expr = new Expression(processedExpression, EvaluateOptions.IgnoreCase); + + if (processedVariables != null) + { + foreach (var kvp in processedVariables) + { + expr.Parameters[kvp.Key] = kvp.Value; + } + } + + if (expr.HasErrors()) + { + throw new ArgumentException($"条件表达式格式错误: {expr.Error}"); + } + + var result = expr.Evaluate(); + return Convert.ToBoolean(result); + } + catch (Exception ex) + { + throw new ArgumentException($"条件表达式异常: {ex.Message}"); + } + } + + private static (string, Dictionary) PreprocessExpression( + string expression, + Dictionary? variables) + { + if (variables == null || variables.Count == 0) + return (expression, variables ?? new Dictionary()); + + // 生成变量名映射 (中文 -> 英文别名) + var chineseToAlias = new Dictionary(); + var aliasToValue = new Dictionary(); + int counter = 1; + + foreach (var key in variables.Keys) + { + if (ContainsChinese(key)) + { + string alias = $"var_{counter}"; + counter++; + chineseToAlias[key] = alias; + aliasToValue[alias] = variables[key]; + } + } + + // 如果没有中文变量名,直接返回原始数据 + if (chineseToAlias.Count == 0) + return (expression, variables); + + // 替换表达式中的中文变量名 + string processedExpression = expression; + foreach (var pair in chineseToAlias) + { + // 使用正则确保完整匹配变量名 + string pattern = $@"\b{Regex.Escape(pair.Key)}\b"; + processedExpression = Regex.Replace( + processedExpression, + pattern, + pair.Value, + RegexOptions.Compiled + ); + } + + // 创建新变量字典(英文别名 + 原始英文变量) + var newVariables = new Dictionary(aliasToValue); + foreach (var key in variables.Keys) + { + if (!ContainsChinese(key)) + { + newVariables[key] = variables[key]; + } + } + + return (processedExpression, newVariables); + } + + // 检查字符串是否包含中文字符 + private static bool ContainsChinese(string text) + { + return text.Any(c => c >= 0x4E00 && c <= 0x9FFF); + } + } +} \ No newline at end of file diff --git a/Logger/LoggerHelper.cs b/Logger/LoggerHelper.cs index a6895e8..1d20811 100644 --- a/Logger/LoggerHelper.cs +++ b/Logger/LoggerHelper.cs @@ -14,6 +14,7 @@ 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) @@ -50,7 +51,32 @@ namespace Logger Logger.Error(message); NotifyUI(message, "red"); } + public static void InfoWithNotify(string message,int depth) + { + Logger.Info(message); // 写入 NLog + NotifyUI(message, "lightblue"); // 触发UI显示 + } + public static void SuccessWithNotify(string message, int depth ) + { + Logger.Info(message); + NotifyUI(message, "lightgreen"); + } + public static void WarnWithNotify(string message, int depth ) + { + + + Logger.Warn(message); + NotifyUI(message, "orange"); + } + + public static void ErrorWithNotify(string message, int depth ) + { + + + Logger.Error(message); + NotifyUI(message, "red"); + } private static void NotifyUI(string message, string color) { LogAdded?.Invoke(message,color);