添加项目文件。

This commit is contained in:
czj
2026-06-05 10:57:09 +08:00
parent f29671b374
commit d960cb5912
166 changed files with 15996 additions and 0 deletions

View File

@@ -0,0 +1,54 @@
using Microsoft.Xaml.Behaviors;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
namespace UIShare.Behaviors
{
//给Border添加双击事件
public class MouseDoubleClickBehavior : Behavior<Border>
{
public static readonly DependencyProperty CommandProperty =
DependencyProperty.Register("Command", typeof(ICommand), typeof(MouseDoubleClickBehavior), new PropertyMetadata(null));
public ICommand Command
{
get => (ICommand)GetValue(CommandProperty);
set => SetValue(CommandProperty, value);
}
public static readonly DependencyProperty CommandParameterProperty =
DependencyProperty.Register("CommandParameter", typeof(object), typeof(MouseDoubleClickBehavior), new PropertyMetadata(null));
public object CommandParameter
{
get => GetValue(CommandParameterProperty);
set => SetValue(CommandParameterProperty, value);
}
protected override void OnAttached()
{
base.OnAttached();
// 确保 Border 的 Background 不为 null否则无法触发点击事件
this.AssociatedObject.MouseLeftButtonDown += OnMouseLeftButtonDown;
}
protected override void OnDetaching()
{
base.OnDetaching();
this.AssociatedObject.MouseLeftButtonDown -= OnMouseLeftButtonDown;
}
private void OnMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
// 判断点击次数是否为 2
if (e.ClickCount == 2)
{
if (Command != null && Command.CanExecute(CommandParameter))
{
Command.Execute(CommandParameter);
}
}
}
}
}

View File

@@ -0,0 +1,59 @@
using Microsoft.Xaml.Behaviors;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
namespace UIShare.Behaviors
{
public class TabControlSelectionChangedBehavior : Behavior<TabControl>
{
public static readonly DependencyProperty CommandProperty =
DependencyProperty.Register(
"Command", typeof(ICommand), typeof(TabControlSelectionChangedBehavior), new PropertyMetadata(null));
public static readonly DependencyProperty CommandParameterProperty =
DependencyProperty.Register(
"CommandParameter", typeof(object), typeof(TabControlSelectionChangedBehavior), new PropertyMetadata(null));
public ICommand Command
{
get { return (ICommand)GetValue(CommandProperty); }
set { SetValue(CommandProperty, value); }
}
public object CommandParameter
{
get { return GetValue(CommandParameterProperty); }
set { SetValue(CommandParameterProperty, value); }
}
protected override void OnAttached()
{
base.OnAttached();
if (AssociatedObject != null)
{
AssociatedObject.SelectionChanged += OnSelectionChanged;
}
}
protected override void OnDetaching()
{
if (AssociatedObject != null)
{
AssociatedObject.SelectionChanged -= OnSelectionChanged;
}
base.OnDetaching();
}
private void OnSelectionChanged(object sender, SelectionChangedEventArgs e)
{
var tabItem = AssociatedObject.SelectedItem as TabItem;
// 获取选中 TabItem 的 Header
if (Command != null && Command.CanExecute(tabItem.Header))
{
// 使用选中 TabItem 的 Header 作为 CommandParameter
Command.Execute(((TabItem)AssociatedObject.SelectedItem)?.Header);
}
}
}
}

View File

@@ -0,0 +1,40 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Data;
namespace UIShare.Converters
{
public class BoolArrayConverter : IValueConverter
{
// bool[] -> string
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is bool[] arr)
return "[" + string.Join(",", arr.Select(b => b.ToString().ToLower())) + "]";
return "";
}
// string -> bool[]
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is string s)
{
s = s.Trim('[', ']', ' ');
if (string.IsNullOrWhiteSpace(s)) return Array.Empty<bool>();
var parts = s.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
return parts.Select(p =>
{
if (bool.TryParse(p, out var b)) return b;
if (p == "1") return true;
if (p == "0") return false;
return false;
}).ToArray();
}
return Array.Empty<bool>();
}
}
}

View File

@@ -0,0 +1,40 @@
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 UIShare.Converters
{
public class BooleanToVisibilityConverter : IValueConverter
{
// 同时接受 "invert" / "inverse" / "inverted" / "reverse"(大小写不敏感)作为反转参数,
// 避免因 XAML 处用了 ConverterParameter=Inverse 而静默失效。
private static bool IsInvert(object parameter)
{
var s = parameter?.ToString()?.ToLowerInvariant();
return s == "invert" || s == "inverse" || s == "inverted" || s == "reverse";
}
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
bool input = value is bool b && b;
if (IsInvert(parameter))
input = !input;
return input ? Visibility.Visible : Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
bool output = value is Visibility v && v == Visibility.Visible;
if (IsInvert(parameter))
output = !output;
return output;
}
}
}

View File

@@ -0,0 +1,49 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Data;
namespace UIShare.Converters
{
public class DeviceNameConverter : IValueConverter
{
private readonly string[] specialName = { "奇偶" };
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is string name)
{
if (specialName.Contains(name))
{
if (parameter?.ToString() == "Inverse")
{
return Visibility.Visible;
}
else if (parameter?.ToString() == "Items")
{
switch (name)
{
case "奇偶":
return new List<string> { "无", "奇", "偶" };
}
}
return Visibility.Collapsed;
}
}
if (parameter?.ToString() == "Inverse")
{
return Visibility.Collapsed;
}
return Visibility.Visible;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}

