- `StandardScene` 是 AGV/AMR 场景插件库,不是独立 EXE。它依赖宿主 `SimpleComposer.exe` 运行,能力覆盖搬运调度、环线任务、
+ `StandardScene` 是 AGV/AMR 场景插件库,不是独立 EXE。它依赖宿主 `SimpleLite.exe`(CycleGUI 应用)运行,能力覆盖搬运调度、环线任务、
交通互锁、区域流量控制、充电协同、门控联动,以及 HTTP / MQTT / Modbus 等外围接口。
| 场景逻辑层 |
@@ -650,21 +650,23 @@
04. 构建与运行
本机依赖路径
D:\MDCS\Dependencies\Commons\CommonUsage.dll
-D:\MDCS\Dependencies\deps\LessokajiWeaverUtilities.dll
D:\MDCS\Dependencies\Commons\MDCSToolBox.dll
-D:\MDCS\Dependencies\Simple\RefSimpleCore.dll
-D:\MDCS\Executables\Simple\SimpleComposer.exe
+D:\MDCS\Dependencies\Commons\CycleGUI.dll
+..\Simple\SimpleLite\bin\Debug\SimpleLite.dll
+..\Simple\SimpleLite\bin\Debug\LessokajiWeaverUtilities.dll
+..\Simple\SimpleCore\bin\Debug\netstandard2.0\SimpleCore.dll
构建步骤
+ - 先构建宿主依赖:
dotnet build Simple\SimpleLite\SimpleLite.csproj(一并构建 SimpleCore)
- 打开
StandardScene.sln
- - 编译
Debug|Any CPU 或 Release|Any CPU
- - 确认构建事件已将插件复制到
build/plugins
- - 运行
build/SimpleComposer.exe
+ - 编译
Debug|x64 或 Release|x64
+ - 确认
Directory.Build.targets 已将 5 个插件 DLL 复制到 build/plugins
+ - 将
build/plugins 部署到宿主 plugins/,运行 SimpleLite.exe
- 当前项目不是 SDK 风格工程。新增 `.cs` 文件后,必须确认它已经被加入 `.csproj`;否则文件存在但不会参与编译。
+ 当前项目已是 SDK 风格工程(`net8.0-windows`),目录下 `.cs` 文件会被自动包含,无需手工加入 `.csproj`。注意:StandardScene 通过 `HintPath` 引用宿主产物,构建前必须先编译 Simple 解决方案,否则会报 `SimpleCore` 版本不匹配。
@@ -677,7 +679,7 @@ D:\MDCS\Executables\Simple\SimpleComposer.exe
using System.Threading;
using Newtonsoft.Json;
-using SimpleComposer.RCS;
+using SimpleLite.RCS;
using SimpleCore;
namespace StandardScene.Scheduler
@@ -721,9 +723,9 @@ namespace StandardScene.Scheduler
}
}
- - 新建
Scheduler/HelloMission.cs
- - 确认它已被加入工程
- - 编译并打开
build/SimpleComposer.exe
+ - 新建
Scheduler/HelloMission.cs(SDK 工程自动包含)
+ - 补上
[MissionType] 特性与静态 Create()
+ - 编译后部署到宿主
plugins/,运行 SimpleLite.exe
- 启动后观察
status.status 是否按秒递增
diff --git a/README.md b/README.md
index a0d83a2..e612b6d 100644
--- a/README.md
+++ b/README.md
@@ -1,22 +1,38 @@
# StandardScene
-`StandardScene` 是一个基于 `.NET Framework 4.8` 的场景插件库,运行时由 `SimpleComposer.exe` 作为宿主加载。项目面向 AGV/AMR 场内调度与联动控制,覆盖搬运任务、环线任务、区域交通管制、充电管理、门禁联动,以及 HTTP / MQTT / Modbus 等对外通信能力。
+`StandardScene` 是一个基于 `.NET 8`(`net8.0-windows`)的场景插件库,运行时由 `SimpleLite.exe` 作为宿主加载。项目面向 AGV/AMR 场内调度与联动控制,覆盖搬运任务、环线任务、区域交通管制、充电管理、门禁联动,以及 HTTP / MQTT / Modbus 等对外通信能力。
+
+> 历史说明:早期版本基于 `.NET Framework 4.8` 并由 `SimpleComposer.exe` 加载;现已迁移到 `net8.0-windows` + `SimpleLite.exe` 宿主,并拆分为「基座 + 4 个卫星」共 5 个插件 DLL。界面正逐步从 WinForms 迁移到 CycleGUI(与宿主同栈),任务列表 `DeliveryViewer` 已完成迁移。
## 文档入口
- `DocumentHub.html`:单文件文档门户,双击即可在浏览器中打开
-- `DEVELOPMENT_GUIDE.md`:面向整个仓库的开发指南
-- `QUICK_REFERENCE.md`:高频开发速查表
-- `INDEX.md`:阅读顺序与导航说明
+- `Doc/ARCHITECTURE.md`:整体代码架构文档(模块地图、插件机制、扩展点、接手清单)
+- `Doc/DEVELOPMENT_GUIDE.md`:面向整个仓库的开发指南
+- `Doc/QUICK_REFERENCE.md`:高频开发速查表
+- `Doc/INDEX.md`:阅读顺序与导航说明
## 项目定位
-- 工程类型:类库插件,不是独立 EXE
-- 宿主程序:`SimpleComposer.exe`
-- 目标框架:`.NET Framework 4.8`
+- 工程类型:类库插件(基座 + 4 个卫星,共 5 个 DLL),不是独立 EXE
+- 宿主程序:`SimpleLite.exe`(CycleGUI 应用)
+- 目标框架:`net8.0-windows`
- 主要语言:`C#`
+- 界面技术:逐步从 WinForms 迁移到 CycleGUI(宿主同栈);`DeliveryViewer`(任务列表)已迁移
- 典型场景:仓储搬运、区域流控、充电协同、门控联动、车辆协议接入
+## 插件构成
+
+| 插件 DLL | 场景 id | 职责 |
+| --- | --- | --- |
+| `StandardScene.dll`(Core,基座) | —(alwaysLoad) | 任务调度、交通互锁、充电编排、门禁/按钮盒、HTTP API、数据模型、抽象契约 |
+| `StandardScene.Magnetic.dll` | `scene.mag` | 磁导航平台车型与磁循迹 Coder |
+| `StandardScene.QrLidar.dll` | `scene.qrlidar` | 激光 SLAM + 二维码导航,多种车型 |
+| `StandardScene.Devices.dll` | `scene.device` | 具体硬件驱动(Modbus 门控、充电桩、按钮盒) |
+| `StandardScene.Protocol.VDA5050.dll` | `scene.vda5050` | VDA5050(MQTT)标准协议栈与标准车型 |
+
+卫星插件依赖基座(星型,单向,无循环引用);各卫星附带 `.scene.json` 清单,由宿主按 `active-scenes.json` 选择性加载。
+
## 顶层模块
| 目录 | 作用 |
@@ -33,15 +49,19 @@
## 构建与运行
-1. 使用 Visual Studio 打开 `StandardScene.sln`
-2. 确认本机依赖路径存在:
- - `D:\MDCS\Dependencies\Commons\CommonUsage.dll`
- - `D:\MDCS\Dependencies\deps\LessokajiWeaverUtilities.dll`
- - `D:\MDCS\Dependencies\Simple\RefSimpleCore.dll`
- - `D:\MDCS\Executables\Simple\SimpleComposer.exe`
-3. 编译 `Debug|Any CPU` 或 `Release|Any CPU`
-4. 编译后 `PostBuildEvent` 会将 `StandardScene.dll` 复制到 `build\plugins\`
-5. 运行 `build\SimpleComposer.exe`,由宿主加载插件
+> 重要:StandardScene 通过 `HintPath` 引用宿主产物,必须先构建 Simple 解决方案,否则会出现 `SimpleCore` 版本不匹配等编译错误。
+
+1. 先构建宿主依赖:`dotnet build Simple\SimpleLite\SimpleLite.csproj`(会一并构建 `SimpleCore` 项目),产出:
+ - `Simple\SimpleLite\bin\Debug\SimpleLite.dll`
+ - `Simple\SimpleCore\bin\Debug\netstandard2.0\SimpleCore.dll`
+2. 确认本机依赖路径存在(见各 `.csproj` 的 `HintPath`):
+ - `D:\MDCS\Dependencies\Commons\CommonUsage.dll`、`MDCSToolBox.dll`、`CycleGUI.dll`
+ - `Simple\SimpleLite\bin\Debug\SimpleLite.dll`、`LessokajiWeaverUtilities.dll`
+ - `Simple\SimpleCore\bin\Debug\netstandard2.0\SimpleCore.dll`
+3. 使用 Visual Studio 打开 `StandardScene.sln`,或执行 `dotnet build StandardScene.sln`
+4. 编译 `Debug|x64` 或 `Release|x64`
+5. 编译后 `Directory.Build.targets` 会自动将 5 个插件 DLL(+ PDB + `*.scene.json` + Devices 的 `leegKeys-sdk.dll`)复制到 `build\plugins\`
+6. 将 `build\plugins\` 内容部署到 `SimpleLite.exe` 进程工作目录下的 `plugins\`,运行 `SimpleLite.exe`,由宿主按 `active-scenes.json` 选择性加载插件
## 开发阅读顺序
diff --git a/StandardScene.Core/CarTypes/CoderFieldsMetadata.cs b/StandardScene.Core/CarTypes/CoderFieldsMetadata.cs
new file mode 100644
index 0000000..290cc94
--- /dev/null
+++ b/StandardScene.Core/CarTypes/CoderFieldsMetadata.cs
@@ -0,0 +1,102 @@
+#nullable enable
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+
+namespace StandardScene.CarTypes
+{
+ ///
+ /// Coder 字段袋元数据导出。
+ /// 在 StandardScene 程序集内执行反射,可正确读取 internal Fields 类(如 BasicTrackFields)。
+ ///
+ public static class CoderFieldsMetadata
+ {
+ ///
+ /// 描述单个 Fields 类型的字段清单(含继承链上的 public 字段)。
+ ///
+ /// Fields 字段袋类型
+ /// 供 SimpleLite API 序列化的匿名结构;fieldsType 为 null 时返回 null
+ public static object? Describe(Type? fieldsType)
+ {
+ if (fieldsType == null)
+ {
+ return null;
+ }
+
+ var fieldInfos = CollectPublicFieldInfos(fieldsType);
+ return new
+ {
+ typeName = fieldsType.FullName ?? fieldsType.Name,
+ shortName = fieldsType.Name,
+ assemblyName = fieldsType.Assembly.GetName().Name ?? "",
+ baseTypeName = fieldsType.BaseType?.FullName,
+ fields = fieldInfos.Select(fi => new
+ {
+ name = fi.Name,
+ typeName = fi.FieldType.FullName ?? fi.FieldType.Name,
+ defaultValue = ReadFieldDefault(fieldsType, fi)
+ }).ToArray()
+ };
+ }
+
+ ///
+ /// 自基类到派生类收集 public 实例字段。
+ ///
+ static List CollectPublicFieldInfos(Type type)
+ {
+ var ordered = new List();
+ var seen = new HashSet(StringComparer.Ordinal);
+
+ var chain = new List();
+ for (var t = type; t != null && t != typeof(object); t = t.BaseType)
+ {
+ chain.Insert(0, t);
+ }
+
+ const BindingFlags flags = BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly;
+ foreach (var t in chain)
+ {
+ foreach (var fi in t.GetFields(flags))
+ {
+ if (fi.IsStatic || !seen.Add(fi.Name))
+ {
+ continue;
+ }
+
+ ordered.Add(fi);
+ }
+ }
+
+ return ordered;
+ }
+
+ static object? ReadFieldDefault(Type fieldsType, FieldInfo fi)
+ {
+ try
+ {
+ var instance = Activator.CreateInstance(fieldsType, true);
+ return NormalizeDefaultValue(fi.GetValue(instance));
+ }
+ catch
+ {
+ return null;
+ }
+ }
+
+ static object? NormalizeDefaultValue(object? value)
+ {
+ if (value == null)
+ {
+ return null;
+ }
+
+ return value switch
+ {
+ string or bool or int or long or short or byte or uint or ulong or float or double or decimal => value,
+ _ => value.ToString()
+ };
+ }
+ }
+}
diff --git a/StandardScene.Core/CarTypes/DummyCar.cs b/StandardScene.Core/CarTypes/DummyCar.cs
index a46c964..ea4e028 100644
--- a/StandardScene.Core/CarTypes/DummyCar.cs
+++ b/StandardScene.Core/CarTypes/DummyCar.cs
@@ -1,5 +1,5 @@
using AMRScene1;
-using SimpleLite;
+using SimpleLite.Rendering;
using SimpleLite.RCS;
using SimpleLite.RCS.CarTypes;
using SimpleLite.CADTools;
@@ -23,7 +23,8 @@ using System.Numerics;
using System.Runtime.InteropServices.ComTypes;
using System.Threading;
using System.Threading.Tasks;
-using System.Windows.Forms;
+using StandardScene.Utils;
+using SimpleLite;
namespace AMRScene1
{
@@ -36,12 +37,10 @@ namespace AMRScene1
class DummyCarSiteField
{
public bool Shelf = false;
-
}
class DummyCarPlanField
{
public string action = "/";
-
}
[TemplateTrackCoderSettings(
@@ -118,14 +117,6 @@ namespace AMRScene1
haveCoordination = true
};
}
-
-
- public override void rightClickAction(float mouseX, float mouseY)
- {
- x = mouseX;
- y = mouseY;
- }
-
public class AGV: AGVInterface
{
@@ -518,7 +509,7 @@ namespace AMRScene1
{
Task.Run(() =>
{
- MessageBox.Show("go?");
+ CycleUiHelper.Alert("提示", "go?");
go();
});
});
@@ -608,7 +599,7 @@ namespace AMRScene1
string tag = InputBox.ResultValue;
if (tag.Contains(":"))
{
- MessageBox.Show("需要切换英文输入法输入:");
+ CycleUiHelper.Alert("提示", "需要切换英文输入法输入:");
return;
}
if (!string.IsNullOrEmpty(tag) && tag.Contains(":"))
@@ -649,22 +640,6 @@ namespace AMRScene1
Task.Run(hijiack_fun);
}
- [MethodMember(Name = "设置位姿", Description = "拖拽以设置位姿")]
- public void SetPosition()
- {
- SimpleMonitor.registerDownevent((sender, args) =>
- {
- x = SimpleMonitor.mouseX;
- y = SimpleMonitor.mouseY;
- },null, (sender, args) =>
- {
- th = (float)(Math.Atan2(SimpleMonitor.mouseY - y, SimpleMonitor.mouseX - x) / Math.PI * 180);
- }, (sender, args) => SimpleMonitor.clearDownevent());
- }
-
-
-
-
[MethodMember(Name = "走到指定位置并设置Escape", Description = "点一个位置,再点一个位置")]
public void GoEscaped()
{
diff --git a/StandardScene.Core/CarTypes/VehicleMonitor.Designer.cs b/StandardScene.Core/CarTypes/VehicleMonitor.Designer.cs
deleted file mode 100644
index 4b2dccf..0000000
--- a/StandardScene.Core/CarTypes/VehicleMonitor.Designer.cs
+++ /dev/null
@@ -1,46 +0,0 @@
-namespace StandardScene
-{
- partial class VehicleMonitor
- {
- ///
- /// Required designer variable.
- ///
- private System.ComponentModel.IContainer components = null;
-
- ///
- /// Clean up any resources being used.
- ///
- /// true if managed resources should be disposed; otherwise, false.
- protected override void Dispose(bool disposing)
- {
- if (disposing && (components != null))
- {
- components.Dispose();
- }
- base.Dispose(disposing);
- }
-
- #region Windows Form Designer generated code
-
- ///
- /// Required method for Designer support - do not modify
- /// the contents of this method with the code editor.
- ///
- private void InitializeComponent()
- {
- this.SuspendLayout();
- //
- // VehicleMonitor
- //
- this.AutoScaleDimensions = new System.Drawing.SizeF(9F, 18F);
- this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
- this.ClientSize = new System.Drawing.Size(1800, 900);
- this.Name = "VehicleMonitor";
- this.Text = "车辆状态监控系统";
- this.ResumeLayout(false);
- }
-
- #endregion
- }
-}
-
diff --git a/StandardScene.Core/CarTypes/VehicleMonitor.cs b/StandardScene.Core/CarTypes/VehicleMonitor.cs
deleted file mode 100644
index 1239b41..0000000
--- a/StandardScene.Core/CarTypes/VehicleMonitor.cs
+++ /dev/null
@@ -1,1710 +0,0 @@
-using SimpleLite;
-using SimpleLite.RCS;
-using SimpleLite.RCS.CarTypes;
-using SimpleCore;
-using SimpleCore.Library;
-using StandardScene.Model;
-using System;
-using System.Collections.Generic;
-using System.ComponentModel;
-using System.Diagnostics;
-using System.Drawing;
-using System.Drawing.Drawing2D;
-using System.Linq;
-using System.Reflection;
-using System.Windows.Forms;
-using static System.Windows.Forms.VisualStyles.VisualStyleElement.TextBox;
-
-namespace StandardScene
-{
- public partial class VehicleMonitor : Form
- {
- private static VehicleMonitor instance;
- private static readonly object lockObject = new object();
-
- private Timer updateTimer;
- private DataGridView vehicleGrid;
- private Label titleLabel;
- private Panel statusPanel;
- private Panel alarmPanel;
- private Panel actionPanel;
- private Label totalLabel;
- private Label onlineLabel;
- private Label offlineLabel;
- private Label runningLabel;
- private Label chargingLabel;
- private Label faultLabel;
- private Label timeLabel;
- private Label alarmLabel;
-
- private Button onlineButton;
- private Button offlineButton;
- private Button returnButton;
- private Button simulateAlarmButton;
- private Button cancelSimAlarmButton;
- private Button toggleSecondaryButton;
-
- private Timer alarmScrollTimer;
- private string lastAlarmText;
- private string pendingAlarmText;
- private Color pendingAlarmColor = Color.FromArgb(255, 60, 60);
- private bool pendingAlarmScrolling;
- private readonly List simulatedAlarmNames = new List();
- private int simulatedAlarmIndex = 1;
- private const string SimulatedAlarmInfo = "脱轨异常";
-
- private bool showSecondaryColumns;
-
- private readonly Dictionary rowHeightCache = new Dictionary();
- private readonly HashSet selectedCarIds = new HashSet();
- private readonly List statusCards = new List();
- private string lastSortColumnName;
- private SortOrder lastSortOrder = SortOrder.None;
- private int lastFirstDisplayedRowIndex = -1;
-
- private CheckBox selectAllCheckBox;
- private bool suppressSelectSync;
-
- private const int TitleBarHeight = 70;
- private const int AlarmPanelHeight = 42;
- private const int AlarmPanelMarginTop = 9;
- private const int StatusPanelHeight = 90;
- private const int ActionPanelHeight = 60;
- private const int VerticalSpacing = 16;
- private const string QuickOperationHeaderText = "车体快捷操作";
-
- private const int CombinedPanelPadding = 12;
- private const int CombinedPanelSpacing = 10;
-
- private Panel statusActionPanel;
-
- private readonly Color pageBackColor = Color.FromArgb(236, 239, 243);
- private readonly Color panelBackColor = Color.FromArgb(248, 249, 251);
- private readonly Color panelBorderColor = Color.FromArgb(185, 192, 199);
- private readonly Color titleTextColor = Color.FromArgb(35, 35, 40);
- private readonly Color subTextColor = Color.FromArgb(110, 120, 130);
- private readonly Color gridTextColor = Color.FromArgb(50, 55, 60);
- private readonly Color gridHeaderBackColor = Color.FromArgb(238, 241, 245);
- private readonly Color gridHeaderTextColor = Color.FromArgb(70, 80, 90);
- private readonly Color gridAltRowColor = Color.FromArgb(246, 248, 251);
- private readonly Color gridSelectionBackColor = Color.FromArgb(224, 236, 248);
- private readonly Color gridSelectionTextColor = Color.FromArgb(30, 40, 50);
- private readonly Color buttonBackColor = Color.FromArgb(246, 248, 251);
- private readonly Color buttonBorderColor = Color.FromArgb(190, 198, 206);
- private readonly Color buttonHoverColor = Color.FromArgb(238, 242, 247);
- private readonly Color buttonDownColor = Color.FromArgb(228, 234, 240);
- private readonly Color buttonTextColor = Color.FromArgb(40, 60, 80);
- private readonly Color subtleLineColor = Color.FromArgb(230, 235, 240);
- private readonly Color combinedBorderColor = Color.Black;
- private readonly Color statusCardBackColor = Color.FromArgb(236, 239, 243);
- private readonly Color statusCardBorderColor = Color.Black;
- private const float OuterBorderWidth = 2f;
-
- private sealed class NoFocusPanel : Panel
- {
- public NoFocusPanel()
- {
- SetStyle(ControlStyles.Selectable, false);
- SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.OptimizedDoubleBuffer | ControlStyles.UserPaint, true);
- TabStop = false;
- UpdateStyles();
- }
- }
-
- public VehicleMonitor()
- {
- InitializeComponent();
- InitializeCustomComponents();
- }
-
- ///
- /// 显示或激活车辆监控窗口
- ///
- public static void ShowMonitor()
- {
- lock (lockObject)
- {
- if (instance == null || instance.IsDisposed)
- {
- instance = new VehicleMonitor();
- }
-
- if (!instance.Visible)
- {
- instance.Show();
- }
-
- instance.WindowState = FormWindowState.Normal;
- instance.BringToFront();
- instance.Activate();
- }
- }
-
- private void InitializeCustomComponents()
- {
- // 设置窗体属性
- this.Text = "车辆状态监控系统";
- this.WindowState = FormWindowState.Maximized;
- this.BackColor = pageBackColor;
- this.ForeColor = titleTextColor;
- this.FormBorderStyle = FormBorderStyle.Sizable;
- this.DoubleBuffered = true;
- SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.OptimizedDoubleBuffer | ControlStyles.UserPaint, true);
- UpdateStyles();
-
- // 创建标题栏
- CreateTitleBar();
-
- // 创建报警滚动条
- CreateAlarmTickerPanel();
-
- // 创建状态统计面板
- CreateStatusPanel();
-
- // 创建中间操作面板
- CreateActionPanel();
-
- // 创建车辆数据表格
- CreateVehicleGrid();
-
- // 创建更新定时器
- updateTimer = new Timer
- {
- Interval = 1000, // 1秒更新一次
- Enabled = true
- };
- updateTimer.Tick += UpdateTimer_Tick;
-
- // 初始更新
- UpdateVehicleData();
- }
-
- private void CreateTitleBar()
- {
- // 标题背景面板
- Panel titlePanel = new Panel
- {
- Location = new System.Drawing.Point(0, 0),
- Size = new System.Drawing.Size(this.ClientSize.Width, TitleBarHeight),
- BackColor = panelBackColor,
- Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right
- };
- AttachBottomBorder(titlePanel);
-
- // 标题标签
- titleLabel = new Label
- {
- Text = "车辆状态监控系统",
- Font = new Font("微软雅黑", 24F, FontStyle.Bold),
- ForeColor = titleTextColor,
- Location = new System.Drawing.Point(28, 16),
- Size = new System.Drawing.Size(600, 45),
- AutoSize = false
- };
- titlePanel.Controls.Add(titleLabel);
-
- // 时间标签
- timeLabel = new Label
- {
- Text = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"),
- Font = new Font("微软雅黑", 11F),
- ForeColor = subTextColor,
- Location = new System.Drawing.Point(this.ClientSize.Width - 250, 25),
- Size = new System.Drawing.Size(220, 25),
- TextAlign = ContentAlignment.MiddleRight,
- Anchor = AnchorStyles.Top | AnchorStyles.Right
- };
- titlePanel.Controls.Add(timeLabel);
-
- this.Controls.Add(titlePanel);
- }
-
- private void CreateAlarmTickerPanel()
- {
- alarmPanel = new NoFocusPanel
- {
- Location = new System.Drawing.Point(20, GetAlarmPanelTop()),
- Size = new System.Drawing.Size(this.ClientSize.Width - 40, AlarmPanelHeight),
- BackColor = panelBackColor,
- BorderStyle = BorderStyle.None,
- Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right
- };
- alarmPanel.Paint += DrawPanelBorder;
-
- alarmLabel = new Label
- {
- AutoSize = true,
- ForeColor = Color.FromArgb(235, 80, 80),
- Font = new Font("微软雅黑", 14F, FontStyle.Bold),
- Location = new System.Drawing.Point(alarmPanel.Width, 6)
- };
-
- alarmPanel.Controls.Add(alarmLabel);
- this.Controls.Add(alarmPanel);
-
- alarmScrollTimer = new Timer
- {
- Interval = 13,
- Enabled = true
- };
- alarmScrollTimer.Tick += AlarmScrollTimer_Tick;
- }
-
- private void CreateStatusPanel()
- {
- statusPanel = new NoFocusPanel
- {
- Location = new System.Drawing.Point(20, GetStatusPanelTop()),
- Size = new System.Drawing.Size(GetStatusPanelWidth(6), StatusPanelHeight),
- BackColor = panelBackColor,
- BorderStyle = BorderStyle.None,
- Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right
- };
- statusPanel.Paint += DrawPanelBorder;
-
- int xPos = 0;
- int yPos = 0;
- int cardWidth = 140;
- int cardHeight = 60;
-
- // 总车辆数
- totalLabel = CreateStatusCard("总车辆", xPos, yPos, cardWidth, cardHeight, titleTextColor, panelBackColor);
-
- // 在线车辆
- onlineLabel = CreateStatusCard("在线", xPos, yPos, cardWidth, cardHeight, Color.FromArgb(136, 205, 246), panelBackColor);
-
- // 离线车辆
- offlineLabel = CreateStatusCard("离线", xPos, yPos, cardWidth, cardHeight, Color.FromArgb(150, 150, 150), panelBackColor);
-
- // 运行中
- runningLabel = CreateStatusCard("运行中", xPos, yPos, cardWidth, cardHeight, Color.FromArgb(0, 180, 90), panelBackColor);
-
- // 充电中
- chargingLabel = CreateStatusCard("充电中", xPos, yPos, cardWidth, cardHeight, Color.FromArgb(255, 220, 0), panelBackColor);
-
- // 故障
- faultLabel = CreateStatusCard("故障", xPos, yPos, cardWidth, cardHeight, Color.FromArgb(255, 80, 80), panelBackColor);
-
- this.Controls.Add(statusPanel);
- UpdateStatusPanelLayout();
- }
-
- private Label CreateStatusCard(string text, int x, int y, int width, int height, Color textColor, Color bgColor)
- {
- Panel card = new Panel
- {
- Location = new System.Drawing.Point(x, y),
- Size = new System.Drawing.Size(width, height),
- BackColor = statusCardBackColor,
- BorderStyle = BorderStyle.None
- };
- card.Paint += DrawStatusCardBorder;
-
- // 状态指示灯(圆形)
- Color indicatorColor = textColor; // 保存颜色引用
- Panel indicator = new Panel
- {
- Location = new System.Drawing.Point(12, 20),
- Size = new System.Drawing.Size(14, 14),
- BackColor = Color.Transparent
- };
- // 使指示灯呈圆形
- indicator.Paint += (s, evt) =>
- {
- using (SolidBrush brush = new SolidBrush(indicatorColor))
- {
- evt.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
- evt.Graphics.FillEllipse(brush, 0, 0, indicator.Width, indicator.Height);
- }
- };
- card.Controls.Add(indicator);
-
- // 文本标签
- var label = new Label
- {
- Text = $"{text}\n0",
- Font = new Font("微软雅黑", 11F, FontStyle.Bold),
- ForeColor = textColor,
- Dock = DockStyle.Fill,
- Padding = new Padding(35, 8, 10, 8),
- TextAlign = ContentAlignment.MiddleLeft
- };
- card.Controls.Add(label);
-
- // 将Panel添加到statusPanel,但返回Label以便更新
- statusPanel.Controls.Add(card);
- statusCards.Add(card);
- return label;
- }
-
- private void CreateActionPanel()
- {
- statusActionPanel = new NoFocusPanel
- {
- Location = new System.Drawing.Point(20, statusPanel.Bottom + VerticalSpacing),
- Size = new System.Drawing.Size(this.ClientSize.Width - 40, this.ClientSize.Height - (statusPanel.Bottom + VerticalSpacing + 20)),
- BackColor = panelBackColor,
- BorderStyle = BorderStyle.None,
- Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right
- };
- statusActionPanel.Paint += DrawCombinedBorder;
- this.Controls.Add(statusActionPanel);
-
- actionPanel = new NoFocusPanel
- {
- Location = new System.Drawing.Point(CombinedPanelPadding, CombinedPanelPadding),
- Size = new System.Drawing.Size(statusActionPanel.Width - CombinedPanelPadding * 2, ActionPanelHeight),
- BackColor = panelBackColor,
- BorderStyle = BorderStyle.None,
- Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right
- };
-
- var flow = new FlowLayoutPanel
- {
- Dock = DockStyle.Fill,
- FlowDirection = FlowDirection.LeftToRight,
- WrapContents = false,
- Padding = new Padding(15, 10, 15, 10)
- };
-
- onlineButton = CreateActionButton("小车上线-批量");
- offlineButton = CreateActionButton("小车下线-批量");
- returnButton = CreateActionButton("故障返厂-批量");
- simulateAlarmButton = CreateActionButton("模拟报警");
- cancelSimAlarmButton = CreateActionButton("取消模拟报警");
- toggleSecondaryButton = CreateActionButton("显示次要列");
-
- onlineButton.Click += (s, e) => ExecuteForSelectedCars(BringCarOnline, "小车上线");
- offlineButton.Click += (s, e) => ExecuteForSelectedCars(BringCarOffline, "小车下线");
- returnButton.Click += (s, e) => ExecuteForSelectedCars(ReturnCarToFactory, "故障返厂");
- simulateAlarmButton.Click += (s, e) => AddSimulatedAlarm();
- cancelSimAlarmButton.Click += (s, e) => ClearSimulatedAlarms();
- toggleSecondaryButton.Click += (s, e) => ToggleSecondaryColumns();
-
- flow.Controls.Add(onlineButton);
- flow.Controls.Add(offlineButton);
- flow.Controls.Add(returnButton);
- flow.Controls.Add(simulateAlarmButton);
- flow.Controls.Add(cancelSimAlarmButton);
- flow.Controls.Add(toggleSecondaryButton);
-
- actionPanel.Controls.Add(flow);
- statusActionPanel.Controls.Add(actionPanel);
- }
-
- private Button CreateActionButton(string text)
- {
- var button = new Button
- {
- Text = text,
- Width = 130,
- Height = 38,
- Margin = new Padding(8, 0, 8, 0),
- FlatStyle = FlatStyle.Flat,
- BackColor = buttonBackColor,
- ForeColor = buttonTextColor,
- Font = new Font("微软雅黑", 10F, FontStyle.Bold)
- };
-
- button.FlatAppearance.BorderColor = buttonBorderColor;
- button.FlatAppearance.BorderSize = 1;
- button.FlatAppearance.MouseOverBackColor = buttonHoverColor;
- button.FlatAppearance.MouseDownBackColor = buttonDownColor;
- return button;
- }
-
- private void CreateVehicleGrid()
- {
- vehicleGrid = new DataGridView
- {
- Location = new System.Drawing.Point(CombinedPanelPadding, actionPanel.Bottom + CombinedPanelSpacing),
- Size = new System.Drawing.Size(statusActionPanel.Width - CombinedPanelPadding * 2, statusActionPanel.Height - (actionPanel.Bottom + CombinedPanelSpacing + CombinedPanelPadding)),
- Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right,
- AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.AllCells,
- AllowUserToAddRows = false,
- AllowUserToDeleteRows = false,
- AllowUserToResizeRows = true,
- ReadOnly = false,
- SelectionMode = DataGridViewSelectionMode.FullRowSelect,
- MultiSelect = false,
- BackgroundColor = panelBackColor,
- GridColor = panelBorderColor,
- BorderStyle = BorderStyle.FixedSingle,
- Font = new Font("微软雅黑", 10F),
- EnableHeadersVisualStyles = false,
- RowHeadersVisible = false,
- ColumnHeadersHeight = 42,
- ColumnHeadersHeightSizeMode = DataGridViewColumnHeadersHeightSizeMode.DisableResizing, // 禁止调整高度
- DefaultCellStyle = new DataGridViewCellStyle
- {
- BackColor = panelBackColor,
- ForeColor = gridTextColor,
- SelectionBackColor = gridSelectionBackColor,
- SelectionForeColor = gridSelectionTextColor,
- Padding = new Padding(5, 2, 5, 2)
- },
- ColumnHeadersDefaultCellStyle = new DataGridViewCellStyle
- {
- BackColor = gridHeaderBackColor,
- ForeColor = gridHeaderTextColor,
- Font = new Font("微软雅黑", 11F, FontStyle.Bold),
- Alignment = DataGridViewContentAlignment.MiddleCenter,
- Padding = new Padding(5, 8, 5, 8), // 增加上下内边距
- WrapMode = DataGridViewTriState.False // 禁止文字换行
- },
- AlternatingRowsDefaultCellStyle = new DataGridViewCellStyle
- {
- BackColor = gridAltRowColor
- }
- };
-
- SetGridDoubleBuffered(vehicleGrid);
- vehicleGrid.RowTemplate.Height = (int)(vehicleGrid.RowTemplate.Height * 1.5);
-
- // 添加列
- var selectColumn = new DataGridViewCheckBoxColumn
- {
- Name = "Select",
- HeaderText = "",
- Width = 40,
- ReadOnly = false,
- SortMode = DataGridViewColumnSortMode.NotSortable
- };
- vehicleGrid.Columns.Add(selectColumn);
-
- var remoteColumn = new DataGridViewButtonColumn
- {
- Name = "Remote",
- HeaderText = "远程",
- Width = 60,
- Text = "远程",
- UseColumnTextForButtonValue = true,
- FlatStyle = FlatStyle.Flat,
- SortMode = DataGridViewColumnSortMode.NotSortable
- };
- vehicleGrid.Columns.Add(remoteColumn);
-
- var onlineColumn = new DataGridViewButtonColumn
- {
- Name = "Online",
- HeaderText = "上线",
- Width = 60,
- Text = "上线",
- UseColumnTextForButtonValue = true,
- FlatStyle = FlatStyle.Flat,
- SortMode = DataGridViewColumnSortMode.NotSortable
- };
- vehicleGrid.Columns.Add(onlineColumn);
-
- var offlineColumn = new DataGridViewButtonColumn
- {
- Name = "Offline",
- HeaderText = "下线",
- Width = 60,
- Text = "下线",
- UseColumnTextForButtonValue = true,
- FlatStyle = FlatStyle.Flat,
- SortMode = DataGridViewColumnSortMode.NotSortable
- };
- vehicleGrid.Columns.Add(offlineColumn);
-
- var returnColumn = new DataGridViewButtonColumn
- {
- Name = "Return",
- HeaderText = "返厂",
- Width = 60,
- Text = "返厂",
- UseColumnTextForButtonValue = true,
- FlatStyle = FlatStyle.Flat,
- SortMode = DataGridViewColumnSortMode.NotSortable
- };
- vehicleGrid.Columns.Add(returnColumn);
-
- vehicleGrid.Columns.Add("CarId", "车辆ID");
- vehicleGrid.Columns.Add("CarName", "车辆名称");
- vehicleGrid.Columns.Add("CarType", "车辆类型");
- vehicleGrid.Columns.Add("Status", "运行状态");
- vehicleGrid.Columns.Add("IsOnline", "在线");
- vehicleGrid.Columns.Add("X", "X坐标");
- vehicleGrid.Columns.Add("Y", "Y坐标");
- vehicleGrid.Columns.Add("Theta", "角度(°)");
- vehicleGrid.Columns.Add("Battery", "电量");
- vehicleGrid.Columns.Add("BatteryBar", "电量进度");
- vehicleGrid.Columns.Add("Voltage", "电压(V)");
- vehicleGrid.Columns.Add("Current", "电流(A)");
- vehicleGrid.Columns.Add("Speed", "速度(m/s)");
- vehicleGrid.Columns.Add("SiteId", "站点ID");
- vehicleGrid.Columns.Add("TaskStatus", "任务状态");
- vehicleGrid.Columns.Add("CPUUsage", "CPU");
- vehicleGrid.Columns.Add("CPUBar", "CPU进度");
- vehicleGrid.Columns.Add("Memory", "内存");
- vehicleGrid.Columns.Add("Address", "IP地址");
-
- // 设置数值列的对齐方式
- vehicleGrid.Columns["Select"].DefaultCellStyle.Alignment = DataGridViewContentAlignment.MiddleCenter;
- vehicleGrid.Columns["Remote"].DefaultCellStyle.Alignment = DataGridViewContentAlignment.MiddleCenter;
- vehicleGrid.Columns["Online"].DefaultCellStyle.Alignment = DataGridViewContentAlignment.MiddleCenter;
- vehicleGrid.Columns["Offline"].DefaultCellStyle.Alignment = DataGridViewContentAlignment.MiddleCenter;
- vehicleGrid.Columns["Return"].DefaultCellStyle.Alignment = DataGridViewContentAlignment.MiddleCenter;
- ApplyGridButtonStyle(vehicleGrid.Columns["Remote"]);
- ApplyGridButtonStyle(vehicleGrid.Columns["Online"]);
- ApplyGridButtonStyle(vehicleGrid.Columns["Offline"]);
- ApplyGridButtonStyle(vehicleGrid.Columns["Return"]);
- vehicleGrid.Columns["X"].DefaultCellStyle.Alignment = DataGridViewContentAlignment.MiddleRight;
- vehicleGrid.Columns["Y"].DefaultCellStyle.Alignment = DataGridViewContentAlignment.MiddleRight;
- vehicleGrid.Columns["Theta"].DefaultCellStyle.Alignment = DataGridViewContentAlignment.MiddleRight;
- vehicleGrid.Columns["Battery"].DefaultCellStyle.Alignment = DataGridViewContentAlignment.MiddleRight;
- vehicleGrid.Columns["Voltage"].DefaultCellStyle.Alignment = DataGridViewContentAlignment.MiddleRight;
- vehicleGrid.Columns["Current"].DefaultCellStyle.Alignment = DataGridViewContentAlignment.MiddleRight;
- vehicleGrid.Columns["Speed"].DefaultCellStyle.Alignment = DataGridViewContentAlignment.MiddleRight;
- vehicleGrid.Columns["CPUUsage"].DefaultCellStyle.Alignment = DataGridViewContentAlignment.MiddleRight;
- vehicleGrid.Columns["Memory"].DefaultCellStyle.Alignment = DataGridViewContentAlignment.MiddleRight;
-
- // 设置进度条列为只读
- vehicleGrid.Columns["BatteryBar"].ReadOnly = true;
- vehicleGrid.Columns["CPUBar"].ReadOnly = true;
-
- foreach (DataGridViewColumn column in vehicleGrid.Columns)
- {
- if (column.Name == "Select" || column.Name == "Remote" || column.Name == "Online" || column.Name == "Offline" || column.Name == "Return")
- {
- column.ReadOnly = column.Name != "Select";
- continue;
- }
-
- column.ReadOnly = true;
- }
-
- foreach (DataGridViewColumn column in vehicleGrid.Columns)
- {
- if (column.SortMode == DataGridViewColumnSortMode.NotSortable) continue;
- column.SortMode = DataGridViewColumnSortMode.Automatic;
- }
-
- ApplySecondaryColumnVisibility();
-
- // 注册CellFormatting事件来绘制进度条
- vehicleGrid.CellFormatting += VehicleGrid_CellFormatting;
- vehicleGrid.CellPainting += VehicleGrid_CellPainting;
- vehicleGrid.CellContentClick += VehicleGrid_CellContentClick;
- vehicleGrid.CurrentCellDirtyStateChanged += VehicleGrid_CurrentCellDirtyStateChanged;
- vehicleGrid.ColumnWidthChanged += VehicleGrid_ColumnWidthChanged;
- vehicleGrid.Scroll += VehicleGrid_Scroll;
- vehicleGrid.SizeChanged += VehicleGrid_SizeChanged;
-
- InitializeSelectAllCheckBox();
-
- statusActionPanel.Controls.Add(vehicleGrid);
- UpdateActionGridPanelLayout();
- }
-
- private void AttachBottomBorder(Panel panel)
- {
- if (panel == null) return;
- panel.Paint += (s, e) =>
- {
- using (var pen = new Pen(subtleLineColor))
- {
- e.Graphics.DrawLine(pen, 0, panel.Height - 1, panel.Width, panel.Height - 1);
- }
- };
- }
-
- private void DrawPanelBorder(object sender, PaintEventArgs e)
- {
- if (!(sender is Panel panel)) return;
- using (var pen = new Pen(panelBorderColor, OuterBorderWidth))
- {
- var rect = panel.ClientRectangle;
- rect.Width -= 1;
- rect.Height -= 1;
- e.Graphics.DrawRectangle(pen, rect);
- }
- }
-
- private void DrawCombinedBorder(object sender, PaintEventArgs e)
- {
- if (!(sender is Panel panel)) return;
- using (var pen = new Pen(combinedBorderColor, OuterBorderWidth))
- {
- var rect = panel.ClientRectangle;
- rect.Width -= 1;
- rect.Height -= 1;
- e.Graphics.DrawRectangle(pen, rect);
- }
- }
-
- private void DrawStatusCardBorder(object sender, PaintEventArgs e)
- {
- if (!(sender is Panel panel)) return;
- using (var pen = new Pen(statusCardBorderColor))
- {
- var rect = panel.ClientRectangle;
- rect.Width -= 1;
- rect.Height -= 1;
- e.Graphics.DrawRectangle(pen, rect);
- }
- }
-
- private void SetGridDoubleBuffered(DataGridView grid)
- {
- typeof(DataGridView).GetProperty("DoubleBuffered", BindingFlags.Instance | BindingFlags.NonPublic)
- ?.SetValue(grid, true, null);
- }
-
- private void ApplyGridButtonStyle(DataGridViewColumn column)
- {
- if (column == null) return;
-
- column.DefaultCellStyle.BackColor = buttonBackColor;
- column.DefaultCellStyle.ForeColor = buttonTextColor;
- column.DefaultCellStyle.SelectionBackColor = buttonHoverColor;
- column.DefaultCellStyle.SelectionForeColor = buttonTextColor;
- }
-
- private void VehicleGrid_CellFormatting(object sender, DataGridViewCellFormattingEventArgs e)
- {
- if (vehicleGrid.Columns[e.ColumnIndex].Name == "BatteryBar" ||
- vehicleGrid.Columns[e.ColumnIndex].Name == "CPUBar")
- {
- e.Value = ""; // 清空文本,我们将绘制进度条
- }
- }
-
- private void VehicleGrid_CellPainting(object sender, DataGridViewCellPaintingEventArgs e)
- {
- if (e.RowIndex == -1)
- {
- var columnName = vehicleGrid.Columns[e.ColumnIndex].Name;
- if (columnName == "Remote" || columnName == "Online" || columnName == "Offline" || columnName == "Return")
- {
- if (columnName == "Remote")
- {
- var firstRect = vehicleGrid.GetCellDisplayRectangle(vehicleGrid.Columns["Remote"].Index, -1, true);
- var lastRect = vehicleGrid.GetCellDisplayRectangle(vehicleGrid.Columns["Return"].Index, -1, true);
- if (firstRect.Width > 0 && lastRect.Width > 0)
- {
- var mergedRect = new Rectangle(firstRect.Left, firstRect.Top, lastRect.Right - firstRect.Left, firstRect.Height);
- using (var backBrush = new SolidBrush(vehicleGrid.ColumnHeadersDefaultCellStyle.BackColor))
- using (var textBrush = new SolidBrush(vehicleGrid.ColumnHeadersDefaultCellStyle.ForeColor))
- using (var borderPen = new Pen(panelBorderColor))
- using (var format = new StringFormat { Alignment = StringAlignment.Center, LineAlignment = StringAlignment.Center })
- {
- e.Graphics.FillRectangle(backBrush, mergedRect);
- var font = vehicleGrid.ColumnHeadersDefaultCellStyle.Font ?? vehicleGrid.Font;
- e.Graphics.DrawString(QuickOperationHeaderText, font, textBrush, mergedRect, format);
- e.Graphics.DrawRectangle(borderPen, mergedRect.X, mergedRect.Y, mergedRect.Width - 1, mergedRect.Height - 1);
- }
- }
- }
-
- e.Handled = true;
- return;
- }
- }
-
- if (e.RowIndex < 0) return;
-
- // 绘制电量进度条
- if (e.ColumnIndex == vehicleGrid.Columns["BatteryBar"].Index)
- {
- e.Paint(e.CellBounds, DataGridViewPaintParts.All & ~DataGridViewPaintParts.ContentForeground);
-
- double battery = 0;
- if (e.RowIndex < vehicleGrid.Rows.Count)
- {
- var batteryCell = vehicleGrid.Rows[e.RowIndex].Cells["Battery"];
- if (batteryCell != null && batteryCell.Value != null)
- {
- string batteryStr = batteryCell.Value.ToString().Replace("%", "");
- double.TryParse(batteryStr, out battery);
- }
- }
-
- // 根据电量设置颜色
- Color barColor;
- if (battery < 20)
- barColor = Color.FromArgb(255, 80, 80);
- else if (battery < 30)
- barColor = Color.FromArgb(255, 200, 0);
- else if (battery < 50)
- barColor = Color.FromArgb(255, 220, 100);
- else
- barColor = Color.FromArgb(0, 220, 100);
-
- int barWidth = (int)((e.CellBounds.Width - 10) * battery / 100.0);
- Rectangle barRect = new Rectangle(e.CellBounds.X + 5, e.CellBounds.Y + 4, barWidth, e.CellBounds.Height - 8);
-
- using (SolidBrush brush = new SolidBrush(barColor))
- {
- e.Graphics.FillRectangle(brush, barRect);
- }
-
- // 绘制边框
- using (Pen pen = new Pen(panelBorderColor, 1))
- {
- e.Graphics.DrawRectangle(pen, e.CellBounds.X + 5, e.CellBounds.Y + 4, e.CellBounds.Width - 10, e.CellBounds.Height - 8);
- }
-
- e.Handled = true;
- }
-
- // 绘制CPU进度条
- if (e.ColumnIndex == vehicleGrid.Columns["CPUBar"].Index)
- {
- e.Paint(e.CellBounds, DataGridViewPaintParts.All & ~DataGridViewPaintParts.ContentForeground);
-
- double cpuUsage = 0;
- if (e.RowIndex < vehicleGrid.Rows.Count)
- {
- var cpuCell = vehicleGrid.Rows[e.RowIndex].Cells["CPUUsage"];
- if (cpuCell != null && cpuCell.Value != null)
- {
- string cpuStr = cpuCell.Value.ToString();
- if (cpuStr != "N/A")
- {
- cpuStr = cpuStr.Replace("%", "");
- double.TryParse(cpuStr, out cpuUsage);
- }
- }
- }
-
- // 根据CPU使用率设置颜色
- Color barColor;
- if (cpuUsage > 80)
- barColor = Color.FromArgb(255, 80, 80);
- else if (cpuUsage > 60)
- barColor = Color.FromArgb(255, 200, 0);
- else if (cpuUsage > 40)
- barColor = Color.FromArgb(255, 220, 100);
- else
- barColor = Color.FromArgb(0, 220, 100);
-
- int barWidth = (int)((e.CellBounds.Width - 10) * cpuUsage / 100.0);
- Rectangle barRect = new Rectangle(e.CellBounds.X + 5, e.CellBounds.Y + 4, barWidth, e.CellBounds.Height - 8);
-
- using (SolidBrush brush = new SolidBrush(barColor))
- {
- e.Graphics.FillRectangle(brush, barRect);
- }
-
- // 绘制边框
- using (Pen pen = new Pen(panelBorderColor, 1))
- {
- e.Graphics.DrawRectangle(pen, e.CellBounds.X + 5, e.CellBounds.Y + 4, e.CellBounds.Width - 10, e.CellBounds.Height - 8);
- }
-
- e.Handled = true;
- }
- }
-
- private void UpdateTimer_Tick(object sender, EventArgs e)
- {
- UpdateVehicleData();
- // 更新时间显示
- if (timeLabel != null)
- {
- timeLabel.Text = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
- }
- Invalidate(true);
- }
-
- private void UpdateVehicleData()
- {
- try
- {
- CaptureGridViewState();
-
- var cars = SimpleLib.GetAllCars().OfType().ToList();
- if (string.IsNullOrEmpty(lastSortColumnName))
- {
- cars = cars.OrderBy(c => c.id).ToList();
- }
-
- // 更新统计信息
- UpdateStatistics(cars);
- UpdateAlarmTicker(cars);
-
- // 更新表格数据
- vehicleGrid.SuspendLayout();
- vehicleGrid.Rows.Clear();
- foreach (var car in cars)
- {
- var carInfo = CarBaseInfo.FromCar(car);
- var isOnline = carInfo.IsOnline;
- var state = carInfo.CurrState;
-
- // 获取CPU使用率(如果存在)
- string cpuUsage = "N/A";
- double cpuValue = 0;
- if (car.status.enums.ContainsKey("CPUUsage"))
- {
- cpuUsage = car.status.enums["CPUUsage"];
- double.TryParse(cpuUsage, out cpuValue);
- }
- else if (car.status.enums.ContainsKey("CpuUsage"))
- {
- cpuUsage = car.status.enums["CpuUsage"];
- double.TryParse(cpuUsage, out cpuValue);
- }
- else if (car.status.enums.ContainsKey("CPU"))
- {
- cpuUsage = car.status.enums["CPU"];
- double.TryParse(cpuUsage, out cpuValue);
- }
-
- // 获取内存使用率(如果存在)
- string memoryUsage = "N/A";
- if (car.status.enums.ContainsKey("MemoryUsage"))
- {
- memoryUsage = car.status.enums["MemoryUsage"] + "%";
- }
- else if (car.status.enums.ContainsKey("Memory"))
- {
- memoryUsage = car.status.enums["Memory"] + "%";
- }
- else if (car.status.enums.ContainsKey("RAM"))
- {
- memoryUsage = car.status.enums["RAM"] + "%";
- }
-
- // 获取任务状态
- string taskStatus = GetTaskStatus(car);
-
- // 获取电量
- double battery = carInfo.Battery;
- if (car.status.enums.ContainsKey("Soc"))
- {
- double.TryParse(car.status.enums["Soc"], out battery);
- }
- else if (car.status.enums.ContainsKey("soc"))
- {
- double.TryParse(car.status.enums["soc"], out battery);
- }
-
- // 格式化状态显示
- string statusDisplay = GetStatusDisplay(state);
- string onlineDisplay = isOnline ? "●" : "○";
- string batteryDisplay = battery.ToString("F1") + "%";
- string carId = car.id.ToString();
-
- int rowIndex = vehicleGrid.Rows.Add(
- selectedCarIds.Contains(carId),
- "远程",
- "上线",
- "下线",
- "返厂",
- car.id.ToString(),
- car.name,
- carInfo.CarType,
- statusDisplay,
- onlineDisplay,
- car.x.ToString("F2"),
- car.y.ToString("F2"),
- (car.th * 180 / Math.PI).ToString("F1"), // 转换为度数
- batteryDisplay,
- "", // BatteryBar - 由CellPainting绘制
- carInfo.Voltage.ToString("F2"),
- carInfo.ElectricCurrent.ToString("F2"),
- car.speed.ToString("F2"),
- car.GetLastSite().ToString(),
- taskStatus,
- cpuUsage == "N/A" ? cpuUsage : cpuValue.ToString("F1") + "%",
- "", // CPUBar - 由CellPainting绘制
- memoryUsage,
- carInfo.Address
- );
-
- // 根据状态设置行颜色
- var row = vehicleGrid.Rows[rowIndex];
- if (rowHeightCache.TryGetValue(carId, out var rowHeight))
- {
- row.Height = rowHeight;
- }
- SetRowColor(row, state, isOnline, battery, cpuValue);
- }
-
- ApplyGridViewState();
- vehicleGrid.ResumeLayout();
- }
- catch (Exception ex)
- {
- Diagnosis.Log($"更新车辆数据异常: {ex.Message}", "VehicleMonitor", true);
- }
- }
-
- private string GetStatusDisplay(string state)
- {
- switch (state)
- {
- case "Running":
- return "运行中";
- case "Charging":
- return "充电中";
- case "Faulting":
- return "故障";
- case "Idle":
- return "空闲";
- case "Offline":
- return "离线";
- default:
- return state;
- }
- }
-
- private void UpdateStatistics(List cars)
- {
- int total = cars.Count;
- int online = cars.Count(c => CarBaseInfo.FromCar(c).IsOnline);
- int offline = total - online;
- int running = cars.Count(c =>
- {
- var info = CarBaseInfo.FromCar(c);
- return info.IsOnline && info.CurrState == "Running";
- });
- int charging = cars.Count(c =>
- {
- var info = CarBaseInfo.FromCar(c);
- return info.IsOnline && info.CurrState == "Charging";
- });
- int fault = cars.Count(c =>
- {
- var info = CarBaseInfo.FromCar(c);
- return info.IsOnline && info.CurrState == "Faulting";
- });
-
- totalLabel.Text = $"总车辆\n{total}";
- onlineLabel.Text = $"在线\n{online}";
- offlineLabel.Text = $"离线\n{offline}";
- runningLabel.Text = $"运行中\n{running}";
- chargingLabel.Text = $"充电中\n{charging}";
- faultLabel.Text = $"故障\n{fault}";
- }
-
- private string GetTaskStatus(Car car)
- {
- if (car.tags.Contains("occupied"))
- {
- if (car.tags.Contains("charging"))
- return "充电中";
- if (car.tags.Contains("deliver"))
- return "执行任务";
- if (car.tags.Contains("redirect"))
- return "避让中";
- return "占用中";
- }
- if (car.tags.Contains("idle"))
- return "空闲";
- if (car.tags.Contains("blocking"))
- return "阻塞中";
- if (car.status.pendingLocks.Length > 0)
- return "路径规划中";
- return "待机";
- }
-
- private void SetRowColor(DataGridViewRow row, string state, bool isOnline, double battery, double cpuUsage)
- {
- if (!isOnline)
- {
- row.DefaultCellStyle.BackColor = Color.FromArgb(244, 246, 248);
- row.DefaultCellStyle.ForeColor = Color.FromArgb(150, 160, 170);
- }
- else
- {
- switch (state)
- {
- case "Running":
- row.DefaultCellStyle.BackColor = Color.FromArgb(231, 248, 236);
- row.DefaultCellStyle.ForeColor = Color.FromArgb(0, 180, 90);
- break;
- case "Charging":
- row.DefaultCellStyle.BackColor = Color.FromArgb(255, 250, 230);
- row.DefaultCellStyle.ForeColor = Color.FromArgb(255, 220, 0);
- break;
- case "Faulting":
- row.DefaultCellStyle.BackColor = Color.FromArgb(255, 236, 236);
- row.DefaultCellStyle.ForeColor = Color.FromArgb(255, 80, 80);
- break;
- default:
- row.DefaultCellStyle.BackColor = panelBackColor;
- row.DefaultCellStyle.ForeColor = gridTextColor;
- break;
- }
-
- // 在线状态指示器颜色
- if (row.Cells["IsOnline"] != null)
- {
- row.Cells["IsOnline"].Style.ForeColor = Color.FromArgb(0, 255, 100);
- row.Cells["IsOnline"].Style.Font = new Font("微软雅黑", 12F, FontStyle.Bold);
- }
-
- if (row.Cells["Battery"] != null)
- {
- row.Cells["Battery"].Style.BackColor = row.DefaultCellStyle.BackColor;
- row.Cells["Battery"].Style.ForeColor = row.DefaultCellStyle.ForeColor;
- }
-
- if (row.Cells["CPUUsage"] != null)
- {
- row.Cells["CPUUsage"].Style.BackColor = row.DefaultCellStyle.BackColor;
- row.Cells["CPUUsage"].Style.ForeColor = row.DefaultCellStyle.ForeColor;
- }
-
- // 电量低警告
- if (battery < 20)
- {
- row.Cells["Battery"].Style.BackColor = Color.FromArgb(255, 235, 235);
- row.Cells["Battery"].Style.ForeColor = Color.FromArgb(220, 60, 60);
- }
- else if (battery < 30)
- {
- row.Cells["Battery"].Style.BackColor = Color.FromArgb(255, 247, 225);
- row.Cells["Battery"].Style.ForeColor = Color.FromArgb(200, 140, 0);
- }
-
- // CPU使用率高警告
- if (cpuUsage > 80)
- {
- row.Cells["CPUUsage"].Style.BackColor = Color.FromArgb(255, 235, 235);
- row.Cells["CPUUsage"].Style.ForeColor = Color.FromArgb(220, 60, 60);
- }
- else if (cpuUsage > 60)
- {
- row.Cells["CPUUsage"].Style.BackColor = Color.FromArgb(255, 247, 225);
- row.Cells["CPUUsage"].Style.ForeColor = Color.FromArgb(200, 140, 0);
- }
- }
- }
-
- protected override void OnFormClosing(FormClosingEventArgs e)
- {
- if (e.CloseReason == CloseReason.UserClosing)
- {
- e.Cancel = true;
- this.Hide();
- }
- else
- {
- // 如果不是用户关闭,则释放资源
- instance = null;
- }
- base.OnFormClosing(e);
- }
-
- protected override void OnResize(EventArgs e)
- {
- base.OnResize(e);
-
- if (alarmPanel != null)
- {
- alarmPanel.Left = 20;
- alarmPanel.Top = GetAlarmPanelTop();
- alarmPanel.Width = this.ClientSize.Width - 40;
- PositionAlarmLabel();
- }
-
- // 更新状态面板宽度
- UpdateStatusPanelLayout();
-
- if (vehicleGrid != null)
- {
- UpdateActionGridPanelLayout();
- }
-
- // 更新时间标签位置
- if (timeLabel != null && this.Controls.Count > 0)
- {
- var titlePanel = this.Controls[0] as Panel;
- if (titlePanel != null)
- {
- timeLabel.Left = this.ClientSize.Width - 250;
- }
- }
- }
-
- private int GetStatusPanelWidth(int cardCount)
- {
- int fullWidth = this.ClientSize.Width - 40;
- int preferred = (int)(fullWidth * 2.0 / 3.0);
- int minCardWidth = 100;
- int padding = 12;
- int spacing = 8;
- int minWidth = cardCount * minCardWidth + spacing * (cardCount - 1) + padding * 2;
-
- int target = Math.Max(preferred, minWidth);
- return Math.Min(fullWidth, target);
- }
-
- private void UpdateStatusPanelLayout()
- {
- if (statusPanel == null) return;
-
- int targetWidth = alarmPanel != null ? alarmPanel.Width : (this.ClientSize.Width - 40);
- statusPanel.Width = targetWidth;
- statusPanel.Left = 20;
- statusPanel.Top = GetStatusPanelTop();
- LayoutStatusCards();
- }
-
- private void UpdateActionGridPanelLayout()
- {
- if (statusActionPanel == null || actionPanel == null || vehicleGrid == null) return;
-
- statusActionPanel.Left = 20;
- statusActionPanel.Top = statusPanel.Bottom + VerticalSpacing;
- statusActionPanel.Width = this.ClientSize.Width - 40;
- statusActionPanel.Height = this.ClientSize.Height - (statusActionPanel.Top + 20);
-
- actionPanel.Left = CombinedPanelPadding;
- actionPanel.Top = CombinedPanelPadding;
- actionPanel.Width = statusActionPanel.Width - CombinedPanelPadding * 2;
-
- vehicleGrid.Left = CombinedPanelPadding;
- vehicleGrid.Top = actionPanel.Bottom + CombinedPanelSpacing;
- vehicleGrid.Width = statusActionPanel.Width - CombinedPanelPadding * 2;
- vehicleGrid.Height = statusActionPanel.Height - (vehicleGrid.Top + CombinedPanelPadding);
- }
-
- private int GetAlarmPanelTop()
- {
- return TitleBarHeight + AlarmPanelMarginTop;
- }
-
- private int GetStatusPanelTop()
- {
- return GetAlarmPanelTop() + AlarmPanelHeight + VerticalSpacing;
- }
-
- private void LayoutStatusCards()
- {
- if (statusPanel == null || statusCards.Count == 0) return;
-
- int cardCount = statusCards.Count;
- int padding = 12;
- int spacing = 8;
- int availableWidth = statusPanel.Width - padding * 2 - spacing * (cardCount - 1);
- int cardWidth = Math.Max(100, availableWidth / cardCount);
- int cardHeight = 60;
- int x = padding;
- int y = (statusPanel.Height - cardHeight) / 2;
-
- foreach (var card in statusCards)
- {
- card.Location = new System.Drawing.Point(x, y);
- card.Size = new System.Drawing.Size(cardWidth, cardHeight);
-
- var indicator = card.Controls.OfType().FirstOrDefault();
- if (indicator != null)
- {
- indicator.Top = (cardHeight - indicator.Height) / 2;
- }
-
- x += cardWidth + spacing;
- }
- }
-
- private void VehicleGrid_CurrentCellDirtyStateChanged(object sender, EventArgs e)
- {
- if (vehicleGrid.IsCurrentCellDirty)
- {
- vehicleGrid.CommitEdit(DataGridViewDataErrorContexts.Commit);
- }
- }
-
- private void VehicleGrid_CellContentClick(object sender, DataGridViewCellEventArgs e)
- {
- if (e.RowIndex < 0) return;
-
- var columnName = vehicleGrid.Columns[e.ColumnIndex].Name;
- if (columnName == "Select")
- {
- UpdateSelectedCarIdFromRow(e.RowIndex);
- UpdateHeaderCheckBoxState();
- }
- else if (columnName == "Remote")
- {
- var ip = vehicleGrid.Rows[e.RowIndex].Cells["Address"]?.Value?.ToString();
- if (!string.IsNullOrWhiteSpace(ip))
- {
- LaunchRemoteDesktop(ip);
- }
- }
- else if (columnName == "Online")
- {
- ExecuteForSingleRow(e.RowIndex, BringCarOnline, "小车上线");
- }
- else if (columnName == "Offline")
- {
- ExecuteForSingleRow(e.RowIndex, BringCarOffline, "小车下线");
- }
- else if (columnName == "Return")
- {
- ExecuteForSingleRow(e.RowIndex, ReturnCarToFactory, "故障返厂");
- }
- }
-
- private void UpdateSelectedCarIdFromRow(int rowIndex)
- {
- var row = vehicleGrid.Rows[rowIndex];
- var id = row.Cells["CarId"]?.Value?.ToString();
- if (string.IsNullOrWhiteSpace(id)) return;
-
- var selected = row.Cells["Select"]?.Value is bool flag && flag;
- if (selected)
- {
- selectedCarIds.Add(id);
- }
- else
- {
- selectedCarIds.Remove(id);
- }
- }
-
- private void CaptureGridViewState()
- {
- if (vehicleGrid == null) return;
-
- lastSortColumnName = vehicleGrid.SortedColumn?.Name;
- lastSortOrder = vehicleGrid.SortOrder;
-
- try
- {
- lastFirstDisplayedRowIndex = vehicleGrid.FirstDisplayedScrollingRowIndex;
- }
- catch
- {
- lastFirstDisplayedRowIndex = -1;
- }
-
- rowHeightCache.Clear();
- selectedCarIds.Clear();
-
- foreach (DataGridViewRow row in vehicleGrid.Rows)
- {
- if (row.IsNewRow) continue;
- var id = row.Cells["CarId"]?.Value?.ToString();
- if (string.IsNullOrWhiteSpace(id)) continue;
-
- rowHeightCache[id] = row.Height;
-
- var selected = row.Cells["Select"]?.Value is bool flag && flag;
- if (selected)
- {
- selectedCarIds.Add(id);
- }
- }
- }
-
- private void ApplyGridViewState()
- {
- if (vehicleGrid == null) return;
-
- if (!string.IsNullOrEmpty(lastSortColumnName) && vehicleGrid.Columns.Contains(lastSortColumnName) &&
- lastSortOrder != SortOrder.None)
- {
- var direction = lastSortOrder == SortOrder.Descending
- ? ListSortDirection.Descending
- : ListSortDirection.Ascending;
- vehicleGrid.Sort(vehicleGrid.Columns[lastSortColumnName], direction);
- }
-
- if (lastFirstDisplayedRowIndex >= 0 && lastFirstDisplayedRowIndex < vehicleGrid.Rows.Count)
- {
- vehicleGrid.FirstDisplayedScrollingRowIndex = lastFirstDisplayedRowIndex;
- }
-
- UpdateHeaderCheckBoxState();
- PositionSelectAllCheckBox();
- }
-
- private void InitializeSelectAllCheckBox()
- {
- selectAllCheckBox = new CheckBox
- {
- Size = new System.Drawing.Size(14, 14),
- BackColor = Color.Transparent
- };
-
- selectAllCheckBox.CheckedChanged += SelectAllCheckBox_CheckedChanged;
- vehicleGrid.Controls.Add(selectAllCheckBox);
- PositionSelectAllCheckBox();
- }
-
- private void PositionSelectAllCheckBox()
- {
- if (selectAllCheckBox == null || vehicleGrid == null) return;
- if (!vehicleGrid.Columns.Contains("Select")) return;
-
- var rect = vehicleGrid.GetCellDisplayRectangle(vehicleGrid.Columns["Select"].Index, -1, true);
- if (rect.Width <= 0 || rect.Height <= 0) return;
-
- int x = rect.X + (rect.Width - selectAllCheckBox.Width) / 2;
- int y = rect.Y + (rect.Height - selectAllCheckBox.Height) / 2;
- selectAllCheckBox.Location = new System.Drawing.Point(x, y);
- }
-
- private void SelectAllCheckBox_CheckedChanged(object sender, EventArgs e)
- {
- if (suppressSelectSync) return;
-
- suppressSelectSync = true;
- bool isChecked = selectAllCheckBox.Checked;
-
- foreach (DataGridViewRow row in vehicleGrid.Rows)
- {
- if (row.IsNewRow) continue;
- row.Cells["Select"].Value = isChecked;
-
- var id = row.Cells["CarId"]?.Value?.ToString();
- if (string.IsNullOrWhiteSpace(id)) continue;
-
- if (isChecked)
- {
- selectedCarIds.Add(id);
- }
- else
- {
- selectedCarIds.Remove(id);
- }
- }
-
- suppressSelectSync = false;
- }
-
- private void UpdateHeaderCheckBoxState()
- {
- if (selectAllCheckBox == null || vehicleGrid == null) return;
- if (suppressSelectSync) return;
-
- int total = 0;
- int selected = 0;
- foreach (DataGridViewRow row in vehicleGrid.Rows)
- {
- if (row.IsNewRow) continue;
- total++;
- var isSelected = row.Cells["Select"]?.Value is bool flag && flag;
- if (isSelected) selected++;
- }
-
- suppressSelectSync = true;
- selectAllCheckBox.Checked = total > 0 && selected == total;
- suppressSelectSync = false;
- }
-
- private void VehicleGrid_ColumnWidthChanged(object sender, DataGridViewColumnEventArgs e)
- {
- if (e.Column.Name == "Select")
- {
- PositionSelectAllCheckBox();
- }
- }
-
- private void VehicleGrid_Scroll(object sender, ScrollEventArgs e)
- {
- if (e.ScrollOrientation == ScrollOrientation.HorizontalScroll)
- {
- PositionSelectAllCheckBox();
- }
- }
-
- private void VehicleGrid_SizeChanged(object sender, EventArgs e)
- {
- PositionSelectAllCheckBox();
- }
-
- private void AlarmScrollTimer_Tick(object sender, EventArgs e)
- {
- if (alarmLabel == null || alarmPanel == null) return;
- if (string.IsNullOrWhiteSpace(alarmLabel.Text)) return;
-
- alarmLabel.Left -= 3;
- if (alarmLabel.Right < 0)
- {
- if (!string.IsNullOrEmpty(pendingAlarmText))
- {
- ApplyAlarmText(pendingAlarmText, pendingAlarmColor, pendingAlarmScrolling);
- pendingAlarmText = null;
- }
- alarmLabel.Left = alarmPanel.Width;
- }
- }
-
- private void UpdateAlarmTicker(List cars)
- {
- var messages = new List();
- if (cars != null)
- {
- foreach (var car in cars)
- {
- var alarmInfo = GetCarAlarmInfo(car);
- if (!string.IsNullOrWhiteSpace(alarmInfo))
- {
- messages.Add($"{car.name}: {alarmInfo}");
- }
- }
- }
-
- foreach (var name in simulatedAlarmNames)
- {
- messages.Add($"{name}: {SimulatedAlarmInfo}");
- }
-
- if (messages.Count == 0)
- {
- SetAlarmText("暂无报警信息", subTextColor, scrolling: false);
- return;
- }
-
- SetAlarmText(string.Join(" | ", messages), Color.FromArgb(255, 60, 60), scrolling: true);
- }
-
- private string GetCarAlarmInfo(Car car)
- {
- if (car?.status?.enums == null) return string.Empty;
-
- string alarmInfo;
- if (car.status.enums.TryGetValue("AlarmInfo", out alarmInfo) && !string.IsNullOrWhiteSpace(alarmInfo))
- return alarmInfo;
- if (car.status.enums.TryGetValue("alarmInfo", out alarmInfo) && !string.IsNullOrWhiteSpace(alarmInfo))
- return alarmInfo;
- if (car.status.enums.TryGetValue("Alarm", out alarmInfo) && !string.IsNullOrWhiteSpace(alarmInfo))
- return alarmInfo;
- if (car.status.enums.TryGetValue("AlarmMsg", out alarmInfo) && !string.IsNullOrWhiteSpace(alarmInfo))
- return alarmInfo;
- if (car.status.enums.TryGetValue("AlarmMessage", out alarmInfo) && !string.IsNullOrWhiteSpace(alarmInfo))
- return alarmInfo;
- if (car.status.enums.TryGetValue("AlarmText", out alarmInfo) && !string.IsNullOrWhiteSpace(alarmInfo))
- return alarmInfo;
-
- return string.Empty;
- }
-
- private void SetAlarmText(string text, Color color, bool scrolling)
- {
- if (alarmLabel == null || alarmPanel == null) return;
- if (lastAlarmText == text && alarmScrollTimer.Enabled == scrolling)
- {
- return;
- }
-
- if (alarmScrollTimer.Enabled && scrolling)
- {
- pendingAlarmText = text;
- pendingAlarmColor = color;
- pendingAlarmScrolling = scrolling;
- return;
- }
-
- ApplyAlarmText(text, color, scrolling);
- }
-
- private void ApplyAlarmText(string text, Color color, bool scrolling)
- {
- alarmLabel.ForeColor = color;
- alarmLabel.Text = text;
- lastAlarmText = text;
- alarmScrollTimer.Enabled = scrolling;
- PositionAlarmLabel();
- }
-
- private void ToggleSecondaryColumns()
- {
- showSecondaryColumns = !showSecondaryColumns;
- ApplySecondaryColumnVisibility();
- }
-
- private void ApplySecondaryColumnVisibility()
- {
- if (vehicleGrid == null) return;
- SetColumnVisible("CarId", showSecondaryColumns);
- SetColumnVisible("X", showSecondaryColumns);
- SetColumnVisible("Y", showSecondaryColumns);
- SetColumnVisible("Theta", showSecondaryColumns);
- SetColumnVisible("Voltage", showSecondaryColumns);
- SetColumnVisible("Current", showSecondaryColumns);
- SetColumnVisible("CPUUsage", showSecondaryColumns);
- SetColumnVisible("CPUBar", showSecondaryColumns);
- //SetColumnVisible("Speed", showSecondaryColumns);
- SetColumnVisible("Memory", showSecondaryColumns);
- //SetColumnVisible("Address", showSecondaryColumns);
-
- if (toggleSecondaryButton != null)
- {
- toggleSecondaryButton.Text = showSecondaryColumns ? "隐藏更多属性" : "显示更多属性";
- }
- }
-
- private void SetColumnVisible(string name, bool visible)
- {
- if (!vehicleGrid.Columns.Contains(name)) return;
- vehicleGrid.Columns[name].Visible = visible;
- }
-
- private void PositionAlarmLabel()
- {
- if (alarmLabel == null || alarmPanel == null) return;
-
- if (!alarmScrollTimer.Enabled)
- {
- alarmLabel.Left = (alarmPanel.Width - alarmLabel.Width) / 2;
- }
- else
- {
- alarmLabel.Left = alarmPanel.Width;
- }
- alarmLabel.Top = (alarmPanel.Height - alarmLabel.Height) / 2;
- }
-
- private void AddSimulatedAlarm()
- {
- var name = $"NS{simulatedAlarmIndex:000}";
- simulatedAlarmNames.Add(name);
- simulatedAlarmIndex++;
- }
-
- private void ClearSimulatedAlarms()
- {
- simulatedAlarmNames.Clear();
- simulatedAlarmIndex = 1;
- }
-
- private void ExecuteForSelectedCars(Action action, string actionName)
- {
- var cars = GetSelectedCars();
- if (cars.Count == 0)
- {
- MessageBox.Show("请先勾选车辆。");
- return;
- }
-
- foreach (var car in cars)
- {
- try
- {
- action(car);
- }
- catch (Exception ex)
- {
- Diagnosis.Log($"{actionName}失败: {ex.Message}", "VehicleMonitor", true);
- }
- }
- }
-
- private void ExecuteForSingleRow(int rowIndex, Action action, string actionName)
- {
- var car = GetCarFromRow(rowIndex);
- if (car == null)
- {
- MessageBox.Show("未找到对应车辆。");
- return;
- }
-
- try
- {
- action(car);
- }
- catch (Exception ex)
- {
- Diagnosis.Log($"{actionName}失败: {ex.Message}", "VehicleMonitor", true);
- }
- }
-
- private Car GetCarFromRow(int rowIndex)
- {
- var row = vehicleGrid.Rows[rowIndex];
- var idStr = row.Cells["CarId"]?.Value?.ToString();
- if (string.IsNullOrWhiteSpace(idStr)) return null;
-
- if (!int.TryParse(idStr, out var id)) return null;
-
- return SimpleLib.GetAllCars().OfType().FirstOrDefault(c => c.id == id);
- }
-
- private List GetSelectedCars()
- {
- RefreshSelectedCarIdsFromGrid();
- var selected = new HashSet(selectedCarIds);
- return SimpleLib.GetAllCars().OfType()
- .Where(c => selected.Contains(c.id.ToString()))
- .ToList();
- }
-
- private void RefreshSelectedCarIdsFromGrid()
- {
- if (vehicleGrid == null) return;
-
- selectedCarIds.Clear();
- foreach (DataGridViewRow row in vehicleGrid.Rows)
- {
- if (row.IsNewRow) continue;
- var id = row.Cells["CarId"]?.Value?.ToString();
- if (string.IsNullOrWhiteSpace(id)) continue;
-
- var selected = row.Cells["Select"]?.Value is bool flag && flag;
- if (selected)
- {
- selectedCarIds.Add(id);
- }
- }
- }
-
- private void BringCarOnline(Car car)
- {
- if (TryInvokeCarMethod(car, "newReset", new object[] { 0 })) return;
- if (TryInvokeCarMethod(car, "newReset", Array.Empty