View File

@@ -0,0 +1,82 @@
using System;
using System.Globalization;
using System.Linq;
using System.Windows.Data;
namespace UIShare.Converters
{
public class EnumValueConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
// 验证输入参数
if (values.Length < 2 || values[0] == null || values[1] == null)
{
return null;
}
try
{
// 获取枚举类型
Type enumType = values[0] as Type;
if (enumType == null || !enumType.IsEnum)
{
return null;
}
// 获取数值
object value = values[1];
// 确保数值类型匹配枚举的底层类型
Type underlyingType = Enum.GetUnderlyingType(enumType);
object convertedValue;
try
{
convertedValue = System.Convert.ChangeType(value, underlyingType);
}
catch
{
// 如果转换失败,尝试直接使用原始值
convertedValue = value;
}
// 将数值转换为枚举值
return Enum.ToObject(enumType, convertedValue);
}
catch
{
// 发生任何异常时返回null
return null;
}
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
if (value == null)
{
return [null, null];
}
try
{
// 获取枚举值的底层数值
Type enumType = value.GetType();
if (!enumType.IsEnum)
{
return [null, null];
}
Type underlyingType = Enum.GetUnderlyingType(enumType);
object numericValue = System.Convert.ChangeType(value, underlyingType);
// 返回枚举类型和对应的数值
return [enumType, numericValue];
}
catch
{
return [null, null];
}
}
}
}

View File

@@ -0,0 +1,23 @@
using System;
using System.Globalization;
using System.Windows.Data;
namespace UIShare.Converters
{
public class EnumValuesConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is Type type && type.IsEnum)
{
return Enum.GetValues(type);
}
return null;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}

View File

@@ -0,0 +1,74 @@
using UIShare.UIViewModel;
using System;
using System.Globalization;
using System.Linq;
using System.Windows.Data;
namespace UIShare.Converters
{
public class FilteredParametersConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
if (values.Length < 2 || values[0] == null || values[1] == null)
return null;
Type currentParamType = values[0] as Type;
var allParameters = values[1] as System.Collections.IEnumerable;
if (currentParamType == null || allParameters == null)
return allParameters;
// 过滤出类型匹配的参数
return allParameters.Cast<ParameterModel>()
.Where(p => IsTypeMatch(currentParamType, p.Type))
.ToList();
}
private bool IsTypeMatch(Type currentType, Type candidateType)
{
if (candidateType == null) return false;
// 如果候选参数类型是 object则匹配所有类型
if (candidateType == typeof(object)) return true;
// 如果类型完全相同,则匹配
if (candidateType == currentType) return true;
// 处理数值类型的兼容性
if (IsNumericType(currentType) && IsNumericType(candidateType))
return true;
return false;
}
private bool IsNumericType(Type type)
{
if (type == null) return false;
switch (Type.GetTypeCode(type))
{
case TypeCode.Byte:
case TypeCode.SByte:
case TypeCode.UInt16:
case TypeCode.UInt32:
case TypeCode.UInt64:
case TypeCode.Int16:
case TypeCode.Int32:
case TypeCode.Int64:
case TypeCode.Decimal:
case TypeCode.Double:
case TypeCode.Single:
return true;
default:
return false;
}
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}

View File

@@ -0,0 +1,40 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Data;
namespace UIShare.Converters
{
public class HexConverter : IValueConverter
{
// 显示时int → hex 字符串
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is int i)
return $"0x{i:X}"; // 例如 255 → 0xFF
return "0x0";
}
// 用户输入时hex 字符串 → int
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
var str = value?.ToString()?.Trim();
if (string.IsNullOrWhiteSpace(str))
return 0;
if (str.StartsWith("0x", StringComparison.OrdinalIgnoreCase))
str = str.Substring(2);
if (int.TryParse(str, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out int result))
return result;
return 0; // 或 return DependencyProperty.UnsetValue;
}
}
}

View File

@@ -0,0 +1,22 @@
using System;
using System.Globalization;
using System.Windows.Data;
namespace UIShare.Converters
{
public class InverseBooleanConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
// null 视为 false再取反 → true
var b = value as bool?;
return !(b ?? false);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
var b = value as bool?;
return !(b ?? false);
}
}
}

View File

@@ -0,0 +1,36 @@
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 UIShare.Converters
{
public class IsEnumTypeConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is Type type)
{
// 检查是否为枚举类型
bool isEnum = type.IsEnum;
// 根据参数决定返回值类型
if (parameter is string strParam && strParam == "Collapse")
{
return isEnum ? Visibility.Collapsed : Visibility.Visible;
}
return isEnum ? Visibility.Visible : Visibility.Collapsed;
}
return Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}

View File

@@ -0,0 +1,27 @@
using System;
using System.Globalization;
using System.Windows.Data;
namespace UIShare.Converters // 确保命名空间跟你项目一致
{
public class LessThanConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is double actualWidth && parameter != null)
{
if (double.TryParse(parameter.ToString(), out double targetWidth))
{
// 如果实际宽度 小于 设定的阈值比如600返回 True
return actualWidth < targetWidth;
}
}
return false;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}

View File

@@ -0,0 +1,41 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Data;
using static UIShare.UIViewModel.ParameterModel;
namespace UIShare.Converters
{
public class ParameterCategoryToStringConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is ParameterCategory category)
{
switch (category)
{
case ParameterCategory.Input:
return "输入";
case ParameterCategory.Output:
return "输出";
case ParameterCategory.Temp:
return "缓存";
default:
return "未知";
}
}
return "未知";
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}

View File

@@ -0,0 +1,41 @@
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;
using static UIShare.UIViewModel.ParameterModel;
namespace UIShare.Converters
{
public class ParameterCategoryToVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is ParameterCategory category)
{
if (parameter?.ToString() == "Item")
{
if (category == ParameterCategory.Temp) { return Visibility.Collapsed; }
else { return Visibility.Visible; }
}
bool boolValue = category == ParameterCategory.Input;
if (parameter?.ToString() == "Inverse")
{
boolValue = !boolValue;
}
return boolValue ? Visibility.Visible : Visibility.Collapsed;
}
return Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}

View File

@@ -0,0 +1,31 @@
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 UIShare.Converters
{
public class ParameterTypeToBoolConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if(value is Type type)
{
if(type == typeof(CancellationToken))
{
return false;
}
}
return true;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}

View File

@@ -0,0 +1,37 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Data;
namespace UIShare.Converters
{
public class ParameterValueToStringConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is IEnumerable enumerable && !(value is string))
{
var elements = enumerable.Cast<object>().Select(item => item?.ToString() ?? "null");
return $"[{string.Join(", ", elements)}]";
}
else if(value != null)
{
return value.ToString()!;
}
return "";
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value != null)
{
return value.ToString()!;
}
return "";
}
}
}

View File

@@ -0,0 +1,35 @@
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 UIShare.Converters
{
public class StringToVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if(value is string str)
{
if (string.IsNullOrEmpty(str))
{
return Visibility.Collapsed;
}
else
{
return Visibility.Visible;
}
}
return Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}

View File

@@ -0,0 +1,38 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Data;
namespace UIShare.Converters
{
public class TimeSpanToStringConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is TimeSpan timeSpan)
{
// 如果天数大于0则显示天数
if (timeSpan.Days > 0)
{
return $"{timeSpan.Days}天{timeSpan.Hours:D2}:{timeSpan.Minutes:D2}:{timeSpan.Seconds:D2}";
}
else
{
// 如果不超过一天,则只显示时:分:秒
return $"{timeSpan.Hours:D2}:{timeSpan.Minutes:D2}:{timeSpan.Seconds:D2}";
}
}
return "00:00:00";
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
// 通常不需要从字符串转换回TimeSpan所以这里返回UnsetValue
return System.Windows.DependencyProperty.UnsetValue;
}
}
}

View File

@@ -0,0 +1,85 @@
using System;
using System.IO;
using Newtonsoft.Json;
using Logger;
namespace UIShare.GlobalVariable
{
public static class ConfigService
{
private static readonly object _fileLock = new();
/// <summary>
/// 根据标题(格子标识)加载独立的配置文件
/// </summary>
public static SystemConfig Load(string title)
{
if (string.IsNullOrEmpty(title))
{
throw new ArgumentException("配置标题不能为空", nameof(title));
}
// 临时实例化一个对象以获取默认的 SystemPath
var dummy = new SystemConfig();
string configPath = Path.Combine(dummy.SystemPath, $"{title}.json");
if (!File.Exists(configPath))
{
// 如果不存在,创建一个带 Title 的默认配置并保存
var defaultConfig = new SystemConfig { Title = title };
Save(defaultConfig);
return defaultConfig;
}
lock (_fileLock)
{
try
{
string json = File.ReadAllText(configPath);
var config = JsonConvert.DeserializeObject<SystemConfig>(json, new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.All
});
return config ?? new SystemConfig { Title = title };
}
catch (Exception ex)
{
LoggerHelper.ErrorWithNotify($"格子 [{title}] 配置加载失败: {ex.Message}");
return new SystemConfig { Title = title };
}
}
}
/// <summary>
/// 保存指定的配置实例
/// </summary>
public static void Save(SystemConfig config)
{
if (config == null || string.IsNullOrEmpty(config.Title)) return;
lock (_fileLock)
{
try
{
if (!Directory.Exists(config.SystemPath))
Directory.CreateDirectory(config.SystemPath);
string configPath = Path.Combine(config.SystemPath, $"{config.Title}.json");
string json = JsonConvert.SerializeObject(config, Formatting.Indented, new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.All
});
File.WriteAllText(configPath, json);
LoggerHelper.InfoWithNotify($"配置 [{config.Title}] 已保存。");
}
catch (Exception ex)
{
LoggerHelper.ErrorWithNotify($"配置 [{config.Title}] 保存失败: {ex.Message}");
}
}
}
}
}

View File

@@ -0,0 +1,37 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace UIShare.GlobalVariable
{
public class GlobalInfo:BindableBase
{
public event EventHandler? ScopeChanged;
public Dictionary<string,ScopedContext> ContextDic { get; set; }
public Dictionary<string,StepRunning> StepRunningDic { get; set; }
public String UserName { get; set; } = "Not Logged in";
public bool IsAdmin { get; set; } = true;
private string _currentScope = "default";
public string CurrentScope
{
get => _currentScope;
set
{
if (_currentScope != value)
{
_currentScope = value;
ScopeChanged?.Invoke(this, EventArgs.Empty);
}
}
}
public GlobalInfo()
{
ContextDic = new();
StepRunningDic = new();
CurrentScope = "default";
}
}
}

View File

@@ -0,0 +1,42 @@
using MaterialDesignThemes.Wpf;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using UIShare.UIViewModel;
namespace UIShare.GlobalVariable
{
public class ScopedContext
{
private static readonly Random _randomSeed = new Random();
public ProgramModel Program { get; set; } = new();
public String SelectedStepList { get; set; } = "主程序";
public string CurrentFilePath { get; set; }
public bool? IsStop { get; set; }
public bool SingleStep { get; set; }
public string RunState { get; set; } = "运行";
public TimeSpan RunningTime { get; set; } = TimeSpan.Zero;
public Stopwatch SW { get; set; } = new();
public bool IsTerminate { get; set; } = false;
public ObservableCollection<Assembly> Assemblies { get; set; } = new();
public PackIconKind RunIcon { get; set; } = PackIconKind.Play;
public StepModel SelectedStep { get; set; }
public ParameterModel SelectedParameter { get; set; }
// 【新增测试属性】:每个实例被 new 出来时独一无二的随机身份
// 证 ID
public int DebugRandomId { get; private set; }
public ScopedContext()
{
lock (_randomSeed)
{
// 每次诞生一个新上下文,就在 10000 到 99999 之间随机摇一个数
DebugRandomId = _randomSeed.Next(10000, 100000);
}
}
}
}

View File

@@ -0,0 +1,730 @@
using UIShare.UIViewModel;
using UIShare.PubEvent;
using Common.Tools;
using Logger;
using MaterialDesignThemes.Wpf;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using UIShare.GlobalVariable;
using static UIShare.UIViewModel.ParameterModel;
namespace UIShare
{
public class StepRunning
{
private ScopedContext _scopedContext;
private SystemConfig _systemConfig;
//private Devices _devices;
private IContainerProvider containerProvider;
private IEventAggregator _eventAggregator;
private readonly Dictionary<Guid, ParameterModel> tmpParameters = [];
private readonly Stopwatch stepStopwatch = new();
private readonly Stack<Stopwatch> loopStopwatchStack = new();
private readonly Stack<LoopContext> loopStack = new();
public CancellationTokenSource stepCTS = new();
public CancellationTokenSource errorStepCTS = new();
private bool SubSingleStep = false;
private Guid TestRoundID;
public StepRunning(ScopedContext ScopedContext, SystemConfig systemConfig,IEventAggregator eventAggregator,IContainerProvider containerProvider)
{
_scopedContext = ScopedContext;
_systemConfig = systemConfig;
_eventAggregator = eventAggregator;
//_devices = containerProvider.Resolve<Devices>();
}
public async Task<bool> ExecuteErrorSteps(ProgramModel program, int depth = 0, CancellationToken cancellationToken = default)
{
int index = 0;
bool stepSuccess = false;
if (depth == 0)
{
loopStack.Clear();
loopStopwatchStack.Clear();
ResetAllStepStatus(program.ErrorStepCollection);
tmpParameters.Clear();
TestRoundID = Guid.NewGuid();
}
foreach (var item in program.Parameters)
{
tmpParameters.TryAdd(item.ID, item);
}
while (index < program.ErrorStepCollection.Count)
{
if (cancellationToken.IsCancellationRequested)
{
break;
}
var step = program.ErrorStepCollection[index];
if (!step.IsUsed)
{
index++;
continue;
}
step.Result = 0;
if (step.StepType == "循环开始")
{
var endStep = program.ErrorStepCollection.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: 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.ErrorStepCollection.First(x => x.ID == step.LoopStartStepId).Result = 1;
loopStopwatchStack.Pop();
}
}
}
// 处理普通步骤
else
{
if (depth == 0)
{
stepStopwatch.Restart();
}
if (step.SubProgram != null)
{
if (_scopedContext.SingleStep)//子程序的单步执行将执行完保存下的所有Method
{
SubSingleStep = true;
_scopedContext.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;
_scopedContext.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.ErrorStepCollection.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.ErrorStepCollection.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;
}
}
}
return loopStack.Count == 0 && stepSuccess;
}
public async Task<bool> ExecuteSteps(ProgramModel program, int depth = 0, CancellationToken cancellationToken = default)
{
int index = 0;
bool stepSuccess = false;
if (depth == 0)
{
loopStack.Clear();
loopStopwatchStack.Clear();
ResetAllStepStatus(program.StepCollection);
tmpParameters.Clear();
TestRoundID = Guid.NewGuid();
}
foreach (var item in program.Parameters)
{
tmpParameters.TryAdd(item.ID, item);
}
while (index < program.StepCollection.Count)
{
while (_scopedContext.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($"循环开始({step.Name}),共{context.LoopCount}次", depth);
index++;
}
// 处理循环结束
else if (step.StepType == "循环结束")
{
if (loopStack.Count == 0)
{
LoggerHelper.ErrorWithNotify("未匹配的循环结束指令", depth: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 (_scopedContext.SingleStep)//子程序的单步执行将执行完保存下的所有Method
{
SubSingleStep = true;
_scopedContext.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;
_scopedContext.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 (_scopedContext.SingleStep)
{
_scopedContext.IsStop = true;
_scopedContext.RunState = "运行";
_scopedContext.SingleStep = false;
_eventAggregator.GetEvent<RunSingalCompletedEvent>().Publish("Play");
}
}
}
return loopStack.Count == 0 && stepSuccess;
}
public async Task ExecuteMethodStep(StepModel step, Dictionary<Guid, ParameterModel> parameters, int depth, CancellationToken cancellationToken = default)
{
try
{
if(_scopedContext.Program.StepCollection.Count>1)
_scopedContext.SelectedStep = null;
await Task.Delay(_systemConfig.PerformanceLevel);
// 1. 查找类型
Type? targetType = null;
foreach (var assembly in _scopedContext.Assemblies)
{
targetType = assembly.GetType(step.Method!.FullName!);
if (targetType != null) break;
}
if (targetType == null)
{
LoggerHelper.ErrorWithNotify($"指令 [ {step.Index} ] 执行错误:未找到类型 {step.Method!.FullName}", depth: depth);
step.Result = 2;
}
// 2. 创建实例(仅当方法不是静态时才需要)
object? instance = null;
bool isMethod = false;
// 3. 准备参数
var inputParams = new List<object?>();
var paramTypes = new List<Type>();
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
{
if (stringArray[i] is string s && s.StartsWith("0x", StringComparison.OrdinalIgnoreCase))
{
// 先转成整数
var intValue = Convert.ToInt64(s, 16);
// 再转成目标类型
array.SetValue(Convert.ChangeType(intValue, elementType), 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
{
if (actualValue is string s && s.StartsWith("0x", StringComparison.OrdinalIgnoreCase))
{
// 先转成整数
var intValue = Convert.ToInt64(s, 16);
// 再转成目标类型
actualValue = Convert.ChangeType(intValue, param.Type);
}
else
{
actualValue = Convert.ChangeType(actualValue, param.Type);
}
}
}
}
catch (Exception ex)
{
LoggerHelper.WarnWithNotify($"指令 [ {step.Index} ] 执行错误:参数 {param.Name} 类型转换失败: {ex.Message}", depth: 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: depth);
step.Result = 2;
}
// 检查是否是静态方法
bool isStaticMethod = method!.IsStatic;
// 如果是实例方法,需要创建实例
if (!isStaticMethod)
{
try
{
//instance = _devices.DeviceDic[targetType.Name];
}
catch (Exception ex)
{
LoggerHelper.ErrorWithNotify($"指令 [ {step.Index} ] 执行错误:创建实例失败 - {ex.Message}", depth: depth);
step.Result = 2;
}
}
// 5. 执行方法
object? returnValue = method.Invoke(instance, inputParams.ToArray());
try
{
// 处理异步方法
if (returnValue is Task task)
{
await task.ConfigureAwait(false);
// 获取结果如果是Task<T>
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: 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 && _scopedContext.Program.Parameters.FirstOrDefault(x => x.ID == currentPara.ID) != null)
//{
// _ = SaveDataToDatabase(_scopedContext.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<object>().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: depth);
step.Result = 2;
return;
}
}
public void ResetAllStepStatus(ObservableCollection<StepModel> StepCollection)
{
foreach (var step in 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<string, object> 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: depth);
}
}
}
else
{
if (!paraResult)
{
LoggerHelper.WarnWithNotify("参数限值校验失败", depth: depth);
}
step.Result = 2;
}
}
#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
}
}

View File

@@ -0,0 +1,41 @@
using Logger;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using UIShare.UIViewModel;
namespace UIShare.GlobalVariable
{
public class SystemConfig
{
[JsonIgnore]
public string SystemPath { get; set; } = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "UIShare");
public string Title { get; set; } = string.Empty;
public int PerformanceLevel { get; set; } = 50;
public string DLLFilePath { get; set; } = @"D:\ADP\指令\";
public string SubProgramFilePath { get; set; } = @"D:\ADP\子程序\";
public string DefaultProgramFilePath { get; set; } = "";
public string DefaultBLFFilePath { get; set; } = "";
public string DefaultDBCFilePath { get; set; } = "";
public string TSMasterName { get; set; } = "ADP测试上位机";
/// <summary>
/// 设备列表每个工位独立一份SystemConfig 已注册为 Scoped
/// 当前阶段预置几条模拟设备,便于设置界面直接展示左侧列表。
/// </summary>
public ObservableCollection<DeviceInfoModel> DeviceList { get; set; } = new()
{
new DeviceInfoModel { DeviceName = "DAQ_001", DeviceType = "数据采集卡", Remark = "8 通道电压采集", IsEnabled = true, IsConnected = false },
new DeviceInfoModel { DeviceName = "PSU_002", DeviceType = "可编程电源", Remark = "0-30V / 0-5A", IsEnabled = true, IsConnected = false },
new DeviceInfoModel { DeviceName = "CAN_003", DeviceType = "CAN 通讯卡", Remark = "双通道 CAN-FD", IsEnabled = false, IsConnected = false },
new DeviceInfoModel { DeviceName = "IO_004", DeviceType = "数字 I/O", Remark = "16 进 16 出", IsEnabled = true, IsConnected = false },
new DeviceInfoModel { DeviceName = "Serial_005", DeviceType = "串口", Remark = "RS232 / RS485", IsEnabled = true, IsConnected = false },
};
}
}

View File

@@ -0,0 +1,52 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
namespace UIShare.Helpers
{
public static class PasswordBoxHelper
{
public static readonly DependencyProperty PasswordProperty =
DependencyProperty.RegisterAttached(
"Password",
typeof(string),
typeof(PasswordBoxHelper),
new FrameworkPropertyMetadata(
string.Empty,
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
OnPasswordPropertyChanged));
public static void SetPassword(DependencyObject d, string value)
=> d.SetValue(PasswordProperty, value);
public static string GetPassword(DependencyObject d)
=> (string)d.GetValue(PasswordProperty);
private static void OnPasswordPropertyChanged(
DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
if (d is PasswordBox pb)
{
pb.PasswordChanged -= PasswordChanged;
if (pb.Password != (string)e.NewValue)
{
pb.Password = (string)e.NewValue;
}
pb.PasswordChanged += PasswordChanged;
}
}
private static void PasswordChanged(object sender, RoutedEventArgs e)
{
if (sender is PasswordBox pb)
{
SetPassword(pb, pb.Password);
}
}
}
}

View File

@@ -0,0 +1,41 @@
using System.Windows;
using System.Windows.Input;
namespace UIShare.Helpers
{
public static class WindowDragHelper
{
public static readonly DependencyProperty EnableWindowDragProperty =
DependencyProperty.RegisterAttached(
"EnableWindowDrag",
typeof(bool),
typeof(WindowDragHelper),
new PropertyMetadata(false, OnEnableWindowDragChanged));
public static bool GetEnableWindowDrag(DependencyObject obj) =>
(bool)obj.GetValue(EnableWindowDragProperty);
public static void SetEnableWindowDrag(DependencyObject obj, bool value) =>
obj.SetValue(EnableWindowDragProperty, value);
private static void OnEnableWindowDragChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is FrameworkElement element)
{
if ((bool)e.NewValue)
element.MouseLeftButtonDown += Element_MouseLeftButtonDown;
else
element.MouseLeftButtonDown -= Element_MouseLeftButtonDown;
}
}
private static void Element_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
if (sender is FrameworkElement element)
{
var window = Window.GetWindow(element);
window?.DragMove();
}
}
}
}

View File

@@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace UIShare.PubEvent
{
public class AlarmEvent :PubSubEvent
{
}
}

View File

@@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace UIShare.PubEvent
{
public class ChangeCurrentTagEvent:PubSubEvent<string>
{
}
}

View File

@@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace UIShare.PubEvent
{
public class CollectedCANMessageChangedEvent:PubSubEvent
{
}
}

View File

@@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace UIShare.PubEvent
{
public class ConnectionChangeEvent:PubSubEvent<(string,bool)>
{
}
}

View File

@@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace UIShare.PubEvent
{
public class CurveDataEvent:PubSubEvent<(string, Dictionary<string,double>)>
{
}
}

View File

@@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace UIShare.PubEvent
{
public class CurveInteractionEvent:PubSubEvent<(bool,double)>
{
}
}

View File

@@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace UIShare.PubEvent
{
public class DeletedStepEvent : PubSubEvent<Guid>
{
}
}

View File

@@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace UIShare.PubEvent
{
public class EditSetpEvent : PubSubEvent
{
}
}

View File

@@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace UIShare.PubEvent
{
public class ExpandViewEvent:PubSubEvent<string>
{
}
}

View File

@@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace UIShare.PubEvent
{
public class LoginSuccessEvent:PubSubEvent
{
}
}

View File

@@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace UIShare.PubEvent
{
public class OverlayEvent : PubSubEvent<bool>
{
}
}

View File

@@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace UIShare.PubEvent
{
public class ParamsChangedEvent:PubSubEvent
{
}
}

View File

@@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace UIShare.PubEvent
{
public class RunSingalCompletedEvent : PubSubEvent<string>
{
}
}

View File

@@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace UIShare.PubEvent
{
public class SettingChangedEvent:PubSubEvent
{
}
}

View File

@@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace UIShare.PubEvent
{
public class StartProcessEvent:PubSubEvent<(string,bool)>
{
}
}

View File

@@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace UIShare.PubEvent
{
public class WaitingEvent : PubSubEvent<bool>
{
}
}

View File

@@ -0,0 +1,21 @@
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<ResourceDictionary.MergedDictionaries>
<!-- 引用MaterialDesign和MahApps的资源 -->
<ResourceDictionary Source="pack://application:,,,/MaterialDesignThemes.MahApps;component/Themes/MaterialDesignTheme.MahApps.Fonts.xaml" />
<ResourceDictionary Source="pack://application:,,,/MaterialDesignThemes.MahApps;component/Themes/MaterialDesignTheme.MahApps.Flyout.xaml" />
<!-- MahApps资源 -->
<ResourceDictionary Source="pack://application:,,,/MahApps.Metro;component/Styles/Controls.xaml" />
<ResourceDictionary Source="pack://application:,,,/MahApps.Metro;component/Styles/Fonts.xaml" />
<ResourceDictionary Source="pack://application:,,,/MahApps.Metro;component/Styles/Themes/Light.Blue.xaml" />
<!-- Material Design资源 -->
<ResourceDictionary Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.Light.xaml" />
<ResourceDictionary Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesign3.Defaults.xaml" />
<ResourceDictionary Source="pack://application:,,,/MaterialDesignColors;component/Themes/Recommended/Primary/MaterialDesignColor.DeepPurple.xaml" />
<ResourceDictionary Source="pack://application:,,,/MaterialDesignColors;component/Themes/Recommended/Secondary/MaterialDesignColor.Lime.xaml" />
<!--自定义style-->
<ResourceDictionary Source="/UIShare;component/Styles/WindowStyle.xaml"></ResourceDictionary>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>

View File

@@ -0,0 +1,22 @@
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Style x:Key="DialogUserManageStyle"
TargetType="Window">
<Setter Property="WindowStyle"
Value="None" />
<Setter Property="Topmost"
Value="True" />
<Setter Property="ResizeMode"
Value="NoResize" />
<Setter Property="ShowInTaskbar"
Value="False" />
<Setter Property="AllowsTransparency"
Value="true" />
<Setter Property="Background"
Value="Transparent" />
<Setter Property="SizeToContent"
Value="WidthAndHeight" />
</Style>
</ResourceDictionary>

23
UIShare/UIShare.csproj Normal file
View File

@@ -0,0 +1,23 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0-windows</TargetFramework>
<Nullable>enable</Nullable>
<UseWPF>true</UseWPF>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="MahApps.Metro" Version="3.0.0-rc0529" />
<PackageReference Include="Microsoft.Xaml.Behaviors.Wpf" Version="1.1.142" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.5-beta1" />
<PackageReference Include="Prism.Unity" Version="9.0.537" />
<PackageReference Include="MaterialDesignColors" Version="5.3.0" />
<PackageReference Include="MaterialDesignThemes" Version="5.3.0" />
<PackageReference Include="MaterialDesignThemes.MahApps" Version="5.3.0" />
<PackageReference Include="Notifications.Wpf.Core" Version="2.0.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Common\Common.csproj" />
<ProjectReference Include="..\Logger\Logger.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,71 @@
namespace UIShare.UIViewModel
{
public class CanMessageShowModel : BindableBase
{
// 字段声明
private byte _通道;
private int _报文ID;
private ulong _时间戳;
private byte _长度;
private bool _fd;
private bool _isTx;
private byte[] _bytes = new byte[64];
private string _报文;
// 通道属性
public byte
{
get { return _通道; }
set { SetProperty(ref _通道, value); }
}
// 报文ID属性
public int ID
{
get { return _报文ID; }
set { SetProperty(ref _报文ID, value); }
}
// 时间戳属性
public ulong
{
get { return _时间戳; }
set { SetProperty(ref _时间戳, value); }
}
// 长度属性
public byte
{
get { return _长度; }
set { SetProperty(ref _长度, value); }
}
// FD属性
public bool FD
{
get { return _fd; }
set { SetProperty(ref _fd, value); }
}
// IsTx属性
public bool IsTx
{
get { return _isTx; }
set { SetProperty(ref _isTx, value); }
}
// Bytes属性
public byte[] Bytes
{
get { return _bytes; }
set { SetProperty(ref _bytes, value); }
}
// 报文属性
public string
{
get { return _报文; }
set { SetProperty(ref _报文, value); }
}
}
}

View File

@@ -0,0 +1,22 @@
using Prism.Mvvm;
namespace UIShare.UIViewModel
{
public class CustomPanelItem : BindableBase
{
private string _name;
private string _pointY;
public string Name
{
get => _name;
set => SetProperty(ref _name, value);
}
public string PointY
{
get => _pointY;
set => SetProperty(ref _pointY, value);
}
}
}

View File

@@ -0,0 +1,44 @@
namespace UIShare.UIViewModel
{
public class DeviceInfoModel : BindableBase
{
private string _deviceName;
public string DeviceName
{
get => _deviceName;
set => SetProperty(ref _deviceName, value);
}
private string _deviceType;
public string DeviceType
{
get => _deviceType;
set => SetProperty(ref _deviceType, value);
}
private string _remark;
public string Remark
{
get => _remark;
set => SetProperty(ref _remark, value);
}
private bool _isEnabled;
public bool IsEnabled
{
get => _isEnabled;
set => SetProperty(ref _isEnabled, value);
}
private bool _isConnected;
public bool IsConnected
{
get => _isConnected;
set => SetProperty(ref _isConnected, value);
}
}
}

View File

@@ -0,0 +1,18 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
namespace UIShare.UIViewModel
{
public class InstructionNode
{
public string Name { get; set; }
public ObservableCollection<InstructionNode> Children { get; set; } = new();
public object Tag { get; set; }
}
}

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 UIShare.UIViewModel
{
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 UIShare.UIViewModel
{
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,51 @@
using Prism.Mvvm; // 引入 Prism 的 BindableBase
using System;
using System.Collections.ObjectModel;
using System.Linq;
namespace UIShare.UIViewModel
{
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)));
ErrorStepCollection = new ObservableCollection<StepModel>(source.ErrorStepCollection.Select(p => new StepModel(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);
}
private ObservableCollection<StepModel> _errorStepCollection = new ObservableCollection<StepModel>();
public ObservableCollection<StepModel> ErrorStepCollection
{
get => _errorStepCollection;
set => SetProperty(ref _errorStepCollection, value);
}
private ObservableCollection<ParameterModel> _parameters = new ObservableCollection<ParameterModel>();
public ObservableCollection<ParameterModel> Parameters
{
get => _parameters;
set => SetProperty(ref _parameters, value);
}
}
}

View File

@@ -0,0 +1,178 @@
using Newtonsoft.Json;
using Prism.Mvvm; // 引入 Prism 的 BindableBase
using System;
namespace UIShare.UIViewModel
{
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;
GotoSettingString = source.GotoSettingString;
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();
public Guid ID
{
get => _id;
set => SetProperty(ref _id, value);
}
private bool _isUsed = true;
public bool IsUsed
{
get => _isUsed;
set => SetProperty(ref _isUsed, value);
}
private int _index;
public int Index
{
get => _index;
set => SetProperty(ref _index, value);
}
private string? _name;
public string? Name
{
get => _name;
set => SetProperty(ref _name, value);
}
private string? _stepType;
public string? StepType
{
get => _stepType;
set => SetProperty(ref _stepType, value);
}
private MethodModel? _method;
public MethodModel? Method
{
get => _method;
set => SetProperty(ref _method, value);
}
private ProgramModel? _subProgram;
public ProgramModel? SubProgram
{
get => _subProgram;
set => SetProperty(ref _subProgram, value);
}
private int? _loopCount;
public int? LoopCount
{
get => _loopCount;
set => SetProperty(ref _loopCount, value);
}
[JsonIgnore]
private int? _currentLoopCount;
[JsonIgnore]
public int? CurrentLoopCount
{
get => _currentLoopCount;
set => SetProperty(ref _currentLoopCount, value);
}
private Guid? _loopStartStepId;
public Guid? LoopStartStepId
{
get => _loopStartStepId;
set => SetProperty(ref _loopStartStepId, value);
}
[JsonIgnore]
private int _result = -1;
[JsonIgnore]
public int Result
{
get => _result;
set => SetProperty(ref _result, value);
}
[JsonIgnore]
private int? _runTime;
[JsonIgnore]
public int? RunTime
{
get => _runTime;
set => SetProperty(ref _runTime, value);
}
private string? _okExpression;
public string? OKExpression
{
get => _okExpression;
set => SetProperty(ref _okExpression, value);
}
private string _gotoSettingString = "";
public string GotoSettingString
{
get => _gotoSettingString;
set => SetProperty(ref _gotoSettingString, value);
}
private Guid? _okGotoStepID;
public Guid? OKGotoStepID
{
get => _okGotoStepID;
set => SetProperty(ref _okGotoStepID, value);
}
private Guid? _ngGotoStepID;
public Guid? NGGotoStepID
{
get => _ngGotoStepID;
set => SetProperty(ref _ngGotoStepID, value);
}
private string? _description;
public string? Description
{
get => _description;
set => SetProperty(ref _description, value);
}
}
}

View File

@@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace UIShare.UIViewModel
{
public class SubProgramItem
{
public string Name { get; set; } = "";
public string FilePath { get; set; } = "";
}
}

View File

@@ -0,0 +1,27 @@
using Notifications.Wpf.Core;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace UIShare.ViewModelBase
{
public abstract class DialogViewModelBase : BindableBase,IDialogAware
{
public DialogCloseListener RequestClose { get; set; }
public IEventAggregator _eventAggregator;
private INotificationManager _notificationManager;
public DialogViewModelBase(IContainerProvider containerProvider)
{
_eventAggregator = containerProvider.Resolve<IEventAggregator>();
_notificationManager = containerProvider.Resolve<INotificationManager>();
}
#region Dialog
public virtual bool CanCloseDialog() => true;
public virtual void OnDialogClosed() { }
public virtual void OnDialogOpened(IDialogParameters parameters) { }
#endregion
}
}

View File

@@ -0,0 +1,63 @@
using Notifications.Wpf.Core;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UIShare.GlobalVariable;
namespace UIShare.ViewModelBase
{
public abstract class NavigateViewModelBase : BindableBase, INavigationAware
{
public DialogCloseListener RequestClose { get; set; }
public IEventAggregator _eventAggregator;
public IDialogService _dialogService;
public IRegionManager _regionManager;
private INotificationManager _notificationManager;
private GlobalInfo _globalInfo;
public NavigateViewModelBase(IContainerProvider containerProvider)
{
_globalInfo=containerProvider.Resolve<GlobalInfo>();
_eventAggregator = containerProvider.Resolve<IEventAggregator>();
_dialogService = containerProvider.Resolve<IDialogService>();
_regionManager = containerProvider.Resolve<IRegionManager>();
_notificationManager = containerProvider.Resolve<INotificationManager>();
}
protected void ShowInfoMessageBox(string Message,Action callback)
{
var dialogParams = new DialogParameters();
dialogParams.Add("Title", "提示");
dialogParams.Add("Message", Message);
dialogParams.Add("Icon", "info");
dialogParams.Add("ShowOk", true);
_dialogService.ShowDialog("MessageBoxView", dialogParams, result =>
{
callback();
});
}
protected void ShowErrorMessageBox(string Message,Action callback)
{
var dialogParams = new DialogParameters();
dialogParams.Add("Title", "错误");
dialogParams.Add("Message", Message);
dialogParams.Add("Icon", "info");
dialogParams.Add("ShowOk", true);
_dialogService.ShowDialog("MessageBoxView", dialogParams, result =>
{
callback();
});
}
#region Navigation
public virtual void OnNavigatedTo(NavigationContext navigationContext) { }
public virtual bool IsNavigationTarget(NavigationContext navigationContext) => true;
public virtual void OnNavigatedFrom(NavigationContext navigationContext) { }
#endregion
}
}