diff --git a/Directory.Build.props b/Directory.Build.props new file mode 100644 index 0000000..76682cb --- /dev/null +++ b/Directory.Build.props @@ -0,0 +1,5 @@ + + + D:\MDCS\Dependencies\Commons + + diff --git a/Directory.Build.targets b/Directory.Build.targets new file mode 100644 index 0000000..e895908 --- /dev/null +++ b/Directory.Build.targets @@ -0,0 +1,40 @@ + + + + $(MSBuildThisFileDirectory) + $(StandardSceneRepoRoot)build\plugins + false + true + true + true + true + true + + + + + + + + + + + + + + + + + + diff --git a/Doc/ARCHITECTURE.md b/Doc/ARCHITECTURE.md new file mode 100644 index 0000000..b1226fa --- /dev/null +++ b/Doc/ARCHITECTURE.md @@ -0,0 +1,525 @@ +# StandardScene 代码架构文档 + +> **目标读者**:接手本仓库的工程师、AI Agent、架构审查人员 +> **最后更新**:2026-06-25 +> **配套文档**:`DEVELOPMENT_GUIDE.md`(开发实操)、`QUICK_REFERENCE.md`(速查)、`StandardScene架构重构方案.md`(演进方向) + +--- + +## 1. 项目定位 + +StandardScene 是一套 **AGV/AMR 场内调度与联动控制** 的场景插件库,不是独立可执行程序。 + +| 维度 | 说明 | +| --- | --- | +| 工程类型 | 类库插件(1 基座 + 4 卫星 = 5 个 DLL) | +| 宿主 | `SimpleLite.exe`(CycleGUI 桌面应用) | +| 内核 | `SimpleCore.dll`(Mission、交通、路径编译、导航契约) | +| 目标框架 | `net8.0-windows`(x64) | +| 语言 | C# | +| UI 演进 | WinForms → CycleGUI(`DeliveryViewer` 等已迁移) | + +典型业务能力:搬运任务、环线任务、区域交通管制、充电编排、门禁/按钮盒联动、HTTP / MQTT / Modbus 对外接口。 + +--- + +## 2. 解决方案结构 + +``` +StandardScene.sln +├── StandardScene.Core → 输出 StandardScene.dll(基座,始终加载) +├── StandardScene.Magnetic → 输出 StandardScene.Magnetic.dll(scene.mag) +├── StandardScene.QrLidar → 输出 StandardScene.QrLidar.dll(scene.qrlidar) +├── StandardScene.Devices → 输出 StandardScene.Devices.dll(scene.device) +└── StandardScene.Protocol.VDA5050 → 输出 StandardScene.Protocol.VDA5050.dll(scene.vda5050) +``` + +### 2.1 依赖关系(星型拓扑) + +```mermaid +graph TD + Host["SimpleLite.exe"] + SC["SimpleCore.dll"] + SL["SimpleLite.dll"] + + Core["StandardScene.dll
基座"] + Mag["StandardScene.Magnetic.dll"] + Qr["StandardScene.QrLidar.dll"] + Dev["StandardScene.Devices.dll"] + VDA["StandardScene.Protocol.VDA5050.dll"] + + Host --> Core + Host --> Mag + Host --> Qr + Host --> Dev + Host --> VDA + + Core --> SC + Core --> SL + Mag --> Core + Qr --> Core + Dev --> Core + VDA --> Core +``` + +**规则**: +- 卫星 **只能** 引用基座,禁止循环引用 +- 所有插件 **运行时** 依赖 `SimpleCore` + `SimpleLite` +- 卫星通过 `InternalsVisibleTo` 访问 Core 的 `internal` 字段袋类型,避免大规模 `public` 暴露 + +### 2.2 插件清单对照表 + +| DLL | scene id | 始终加载 | 职责 | +| --- | --- | --- | --- | +| `StandardScene.dll` | — | 是 | 任务调度、交通互锁、充电编排、门禁抽象、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)协议栈与标准车型 | + +--- + +## 3. 运行时架构 + +### 3.1 启动与加载流程 + +```mermaid +sequenceDiagram + participant Host as SimpleLite.exe + participant PM as PluginManager + participant Core as StandardScene.dll + participant Sat as 卫星 DLL + + Host->>PM: 扫描 plugins\ 目录 + PM->>Core: 加载 requiresCore 指向的基座(不可回收) + PM->>PM: 读取 active-scenes.json / CLI --scenes + PM->>Sat: 按场景 id 加载选中的卫星 DLL + Sat->>Core: 引用已加载的基座类型 + PM->>PM: UiTypeDiscovery 反射 [MissionType]/[CarType]/[DoorType] 等 + alt 导航插件 + PM->>Sat: 实例化 NavigationProfileBase → OnActivate + end + Host->>Core: CustomOperationsBeforeLoading.Set()(死锁回调等) +``` + +**场景选择优先级**(高 → 低): +1. CLI 参数 `--scenes` +2. `active-scenes.json` +3. `simple.json` 中的 `scenes` 字段 +4. 加载全部已发现的卫星 + +> **注意**:若 `active-scenes.json` 仅包含 `scene.mag`,则 `scene.device` / `scene.vda5050` 不会加载,门控/充电桩/VDA5050 车型将不可用。生产部署需显式包含所需场景 id。 + +### 3.2 构建与部署 + +`Directory.Build.targets` 在编译后自动将产物复制到 `build\plugins\`: + +- 各插件 DLL + PDB +- 各卫星的 `*.scene.json` +- Devices 插件附带的 `leegKeys-sdk.dll` + +部署步骤: +1. 先构建宿主:`dotnet build Simple\SimpleLite\SimpleLite.csproj` +2. 构建本方案:`dotnet build StandardScene.sln` +3. 将 `build\plugins\` 内容复制到 `SimpleLite.exe` 工作目录的 `plugins\` +4. 配置 `active-scenes.json` 后启动 `SimpleLite.exe` + +### 3.3 业务运行时链路 + +``` +外部系统 (HTTP/MQTT/Modbus) + ↓ +WebApi / 设备驱动 / 车型通信 + ↓ +Mission(后台进程:搬运/环线/充电/门控/心跳…) + ↓ +SimpleLib / TrafficControl / SegmentPlan(SimpleCore) + ↓ +GhostCar / Car 实例(车辆状态机 + 下位机通信) + ↓ +地图站点 / 轨道 / fields & tags 配置 +``` + +--- + +## 4. 核心概念 + +### 4.1 Mission(场景进程) + +Mission 是本系统的 **可启动后台业务单元**,继承自 `SimpleCore.Mission`,由宿主 UI 或 HTTP 反射 API 启停。 + +**生命周期模式**(以 `HeartBeatMission` 为范本): + +| 阶段 | 典型实现 | +| --- | --- | +| 注册 | `[MissionType(Name="...", editor=typeof(...))]` | +| 工厂 | `public static Mission Create()` | +| 启动 | `[MethodMember(Name="启动进程")] public override void Execute()` | +| 后台 | `Thread` / `Task.Run` / `async` + `CancellationTokenSource` | +| 状态 | 更新 `status.status` 或自定义 `MissionStatus` 子类 | +| 停止 | `[MethodMember(Name="关闭进程")] public void Stop()` | + +**Mission 继承树**: + +``` +Mission (SimpleCore) +├── HeartBeatMission, RegionalTrafficControlMission, SecuritySignalMission +├── NodeIsEnableMission +├── DoorMission, ButtonMission +├── AbstractInterlockMission +│ ├── TrafficInterlockMission +│ └── AbstractChargeLogicMission → StandardChargeMission +├── ChainedDeliveryMission → TransportMission(搬运调度) +└── AbstractLoopMission → LoopMission(环线任务) +``` + +### 4.2 CarType(车型) + +车型代表一种 AGV 下位机协议与行为模型,继承自 `SimpleLite.RCS.CarTypes.GhostCar` 或 `Car`。 + +**注册三要素**: + +1. **类型特性**:`[CarType(Name="显示名", editor=typeof(XxxCar))]` +2. **轨迹编码器**:`[ProgramTrackCoderSettings(priority=N, program=typeof(XxxCoder))]` +3. **场景清单**:`scene.json` 的 `provides.carTypes` +(导航插件)`NavigationProfileBase.CarTypes` + +**Coder(轨迹编码器)** 实现 `SimpleCore.Compiler.ITrackCoder`,在路径规划时将站点/轨道上的 `fields` 编译为 Topaz 脚本片段(如 `agv.MagGo`、`agv.BasicGo`)。 + +### 4.3 设备驱动(门 / 充电桩 / 按钮盒) + +设备采用 **抽象在 Core、实现在 Devices** 的模式: + +| 设备 | Core 抽象 | Devices 实现 | 发现机制 | +| --- | --- | --- | --- | +| 门控 | `BasicDoorController` | `ModbusDoorController` | `[DoorType("...")]` + `UiTypeDiscovery` | +| 充电桩 | `AbstractChargeStation` | `FL/PCB/MuXingChargeStation` | 类型全名 + `UiTypeDiscovery` | +| 按钮盒 | `BasicButtonBox` | `Leeg/AzowieButtonBox` | 类名 + `UiTypeDiscovery` | + +### 4.4 Fields & Tags(轻量配置) + +除 JSON 配置文件外,系统大量使用地图上的 **fields**(键值对)和 **tags** 驱动行为,例如: +- 站点 `Region1=1` → 区域流控 +- 轨道 `Magnet=1` → 磁导航段 +- 车辆 `address` → 下位机 IP + +通用读写入口:`Commons.cs` 中的 `GetXxxField` / `AddOrUpdateXxxField` 系列方法。 + +### 4.5 NavigationProfile(导航场景画像) + +仅 **Magnetic** 与 **QrLidar** 两个导航卫星实现 `SimpleCore.Navigation.NavigationProfileBase`: + +```csharp +public sealed class MagneticSceneProfile : NavigationProfileBase +{ + public override NavKind Kind => NavKind.Magnetic; + public override string SceneId => "scene.mag"; + public override IReadOnlyList CarTypes => new[] { typeof(MagCar) }; + public override void OnActivate(ISceneContext context) { ... } +} +``` + +`scene.json` 与 `NavigationProfile` 中的 `CarTypes` **必须保持一致**。 + +--- + +## 5. 模块地图(StandardScene.Core) + +基座约 110+ 源文件,按目录职责划分如下: + +| 目录 | 职责 | 关键类型 | +| --- | --- | --- | +| `Scheduler/` | 辅助后台 Mission(心跳、区域流控、安全信号) | `HeartBeatMission`, `RegionalTrafficControlMission` | +| `Chained/` | 搬运与环线任务主流程 | `ChainedDeliveryMission`, `TransportMission`, `AbstractLoopMission`, `LoopMission` | +| `Chained/Loop/` | 环线规则策略接口 | `IEnterRule`, `IExitRule`, `IJoinRule`, `IBranchRule`, `ITaskStrategy` | +| `Charge/` | 充电策略、站点管理、UDP 通信、配置 UI | `StandardChargeMission`, `AbstractChargeLogicMission` | +| `ChargeStationType/` | 充电桩抽象基类 | `AbstractChargeStation` | +| `InterLock/` | 区域互锁与交通管制 | `AbstractInterlockMission`, `TrafficInterlockMission` | +| `ExtendDevice/Door/` | 门控抽象、管理器、Mission | `BasicDoorController`, `DoorMission`, `DoorManager` | +| `ExtendDevice/ButtonBox/` | 按钮盒抽象与管理 | `BasicButtonBox`, `ButtonMission`, `ButtonBoxManager` | +| `CarTypes/` | 共享字段袋、模拟车 | `BasicFields`, `DummyCar`, `KivaFields` | +| `Coders/` | 导航无关的通用轨迹编码器 | `CommonTrackCoders`, `LidarAreaSwitchCoder` | +| `Model/` | 任务、地图、配置数据模型 | `TaskModel`, `LoopTask`, `Map`, `ChargingSetting` | +| `TCP/` | 异步 TCP 客户端 | `AsyncTcpClient` | +| `Utils/` | JSON、Modbus、WebAPI、CycleGUI 辅助 | `WebAPIHelper`, `CycleUiHelper`, `CarRemoteHelper` | +| `CommonTools/` | 原子文件写入、雪花 ID | `AtomicFileUpdateHelper`, `SnowflakeIdGenerator` | +| 根目录 | 全局工具与 HTTP 入口 | `Commons.cs`, `WebApi.cs`, `Heuristic.cs`, `LadderLogic.cs` | + +### 5.1 卫星插件内容 + +**StandardScene.Magnetic**(5 文件) +- `CarTypes/MagCar.cs` — 磁导航 UDP 车型 +- `Coders/MagneticTrackCoder.cs` — `agv.MagGo` / `agv.NaiveMagGo` +- `MagneticSceneProfile.cs` + `StandardScene.Magnetic.scene.json` + +**StandardScene.QrLidar**(12 文件) +- `CarTypes/` — Forklift, Kiva, ArmCar, DualLiftingCar, MultiVehicleCar, MultiWheelForkLifter, MultiWheelLifterCar +- `Cad/SyncQrMap.cs` — 二维码地图同步 CAD 工具 +- `QrLidarSceneProfile.cs` + `StandardScene.QrLidar.scene.json` + +**StandardScene.Devices**(8 文件) +- `Door/ModbusDoorController.cs` +- `Charge/FLChargeStation.cs`, `PCBChargeStation.cs`, `MuXingChargeStation.cs` +- `ButtonBox/LeegButtonBox.cs`, `AzowieButtonBox.cs` +- `StandardScene.Devices.scene.json` + +**StandardScene.Protocol.VDA5050**(13 文件) +- `VDACar/VDA5050Car.cs` — MQTT VDA5050 标准车 +- `VDACar/MasterMQTTCommunication.cs` — MQTT 通信层 +- `StandardScene.Protocol.VDA5050.scene.json` + +### 5.2 已注册车型一览 + +| 类名 | 所属插件 | 显示名 | +| --- | --- | --- | +| `DummyCar` | Core | 模拟车-包络 | +| `MagCar` | Magnetic | 磁导航车 | +| `Forklift` | QrLidar | 叉车 | +| `MultiWheelForkLifter` | QrLidar | 多舵轮叉车 | +| `DualLiftingCar` | QrLidar | 锂电双举升 | +| `MultiVehicleCar` | QrLidar | 多车联动 AGV | +| `ArmCar` | QrLidar | ArmCar | +| `Kiva` | QrLidar | Kiva | +| `MultiWheelLifterCar` | QrLidar | 多舵轮顶升车 | +| `VDA5050Car` | Protocol.VDA5050 | VDA5050 标准车 | + +### 5.3 已注册 Mission 一览 + +| 类名 | 显示名 | 目录 | +| --- | --- | --- | +| `HeartBeatMission` | 调度心跳进程 | Scheduler | +| `RegionalTrafficControlMission` | 区域流量监控 | Scheduler | +| `SecuritySignalMission` | 安全信号交互 | Scheduler | +| `NodeIsEnableMission` | 锁点上传迷毂 | Scheduler | +| `TransportMission` | 搬运任务进程 | Chained | +| `LoopMission` | 环线进程 | Chained | +| `StandardChargeMission` | 充电进程 | Charge | +| `TrafficInterlockMission` | 交通管制 | InterLock | +| `DoorMission` | 门控进程 | ExtendDevice/Door | +| `ButtonMission` | 按钮进程 | ExtendDevice/ButtonBox | + +--- + +## 6. 扩展点指南 + +接手后最常见的三类扩展: + +### 6.1 新增 Mission + +1. 在 `StandardScene.Core` 合适目录新建类,继承 `Mission`(或现有抽象基类) +2. 添加 `[MissionType(Name="...", editor=typeof(...))]` +3. 实现 `Create()`、`Execute()`、`Stop()` 三件套 +4. **参考**:`Scheduler/HeartBeatMission.cs`(最小)、`Scheduler/RegionalTrafficControlMission.cs`(事件订阅) + +### 6.2 新增车型(导航卫星) + +1. 确定目标卫星(Magnetic / QrLidar / VDA5050) +2. 新建 `CarTypes/XxxCar.cs`,继承 `GhostCar`,标注 `[CarType]` + `[ProgramTrackCoderSettings]` +3. 如需新 Coder,在同插件 `Coders/` 实现 `ITrackCoder` +4. 更新 `XxxSceneProfile.CarTypes` 与 `*.scene.json` 的 `provides.carTypes` +5. 字段袋扩展放在 Core `CarTypes/`(`internal`,卫星通过 `InternalsVisibleTo` 访问) + +### 6.3 新增设备驱动 + +1. 在 `StandardScene.Devices` 实现 Core 抽象(如 `BasicDoorController`) +2. 添加类型特性(如 `[DoorType("MyDoor")]`) +3. 更新 `StandardScene.Devices.scene.json` 的 `provides` 列表 +4. 确保 `active-scenes.json` 包含 `scene.device` + +### 6.4 新增 HTTP 接口 + +入口在 `StandardScene.Core/WebApi.cs`(`ApiController`,Nancy 框架)。常见路由前缀: +- `/car/*` — 车辆与任务 +- `/map/*` — 地图 +- `/task/*` — 任务查询 +- `/mission_reflection/*` — Mission 反射调用 + +> 长期计划是将 WebApi 从 Core 拆出并迁移到 SimpleLite EmbedIO,当前仍以 `WebApi.cs` 为唯一活跃 HTTP 入口。 + +--- + +## 7. 配置文件 + +| 文件 | 位置 | 用途 | +| --- | --- | --- | +| `active-scenes.json` | 宿主 `plugins\` | 选择加载哪些卫星场景 | +| `simple.json` | 宿主工作目录 | 宿主基础配置(含 scenes 备选) | +| `Config/traffic.json` | 运行时 | 交通互锁 / 区域配置 | +| `Config/ChargeStations.json` | 运行时 | 充电桩定义 | +| `Config/ChargeStrategyConfig.json` | 运行时 | 充电策略 | +| `Config/AlarmConfigs.json` | 运行时 | 报警配置 | +| `DoorConfig.json` | 运行时 | 门禁配置 | +| `tasklist.json` | 运行时 | 环线任务配置 | + +--- + +## 8. 外部依赖 + +### 8.1 程序集引用(HintPath,需本机构建) + +| DLL | 用途 | +| --- | --- | +| `SimpleLite.dll` | 宿主框架:RCS、CAD、UI、`UiTypeDiscovery`、特性标注 | +| `SimpleCore.dll` | 内核:Mission、Car、交通、路径编译、导航契约 | +| `CommonUsage.dll` | 数学、VDA5050 消息类型 | +| `Topaz.dll` | Coder 脚本模板引擎 | +| `CycleGUI.dll` | 3D / 立即模式 UI(`Private=false`,由宿主加载) | +| `MDCSToolBox.dll` | 运动学 / 数学(Core、QrLidar) | +| `LessokajiWeaverUtilities.dll` | i18n、诊断工具 | +| `leegKeys-sdk.dll` | Leeg 按钮盒 SDK | + +### 8.2 NuGet(Core 最重) + +`Nancy`, `MQTTnet`, `EasyModbusTCP`, `IoTClient`, `Jint`, `DocumentFormat.OpenXml`, `Newtonsoft.Json` + +Protocol.VDA5050 额外使用 `MQTTnet`;Magnetic / QrLidar 仅 `Newtonsoft.Json`。 + +--- + +## 9. 关键设计决策与现状问题 + +### 9.1 当前设计的合理之处 + +- **星型插件拓扑**:基座承载全部 Mission 与设备抽象,卫星按场景热插拔 +- **`scene.json` 清单**:声明式描述插件能力,宿主无需硬编码 +- **`NavigationProfileBase`**:导航平台与车型注册的标准契约 +- **`Chained/Loop/` 规则接口**:环线策略可插拔(`IEnterRule` 等) +- **`InternalsVisibleTo` 拆分策略**:在不破坏封装的前提下外移车型代码 + +### 9.2 已知架构债务(详见 `StandardScene架构重构方案.md`) + +| 编号 | 问题 | 影响 | +| --- | --- | --- | +| S1 | Core 背负 MQTT/Modbus/Nancy/OpenXml 等协议依赖 | 基座臃肿,卫星无法独立瘦身 | +| S2 | WinForms 与 CycleGUI 并存 | 阻塞纯 `net8.0` 跨平台 | +| S3 | 多个 600~2700 行 God-class(WebApi、AbstractLoopMission 等) | 改动风险高、难测试 | +| S8 | Coder 注册表限定内核程序集反射 | 导航 Coder 热插拔受限 | +| S9 | csproj 绝对路径 HintPath | CI / 换机构建需手动适配 | + +### 9.3 演进方向(摘要) + +1. 抽离 `StandardScene.Abstractions`(纯契约 + 模型) +2. 充电子系统独立为卫星 `StandardScene.Charge` +3. WebApi 按资源拆模块并迁出 Core +4. UI 全部迁至 CycleGUI 或独立 UI 程序集 +5. 目标 TFM 从 `net8.0-windows` 过渡到 `net8.0`(基础设施层) + +--- + +## 10. AI / 新工程师接手清单 + +### 10.1 第一天:建立全局认知 + +1. 阅读 `README.md` → 本文档 → `DEVELOPMENT_GUIDE.md` +2. 打开 `DocumentHub.html` 浏览模块导航 +3. 本地构建:先 SimpleLite,再 StandardScene,部署到 `plugins\` +4. 启动宿主,确认 `active-scenes.json` 包含所需场景 + +### 10.2 第二天:跟踪一条完整链路 + +**搬运任务链路**(推荐): +``` +WebApi /car/createTask + → TransportMission(ChainedDeliveryMission.LoopAsync) + → Commons.NearestTask / 选车逻辑 + → GhostCar 下发路径(SegmentPlan + Coder 脚本) + → TrafficControl 互锁 + → 下位机 HTTP/MQTT/UDP 通信 +``` + +**充电链路**: +``` +StandardChargeMission + → AbstractChargeLogicMission(电量策略) + → AbstractInterlockMission(站点互锁) + → AbstractChargeStation 实例(Devices 插件驱动) +``` + +### 10.3 按任务类型定位文件 + +| 我要做… | 先看 | +| --- | --- | +| 最小 Mission 样板 | `Scheduler/HeartBeatMission.cs` | +| 搬运调度 | `Chained/ChainedDeliveryMission.cs`, `Chained/TransportMission.cs` | +| 环线任务 | `Chained/AbstractLoopMission.cs`, `Chained/LoopMission.cs` | +| 区域流控 | `Scheduler/RegionalTrafficControlMission.cs` | +| 充电 | `Charge/StandardChargeMission.cs`, `Charge/AbstractChargeLogicMission.cs` | +| 门控 | `ExtendDevice/Door/DoorMission.cs`, `Devices/Door/ModbusDoorController.cs` | +| HTTP API | `WebApi.cs` | +| 新车型 | 对应卫星 `CarTypes/` + `Coders/` + `*SceneProfile.cs` | +| 字段/选车/路径辅助 | `Commons.cs` | +| 地图与任务模型 | `Model/` | + +### 10.4 修改前的安全检查 + +- [ ] 确认目标变更属于 Core 还是卫星(避免在 Core 放平台专有逻辑) +- [ ] 若改车型,同步 `scene.json` + `NavigationProfile.CarTypes` +- [ ] 若改设备驱动,确认 `scene.device` 在 `active-scenes.json` 中 +- [ ] 若改 Coder,注意 `priority` 与字段袋哨兵值(`-1` 语义) +- [ ] 编译后检查 `build\plugins\` 产物是否完整 + +--- + +## 11. 文档索引 + +| 文档 | 用途 | +| --- | --- | +| **本文档 `ARCHITECTURE.md`** | 整体架构、模块地图、扩展点、接手清单 | +| `DEVELOPMENT_GUIDE.md` | 开发环境、案例教程、调试建议 | +| `QUICK_REFERENCE.md` | 高频入口与配置速查 | +| `StandardScene架构重构方案.md` | 架构演进与分层目标 | +| `StandardScene拆分计划.md` | 程序集拆分进度 | +| `StandardScene代码审查报告.md` | 质量缺陷与修复记录 | +| `StandardScene.Core/Docs/` | 充电、门控、Coder 专题手册 | + +--- + +## 附录 A:scene.json 完整示例 + +**导航插件(scene.mag)**: +```json +{ + "id": "scene.mag", + "displayName": "磁导航平台", + "navKind": "magnetic", + "assembly": "StandardScene.Magnetic.dll", + "coreVersion": ">=1.0.0", + "requiresCore": "StandardScene.dll", + "provides": { + "carTypes": ["MagCar"], + "missionTypes": [] + } +} +``` + +**设备插件(scene.device)**: +```json +{ + "id": "scene.device", + "displayName": "设备驱动(门 / 充电桩 / 按钮盒)", + "assembly": "StandardScene.Devices.dll", + "coreVersion": ">=1.0.0", + "requiresCore": "StandardScene.dll", + "provides": { + "doorControllers": ["ModbusDoorController"], + "chargeStations": ["FLChargeStation", "PCBChargeStation", "MuXingChargeStation"], + "buttonBoxes": ["LeegButtonBox", "AzowieButtonBox"] + } +} +``` + +## 附录 B:插件引导钩子 + +基座在宿主加载早期通过反射调用: + +```csharp +// StandardScene.Core/Commons.cs +public class CustomOperationsBeforeLoading +{ + public static void Set() + { + TrafficControl.OnDeadLock = (loopingCar) => { /* 死锁告警 */ }; + } +} +``` + +这是少数几个 **无 Mission 启动即可生效** 的全局初始化入口之一。 diff --git a/DEFENSE_QUESTIONS.md b/Doc/DEFENSE_QUESTIONS.md similarity index 100% rename from DEFENSE_QUESTIONS.md rename to Doc/DEFENSE_QUESTIONS.md diff --git a/DEFENSE_REFERENCE.md b/Doc/DEFENSE_REFERENCE.md similarity index 100% rename from DEFENSE_REFERENCE.md rename to Doc/DEFENSE_REFERENCE.md diff --git a/DEVELOPMENT_GUIDE.md b/Doc/DEVELOPMENT_GUIDE.md similarity index 73% rename from DEVELOPMENT_GUIDE.md rename to Doc/DEVELOPMENT_GUIDE.md index 33b7177..7a1565b 100644 --- a/DEVELOPMENT_GUIDE.md +++ b/Doc/DEVELOPMENT_GUIDE.md @@ -2,7 +2,7 @@ ## 1. 项目定位 -`StandardScene` 是一个由 `SimpleComposer.exe` 宿主加载的场景插件库,输出为 `StandardScene.dll`,不是独立 EXE。仓库主要面向 AGV/AMR 场内调度与联动控制,覆盖: +`StandardScene` 是一个由 `SimpleLite.exe`(CycleGUI 应用)宿主加载的场景插件库,已拆分为「基座 + 4 个卫星」共 5 个插件 DLL(基座输出 `StandardScene.dll`),不是独立 EXE。仓库主要面向 AGV/AMR 场内调度与联动控制,覆盖: - 搬运任务与环线任务 - 区域流控与交通互锁 @@ -15,27 +15,28 @@ | 项目项 | 说明 | | --- | --- | | 语言 | `C#` | -| 框架 | `.NET Framework 4.8` | -| 工程类型 | `Library` | -| 宿主 | `SimpleComposer.exe` | +| 框架 | `net8.0-windows` | +| 工程类型 | `Library`(基座 + 4 卫星,共 5 个插件 DLL) | +| 宿主 | `SimpleLite.exe`(CycleGUI 应用) | +| 界面技术 | 由 WinForms 迁移到 CycleGUI(宿主同栈);`DeliveryViewer` 已迁移 | | 关键入口 | `MissionType`、`CarType`、`WebApi.cs` | ### 本机依赖 -工程文件里可见以下固定依赖路径: +各 `.csproj` 的 `HintPath` 指向以下依赖(宿主产物需先构建 Simple 解决方案): -- `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\CommonUsage.dll`、`MDCSToolBox.dll`、`CycleGUI.dll` +- `..\Simple\SimpleLite\bin\Debug\SimpleLite.dll`、`LessokajiWeaverUtilities.dll` +- `..\Simple\SimpleCore\bin\Debug\netstandard2.0\SimpleCore.dll` ### 构建与运行 -1. 打开 `StandardScene.sln` -2. 编译 `Debug|Any CPU` 或 `Release|Any CPU` -3. 编译后,构建事件会把 `StandardScene.dll` 复制到 `build\plugins` -4. 运行 `build\SimpleComposer.exe` +> 必须先构建宿主依赖,否则会出现 `SimpleCore` 版本不匹配等编译错误。 + +1. 先构建宿主:`dotnet build Simple\SimpleLite\SimpleLite.csproj`(会一并构建 `SimpleCore` 项目) +2. 打开 `StandardScene.sln`,编译 `Debug|x64` 或 `Release|x64` +3. 编译后 `Directory.Build.targets` 会把 5 个插件 DLL(+ PDB + `*.scene.json`)复制到 `build\plugins` +4. 将 `build\plugins` 部署到 `SimpleLite.exe` 工作目录下的 `plugins\`,运行 `SimpleLite.exe` ## 3. 目录结构 @@ -78,7 +79,7 @@ 主业务调度的核心区域。 -- `AbstractChainedDeliveryMission.cs`:搬运任务总控 +- `ChainedDeliveryMission.cs`:搬运任务总控(旧版 `AbstractChainedDeliveryMission.cs` 已删除) - `TransportMission.cs`:常规运输 Mission - `AbstractLoopMission.cs`:环线任务骨架 - `LoopMission.cs`:环线业务实例 @@ -136,7 +137,7 @@ ```csharp using System.Threading; using Newtonsoft.Json; -using SimpleComposer.RCS; +using SimpleLite.RCS; using SimpleCore; namespace StandardScene.Scheduler @@ -183,16 +184,12 @@ namespace StandardScene.Scheduler #### 关键提醒 -当前工程是旧式 `.csproj`,新增 `.cs` 文件后必须确认文件已加入工程;否则文件存在但不会参与编译。必要时手工补: - -```xml - -``` +当前工程已是 SDK 风格 `.csproj`(`net8.0-windows`),目录下的 `.cs` 文件会被自动包含,无需再手工添加 ``。新增任务/车型/驱动后,记得补上对应特性(`[MissionType]`、`[CarType]`、`[DoorType]` 等)与静态 `Create()`,否则宿主反射不到。 #### 验证方式 -1. 编译解决方案 -2. 打开 `build\SimpleComposer.exe` +1. 编译解决方案并把插件部署到宿主 `plugins\` +2. 运行 `SimpleLite.exe` 3. 启动 `Hello Mission` 4. 观察 `status.status` 是否变成 `tick:1`、`tick:2` @@ -241,7 +238,7 @@ Region1 = 1 推荐调试方式: -- 以 `build\SimpleComposer.exe` 作为外部程序启动调试 +- 以 `SimpleLite.exe` 作为外部程序启动调试 - 或先运行宿主,再附加进程 ## 10. 常见坑 @@ -252,8 +249,8 @@ Region1 = 1 - 是否加了 `MissionType` - 是否有静态 `Create()` -- 是否复制到了 `build\plugins` -- 是否已经加入 `.csproj` +- 是否复制到了 `build\plugins` 并部署到宿主 `plugins\` +- 卫星插件是否已在 `active-scenes.json` 中启用对应场景 ### 区域流控不生效 diff --git a/INDEX.md b/Doc/INDEX.md similarity index 84% rename from INDEX.md rename to Doc/INDEX.md index ab1b464..f07c164 100644 --- a/INDEX.md +++ b/Doc/INDEX.md @@ -6,6 +6,7 @@ | --- | --- | --- | | `DocumentHub.html` | 所有人 | 单文件浏览入口,双击直接打开 | | `README.md` | 第一次接触仓库的人 | 快速了解项目定位与运行方式 | +| `ARCHITECTURE.md` | 工程师 / AI Agent 接手 | **整体代码架构**、模块地图、扩展点、接手清单 | | `DEVELOPMENT_GUIDE.md` | 要开始开发的人 | 系统化理解架构、模块、配置、案例 | | `QUICK_REFERENCE.md` | 正在写代码的人 | 快速查入口、路径、配置、排错点 | @@ -15,9 +16,10 @@ 1. 打开 `DocumentHub.html` 2. 阅读 `README.md` -3. 阅读 `DEVELOPMENT_GUIDE.md` -4. 打开 `Scheduler\HeartBeatMission.cs` -5. 动手做 HelloMission 案例 +3. 阅读 `ARCHITECTURE.md`(建立架构全局观) +4. 阅读 `DEVELOPMENT_GUIDE.md` +5. 打开 `Scheduler\HeartBeatMission.cs` +6. 动手做 HelloMission 案例 ### 路线 B:已经会跑工程,准备开发 @@ -53,6 +55,6 @@ ## 5. 建议的第一个练习 1. 按 `DEVELOPMENT_GUIDE.md` 的案例 A 新建 `HelloMission` -2. 编译后打开 `build\SimpleComposer.exe` +2. 编译后部署到宿主 `plugins\`,运行 `SimpleLite.exe` 3. 启动 Mission,确认状态每秒递增 4. 再按案例 B 给站点添加 `Region1=1`,体验区域流控 \ No newline at end of file diff --git a/LooMission.md b/Doc/LooMission.md similarity index 100% rename from LooMission.md rename to Doc/LooMission.md diff --git a/QUICK_REFERENCE.md b/Doc/QUICK_REFERENCE.md similarity index 84% rename from QUICK_REFERENCE.md rename to Doc/QUICK_REFERENCE.md index 724c87a..e1abbe2 100644 --- a/QUICK_REFERENCE.md +++ b/Doc/QUICK_REFERENCE.md @@ -4,9 +4,9 @@ | 项目项 | 说明 | | --- | --- | -| 工程类型 | `StandardScene.dll` 插件库 | -| 运行方式 | 由 `build\SimpleComposer.exe` 加载 | -| 目标框架 | `.NET Framework 4.8` | +| 工程类型 | 插件库(基座 `StandardScene.dll` + 4 卫星,共 5 个 DLL) | +| 运行方式 | 由 `SimpleLite.exe`(CycleGUI 宿主)从 `plugins\` 加载 | +| 目标框架 | `net8.0-windows` | | 核心入口 | `MissionType`、`CarType`、`WebApi.cs` | | 新手起步文件 | `Scheduler\HeartBeatMission.cs` | | 进阶起步文件 | `Scheduler\RegionalTrafficControlMission.cs` | @@ -27,19 +27,15 @@ ## 3. 构建运行速记 -1. 打开 `StandardScene.sln` -2. 确保本机依赖路径存在 -3. 编译解决方案 -4. 打开 `build\SimpleComposer.exe` -5. 确认插件已从 `build\plugins` 加载 +1. 先构建宿主依赖:`dotnet build Simple\SimpleLite\SimpleLite.csproj` +2. 打开 `StandardScene.sln`,确保本机依赖路径存在 +3. 编译解决方案(`Debug|x64` / `Release|x64`) +4. 将 `build\plugins` 部署到宿主 `plugins\`,运行 `SimpleLite.exe` +5. 确认插件已从 `plugins\` 加载 ### 注意 -当前工程是旧式 `.csproj`。新增 `.cs` 文件后,如果没有通过 VS 正确加入工程,可能需要手工补: - -```xml - -``` +当前工程已是 SDK 风格 `.csproj`(`net8.0-windows`),目录下 `.cs` 文件会被自动包含,无需手工添加 ``。卫星插件需在 `active-scenes.json` 中启用对应场景才会被宿主加载。 ## 4. 新增功能时先看谁 diff --git a/StandardScene拆分计划.md b/Doc/StandardScene拆分计划.md similarity index 100% rename from StandardScene拆分计划.md rename to Doc/StandardScene拆分计划.md diff --git a/DocumentHub.html b/DocumentHub.html index 137ffce..98f60b1 100644 --- a/DocumentHub.html +++ b/DocumentHub.html @@ -480,8 +480,8 @@
-
.NET 4.8
-
工程目标框架
+
.NET 8
+
工程目标框架 (net8.0-windows)
Plugin DLL
@@ -527,7 +527,7 @@

01. 项目总览

- `StandardScene` 是 AGV/AMR 场景插件库,不是独立 EXE。它依赖宿主 `SimpleComposer.exe` 运行,能力覆盖搬运调度、环线任务、 + `StandardScene` 是 AGV/AMR 场景插件库,不是独立 EXE。它依赖宿主 `SimpleLite.exe`(CycleGUI 应用)运行,能力覆盖搬运调度、环线任务、 交通互锁、区域流量控制、充电协同、门控联动,以及 HTTP / MQTT / Modbus 等外围接口。

@@ -546,7 +546,7 @@
输出类型:Library - 宿主:SimpleComposer.exe + 宿主:SimpleLite.exe 外部接口:Nancy / HTTP 典型协议:MQTT / Modbus
@@ -566,7 +566,7 @@ 宿主层 启动程序、装载插件、展示配置与 Mission - build/SimpleComposer.exe + SimpleLite.exe 场景逻辑层 @@ -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

构建步骤

    +
  1. 先构建宿主依赖:dotnet build Simple\SimpleLite\SimpleLite.csproj(一并构建 SimpleCore)
  2. 打开 StandardScene.sln
  3. -
  4. 编译 Debug|Any CPURelease|Any CPU
  5. -
  6. 确认构建事件已将插件复制到 build/plugins
  7. -
  8. 运行 build/SimpleComposer.exe
  9. +
  10. 编译 Debug|x64Release|x64
  11. +
  12. 确认 Directory.Build.targets 已将 5 个插件 DLL 复制到 build/plugins
  13. +
  14. 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
     }
 }
    -
  1. 新建 Scheduler/HelloMission.cs
  2. -
  3. 确认它已被加入工程
  4. -
  5. 编译并打开 build/SimpleComposer.exe
  6. +
  7. 新建 Scheduler/HelloMission.cs(SDK 工程自动包含)
  8. +
  9. 补上 [MissionType] 特性与静态 Create()
  10. +
  11. 编译后部署到宿主 plugins/,运行 SimpleLite.exe
  12. 启动后观察 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())) return; - } - - private void BringCarOffline(Car car) - { - TryInvokeCarMethod(car, "ForceStop", Array.Empty()); - } - - private void ReturnCarToFactory(Car car) - { - TryInvokeCarMethod(car, "Blown", Array.Empty()); - } - - private bool TryInvokeCarMethod(Car car, string methodName, object[] args) - { - try - { - var method = car.GetType().GetMethod(methodName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); - if (method == null) return false; - - var parameters = method.GetParameters(); - if (args.Length == parameters.Length) - { - method.Invoke(car, args); - return true; - } - - if (args.Length == 0 && parameters.Length > 0 && parameters.All(p => p.IsOptional)) - { - var optionalArgs = parameters.Select(p => Type.Missing).ToArray(); - method.Invoke(car, optionalArgs); - return true; - } - } - catch (Exception ex) - { - Diagnosis.Log($"调用{methodName}失败: {ex.Message}", "VehicleMonitor", true); - } - - return false; - } - - private void LaunchRemoteDesktop(string ip) - { - Process.Start( - new ProcessStartInfo - { - FileName = "mstsc", - Arguments = $"/v:{ip}", - UseShellExecute = false, - CreateNoWindow = true - } - ); - } - } -} - diff --git a/StandardScene.Core/Chained/AbstractLoopMission.cs b/StandardScene.Core/Chained/AbstractLoopMission.cs index 619f7e9..2c9f6e6 100644 --- a/StandardScene.Core/Chained/AbstractLoopMission.cs +++ b/StandardScene.Core/Chained/AbstractLoopMission.cs @@ -15,7 +15,6 @@ using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; -using System.Windows.Forms; namespace StandardScene.Chained { @@ -1510,7 +1509,7 @@ namespace StandardScene.Chained public JsonFileTaskStrategy(string jsonPath = null) { JsonPath = string.IsNullOrWhiteSpace(jsonPath) - ? Path.Combine(Application.StartupPath, "tasklist.json") + ? Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "tasklist.json") : jsonPath; EnsureWatcher(); diff --git a/StandardScene.Core/Chained/DeliveryViewer.cs b/StandardScene.Core/Chained/DeliveryViewer.cs index baa34dd..35edc11 100644 --- a/StandardScene.Core/Chained/DeliveryViewer.cs +++ b/StandardScene.Core/Chained/DeliveryViewer.cs @@ -1,13 +1,10 @@ using System; using System.Collections.Generic; -using System.ComponentModel; -using System.Data; using System.Drawing; using System.Linq; using System.Net.Http; -using System.Text; using System.Threading.Tasks; -using System.Windows.Forms; +using CycleGUI; using StandardScene.Model; using SimpleLite; using SimpleCore; @@ -17,35 +14,162 @@ using static StandardScene.Chained.ChainedDeliveryMission; namespace StandardScene.Chained { - public partial class DeliveryViewer : Form + /// + /// 搬运任务管理界面(CycleGUI 版,替代原 WinForms DeliveryViewer 窗体)。 + /// + /// 单实例:再次打开则把已有面板置前。 + /// 约每 1s 节流刷新任务快照(在渲染线程内节流,避免并发),面板 500ms 准实时重绘。 + /// 每行提供「取消 / 重发 / 换车重发」按钮(带二次确认),超时任务整行高亮。 + /// + /// 保留可实例化 + 以兼容既有调用 new DeliveryViewer().Show()。 + /// + public class DeliveryViewer { - private const int OverdueMinutesThreshold = 10000; // 约7天视为超时 - private const int DisplayColumnIndexOverdueFlag = 9; + private const int OverdueMinutesThreshold = 10000; // 约 7 天视为超时 + private const string TableId = "delivery-task-list"; + private static readonly HttpClient SharedHttpClient = new HttpClient(); - /// 选中行的背景色 - private static readonly Color SelectedRowBackColor = Color.FromArgb(220, 230, 250); - /// 缓存选中行索引,避免在 RetrieveVirtualItem 中访问 SelectedIndices 引发递归 - private readonly HashSet _selectedIndicesCache = new HashSet(); + /// 超时任务整行底色(深色主题下的暗红,醒目但不刺眼)。 + private static readonly Color OverdueRowColor = Color.FromArgb(255, 90, 36, 36); - private ListViewItem _item = null; + private static Panel _panel; + private static bool _showFinished = true; // 显示已完成任务 + private static bool _showAbolished = true; // 显示废止任务(Error / Canceled / Terminated) - public DeliveryViewer() + // 渲染快照:由后台线程按 FlushInterval 刷新,渲染线程只读引用;锁/文件 IO 绝不放在渲染线程,避免界面卡死。 + private static volatile List _snapshot = new List(); + private static volatile bool _refreshing; + private static DateTime _lastFlush = DateTime.MinValue; + private static readonly TimeSpan FlushInterval = TimeSpan.FromSeconds(1); + private static volatile string _status = ""; + + /// 打开(或置前)任务管理面板。兼容原 new DeliveryViewer().Show() 调用方式。 + public void Show() => Open(); + + /// 打开(或置前)任务管理面板。 + public static void Open() { - InitializeComponent(); + if (_panel != null) + { + try + { + _panel.BringToFront(); + return; + } + catch + { + _panel = null; + } + } + + var panel = GUI.DeclarePanel() + .ShowTitle("任务列表") + .SetDefaultDocking(Panel.Docking.None) + .InitSize(1500, 620) // 列宽按内容自适应(SizingFixedFit),给足初始宽度避免 10 列横向拥挤 + .InitPos(false, 0, 0, 0.5f, 0.5f, 0.5f, 0.5f); + _panel = panel; + panel.IfTerminalQuit(() => _panel = null); + + panel.Define(pb => + { + if (pb.Closing()) + { + panel.Exit(); + _panel = null; + return; + } + + // 过滤开关:改变时强制立即刷新一次(不必等节流窗口)。 + if (pb.CheckBox("显示已完成任务", ref _showFinished)) _lastFlush = DateTime.MinValue; + pb.SameLine(16); + if (pb.CheckBox("显示废止的任务(Error / Canceled / Terminated)", ref _showAbolished)) _lastFlush = DateTime.MinValue; + + EnsureSnapshotFresh(); + + var items = _snapshot; + pb.Label($"共 {items.Count} 个任务(超时任务高亮置顶)"); + + pb.Table(TableId, + new[] { "任务号", "小车", "取货点", "放货点", "任务状态", "下发时间", "执行时间", "结束时间", "优先级", "操作" }, + items.Count, (row, i) => + { + var dd = items[i]; + if (IsOverdue(dd)) row.SetColor(OverdueRowColor); + + row.Label($"{dd.Id}"); + row.Label(dd.UsingCar?.name ?? ""); + row.Label($"{dd.Src}-{SafeSiteName(dd.Src)}"); + row.Label($"{dd.Dst}-{SafeSiteName(dd.Dst)}"); + row.Label($"{dd.GetStatus()}"); + row.Label($"{dd.CreateTime:yyyy-MM-dd HH:mm:ss}"); + row.Label($"{dd.StartTime:yyyy-MM-dd HH:mm:ss}"); + row.Label($"{dd.FinishTime:yyyy-MM-dd HH:mm:ss}"); + row.Label($"{dd.Priority}"); + + var op = row.ButtonGroup( + new[] { "取消", "重发", "换车" }, + new[] { "取消任务", "重发任务", "换车重发任务" }); + var taskCode = dd.Id; + // 业务操作含文件 IO 与锁竞争,统一用 Task.Run 放后台执行,绝不阻塞渲染线程(否则界面卡死)。 + if (op == 0) CycleUiHelper.ConfirmThen($"是否结束任务 {taskCode}?", () => Task.Run(() => CancelDelivery(taskCode))); + else if (op == 1) CycleUiHelper.ConfirmThen($"是否重发任务 {taskCode}?", () => Task.Run(() => ResendDelivery(taskCode))); + else if (op == 2) CycleUiHelper.ConfirmThen($"是否换车重发任务 {taskCode}?", () => Task.Run(() => ChangeCarResendDelivery(taskCode))); + }, height: 18, enableSearch: true); + + if (!string.IsNullOrEmpty(_status)) + { + pb.Separator(); + pb.Label(_status); + } + + // 节流重绘:任务监控无需高帧率,约 500ms 刷新一次即可保持准实时,显著降低 CPU。 + pb.Panel.Repaint(repaintTimeMs: 500); + }); } - private readonly ContextMenuStrip strip = new ContextMenuStrip(); + private static bool IsOverdue(Delivery dd) => + (DateTime.Now - dd.CreateTime).TotalMinutes > OverdueMinutesThreshold; - private void DeliveryViewer_Load(object sender, EventArgs e) + /// + /// 渲染线程调用:到达刷新间隔且无在途刷新时,在后台线程重新拉取任务快照(超时任务置顶)。 + /// 业务侧的锁与文件 IO 一律放到后台,渲染线程只读 引用,避免界面卡死。 + /// + private static void EnsureSnapshotFresh() { - strip.Items.Clear(); - strip.Items.Add("取消任务", null, CancelClick); - strip.Items.Add("重发任务", null, ResendClick); - strip.Items.Add("换车重发任务", null, ChangeCarResendClick); - currentTaskList.ContextMenuStrip = strip; + if (_refreshing) return; + if (DateTime.Now - _lastFlush < FlushInterval) return; + _lastFlush = DateTime.Now; + _refreshing = true; + + // 捕获当前过滤条件,避免后台读取过程中被 UI 改动。 + bool showFinished = _showFinished, showAbolished = _showAbolished; + Task.Run(() => + { + try + { + var list = new List(); + foreach (var cdm in SimpleProject.proj.Missions.OfType()) + list.AddRange(cdm.GetDeliveries(showFinished, showAbolished, showAbolished, showAbolished)); + + // 与原窗体一致:超时任务排在最前。 + _snapshot = list.OrderByDescending(d => IsOverdue(d) ? 1 : 0).ToList(); + } + catch (Exception ex) + { + Diagnosis.Post($"DeliveryViewer 刷新异常: {ExceptionFormatter.FormatEx(ex)}"); + } + finally + { + _refreshing = false; + } + }); } - private List _listDeliveries = new List(); + private static string SafeSiteName(int siteId) + { + try { return SimpleLib.GetSite(siteId)?.name ?? ""; } + catch { return ""; } + } /// 将任务标记为已取消(Canceled)。 private static void MarkDeliveryCanceled(Delivery d) @@ -93,93 +217,8 @@ namespace StandardScene.Chained } } - protected virtual string[] GetDisplayContent(Delivery dd) + private static void ResendDelivery(string taskCode) { - var srcName =SimpleLib.GetSite(dd.Src).name; - var dstName =SimpleLib.GetSite(dd.Dst).name; - var now = DateTime.Now; - var usingCar = dd.UsingCar == null ? string.Empty : dd.UsingCar.name; - return - [ - $"{dd.Id}", - $"{usingCar}", - $"{dd.Src}-{srcName}", - $"{dd.Dst}-{dstName}", - $"{dd.GetStatus()}", - $"{dd.CreateTime:yyyy-mm-dd HH:mm:ss:fff}", - $"{dd.StartTime:yyyy-mm-dd HH:mm:ss:fff}", - $"{dd.FinishTime:yyyy-mm-dd HH:mm:ss:fff}", - - $"{dd.Priority}", - $"{((now - dd.CreateTime).TotalMinutes > OverdueMinutesThreshold ? 1 : 0)}", - $"{dd.Id}" - ]; - } - - private void TaskFlush() - { - _listDeliveries.Clear(); - try - { - foreach (var cdm in SimpleProject.proj.Missions.OfType()) - foreach (var dd in cdm.GetDeliveries(checkBox1.Checked, checkBox2.Checked,checkBox2.Checked,checkBox2.Checked)) - _listDeliveries.Add(GetDisplayContent(dd)); - - if (_listDeliveries.Count > 0) - { - var len = _listDeliveries[0].Length; - if (len > 0) _listDeliveries = _listDeliveries.OrderByDescending(p => int.Parse(p[len - 2])).ToList(); - } - } - catch (Exception ex) - { - Diagnosis.Post($"TaskFlush 异常: {ExceptionFormatter.FormatEx(ex)}"); - } - - } - - private void timer1_Tick(object sender, EventArgs e) - { - try - { - TaskFlush(); - currentTaskList.VirtualListSize = _listDeliveries.Count; - currentTaskList.Invalidate(); - } - catch (Exception ex) - { - Diagnosis.Post($"timer1_Tick 异常: {ExceptionFormatter.FormatEx(ex)}"); - } - - } - - private void currentTaskList_RetrieveVirtualItem(object sender, RetrieveVirtualItemEventArgs e) - { - try - { - var n = e.ItemIndex; - e.Item = new ListViewItem(_listDeliveries[n]); - if (_listDeliveries[n].Length > DisplayColumnIndexOverdueFlag && _listDeliveries[n][DisplayColumnIndexOverdueFlag] == "1") - e.Item.ForeColor = Color.Red; - if (_selectedIndicesCache.Contains(n)) - e.Item.BackColor = SelectedRowBackColor; - } - catch (Exception) - { - e.Item = new ListViewItem(["", "", "", "", "", "", "", "", ""]); - } - } - - private void currentTaskList_MouseClick(object sender, MouseEventArgs e) - { - if (e.Button != MouseButtons.Right) return; - _item = currentTaskList.GetItemAt(e.X, e.Y); - } - - private void ResendClick(object sender, EventArgs e) - { - if (_item == null) return; - string taskCode = _item.Text; try { var cdm = SimpleProject.proj.Missions.OfType().FirstOrDefault(); @@ -189,44 +228,40 @@ namespace StandardScene.Chained .FirstOrDefault(s => s.Id == taskCode); if (d == null) { - MessageBox.Show("列表中不存在目标任务", "提示", MessageBoxButtons.OK, MessageBoxIcon.Warning); + _status = $"重发失败:列表中不存在任务 {taskCode}"; return; } - var ms = MessageBox.Show($"是否重发任务--{taskCode}", "提示", MessageBoxButtons.OKCancel, MessageBoxIcon.Question); - if (ms != System.Windows.Forms.DialogResult.OK) return; if (!MarkDeliveryWaiting(d, clearCarForChange: false)) { - MessageBox.Show("重发任务失败:当前状态不允许重发", "提示", MessageBoxButtons.OK, MessageBoxIcon.Warning); + _status = "重发任务失败:当前状态不允许重发"; return; } - // 状态已改为 Waiting,持久化 cdm.PersistDelivery(d); + _status = $"已重发任务 {taskCode}"; } - catch (Exception) + catch (Exception ex) { - MessageBox.Show("列表中不存在目标任务", "提示", MessageBoxButtons.OK, MessageBoxIcon.Warning); + _status = $"重发任务 {taskCode} 异常,详见日志"; + Diagnosis.Post($"重发任务 {taskCode} 异常: {ExceptionFormatter.FormatEx(ex)}"); } } - private void CancelClick(object sender, EventArgs e) + private static void CancelDelivery(string taskCode) { - if (_item == null) return; - string str = _item.Text; try { var cdm = SimpleProject.proj.Missions.OfType().FirstOrDefault(); if (cdm == null) return; var d = cdm.GetDeliveries(true, true, true, true) .OfType() - .FirstOrDefault(s => s.Id == str); + .FirstOrDefault(s => s.Id == taskCode); if (d == null) { - MessageBox.Show("列表中不存在目标任务", "提示", MessageBoxButtons.OK, MessageBoxIcon.Warning); + _status = $"取消失败:列表中不存在任务 {taskCode}"; return; } - var ms = MessageBox.Show($"是否结束任务--{str}", "提示", MessageBoxButtons.OKCancel, MessageBoxIcon.Question); - if (ms != System.Windows.Forms.DialogResult.OK || d.IsFinished()) return; + if (d.IsFinished()) return; // 1) 状态上将任务标记为已取消 MarkDeliveryCanceled(d); @@ -242,17 +277,17 @@ namespace StandardScene.Chained // 3) 持久化已取消状态 cdm.PersistDelivery(d); + _status = $"已结束任务 {taskCode}"; } catch (Exception ex) { - Diagnosis.Post($"结束任务 {str} 异常: {ExceptionFormatter.FormatEx(ex)}"); + _status = $"结束任务 {taskCode} 异常,详见日志"; + Diagnosis.Post($"结束任务 {taskCode} 异常: {ExceptionFormatter.FormatEx(ex)}"); } } - private void ChangeCarResendClick(object sender, EventArgs e) + private static void ChangeCarResendDelivery(string taskCode) { - if (_item == null) return; - string taskCode = _item.Text; try { var cdm = SimpleProject.proj.Missions.OfType().FirstOrDefault(); @@ -262,55 +297,24 @@ namespace StandardScene.Chained .FirstOrDefault(s => s.Id == taskCode); if (d == null) { - MessageBox.Show("列表中不存在目标任务", "提示", MessageBoxButtons.OK, MessageBoxIcon.Warning); + _status = $"换车重发失败:列表中不存在任务 {taskCode}"; return; } - var ms = MessageBox.Show($"是否换车重发任务--{taskCode}", "提示", MessageBoxButtons.OKCancel, MessageBoxIcon.Question); - if (ms != System.Windows.Forms.DialogResult.OK) return; - if (!MarkDeliveryWaiting(d, clearCarForChange: true)) { - MessageBox.Show("换车重发失败:仅当任务状态为 Suspended 或 Waiting 且未处于放货阶段时才允许换车重发", "提示", MessageBoxButtons.OK, MessageBoxIcon.Warning); + _status = "换车重发失败:仅当任务状态为 Suspended 或 Waiting 且未处于放货阶段时才允许换车重发"; return; } - // 状态已改为 Waiting 且 UsingCar 已清空,持久化 cdm.PersistDelivery(d); + _status = $"已换车重发任务 {taskCode}"; } catch (Exception ex) { + _status = $"换车重发任务 {taskCode} 异常,详见日志"; Diagnosis.Post($"换车重发任务 {taskCode} 异常: {ExceptionFormatter.FormatEx(ex)}"); - MessageBox.Show("换车重发任务异常,请查看日志", "提示", MessageBoxButtons.OK, MessageBoxIcon.Warning); } } - - private void DeliveryViewer_FormClosing(object sender, FormClosingEventArgs e) - { - if (e.CloseReason == CloseReason.UserClosing) - { - e.Cancel = true; - this.Visible = false; - timer1.Stop(); - } - } - - protected override void SetVisibleCore(bool value) - { - if (!IsHandleCreated && value) - CreateHandle(); - bool wasVisible = Visible; - base.SetVisibleCore(value); - if (value && !wasVisible) - timer1.Start(); - } - - private void currentTaskList_SelectedIndexChanged(object sender, EventArgs e) - { - _selectedIndicesCache.Clear(); - foreach (int i in currentTaskList.SelectedIndices) - _selectedIndicesCache.Add(i); - this.BeginInvoke(() => currentTaskList.Invalidate()); - } } } diff --git a/StandardScene.Core/Chained/DeliveryViewer.designer.cs b/StandardScene.Core/Chained/DeliveryViewer.designer.cs deleted file mode 100644 index d41121f..0000000 --- a/StandardScene.Core/Chained/DeliveryViewer.designer.cs +++ /dev/null @@ -1,206 +0,0 @@ - -using System.Windows.Forms; - -namespace StandardScene.Chained -{ - partial class DeliveryViewer - { - /// - /// 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(); - } - if (disposing) - { - strip?.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.components = new System.ComponentModel.Container(); - this.currentTaskList = new System.Windows.Forms.ListView(); - this.columnHeader8 = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); - this.columnHeader1 = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); - this.columnHeader4 = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); - this.columnHeader5 = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); - this.columnHeader9 = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); - this.columnHeader6 = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); - this.columnHeader2 = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); - this.columnHeader7 = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); - this.columnHeader3 = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); - this.label2 = new System.Windows.Forms.Label(); - this.timer1 = new System.Windows.Forms.Timer(this.components); - this.checkBox1 = new System.Windows.Forms.CheckBox(); - this.checkBox2 = new System.Windows.Forms.CheckBox(); - this.SuspendLayout(); - // - // currentTaskList - // - this.currentTaskList.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) - | System.Windows.Forms.AnchorStyles.Left) - | System.Windows.Forms.AnchorStyles.Right))); - this.currentTaskList.Columns.AddRange(new System.Windows.Forms.ColumnHeader[] { - this.columnHeader8, - this.columnHeader1, - this.columnHeader4, - this.columnHeader5, - this.columnHeader9, - this.columnHeader6, - this.columnHeader2, - this.columnHeader7, - this.columnHeader3}); - this.currentTaskList.Font = new System.Drawing.Font("微软雅黑", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - this.currentTaskList.FullRowSelect = true; - this.currentTaskList.GridLines = true; - this.currentTaskList.HideSelection = false; - this.currentTaskList.Location = new System.Drawing.Point(38, 62); - this.currentTaskList.Name = "currentTaskList"; - this.currentTaskList.Size = new System.Drawing.Size(1146, 429); - this.currentTaskList.TabIndex = 2; - this.currentTaskList.UseCompatibleStateImageBehavior = false; - this.currentTaskList.View = System.Windows.Forms.View.Details; - this.currentTaskList.VirtualMode = true; - this.currentTaskList.RetrieveVirtualItem += new System.Windows.Forms.RetrieveVirtualItemEventHandler(this.currentTaskList_RetrieveVirtualItem); - this.currentTaskList.SelectedIndexChanged += new System.EventHandler(this.currentTaskList_SelectedIndexChanged); - this.currentTaskList.MouseClick += new System.Windows.Forms.MouseEventHandler(this.currentTaskList_MouseClick); - // - // columnHeader8 - // - this.columnHeader8.Text = "任务号"; - this.columnHeader8.Width = 130; - // - // columnHeader1 - // - this.columnHeader1.Text = "小车"; - this.columnHeader1.Width = 100; - // - // columnHeader4 - // - this.columnHeader4.Text = "取货点"; - this.columnHeader4.Width = 130; - // - // columnHeader5 - // - this.columnHeader5.Text = "放货点"; - this.columnHeader5.Width = 130; - // - // columnHeader9 - // - this.columnHeader9.Text = "任务状态"; - this.columnHeader9.Width = 100; - // - // columnHeader6 - // - this.columnHeader6.Text = "下发时间"; - this.columnHeader6.Width = 130; - // - // columnHeader2 - // - this.columnHeader2.Text = "执行时间"; - this.columnHeader2.Width = 130; - // - // columnHeader7 - // - this.columnHeader7.Text = "结束时间"; - this.columnHeader7.Width = 130; - // - // columnHeader3 - // - this.columnHeader3.Text = "优先级"; - this.columnHeader3.Width = 83; - // - // label2 - // - this.label2.AutoSize = true; - this.label2.Font = new System.Drawing.Font("微软雅黑", 14.25F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - this.label2.Location = new System.Drawing.Point(33, 7); - this.label2.Name = "label2"; - this.label2.Size = new System.Drawing.Size(88, 26); - this.label2.TabIndex = 3; - this.label2.Text = "任务列表"; - // - // timer1 - // - this.timer1.Enabled = true; - this.timer1.Interval = 1000; - this.timer1.Tick += new System.EventHandler(this.timer1_Tick); - // - // checkBox1 - // - this.checkBox1.AutoSize = true; - this.checkBox1.Checked = true; - this.checkBox1.CheckState = System.Windows.Forms.CheckState.Checked; - this.checkBox1.Location = new System.Drawing.Point(127, 15); - this.checkBox1.Margin = new System.Windows.Forms.Padding(2, 2, 2, 2); - this.checkBox1.Name = "checkBox1"; - this.checkBox1.Size = new System.Drawing.Size(108, 16); - this.checkBox1.TabIndex = 4; - this.checkBox1.Text = "显示已完成任务"; - this.checkBox1.UseVisualStyleBackColor = true; - // - // checkBox2 - // - this.checkBox2.AutoSize = true; - this.checkBox2.Checked = true; - this.checkBox2.CheckState = System.Windows.Forms.CheckState.Checked; - this.checkBox2.Location = new System.Drawing.Point(239, 14); - this.checkBox2.Margin = new System.Windows.Forms.Padding(2, 2, 2, 2); - this.checkBox2.Name = "checkBox2"; - this.checkBox2.Size = new System.Drawing.Size(318, 16); - this.checkBox2.TabIndex = 5; - this.checkBox2.Text = "显示废止的任务(包括Error、Canceled、Terminated)"; - this.checkBox2.UseVisualStyleBackColor = true; - // - // DeliveryViewer - // - this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F); - this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - this.ClientSize = new System.Drawing.Size(1199, 551); - this.Controls.Add(this.checkBox2); - this.Controls.Add(this.checkBox1); - this.Controls.Add(this.label2); - this.Controls.Add(this.currentTaskList); - this.Name = "DeliveryViewer"; - this.Text = "DeliveryViewer"; - this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.DeliveryViewer_FormClosing); - this.Load += new System.EventHandler(this.DeliveryViewer_Load); - this.ResumeLayout(false); - this.PerformLayout(); - - } - - #endregion - private System.Windows.Forms.Label label2; - private System.Windows.Forms.ColumnHeader columnHeader4; - private System.Windows.Forms.ColumnHeader columnHeader5; - private System.Windows.Forms.ColumnHeader columnHeader6; - private System.Windows.Forms.ColumnHeader columnHeader7; - private System.Windows.Forms.ColumnHeader columnHeader8; - private System.Windows.Forms.Timer timer1; - private System.Windows.Forms.ColumnHeader columnHeader1; - private System.Windows.Forms.ColumnHeader columnHeader2; - private System.Windows.Forms.ColumnHeader columnHeader9; - private System.Windows.Forms.CheckBox checkBox1; - private System.Windows.Forms.CheckBox checkBox2; - public System.Windows.Forms.ListView currentTaskList; - private System.Windows.Forms.ColumnHeader columnHeader3; - } -} \ No newline at end of file diff --git a/StandardScene.Core/Chained/DeliveryViewer.resx b/StandardScene.Core/Chained/DeliveryViewer.resx deleted file mode 100644 index 1f666f2..0000000 --- a/StandardScene.Core/Chained/DeliveryViewer.resx +++ /dev/null @@ -1,123 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - 17, 17 - - \ No newline at end of file diff --git a/StandardScene.Core/Chained/LoopViewer.Designer.cs b/StandardScene.Core/Chained/LoopViewer.Designer.cs deleted file mode 100644 index c9c8888..0000000 --- a/StandardScene.Core/Chained/LoopViewer.Designer.cs +++ /dev/null @@ -1,570 +0,0 @@ -using System; -using System.Drawing; -using System.Windows.Forms; - -namespace LoopViewerApp -{ - partial class LoopViewer - { - private System.ComponentModel.IContainer components = null; - - private ComboBox cmbTaskKind; - private NumericUpDown numCurrent; - private NumericUpDown numTarget; - private NumericUpDown numTraffic; - private CheckBox chkViaPoint; - private ComboBox cmbStartType; - private NumericUpDown numPriority; - private Button btnEdit; // 保留字段以供代码逻辑/样式使用(在界面上隐藏) - private Button btnDelete; // 保留字段以供代码逻辑/样式使用(在界面上隐藏) - private Button btnSave; - private Button btnCancel; - private ListView lstTasks; - private GroupBox grpEdit; - - // 布局控件 - private SplitContainer splitContainer; - private TableLayoutPanel tlpEdit; - private FlowLayoutPanel flpButtons; - - // 中间竖向按钮(列表与编辑区之间) - private Panel pnlMiddle; - private FlowLayoutPanel flpMiddle; - private Button btnMiddleEdit; - private Button btnMiddleDelete; - - // 列头 - private ColumnHeader colId; - private ColumnHeader colTaskType; - private ColumnHeader colCurrent; - private ColumnHeader colTarget; - private ColumnHeader colTraffic; - private ColumnHeader colPriority; - private ColumnHeader colViaPoint; - private ColumnHeader colStartType; - - // 标签字段(编辑区) - private Label lblKind; - private Label lblCurrent; - private Label lblTarget; - private Label lblTraffic; - private Label lblPriority; - private Label lblVia; - private Label lblStartType; - private Label lblEditingId; // 显示当前编辑的任务ID - - protected override void Dispose(bool disposing) - { - if (disposing && (components != null)) - { - components.Dispose(); - } - base.Dispose(disposing); - } - - private void InitializeComponent() - { - this.splitContainer = new System.Windows.Forms.SplitContainer(); - this.pnlMiddle = new System.Windows.Forms.Panel(); - this.flpMiddle = new System.Windows.Forms.FlowLayoutPanel(); - this.btnMiddleEdit = new System.Windows.Forms.Button(); - this.btnMiddleDelete = new System.Windows.Forms.Button(); - this.lstTasks = new System.Windows.Forms.ListView(); - this.colId = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); - this.colTaskType = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); - this.colCurrent = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); - this.colTarget = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); - this.colTraffic = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); - this.colPriority = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); - this.colViaPoint = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); - this.colStartType = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); - this.grpEdit = new System.Windows.Forms.GroupBox(); - this.tlpEdit = new System.Windows.Forms.TableLayoutPanel(); - this.lblEditingId = new System.Windows.Forms.Label(); - this.lblKind = new System.Windows.Forms.Label(); - this.cmbTaskKind = new System.Windows.Forms.ComboBox(); - this.lblCurrent = new System.Windows.Forms.Label(); - this.numCurrent = new System.Windows.Forms.NumericUpDown(); - this.lblTarget = new System.Windows.Forms.Label(); - this.numTarget = new System.Windows.Forms.NumericUpDown(); - this.lblTraffic = new System.Windows.Forms.Label(); - this.numTraffic = new System.Windows.Forms.NumericUpDown(); - this.lblPriority = new System.Windows.Forms.Label(); - this.numPriority = new System.Windows.Forms.NumericUpDown(); - this.lblVia = new System.Windows.Forms.Label(); - this.chkViaPoint = new System.Windows.Forms.CheckBox(); - this.lblStartType = new System.Windows.Forms.Label(); - this.cmbStartType = new System.Windows.Forms.ComboBox(); - this.flpButtons = new System.Windows.Forms.FlowLayoutPanel(); - this.btnSave = new System.Windows.Forms.Button(); - this.btnCancel = new System.Windows.Forms.Button(); - this.btnEdit = new System.Windows.Forms.Button(); - this.btnDelete = new System.Windows.Forms.Button(); - ((System.ComponentModel.ISupportInitialize)(this.splitContainer)).BeginInit(); - this.splitContainer.Panel1.SuspendLayout(); - this.splitContainer.Panel2.SuspendLayout(); - this.splitContainer.SuspendLayout(); - this.pnlMiddle.SuspendLayout(); - this.flpMiddle.SuspendLayout(); - this.grpEdit.SuspendLayout(); - this.tlpEdit.SuspendLayout(); - ((System.ComponentModel.ISupportInitialize)(this.numCurrent)).BeginInit(); - ((System.ComponentModel.ISupportInitialize)(this.numTarget)).BeginInit(); - ((System.ComponentModel.ISupportInitialize)(this.numTraffic)).BeginInit(); - ((System.ComponentModel.ISupportInitialize)(this.numPriority)).BeginInit(); - this.flpButtons.SuspendLayout(); - this.SuspendLayout(); - // - // splitContainer - // - this.splitContainer.Dock = System.Windows.Forms.DockStyle.Fill; - this.splitContainer.Location = new System.Drawing.Point(0, 0); - this.splitContainer.Name = "splitContainer"; - // - // splitContainer.Panel1 - // - this.splitContainer.Panel1.Controls.Add(this.pnlMiddle); - this.splitContainer.Panel1.Controls.Add(this.lstTasks); - // - // splitContainer.Panel2 - // - this.splitContainer.Panel2.Controls.Add(this.grpEdit); - this.splitContainer.Size = new System.Drawing.Size(1200, 600); - this.splitContainer.SplitterDistance = 680; - this.splitContainer.SplitterWidth = 6; - this.splitContainer.TabIndex = 0; - // - // pnlMiddle - // - this.pnlMiddle.Controls.Add(this.flpMiddle); - this.pnlMiddle.Dock = System.Windows.Forms.DockStyle.Right; - this.pnlMiddle.Location = new System.Drawing.Point(614, 0); - this.pnlMiddle.Name = "pnlMiddle"; - this.pnlMiddle.Padding = new System.Windows.Forms.Padding(6); - this.pnlMiddle.Size = new System.Drawing.Size(66, 600); - this.pnlMiddle.TabIndex = 0; - // - // flpMiddle - // - this.flpMiddle.Anchor = System.Windows.Forms.AnchorStyles.None; - this.flpMiddle.Controls.Add(this.btnMiddleEdit); - this.flpMiddle.Controls.Add(this.btnMiddleDelete); - this.flpMiddle.FlowDirection = System.Windows.Forms.FlowDirection.TopDown; - this.flpMiddle.Location = new System.Drawing.Point(0, 220); - this.flpMiddle.Name = "flpMiddle"; - this.flpMiddle.Padding = new System.Windows.Forms.Padding(2); - this.flpMiddle.Size = new System.Drawing.Size(63, 160); - this.flpMiddle.TabIndex = 0; - this.flpMiddle.WrapContents = false; - // - // btnMiddleEdit - // - this.btnMiddleEdit.BackColor = System.Drawing.SystemColors.Control; - this.btnMiddleEdit.FlatStyle = System.Windows.Forms.FlatStyle.Flat; - this.btnMiddleEdit.Font = new System.Drawing.Font("微软雅黑", 9F); - this.btnMiddleEdit.Location = new System.Drawing.Point(6, 12); - this.btnMiddleEdit.Margin = new System.Windows.Forms.Padding(4, 10, 4, 4); - this.btnMiddleEdit.Name = "btnMiddleEdit"; - this.btnMiddleEdit.Size = new System.Drawing.Size(50, 40); - this.btnMiddleEdit.TabIndex = 0; - this.btnMiddleEdit.Text = "编辑"; - this.btnMiddleEdit.UseVisualStyleBackColor = false; - this.btnMiddleEdit.Click += new System.EventHandler(this.btnEdit_Click); - // - // btnMiddleDelete - // - this.btnMiddleDelete.BackColor = System.Drawing.Color.LightCoral; - this.btnMiddleDelete.FlatStyle = System.Windows.Forms.FlatStyle.Flat; - this.btnMiddleDelete.Font = new System.Drawing.Font("微软雅黑", 9F); - this.btnMiddleDelete.Location = new System.Drawing.Point(6, 62); - this.btnMiddleDelete.Margin = new System.Windows.Forms.Padding(4, 6, 4, 4); - this.btnMiddleDelete.Name = "btnMiddleDelete"; - this.btnMiddleDelete.Size = new System.Drawing.Size(50, 40); - this.btnMiddleDelete.TabIndex = 1; - this.btnMiddleDelete.Text = "删除"; - this.btnMiddleDelete.UseVisualStyleBackColor = false; - this.btnMiddleDelete.Click += new System.EventHandler(this.btnDelete_Click); - // - // lstTasks - // - this.lstTasks.Columns.AddRange(new System.Windows.Forms.ColumnHeader[] { - this.colId, - this.colTaskType, - this.colCurrent, - this.colTarget, - this.colTraffic, - this.colPriority, - this.colViaPoint, - this.colStartType}); - this.lstTasks.Dock = System.Windows.Forms.DockStyle.Fill; - this.lstTasks.FullRowSelect = true; - this.lstTasks.HideSelection = false; - this.lstTasks.Location = new System.Drawing.Point(0, 0); - this.lstTasks.Name = "lstTasks"; - this.lstTasks.OwnerDraw = true; - this.lstTasks.Size = new System.Drawing.Size(680, 600); - this.lstTasks.TabIndex = 0; - this.lstTasks.UseCompatibleStateImageBehavior = false; - this.lstTasks.View = System.Windows.Forms.View.Details; - this.lstTasks.DrawColumnHeader += new System.Windows.Forms.DrawListViewColumnHeaderEventHandler(this.lstTasks_DrawColumnHeader); - this.lstTasks.DrawItem += new System.Windows.Forms.DrawListViewItemEventHandler(this.lstTasks_DrawItem); - this.lstTasks.DrawSubItem += new System.Windows.Forms.DrawListViewSubItemEventHandler(this.lstTasks_DrawSubItem); - this.lstTasks.MouseDoubleClick += new System.Windows.Forms.MouseEventHandler(this.lstTasks_MouseDoubleClick); - // - // colId - // - this.colId.Text = "ID"; - this.colId.Width = 40; - // - // colTaskType - // - this.colTaskType.Text = "任务类别"; - this.colTaskType.Width = 110; - // - // colCurrent - // - this.colCurrent.Text = "当前站点"; - this.colCurrent.Width = 90; - // - // colTarget - // - this.colTarget.Text = "目标站点"; - this.colTarget.Width = 90; - // - // colTraffic - // - this.colTraffic.Text = "流量控制"; - this.colTraffic.Width = 90; - // - // colPriority - // - this.colPriority.Text = "优先级"; - this.colPriority.Width = 80; - // - // colViaPoint - // - this.colViaPoint.Text = "途径点"; - this.colViaPoint.Width = 70; - // - // colStartType - // - this.colStartType.Text = "启动类型"; - this.colStartType.Width = 100; - // - // grpEdit - // - this.grpEdit.Controls.Add(this.tlpEdit); - this.grpEdit.Dock = System.Windows.Forms.DockStyle.Fill; - this.grpEdit.Font = new System.Drawing.Font("微软雅黑", 10F, System.Drawing.FontStyle.Bold); - this.grpEdit.Location = new System.Drawing.Point(0, 0); - this.grpEdit.Name = "grpEdit"; - this.grpEdit.Size = new System.Drawing.Size(514, 600); - this.grpEdit.TabIndex = 1; - this.grpEdit.TabStop = false; - this.grpEdit.Text = "任务信息(选中列表项后可编辑)"; - // - // tlpEdit - // - this.tlpEdit.ColumnCount = 2; - this.tlpEdit.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 120F)); - this.tlpEdit.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F)); - this.tlpEdit.Controls.Add(this.lblEditingId, 0, 0); - this.tlpEdit.Controls.Add(this.lblKind, 0, 1); - this.tlpEdit.Controls.Add(this.cmbTaskKind, 1, 1); - this.tlpEdit.Controls.Add(this.lblCurrent, 0, 2); - this.tlpEdit.Controls.Add(this.numCurrent, 1, 2); - this.tlpEdit.Controls.Add(this.lblTarget, 0, 3); - this.tlpEdit.Controls.Add(this.numTarget, 1, 3); - this.tlpEdit.Controls.Add(this.lblTraffic, 0, 4); - this.tlpEdit.Controls.Add(this.numTraffic, 1, 4); - this.tlpEdit.Controls.Add(this.lblPriority, 0, 5); - this.tlpEdit.Controls.Add(this.numPriority, 1, 5); - this.tlpEdit.Controls.Add(this.lblVia, 0, 6); - this.tlpEdit.Controls.Add(this.chkViaPoint, 1, 6); - this.tlpEdit.Controls.Add(this.lblStartType, 0, 7); - this.tlpEdit.Controls.Add(this.cmbStartType, 1, 7); - this.tlpEdit.Controls.Add(this.flpButtons, 1, 8); - this.tlpEdit.Dock = System.Windows.Forms.DockStyle.Fill; - this.tlpEdit.Location = new System.Drawing.Point(3, 25); - this.tlpEdit.Name = "tlpEdit"; - this.tlpEdit.Padding = new System.Windows.Forms.Padding(8); - this.tlpEdit.RowCount = 9; - this.tlpEdit.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 36F)); - this.tlpEdit.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 36F)); - this.tlpEdit.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 36F)); - this.tlpEdit.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 36F)); - this.tlpEdit.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 36F)); - this.tlpEdit.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 36F)); - this.tlpEdit.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 36F)); - this.tlpEdit.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 36F)); - this.tlpEdit.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F)); - this.tlpEdit.Size = new System.Drawing.Size(508, 572); - this.tlpEdit.TabIndex = 0; - // - // lblEditingId - // - this.lblEditingId.Anchor = System.Windows.Forms.AnchorStyles.Left; - this.lblEditingId.AutoSize = true; - this.tlpEdit.SetColumnSpan(this.lblEditingId, 2); - this.lblEditingId.Font = new System.Drawing.Font("微软雅黑", 10F, System.Drawing.FontStyle.Bold); - this.lblEditingId.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(0)))), ((int)(((byte)(120)))), ((int)(((byte)(215))))); - this.lblEditingId.Location = new System.Drawing.Point(11, 14); - this.lblEditingId.Name = "lblEditingId"; - this.lblEditingId.Size = new System.Drawing.Size(78, 24); - this.lblEditingId.TabIndex = 0; - this.lblEditingId.Text = "新增任务"; - // - // lblKind - // - this.lblKind.Dock = System.Windows.Forms.DockStyle.Fill; - this.lblKind.Font = new System.Drawing.Font("微软雅黑", 10F); - this.lblKind.Location = new System.Drawing.Point(11, 44); - this.lblKind.Name = "lblKind"; - this.lblKind.Size = new System.Drawing.Size(114, 36); - this.lblKind.TabIndex = 1; - this.lblKind.Text = "任务类别:"; - this.lblKind.TextAlign = System.Drawing.ContentAlignment.MiddleRight; - // - // cmbTaskKind - // - this.cmbTaskKind.Dock = System.Windows.Forms.DockStyle.Fill; - this.cmbTaskKind.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; - this.cmbTaskKind.Font = new System.Drawing.Font("微软雅黑", 10F); - this.cmbTaskKind.Items.AddRange(new object[] { - "Loop", - "BranchPoint", - "JoinPoint"}); - this.cmbTaskKind.Location = new System.Drawing.Point(131, 47); - this.cmbTaskKind.Name = "cmbTaskKind"; - this.cmbTaskKind.Size = new System.Drawing.Size(366, 31); - this.cmbTaskKind.TabIndex = 2; - // - // lblCurrent - // - this.lblCurrent.Dock = System.Windows.Forms.DockStyle.Fill; - this.lblCurrent.Font = new System.Drawing.Font("微软雅黑", 10F); - this.lblCurrent.Location = new System.Drawing.Point(11, 80); - this.lblCurrent.Name = "lblCurrent"; - this.lblCurrent.Size = new System.Drawing.Size(114, 36); - this.lblCurrent.TabIndex = 3; - this.lblCurrent.Text = "当前站点:"; - this.lblCurrent.TextAlign = System.Drawing.ContentAlignment.MiddleRight; - // - // numCurrent - // - this.numCurrent.Dock = System.Windows.Forms.DockStyle.Left; - this.numCurrent.Font = new System.Drawing.Font("微软雅黑", 10F); - this.numCurrent.Location = new System.Drawing.Point(131, 83); - this.numCurrent.Maximum = new decimal(new int[] { - 1000000, - 0, - 0, - 0}); - this.numCurrent.Name = "numCurrent"; - this.numCurrent.Size = new System.Drawing.Size(120, 29); - this.numCurrent.TabIndex = 4; - // - // lblTarget - // - this.lblTarget.Dock = System.Windows.Forms.DockStyle.Fill; - this.lblTarget.Font = new System.Drawing.Font("微软雅黑", 10F); - this.lblTarget.Location = new System.Drawing.Point(11, 116); - this.lblTarget.Name = "lblTarget"; - this.lblTarget.Size = new System.Drawing.Size(114, 36); - this.lblTarget.TabIndex = 5; - this.lblTarget.Text = "目标站点:"; - this.lblTarget.TextAlign = System.Drawing.ContentAlignment.MiddleRight; - // - // numTarget - // - this.numTarget.Dock = System.Windows.Forms.DockStyle.Left; - this.numTarget.Font = new System.Drawing.Font("微软雅黑", 10F); - this.numTarget.Location = new System.Drawing.Point(131, 119); - this.numTarget.Maximum = new decimal(new int[] { - 1000000, - 0, - 0, - 0}); - this.numTarget.Name = "numTarget"; - this.numTarget.Size = new System.Drawing.Size(120, 29); - this.numTarget.TabIndex = 6; - // - // lblTraffic - // - this.lblTraffic.Dock = System.Windows.Forms.DockStyle.Fill; - this.lblTraffic.Font = new System.Drawing.Font("微软雅黑", 10F); - this.lblTraffic.Location = new System.Drawing.Point(11, 152); - this.lblTraffic.Name = "lblTraffic"; - this.lblTraffic.Size = new System.Drawing.Size(114, 36); - this.lblTraffic.TabIndex = 7; - this.lblTraffic.Text = "流量控制:"; - this.lblTraffic.TextAlign = System.Drawing.ContentAlignment.MiddleRight; - // - // numTraffic - // - this.numTraffic.Dock = System.Windows.Forms.DockStyle.Left; - this.numTraffic.Font = new System.Drawing.Font("微软雅黑", 10F); - this.numTraffic.Location = new System.Drawing.Point(131, 155); - this.numTraffic.Maximum = new decimal(new int[] { - 1000, - 0, - 0, - 0}); - this.numTraffic.Name = "numTraffic"; - this.numTraffic.Size = new System.Drawing.Size(120, 29); - this.numTraffic.TabIndex = 8; - // - // lblPriority - // - this.lblPriority.Dock = System.Windows.Forms.DockStyle.Fill; - this.lblPriority.Font = new System.Drawing.Font("微软雅黑", 10F); - this.lblPriority.Location = new System.Drawing.Point(11, 188); - this.lblPriority.Name = "lblPriority"; - this.lblPriority.Size = new System.Drawing.Size(114, 36); - this.lblPriority.TabIndex = 9; - this.lblPriority.Text = "优先级:"; - this.lblPriority.TextAlign = System.Drawing.ContentAlignment.MiddleRight; - // - // numPriority - // - this.numPriority.Dock = System.Windows.Forms.DockStyle.Left; - this.numPriority.Font = new System.Drawing.Font("微软雅黑", 10F); - this.numPriority.Location = new System.Drawing.Point(131, 191); - this.numPriority.Name = "numPriority"; - this.numPriority.Size = new System.Drawing.Size(120, 29); - this.numPriority.TabIndex = 10; - this.numPriority.Value = new decimal(new int[] { - 1, - 0, - 0, - 0}); - // - // lblVia - // - this.lblVia.Dock = System.Windows.Forms.DockStyle.Fill; - this.lblVia.Font = new System.Drawing.Font("微软雅黑", 10F); - this.lblVia.Location = new System.Drawing.Point(11, 224); - this.lblVia.Name = "lblVia"; - this.lblVia.Size = new System.Drawing.Size(114, 36); - this.lblVia.TabIndex = 11; - this.lblVia.Text = "途径点:"; - this.lblVia.TextAlign = System.Drawing.ContentAlignment.MiddleRight; - // - // chkViaPoint - // - this.chkViaPoint.Dock = System.Windows.Forms.DockStyle.Left; - this.chkViaPoint.Font = new System.Drawing.Font("微软雅黑", 10F); - this.chkViaPoint.Location = new System.Drawing.Point(131, 227); - this.chkViaPoint.Name = "chkViaPoint"; - this.chkViaPoint.Size = new System.Drawing.Size(104, 30); - this.chkViaPoint.TabIndex = 12; - this.chkViaPoint.Text = "是"; - // - // lblStartType - // - this.lblStartType.Dock = System.Windows.Forms.DockStyle.Fill; - this.lblStartType.Font = new System.Drawing.Font("微软雅黑", 10F); - this.lblStartType.Location = new System.Drawing.Point(11, 260); - this.lblStartType.Name = "lblStartType"; - this.lblStartType.Size = new System.Drawing.Size(114, 36); - this.lblStartType.TabIndex = 13; - this.lblStartType.Text = "启动类型:"; - this.lblStartType.TextAlign = System.Drawing.ContentAlignment.MiddleRight; - // - // cmbStartType - // - this.cmbStartType.Dock = System.Windows.Forms.DockStyle.Fill; - this.cmbStartType.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; - this.cmbStartType.Font = new System.Drawing.Font("微软雅黑", 10F); - this.cmbStartType.Items.AddRange(new object[] { - "Api", - "Plc", - "ButtonBox", - "AutoLoop", - "Charge"}); - this.cmbStartType.Location = new System.Drawing.Point(131, 263); - this.cmbStartType.Name = "cmbStartType"; - this.cmbStartType.Size = new System.Drawing.Size(366, 31); - this.cmbStartType.TabIndex = 14; - // - // flpButtons - // - this.flpButtons.AutoSize = true; - this.flpButtons.Controls.Add(this.btnSave); - this.flpButtons.Controls.Add(this.btnCancel); - this.flpButtons.Dock = System.Windows.Forms.DockStyle.Left; - this.flpButtons.Location = new System.Drawing.Point(131, 299); - this.flpButtons.Name = "flpButtons"; - this.flpButtons.Size = new System.Drawing.Size(292, 262); - this.flpButtons.TabIndex = 15; - // - // btnSave - // - this.btnSave.BackColor = System.Drawing.Color.LightBlue; - this.btnSave.FlatStyle = System.Windows.Forms.FlatStyle.Flat; - this.btnSave.Font = new System.Drawing.Font("微软雅黑", 11F, System.Drawing.FontStyle.Bold); - this.btnSave.Location = new System.Drawing.Point(3, 3); - this.btnSave.Name = "btnSave"; - this.btnSave.Size = new System.Drawing.Size(140, 40); - this.btnSave.TabIndex = 0; - this.btnSave.Text = "保存"; - this.btnSave.UseVisualStyleBackColor = false; - this.btnSave.Click += new System.EventHandler(this.btnSave_Click); - // - // btnCancel - // - this.btnCancel.BackColor = System.Drawing.SystemColors.Control; - this.btnCancel.FlatStyle = System.Windows.Forms.FlatStyle.Flat; - this.btnCancel.Font = new System.Drawing.Font("微软雅黑", 11F); - this.btnCancel.Location = new System.Drawing.Point(149, 3); - this.btnCancel.Name = "btnCancel"; - this.btnCancel.Size = new System.Drawing.Size(140, 40); - this.btnCancel.TabIndex = 1; - this.btnCancel.Text = "取消"; - this.btnCancel.UseVisualStyleBackColor = false; - this.btnCancel.Click += new System.EventHandler(this.btnCancel_Click); - // - // btnEdit - // - this.btnEdit.Location = new System.Drawing.Point(0, 0); - this.btnEdit.Name = "btnEdit"; - this.btnEdit.Size = new System.Drawing.Size(75, 23); - this.btnEdit.TabIndex = 0; - this.btnEdit.Visible = false; - // - // btnDelete - // - this.btnDelete.Location = new System.Drawing.Point(0, 0); - this.btnDelete.Name = "btnDelete"; - this.btnDelete.Size = new System.Drawing.Size(75, 23); - this.btnDelete.TabIndex = 0; - this.btnDelete.Visible = false; - // - // LoopViewer - // - this.ClientSize = new System.Drawing.Size(1200, 600); - this.Controls.Add(this.splitContainer); - this.Font = new System.Drawing.Font("微软雅黑", 9F); - this.MinimumSize = new System.Drawing.Size(1000, 420); - this.Name = "LoopViewer"; - this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen; - this.Text = "任务列表管理器"; - this.splitContainer.Panel1.ResumeLayout(false); - this.splitContainer.Panel2.ResumeLayout(false); - ((System.ComponentModel.ISupportInitialize)(this.splitContainer)).EndInit(); - this.splitContainer.ResumeLayout(false); - this.pnlMiddle.ResumeLayout(false); - this.flpMiddle.ResumeLayout(false); - this.grpEdit.ResumeLayout(false); - this.tlpEdit.ResumeLayout(false); - this.tlpEdit.PerformLayout(); - ((System.ComponentModel.ISupportInitialize)(this.numCurrent)).EndInit(); - ((System.ComponentModel.ISupportInitialize)(this.numTarget)).EndInit(); - ((System.ComponentModel.ISupportInitialize)(this.numTraffic)).EndInit(); - ((System.ComponentModel.ISupportInitialize)(this.numPriority)).EndInit(); - this.flpButtons.ResumeLayout(false); - this.ResumeLayout(false); - - } - } -} \ No newline at end of file diff --git a/StandardScene.Core/Chained/LoopViewer.cs b/StandardScene.Core/Chained/LoopViewer.cs index 7a81bc4..75dd27a 100644 --- a/StandardScene.Core/Chained/LoopViewer.cs +++ b/StandardScene.Core/Chained/LoopViewer.cs @@ -1,595 +1,339 @@ -using Newtonsoft.Json; -using StandardScene.Model; -using System; +using System; using System.Collections.Generic; -using System.ComponentModel; -using System.Drawing; using System.IO; using System.Linq; -using System.Windows.Forms; - +using System.Threading.Tasks; +using CycleGUI; +using Newtonsoft.Json; +using SimpleCore.Library; +using StandardScene.Model; +using StandardScene.Utils; namespace LoopViewerApp { - public partial class LoopViewer : Form + /// + /// 环线/循环任务配置管理界面(CycleGUI 版,替代原 WinForms LoopViewer 窗体)。 + /// + /// 维护 tasklist.json of )的增 / 改 / 删;与 AbstractLoopMission 读取同一文件。 + /// 单实例:再次打开则把已有面板置前。 + /// 勾选多行后「删除选中」可批量删除(保留原 ListView 多选删除能力);每行「编辑」按钮打开编辑对话框。 + /// 文件写入放后台线程,绝不阻塞渲染线程(避免界面卡死)。 + /// + /// 沿用 DeliveryViewer 的同套模式(单实例面板、pb.TableCycleUiHelper.ConfirmThen),不另造轮子。 + /// 保留可实例化 + 以兼容既有调用 new LoopViewer().Show()。 + /// + public class LoopViewer { - private readonly string jsonPath = - Path.Combine(Application.StartupPath, "tasklist.json"); + private const string TableId = "loop-task-list"; - private List tasks = new List(); + // 与 AbstractLoopMission 完全一致的读取路径,保证“写哪儿、它就读哪儿”。 + private static string JsonPath => Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "tasklist.json"); - // -1 表示新增模式;>=0 表示正在编辑对应索引 - private int editingIndex = -1; + private static readonly object SaveLock = new object(); + // 直接取自枚举,自动与 TaskKind / TaskStartType 保持同步(含 Charge),无需手写列表。 + private static readonly string[] KindNames = Enum.GetNames(typeof(TaskKind)); + private static readonly string[] StartTypeNames = Enum.GetNames(typeof(TaskStartType)); - public LoopViewer() + private static Panel _panel; + private static Panel _dialog; // 新增/编辑对话框,限单实例 + private static List _tasks = new List(); // 仅渲染线程读写 + private static readonly HashSet _selected = new HashSet(); // 仅渲染线程读写,存被勾选任务的 Id + private static volatile string _status = ""; + + /// 打开(或置前)任务管理面板。兼容原 new LoopViewer().Show() 调用方式。 + public void Show() => Open(); + + /// 打开(或置前)任务管理面板。 + public static void Open() { - InitializeComponent(); + if (_panel != null) + { + try + { + _panel.BringToFront(); + return; + } + catch + { + _panel = null; + } + } - if (LicenseManager.UsageMode == LicenseUsageMode.Designtime) + _selected.Clear(); + LoadTasks(); + + var panel = GUI.DeclarePanel() + .ShowTitle("任务列表管理器") + .SetDefaultDocking(Panel.Docking.None) + .InitSize(1080, 620) + .InitPos(false, 0, 0, 0.5f, 0.5f, 0.5f, 0.5f); + _panel = panel; + panel.IfTerminalQuit(() => _panel = null); + + panel.Define(pb => + { + if (pb.Closing()) + { + panel.Exit(); + _panel = null; + return; + } + + if (pb.Button("新增任务", distinct: "loop-add")) + OpenEditDialog(null); + pb.SameLine(12); + if (pb.Button("删除选中", distinct: "loop-del-selected")) + ConfirmDeleteSelected(); + pb.SameLine(16); + pb.Label($"共 {_tasks.Count} 个任务,已选 {_selected.Count} 个"); + + pb.Table(TableId, + new[] { "选择", "ID", "任务类别", "当前站点", "目标站点", "流量控制", "优先级", "途径点", "启动类型", "操作" }, + _tasks.Count, (row, i) => + { + var t = _tasks[i]; + var id = t.Id; + + var sel = _selected.Contains(id); + if (row.Checkbox(ref sel, "勾选以批量删除")) + { + if (sel) _selected.Add(id); + else _selected.Remove(id); + } + + row.Label($"{t.Id}"); + row.Label($"{t.Kind}"); + row.Label($"{t.CurrentStationId}"); + row.Label($"{t.TargetStationId}"); + row.Label($"{t.TrafficControl}"); + row.Label($"{t.Priority}"); + row.Label(t.IsViaPoint ? "是" : "否"); + row.Label($"{t.StartType}"); + + if (row.ButtonGroup(new[] { "编辑" }, new[] { "编辑该任务" }) == 0) + OpenEditDialog(t); + }, height: 18, enableSearch: true); + + if (!string.IsNullOrEmpty(_status)) + { + pb.Separator(); + pb.Label(_status); + } + + // 事件驱动为主,配合较慢的节流重绘即可保证后台保存结果/状态及时反映。 + pb.Panel.Repaint(repaintTimeMs: 500); + }); + } + + /// 对选中项发起二次确认后删除(保留原多选删除的提示文案)。 + private static void ConfirmDeleteSelected() + { + if (_selected.Count == 0) + { + _status = "未选择任何任务"; + _panel?.Repaint(); return; - - // 应用 ChargeStationManagementForm 风格的运行时样式调整 - ApplyChargeStyle(); - - EnsureComboItems(); - - // 启用多选并绑定右键菜单与 Delete 键删除功能 - try - { - if (lstTasks != null) - { - lstTasks.MultiSelect = true; - - // 右键菜单:删除 - var ctx = new ContextMenuStrip(); - ctx.Items.Add("删除", null, (s, e) => OnDeleteSelectedTasks()); - lstTasks.ContextMenuStrip = ctx; - - // 键盘删除键绑定 - lstTasks.KeyDown += lstTasks_KeyDown; - } - } - catch (Exception ex) - { - System.Diagnostics.Debug.WriteLine($"LoopViewer context menu init error: {ex}"); } - try - { - InitOrLoadJson(); - RenderListView(); - UpdateSaveButtonText(); - } - catch (Exception ex) - { - System.Diagnostics.Debug.WriteLine($"LoopViewer initialization error: {ex}"); - } + string prompt; + if (_selected.Count == 1) + prompt = $"确认删除任务 ID={_selected.First()}?"; + else + prompt = $"确认删除所选 {_selected.Count} 个任务?"; + + // 删除仅做内存列表增删(极快,可在渲染线程执行);真正的文件写入在 SaveTasks 内部放后台线程。 + CycleUiHelper.ConfirmThen(prompt, DeleteSelected); } - private void lstTasks_KeyDown(object sender, KeyEventArgs e) + private static void DeleteSelected() { - try - { - if (e.KeyCode == Keys.Delete) - { - OnDeleteSelectedTasks(); - e.Handled = true; - } - } - catch (Exception ex) - { - System.Diagnostics.Debug.WriteLine($"lstTasks_KeyDown error: {ex}"); - } + var removed = _tasks.RemoveAll(t => _selected.Contains(t.Id)); + _selected.Clear(); + SaveTasks(); + _status = $"已删除 {removed} 个任务"; + _panel?.Repaint(); } /// - /// 删除 ListView 中选中的任务(支持多选) + /// 打开「新增 / 编辑」对话框(置顶非模态、限单实例)。 为 null 表示新增,否则编辑该任务(保留其 Id)。 + /// 每次打开都是全新面板:defaultText 能正确初始化,规避立即模式下文本框缓冲难以重置的问题。 /// - private void OnDeleteSelectedTasks() + private static void OpenEditDialog(LoopTask existing) { - try + if (_dialog != null) { - if (lstTasks == null || lstTasks.SelectedIndices.Count == 0) + try { _dialog.BringToFront(); return; } + catch { _dialog = null; } + } + + bool isAdd = existing == null; + + int kindIdx = isAdd ? 0 : Math.Max(0, Array.IndexOf(KindNames, existing.Kind.ToString())); + int startIdx = Math.Max(0, Array.IndexOf(StartTypeNames, + (isAdd ? TaskStartType.AutoLoop : existing.StartType).ToString())); + string curText = (isAdd ? 0 : Clamp(existing.CurrentStationId, 0, 1000000)).ToString(); + string tgtText = (isAdd ? 0 : Clamp(existing.TargetStationId, 0, 1000000)).ToString(); + string trafficText = (isAdd ? 0 : Clamp(existing.TrafficControl, 0, 1000)).ToString(); + string priText = (isAdd ? 1 : Clamp(existing.Priority, 0, 100)).ToString(); + bool via = !isAdd && existing.IsViaPoint; + string err = ""; + + // 不用 Modal:原生「模态弹窗 + 标题栏关闭X」的 EndPopup 配对 bug 会断言崩溃。 + // 也不用 TopMost:置顶视口带 NoAutoMerge,会让 DropdownBox 的下拉弹窗落到独立非置顶视口里、被对话框挡在后面(看不到选项)。 + // 故采用与 DeliveryViewer 相同的普通浮动面板(非模态、不停靠):Begin/End 路径,X 关闭干净,下拉弹窗 z 序正常。 + var dlg = GUI.DeclarePanel() + .ShowTitle(isAdd ? "新增任务" : $"编辑任务 ID: {existing.Id}") + .SetDefaultDocking(Panel.Docking.None) + .InitSize(420, 380) + .InitPos(false, 0, 0, 0.5f, 0.5f, 0.5f, 0.5f); + _dialog = dlg; + dlg.IfTerminalQuit(() => _dialog = null); + dlg.Define(pb => + { + if (pb.Closing()) + { + dlg.Exit(); + _dialog = null; return; - - // 收集被选中的索引并按降序删除,避免索引移动问题 - var selectedIndices = lstTasks.SelectedIndices.Cast().OrderByDescending(i => i).ToList(); - - // 构造确认提示 - string prompt; - if (selectedIndices.Count == 1) - { - int idx = selectedIndices[0]; - if (idx >= 0 && idx < tasks.Count) - prompt = $"确认删除任务 ID={tasks[idx].Id}?"; - else - prompt = "确认删除选中任务?"; - } - else - { - prompt = $"确认删除所选 {selectedIndices.Count} 个任务?"; } - if (MessageBox.Show(prompt, "确认", MessageBoxButtons.YesNo, MessageBoxIcon.Question) != DialogResult.Yes) - return; + // 控件 id 由 ImHashStr(prompt) 经 Encoding.ASCII 计算:中文会被压成 '?',导致同字数纯中文标签 + // (如“当前站点/目标站点”“任务类别/启动类型”)哈希相同而抛 Duplicated id。故各标签加唯一 ASCII 序号前缀以区分。 + pb.DropdownBox("1. 任务类别", KindNames, ref kindIdx); + var (c, _) = pb.TextInput("2. 当前站点 (0~1000000)", curText, alwaysReturnString: true); + curText = c; + var (tg, _) = pb.TextInput("3. 目标站点 (0~1000000)", tgtText, alwaysReturnString: true); + tgtText = tg; + var (tf, _) = pb.TextInput("4. 流量控制 (0~1000)", trafficText, alwaysReturnString: true); + trafficText = tf; + var (pr, _) = pb.TextInput("5. 优先级 (0~100)", priText, alwaysReturnString: true); + priText = pr; + pb.CheckBox("6. 途径点", ref via); + pb.DropdownBox("7. 启动类型", StartTypeNames, ref startIdx); - // 删除任务 - foreach (var idx in selectedIndices) + if (!string.IsNullOrEmpty(err)) { - if (idx >= 0 && idx < tasks.Count) + pb.Separator(); + pb.Label(err); + } + + pb.Separator(); + if (pb.Button("保存", distinct: "loop-edit-save")) + { + if (!TryParseClamp(curText, 0, 1000000, out var cur)) { err = "当前站点需为 0~1000000 的整数"; return; } + if (!TryParseClamp(tgtText, 0, 1000000, out var tgt)) { err = "目标站点需为 0~1000000 的整数"; return; } + if (!TryParseClamp(trafficText, 0, 1000, out var traffic)) { err = "流量控制需为 0~1000 的整数"; return; } + if (!TryParseClamp(priText, 0, 100, out var pri)) { err = "优先级需为 0~100 的整数"; return; } + + Enum.TryParse(KindNames[kindIdx], out var kind); + Enum.TryParse(StartTypeNames[startIdx], out var st); + + if (isAdd) { - tasks.RemoveAt(idx); - } - } - - // 如果被删除项包含当前正在编辑的项,退出编辑状态 - if (editingIndex >= 0) - { - if (editingIndex >= tasks.Count || selectedIndices.Any(i => i == editingIndex)) - { - editingIndex = -1; - UpdateSaveButtonText(); - ClearPanelInputs(); + _tasks.Add(new LoopTask + { + Id = GetNextTaskId(), + Kind = kind, + CurrentStationId = cur, + TargetStationId = tgt, + TrafficControl = traffic, + Priority = pri, + IsViaPoint = via, + StartType = st + }); + _status = "已新增任务"; } else { - // 重新计算编辑索引在删除后的新位置 - int removedBefore = selectedIndices.Count(i => i < editingIndex); - editingIndex -= removedBefore; + existing.Kind = kind; + existing.CurrentStationId = cur; + existing.TargetStationId = tgt; + existing.TrafficControl = traffic; + existing.Priority = pri; + existing.IsViaPoint = via; + existing.StartType = st; + _status = $"已保存任务 ID={existing.Id}"; } - } - // 持久化并刷新列表视图 - Save(); - RenderListView(); + SaveTasks(); + dlg.Exit(); + _dialog = null; + _panel?.Repaint(); + } + pb.SameLine(8); + if (pb.Button("取消", distinct: "loop-edit-cancel")) + { + dlg.Exit(); + _dialog = null; + } + }); + } + + /// 下一个可用任务 Id(当前最大 Id + 1,空表则为 1)。 + private static int GetNextTaskId() => _tasks.Count == 0 ? 1 : _tasks.Max(t => t.Id) + 1; + + private static void LoadTasks() + { + try + { + var path = JsonPath; + if (!File.Exists(path)) + File.WriteAllText(path, "[]"); + + var text = File.ReadAllText(path); + _tasks = JsonConvert.DeserializeObject>(text) ?? new List(); } catch (Exception ex) { - System.Diagnostics.Debug.WriteLine($"OnDeleteSelectedTasks error: {ex}"); - MessageBox.Show("删除失败:" + ex.Message); + _tasks = new List(); + _status = "加载 tasklist.json 失败,详见日志"; + Diagnosis.Post($"LoopViewer 加载 tasklist.json 异常: {ExceptionFormatter.FormatEx(ex)}"); } } - /// - /// 将 LoopViewer 的运行时样式调整为与 ChargeStationManagementForm 接近的视觉风格: - /// - 全局字体设为微软雅黑 - /// - 表头暖色替换为蓝色沉稳风格(和充电界面一致) - /// - 按钮字号、背景色与充电界面保持一致(保存/删除/取消) - /// - 列表视图设置为整行选择、无边框、交替背景等 - /// 注意:不修改 Designer 文件,仅在运行时统一控件表现,避免破坏设计器生成代码。 - /// - private void ApplyChargeStyle() + /// 序列化在渲染线程完成(极快),文件写入放后台线程,避免阻塞渲染线程。 + private static void SaveTasks() { + string json; try { - // 窗体级设置 - this.StartPosition = FormStartPosition.CenterScreen; - this.MinimumSize = new System.Drawing.Size(1327, 738); - this.Font = new Font("微软雅黑", 9F, FontStyle.Regular); - - // 调整 ListView(如果存在) - if (lstTasks != null) - { - lstTasks.View = View.Details; - lstTasks.FullRowSelect = true; - lstTasks.GridLines = false; - lstTasks.HeaderStyle = ColumnHeaderStyle.Nonclickable; - lstTasks.OwnerDraw = true; // 已有自定义绘制 - lstTasks.BackColor = Color.White; - lstTasks.ForeColor = Color.FromArgb(33, 33, 33); - // 多选由初始化时控制(这里不强制) - } - - // 下拉框统一字体 - if (cmbTaskKind != null) cmbTaskKind.Font = new Font("微软雅黑", 10F, FontStyle.Regular); - if (cmbStartType != null) cmbStartType.Font = new Font("微软雅黑", 10F, FontStyle.Regular); - - // 数值输入框统一字体 - if (numCurrent != null) numCurrent.Font = new Font("微软雅黑", 10F, FontStyle.Regular); - if (numTarget != null) numTarget.Font = new Font("微软雅黑", 10F, FontStyle.Regular); - if (numTraffic != null) numTraffic.Font = new Font("微软雅黑", 10F, FontStyle.Regular); - if (numPriority != null) numPriority.Font = new Font("微软雅黑", 10F, FontStyle.Regular); - - // 标签字体统一 - if (lblEditingId != null) lblEditingId.Font = new Font("微软雅黑", 10F, FontStyle.Bold); - - // 按钮风格:与 ChargeStationManagementForm 保持一致的视觉优先级 - if (btnSave != null) - { - btnSave.BackColor = Color.LightBlue; - btnSave.ForeColor = Color.Black; - btnSave.Font = new Font("微软雅黑", 11F, FontStyle.Bold); - btnSave.FlatStyle = FlatStyle.Flat; - } - if (btnDelete != null) - { - btnDelete.BackColor = Color.LightCoral; - btnDelete.ForeColor = Color.Black; - btnDelete.Font = new Font("微软雅黑", 11F, FontStyle.Bold); - btnDelete.FlatStyle = FlatStyle.Flat; - } - if (btnCancel != null) - { - btnCancel.BackColor = SystemColors.Control; - btnCancel.ForeColor = Color.Black; - btnCancel.Font = new Font("微软雅黑", 11F, FontStyle.Regular); - btnCancel.FlatStyle = FlatStyle.Flat; - } - - // 如果存在额外的操作按钮(例如在面板上),尝试统一风格(容错) - foreach (Control ctrl in this.Controls) - { - if (ctrl is Panel pnl) - { - pnl.Padding = new Padding(12); - } - else if (ctrl is Button btn) - { - // 已设置主要按钮,其他按钮使用中性风格 - if (btn == btnSave || btn == btnDelete || btn == btnCancel) continue; - btn.Font = new Font("微软雅黑", 10F, FontStyle.Regular); - } - } + json = JsonConvert.SerializeObject(_tasks, Formatting.Indented); } catch (Exception ex) { - System.Diagnostics.Debug.WriteLine($"ApplyChargeStyle error: {ex}"); + _status = "保存失败,详见日志"; + Diagnosis.Post($"LoopViewer 序列化 tasklist.json 异常: {ExceptionFormatter.FormatEx(ex)}"); + return; } - } - private void EnsureComboItems() - { - try + var path = JsonPath; + Task.Run(() => { - if (cmbTaskKind != null && cmbTaskKind.Items.Count == 0) + try { - cmbTaskKind.Items.AddRange(new object[] { "Loop", "BranchPoint", "JoinPoint" }); - cmbTaskKind.SelectedIndex = 0; + lock (SaveLock) + File.WriteAllText(path, json); } - - if (cmbStartType != null && cmbStartType.Items.Count == 0) + catch (Exception ex) { - cmbStartType.Items.AddRange(new object[] { "Api", "Plc", "ButtonBox", "AutoLoop" }); - cmbStartType.SelectedIndex = 3; + _status = "保存失败,详见日志"; + Diagnosis.Post($"LoopViewer 保存 tasklist.json 异常: {ExceptionFormatter.FormatEx(ex)}"); + _panel?.Repaint(); } - - // 确保下拉框字体一致(防止 Designer 未设置) - if (cmbTaskKind != null) cmbTaskKind.Font = new Font("微软雅黑", 10F, FontStyle.Regular); - if (cmbStartType != null) cmbStartType.Font = new Font("微软雅黑", 10F, FontStyle.Regular); - } - catch { } + }); } - #region 初始化/加载 - private void InitOrLoadJson() + private static int Clamp(int v, int min, int max) => v < min ? min : (v > max ? max : v); + + private static bool TryParseClamp(string s, int min, int max, out int value) { - try + if (int.TryParse((s ?? "").Trim(), out value)) { - if (!File.Exists(jsonPath)) - File.WriteAllText(jsonPath, "[]"); - - var text = File.ReadAllText(jsonPath); - tasks = JsonConvert.DeserializeObject>(text) ?? new List(); - } - catch (Exception ex) - { - tasks = new List(); - System.Diagnostics.Debug.WriteLine($"Load tasks failed: {ex}"); + value = Clamp(value, min, max); + return true; } + value = min; + return false; } - #endregion - - #region ID 自增逻辑 - - /// - /// 获取下一个可用的任务ID(当前最大ID + 1) - /// - /// 新的任务ID - private int GetNextTaskId() - { - if (tasks == null || tasks.Count == 0) - return 1; - - int maxId = tasks.Max(t => t.Id); - return maxId + 1; - } - - #endregion - - #region OwnerDraw 绘制(已按要求:表头加粗黑字 + 醒目底色,选中行为另一种颜色) - private void lstTasks_DrawColumnHeader(object sender, DrawListViewColumnHeaderEventArgs e) - { - try - { - // 与 ChargeStationManagementForm 表头保持一致的深蓝背景与白色加粗字体 - using (var backBrush = new SolidBrush(Color.FromArgb(63, 81, 181))) // 深蓝(与 Charge 界面一致) - using (var textBrush = new SolidBrush(Color.White)) // 白色文字 - using (var font = new Font("微软雅黑", 9, FontStyle.Bold)) - { - e.Graphics.FillRectangle(backBrush, e.Bounds); - var sf = new StringFormat { LineAlignment = StringAlignment.Center, Alignment = StringAlignment.Near }; - var rect = e.Bounds; - rect.Inflate(-8, 0); - e.Graphics.DrawString(e.Header.Text, font, textBrush, rect, sf); - - // 分隔线 - using (var pen = new Pen(Color.FromArgb(200, 200, 200))) - { - e.Graphics.DrawLine(pen, e.Bounds.Left, e.Bounds.Bottom - 1, e.Bounds.Right, e.Bounds.Bottom - 1); - } - } - } - catch - { - e.DrawBackground(); - e.DrawText(); - } - } - - private void lstTasks_DrawItem(object sender, DrawListViewItemEventArgs e) - { - // 由 DrawSubItem 绘制全部内容以保证每列对齐 - } - - private void lstTasks_DrawSubItem(object sender, DrawListViewSubItemEventArgs e) - { - try - { - var item = e.Item; - bool selected = item.Selected; - Rectangle bounds = e.Bounds; - - // 选中行颜色:与 ChargeStationManagementForm 保持一致的蓝色强调 - Color selectedBack = Color.FromArgb(0, 120, 215); - Color selectedFore = Color.White; - - // 非选中行交替背景 - Color evenBack = Color.White; - Color oddBack = Color.FromArgb(250, 251, 253); - Color normalFore = Color.FromArgb(33, 33, 33); - - // 填充背景 - if (selected) - { - using (var selBrush = new SolidBrush(selectedBack)) - { - e.Graphics.FillRectangle(selBrush, bounds); - } - } - else - { - using (var back = new SolidBrush(e.ItemIndex % 2 == 0 ? evenBack : oddBack)) - { - e.Graphics.FillRectangle(back, bounds); - } - } - - // 绘制文本(加一点内边距) - string text = e.SubItem.Text ?? string.Empty; - Color fore = selected ? selectedFore : normalFore; - TextFormatFlags flags = TextFormatFlags.Left | TextFormatFlags.VerticalCenter; - Rectangle textRect = bounds; - textRect.Inflate(-6, 0); - - using (var font = new Font("微软雅黑", 9)) - { - TextRenderer.DrawText(e.Graphics, text, font, textRect, fore, flags); - } - } - catch - { - e.DrawBackground(); - e.DrawText(); - } - } - #endregion - - #region 渲染/保存 - private void RenderListView() - { - try - { - if (lstTasks == null) return; - lstTasks.BeginUpdate(); - lstTasks.Items.Clear(); - foreach (var t in tasks) - { - var lvi = new ListViewItem(new[] - { - t.Id.ToString(), // ID 列 - t.Kind.ToString(), - t.CurrentStationId.ToString(), - t.TargetStationId.ToString(), - t.TrafficControl.ToString(), - t.Priority.ToString(), - t.IsViaPoint ? "是" : "否", - t.StartType.ToString() - }); - lstTasks.Items.Add(lvi); - } - lstTasks.EndUpdate(); - } - catch (Exception ex) - { - System.Diagnostics.Debug.WriteLine($"RenderListView failed: {ex}"); - } - } - - private void Save() - { - try - { - File.WriteAllText(jsonPath, JsonConvert.SerializeObject(tasks, Formatting.Indented)); - } - catch (Exception ex) - { - MessageBox.Show("保存失败:" + ex.Message); - } - } - #endregion - - #region 按钮事件(在同一界面新增/编辑) - private void UpdateSaveButtonText() - { - if (btnSave != null) - { - // 文案固定为"保存" - btnSave.Text = "保存"; - } - } - - private void btnSave_Click(object sender, EventArgs e) - { - try - { - // 从面板读取值,直接在界面内编辑/新增 - Enum.TryParse(cmbTaskKind?.SelectedItem?.ToString() ?? "Loop", out var kind); - Enum.TryParse(cmbStartType?.SelectedItem?.ToString() ?? "AutoLoop", out var st); - - if (editingIndex >= 0 && editingIndex < tasks.Count) - { - // 更新模式:保留原有ID - var existingTask = tasks[editingIndex]; - existingTask.Kind = kind; - existingTask.CurrentStationId = (int)(numCurrent?.Value ?? 0); - existingTask.TargetStationId = (int)(numTarget?.Value ?? 0); - existingTask.TrafficControl = (int)(numTraffic?.Value ?? 0); - existingTask.Priority = (int)(numPriority?.Value ?? 1); - existingTask.IsViaPoint = chkViaPoint?.Checked ?? false; - existingTask.StartType = st; - } - else - { - // 新增模式:自动分配新ID - var t = new LoopTask - { - Id = GetNextTaskId(), // 自增ID - Kind = kind, - CurrentStationId = (int)(numCurrent?.Value ?? 0), - TargetStationId = (int)(numTarget?.Value ?? 0), - TrafficControl = (int)(numTraffic?.Value ?? 0), - Priority = (int)(numPriority?.Value ?? 1), - IsViaPoint = chkViaPoint?.Checked ?? false, - StartType = st - }; - tasks.Add(t); - } - - Save(); - RenderListView(); - // 恢复新增状态 - editingIndex = -1; - UpdateSaveButtonText(); - ClearPanelInputs(); - } - catch (Exception ex) - { - System.Diagnostics.Debug.WriteLine($"btnSave_Click error: {ex}"); - MessageBox.Show("操作失败:" + ex.Message); - } - } - - private void btnCancel_Click(object sender, EventArgs e) - { - // 取消编辑,清空面板并回到"添加"模式 - editingIndex = -1; - UpdateSaveButtonText(); - ClearPanelInputs(); - } - - private void btnEdit_Click(object sender, EventArgs e) - { - try - { - if (lstTasks == null || lstTasks.SelectedIndices.Count == 0) return; - int idx = lstTasks.SelectedIndices[0]; - if (idx < 0 || idx >= tasks.Count) return; - - editingIndex = idx; - LoadTaskToPanel(tasks[idx]); - UpdateSaveButtonText(); - } - catch (Exception ex) - { - System.Diagnostics.Debug.WriteLine($"btnEdit_Click error: {ex}"); - } - } - - private void btnDelete_Click(object sender, EventArgs e) - { - // 兼容旧的删除按钮:复用统一删除逻辑 - OnDeleteSelectedTasks(); - } - #endregion - - #region 双击编辑(同面板) - private void lstTasks_MouseDoubleClick(object sender, MouseEventArgs e) - { - try - { - if (lstTasks == null) return; - var item = lstTasks.GetItemAt(e.X, e.Y); - if (item == null) return; - int idx = item.Index; - if (idx < 0 || idx >= tasks.Count) return; - - editingIndex = idx; - LoadTaskToPanel(tasks[idx]); - UpdateSaveButtonText(); - } - catch (Exception ex) - { - System.Diagnostics.Debug.WriteLine($"lstTasks_MouseDoubleClick error: {ex}"); - } - } - #endregion - - #region 辅助:面板读写 - private void LoadTaskToPanel(LoopTask t) - { - if (t == null) return; - try - { - // 显示当前编辑的任务ID(只读显示) - if (lblEditingId != null) lblEditingId.Text = $"编辑任务 ID: {t.Id}"; - - if (cmbTaskKind != null) cmbTaskKind.SelectedItem = t.Kind.ToString(); - if (numCurrent != null) numCurrent.Value = Math.Max(numCurrent.Minimum, Math.Min(numCurrent.Maximum, t.CurrentStationId)); - if (numTarget != null) numTarget.Value = Math.Max(numTarget.Minimum, Math.Min(numTarget.Maximum, t.TargetStationId)); - if (numTraffic != null) numTraffic.Value = Math.Max(numTraffic.Minimum, Math.Min(numTraffic.Maximum, t.TrafficControl)); - if (numPriority != null) numPriority.Value = Math.Max(numPriority.Minimum, Math.Min(numPriority.Maximum, t.Priority)); - if (chkViaPoint != null) chkViaPoint.Checked = t.IsViaPoint; - if (cmbStartType != null) cmbStartType.SelectedItem = t.StartType.ToString(); - } - catch (Exception ex) - { - System.Diagnostics.Debug.WriteLine($"LoadTaskToPanel error: {ex}"); - } - } - - private void ClearPanelInputs() - { - try - { - // 清除编辑ID显示 - if (lblEditingId != null) lblEditingId.Text = "新增任务"; - - if (cmbTaskKind != null) cmbTaskKind.SelectedIndex = 0; - if (numCurrent != null) numCurrent.Value = 0; - if (numTarget != null) numTarget.Value = 0; - if (numTraffic != null) numTraffic.Value = 0; - if (numPriority != null) numPriority.Value = 1; - if (chkViaPoint != null) chkViaPoint.Checked = false; - if (cmbStartType != null) cmbStartType.SelectedIndex = 3; - } - catch (Exception ex) - { - System.Diagnostics.Debug.WriteLine($"ClearPanelInputs error: {ex}"); - } - } - - #endregion - - } -} \ No newline at end of file +} diff --git a/StandardScene.Core/Chained/LoopViewer.resx b/StandardScene.Core/Chained/LoopViewer.resx deleted file mode 100644 index 1af7de1..0000000 --- a/StandardScene.Core/Chained/LoopViewer.resx +++ /dev/null @@ -1,120 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - \ No newline at end of file diff --git a/StandardScene.Core/Chained/TransportMission.cs b/StandardScene.Core/Chained/TransportMission.cs index 84f9386..fcd3907 100644 --- a/StandardScene.Core/Chained/TransportMission.cs +++ b/StandardScene.Core/Chained/TransportMission.cs @@ -22,7 +22,6 @@ using System.Runtime.InteropServices; using System.Text; using System.Threading; using System.Threading.Tasks; -using System.Windows.Forms; using Microsoft.Win32.SafeHandles; namespace StandardScene.Chained @@ -377,7 +376,7 @@ namespace StandardScene.Chained { G.pushStatus("选择小车"); var selected = SimpleMonitor.selected.ToArray(); - if (selected.Length == 0) { MessageBox.Show("请先选择需要控制的小车!"); return; } + if (selected.Length == 0) { CycleUiHelper.Alert("提示", "请先选择需要控制的小车!"); return; } var obj = selected[0]; if (obj is Car car) { @@ -401,7 +400,7 @@ namespace StandardScene.Chained } else { - MessageBox.Show("请选择需要控制的小车!"); + CycleUiHelper.Alert("提示", "请选择需要控制的小车!"); } } catch @@ -428,7 +427,7 @@ namespace StandardScene.Chained { G.pushStatus("选择小车"); var selected = SimpleMonitor.selected.ToArray(); - if (selected.Length == 0) { MessageBox.Show("请先选择需要控制的小车!"); return; } + if (selected.Length == 0) { CycleUiHelper.Alert("提示", "请先选择需要控制的小车!"); return; } var obj = selected[0]; if (obj is Car car) { @@ -453,7 +452,7 @@ namespace StandardScene.Chained } else { - MessageBox.Show("请选择需要控制的小车!"); + CycleUiHelper.Alert("提示", "请选择需要控制的小车!"); } } catch diff --git a/StandardScene.Core/Charge/AlarmConfigManagementForm.Designer.cs b/StandardScene.Core/Charge/AlarmConfigManagementForm.Designer.cs deleted file mode 100644 index 58539ac..0000000 --- a/StandardScene.Core/Charge/AlarmConfigManagementForm.Designer.cs +++ /dev/null @@ -1,596 +0,0 @@ -namespace StandardScene.Charge -{ - partial class AlarmConfigManagementForm - { - /// - /// 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() - { - System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle3 = new System.Windows.Forms.DataGridViewCellStyle(); - System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle4 = new System.Windows.Forms.DataGridViewCellStyle(); - this.splitContainer = new System.Windows.Forms.SplitContainer(); - this.pnlList = new System.Windows.Forms.Panel(); - this.dgvAlarmConfigs = new System.Windows.Forms.DataGridView(); - this.pnlListButtons = new System.Windows.Forms.Panel(); - this.lblStatistics = new System.Windows.Forms.Label(); - this.btnClose = new System.Windows.Forms.Button(); - this.btnRefresh = new System.Windows.Forms.Button(); - this.pnlSearch = new System.Windows.Forms.Panel(); - this.cmbLevelFilter = new System.Windows.Forms.ComboBox(); - this.lblLevelFilter = new System.Windows.Forms.Label(); - this.txtSearch = new System.Windows.Forms.TextBox(); - this.lblSearch = new System.Windows.Forms.Label(); - this.pnlEdit = new System.Windows.Forms.Panel(); - this.grpEditInfo = new System.Windows.Forms.GroupBox(); - this.txtRemarks = new System.Windows.Forms.TextBox(); - this.lblRemarks = new System.Windows.Forms.Label(); - this.chkEnabled = new System.Windows.Forms.CheckBox(); - this.cmbLevel = new System.Windows.Forms.ComboBox(); - this.lblLevel = new System.Windows.Forms.Label(); - this.txtAlarmContent = new System.Windows.Forms.TextBox(); - this.lblAlarmContent = new System.Windows.Forms.Label(); - this.numAlarmCode = new System.Windows.Forms.NumericUpDown(); - this.lblAlarmCode = new System.Windows.Forms.Label(); - this.txtAlarmId = new System.Windows.Forms.TextBox(); - this.lblAlarmId = new System.Windows.Forms.Label(); - this.pnlEditButtons = new System.Windows.Forms.Panel(); - this.btnCancel = new System.Windows.Forms.Button(); - this.btnDelete = new System.Windows.Forms.Button(); - this.btnSave = new System.Windows.Forms.Button(); - this.colAlarmId = new System.Windows.Forms.DataGridViewTextBoxColumn(); - this.colAlarmCode = new System.Windows.Forms.DataGridViewTextBoxColumn(); - this.colAlarmContent = new System.Windows.Forms.DataGridViewTextBoxColumn(); - this.colLevel = new System.Windows.Forms.DataGridViewTextBoxColumn(); - this.colEnabled = new System.Windows.Forms.DataGridViewTextBoxColumn(); - this.colRemarks = new System.Windows.Forms.DataGridViewTextBoxColumn(); - ((System.ComponentModel.ISupportInitialize)(this.splitContainer)).BeginInit(); - this.splitContainer.Panel1.SuspendLayout(); - this.splitContainer.Panel2.SuspendLayout(); - this.splitContainer.SuspendLayout(); - this.pnlList.SuspendLayout(); - ((System.ComponentModel.ISupportInitialize)(this.dgvAlarmConfigs)).BeginInit(); - this.pnlListButtons.SuspendLayout(); - this.pnlSearch.SuspendLayout(); - this.pnlEdit.SuspendLayout(); - this.grpEditInfo.SuspendLayout(); - ((System.ComponentModel.ISupportInitialize)(this.numAlarmCode)).BeginInit(); - this.pnlEditButtons.SuspendLayout(); - this.SuspendLayout(); - // - // splitContainer - // - this.splitContainer.Dock = System.Windows.Forms.DockStyle.Fill; - this.splitContainer.Location = new System.Drawing.Point(0, 0); - this.splitContainer.Margin = new System.Windows.Forms.Padding(4); - this.splitContainer.Name = "splitContainer"; - // - // splitContainer.Panel1 - // - this.splitContainer.Panel1.Controls.Add(this.pnlList); - // - // splitContainer.Panel2 - // - this.splitContainer.Panel2.Controls.Add(this.pnlEdit); - this.splitContainer.Size = new System.Drawing.Size(1400, 750); - this.splitContainer.SplitterDistance = 900; - this.splitContainer.SplitterWidth = 5; - this.splitContainer.TabIndex = 0; - // - // pnlList - // - this.pnlList.Controls.Add(this.dgvAlarmConfigs); - this.pnlList.Controls.Add(this.pnlListButtons); - this.pnlList.Controls.Add(this.pnlSearch); - this.pnlList.Dock = System.Windows.Forms.DockStyle.Fill; - this.pnlList.Location = new System.Drawing.Point(0, 0); - this.pnlList.Margin = new System.Windows.Forms.Padding(4); - this.pnlList.Name = "pnlList"; - this.pnlList.Size = new System.Drawing.Size(900, 750); - this.pnlList.TabIndex = 0; - // - // dgvAlarmConfigs - // - this.dgvAlarmConfigs.AllowUserToAddRows = false; - this.dgvAlarmConfigs.AllowUserToDeleteRows = false; - this.dgvAlarmConfigs.AutoSizeColumnsMode = System.Windows.Forms.DataGridViewAutoSizeColumnsMode.Fill; - this.dgvAlarmConfigs.BackgroundColor = System.Drawing.Color.White; - this.dgvAlarmConfigs.BorderStyle = System.Windows.Forms.BorderStyle.None; - this.dgvAlarmConfigs.CellBorderStyle = System.Windows.Forms.DataGridViewCellBorderStyle.SingleHorizontal; - dataGridViewCellStyle3.Alignment = System.Windows.Forms.DataGridViewContentAlignment.MiddleLeft; - dataGridViewCellStyle3.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(63)))), ((int)(((byte)(81)))), ((int)(((byte)(181))))); - dataGridViewCellStyle3.Font = new System.Drawing.Font("微软雅黑", 9F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - dataGridViewCellStyle3.ForeColor = System.Drawing.Color.White; - dataGridViewCellStyle3.SelectionBackColor = System.Drawing.Color.FromArgb(((int)(((byte)(63)))), ((int)(((byte)(81)))), ((int)(((byte)(181))))); - dataGridViewCellStyle3.SelectionForeColor = System.Drawing.SystemColors.HighlightText; - dataGridViewCellStyle3.WrapMode = System.Windows.Forms.DataGridViewTriState.True; - this.dgvAlarmConfigs.ColumnHeadersDefaultCellStyle = dataGridViewCellStyle3; - this.dgvAlarmConfigs.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize; - this.dgvAlarmConfigs.Columns.AddRange(new System.Windows.Forms.DataGridViewColumn[] { - this.colAlarmId, - this.colAlarmCode, - this.colAlarmContent, - this.colLevel, - this.colEnabled, - this.colRemarks}); - dataGridViewCellStyle4.Alignment = System.Windows.Forms.DataGridViewContentAlignment.MiddleLeft; - dataGridViewCellStyle4.BackColor = System.Drawing.Color.White; - dataGridViewCellStyle4.Font = new System.Drawing.Font("微软雅黑", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - dataGridViewCellStyle4.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(64)))), ((int)(((byte)(64)))), ((int)(((byte)(64))))); - dataGridViewCellStyle4.SelectionBackColor = System.Drawing.Color.FromArgb(((int)(((byte)(197)))), ((int)(((byte)(202)))), ((int)(((byte)(233))))); - dataGridViewCellStyle4.SelectionForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(33)))), ((int)(((byte)(33)))), ((int)(((byte)(33))))); - dataGridViewCellStyle4.WrapMode = System.Windows.Forms.DataGridViewTriState.False; - this.dgvAlarmConfigs.DefaultCellStyle = dataGridViewCellStyle4; - this.dgvAlarmConfigs.Dock = System.Windows.Forms.DockStyle.Fill; - this.dgvAlarmConfigs.EnableHeadersVisualStyles = false; - this.dgvAlarmConfigs.GridColor = System.Drawing.Color.FromArgb(((int)(((byte)(224)))), ((int)(((byte)(224)))), ((int)(((byte)(224))))); - this.dgvAlarmConfigs.Location = new System.Drawing.Point(0, 62); - this.dgvAlarmConfigs.Margin = new System.Windows.Forms.Padding(4); - this.dgvAlarmConfigs.MultiSelect = false; - this.dgvAlarmConfigs.Name = "dgvAlarmConfigs"; - this.dgvAlarmConfigs.ReadOnly = true; - this.dgvAlarmConfigs.RowHeadersVisible = false; - this.dgvAlarmConfigs.RowHeadersWidth = 30; - this.dgvAlarmConfigs.RowTemplate.Height = 35; - this.dgvAlarmConfigs.SelectionMode = System.Windows.Forms.DataGridViewSelectionMode.FullRowSelect; - this.dgvAlarmConfigs.Size = new System.Drawing.Size(900, 600); - this.dgvAlarmConfigs.TabIndex = 2; - this.dgvAlarmConfigs.CellDoubleClick += new System.Windows.Forms.DataGridViewCellEventHandler(this.dgvAlarmConfigs_CellDoubleClick); - // - // pnlListButtons - // - this.pnlListButtons.Controls.Add(this.lblStatistics); - this.pnlListButtons.Controls.Add(this.btnClose); - this.pnlListButtons.Controls.Add(this.btnRefresh); - this.pnlListButtons.Dock = System.Windows.Forms.DockStyle.Bottom; - this.pnlListButtons.Location = new System.Drawing.Point(0, 662); - this.pnlListButtons.Margin = new System.Windows.Forms.Padding(4); - this.pnlListButtons.Name = "pnlListButtons"; - this.pnlListButtons.Padding = new System.Windows.Forms.Padding(13, 12, 13, 12); - this.pnlListButtons.Size = new System.Drawing.Size(900, 88); - this.pnlListButtons.TabIndex = 1; - // - // lblStatistics - // - this.lblStatistics.AutoSize = true; - this.lblStatistics.Font = new System.Drawing.Font("微软雅黑", 10F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - this.lblStatistics.Location = new System.Drawing.Point(20, 31); - this.lblStatistics.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); - this.lblStatistics.Name = "lblStatistics"; - this.lblStatistics.Size = new System.Drawing.Size(204, 24); - this.lblStatistics.TabIndex = 2; - this.lblStatistics.Text = "总数: 0 | 启用: 0 | 禁用: 0"; - // - // btnClose - // - this.btnClose.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); - this.btnClose.Font = new System.Drawing.Font("微软雅黑", 10F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - this.btnClose.Location = new System.Drawing.Point(753, 19); - this.btnClose.Margin = new System.Windows.Forms.Padding(4); - this.btnClose.Name = "btnClose"; - this.btnClose.Size = new System.Drawing.Size(120, 50); - this.btnClose.TabIndex = 1; - this.btnClose.Text = "关闭"; - this.btnClose.UseVisualStyleBackColor = true; - this.btnClose.Click += new System.EventHandler(this.btnClose_Click); - // - // btnRefresh - // - this.btnRefresh.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); - this.btnRefresh.Font = new System.Drawing.Font("微软雅黑", 10F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - this.btnRefresh.Location = new System.Drawing.Point(620, 19); - this.btnRefresh.Margin = new System.Windows.Forms.Padding(4); - this.btnRefresh.Name = "btnRefresh"; - this.btnRefresh.Size = new System.Drawing.Size(120, 50); - this.btnRefresh.TabIndex = 0; - this.btnRefresh.Text = "刷新"; - this.btnRefresh.UseVisualStyleBackColor = true; - this.btnRefresh.Click += new System.EventHandler(this.btnRefresh_Click); - // - // pnlSearch - // - this.pnlSearch.Controls.Add(this.cmbLevelFilter); - this.pnlSearch.Controls.Add(this.lblLevelFilter); - this.pnlSearch.Controls.Add(this.txtSearch); - this.pnlSearch.Controls.Add(this.lblSearch); - this.pnlSearch.Dock = System.Windows.Forms.DockStyle.Top; - this.pnlSearch.Location = new System.Drawing.Point(0, 0); - this.pnlSearch.Margin = new System.Windows.Forms.Padding(4); - this.pnlSearch.Name = "pnlSearch"; - this.pnlSearch.Padding = new System.Windows.Forms.Padding(13, 12, 13, 12); - this.pnlSearch.Size = new System.Drawing.Size(900, 62); - this.pnlSearch.TabIndex = 0; - // - // cmbLevelFilter - // - this.cmbLevelFilter.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; - this.cmbLevelFilter.Font = new System.Drawing.Font("微软雅黑", 10F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - this.cmbLevelFilter.FormattingEnabled = true; - this.cmbLevelFilter.Location = new System.Drawing.Point(550, 16); - this.cmbLevelFilter.Margin = new System.Windows.Forms.Padding(4); - this.cmbLevelFilter.Name = "cmbLevelFilter"; - this.cmbLevelFilter.Size = new System.Drawing.Size(150, 31); - this.cmbLevelFilter.TabIndex = 3; - this.cmbLevelFilter.SelectedIndexChanged += new System.EventHandler(this.cmbLevelFilter_SelectedIndexChanged); - // - // lblLevelFilter - // - this.lblLevelFilter.AutoSize = true; - this.lblLevelFilter.Font = new System.Drawing.Font("微软雅黑", 10F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - this.lblLevelFilter.Location = new System.Drawing.Point(463, 21); - this.lblLevelFilter.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); - this.lblLevelFilter.Name = "lblLevelFilter"; - this.lblLevelFilter.Size = new System.Drawing.Size(61, 23); - this.lblLevelFilter.TabIndex = 2; - this.lblLevelFilter.Text = "级别:"; - // - // txtSearch - // - this.txtSearch.Font = new System.Drawing.Font("微软雅黑", 10F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - this.txtSearch.Location = new System.Drawing.Point(100, 16); - this.txtSearch.Margin = new System.Windows.Forms.Padding(4); - this.txtSearch.Name = "txtSearch"; - this.txtSearch.Size = new System.Drawing.Size(300, 29); - this.txtSearch.TabIndex = 1; - this.txtSearch.TextChanged += new System.EventHandler(this.txtSearch_TextChanged); - // - // lblSearch - // - this.lblSearch.AutoSize = true; - this.lblSearch.Font = new System.Drawing.Font("微软雅黑", 10F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - this.lblSearch.Location = new System.Drawing.Point(13, 21); - this.lblSearch.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); - this.lblSearch.Name = "lblSearch"; - this.lblSearch.Size = new System.Drawing.Size(61, 23); - this.lblSearch.TabIndex = 0; - this.lblSearch.Text = "搜索:"; - // - // pnlEdit - // - this.pnlEdit.Controls.Add(this.grpEditInfo); - this.pnlEdit.Controls.Add(this.pnlEditButtons); - this.pnlEdit.Dock = System.Windows.Forms.DockStyle.Fill; - this.pnlEdit.Location = new System.Drawing.Point(0, 0); - this.pnlEdit.Margin = new System.Windows.Forms.Padding(4); - this.pnlEdit.Name = "pnlEdit"; - this.pnlEdit.Size = new System.Drawing.Size(495, 750); - this.pnlEdit.TabIndex = 0; - // - // grpEditInfo - // - this.grpEditInfo.Controls.Add(this.txtRemarks); - this.grpEditInfo.Controls.Add(this.lblRemarks); - this.grpEditInfo.Controls.Add(this.chkEnabled); - this.grpEditInfo.Controls.Add(this.cmbLevel); - this.grpEditInfo.Controls.Add(this.lblLevel); - this.grpEditInfo.Controls.Add(this.txtAlarmContent); - this.grpEditInfo.Controls.Add(this.lblAlarmContent); - this.grpEditInfo.Controls.Add(this.numAlarmCode); - this.grpEditInfo.Controls.Add(this.lblAlarmCode); - this.grpEditInfo.Controls.Add(this.txtAlarmId); - this.grpEditInfo.Controls.Add(this.lblAlarmId); - this.grpEditInfo.Dock = System.Windows.Forms.DockStyle.Fill; - this.grpEditInfo.Font = new System.Drawing.Font("微软雅黑", 10F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - this.grpEditInfo.Location = new System.Drawing.Point(0, 0); - this.grpEditInfo.Margin = new System.Windows.Forms.Padding(4); - this.grpEditInfo.Name = "grpEditInfo"; - this.grpEditInfo.Padding = new System.Windows.Forms.Padding(20, 19, 20, 19); - this.grpEditInfo.Size = new System.Drawing.Size(495, 625); - this.grpEditInfo.TabIndex = 1; - this.grpEditInfo.TabStop = false; - this.grpEditInfo.Text = "报警配置信息"; - // - // txtRemarks - // - this.txtRemarks.Font = new System.Drawing.Font("微软雅黑", 10F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - this.txtRemarks.Location = new System.Drawing.Point(130, 350); - this.txtRemarks.Margin = new System.Windows.Forms.Padding(4); - this.txtRemarks.Multiline = true; - this.txtRemarks.Name = "txtRemarks"; - this.txtRemarks.ScrollBars = System.Windows.Forms.ScrollBars.Vertical; - this.txtRemarks.Size = new System.Drawing.Size(330, 80); - this.txtRemarks.TabIndex = 10; - // - // lblRemarks - // - this.lblRemarks.Font = new System.Drawing.Font("微软雅黑", 10F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - this.lblRemarks.Location = new System.Drawing.Point(27, 350); - this.lblRemarks.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); - this.lblRemarks.Name = "lblRemarks"; - this.lblRemarks.Size = new System.Drawing.Size(100, 31); - this.lblRemarks.TabIndex = 9; - this.lblRemarks.Text = "备注:"; - this.lblRemarks.TextAlign = System.Drawing.ContentAlignment.TopRight; - // - // chkEnabled - // - this.chkEnabled.AutoSize = true; - this.chkEnabled.Checked = true; - this.chkEnabled.CheckState = System.Windows.Forms.CheckState.Checked; - this.chkEnabled.Font = new System.Drawing.Font("微软雅黑", 10F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - this.chkEnabled.Location = new System.Drawing.Point(130, 300); - this.chkEnabled.Margin = new System.Windows.Forms.Padding(4); - this.chkEnabled.Name = "chkEnabled"; - this.chkEnabled.Size = new System.Drawing.Size(83, 27); - this.chkEnabled.TabIndex = 8; - this.chkEnabled.Text = "启用中"; - this.chkEnabled.UseVisualStyleBackColor = true; - this.chkEnabled.Visible = false; - // - // cmbLevel - // - this.cmbLevel.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; - this.cmbLevel.Font = new System.Drawing.Font("微软雅黑", 10F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - this.cmbLevel.FormattingEnabled = true; - this.cmbLevel.Location = new System.Drawing.Point(130, 244); - this.cmbLevel.Margin = new System.Windows.Forms.Padding(4); - this.cmbLevel.Name = "cmbLevel"; - this.cmbLevel.Size = new System.Drawing.Size(330, 31); - this.cmbLevel.TabIndex = 7; - // - // lblLevel - // - this.lblLevel.Font = new System.Drawing.Font("微软雅黑", 10F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - this.lblLevel.Location = new System.Drawing.Point(27, 244); - this.lblLevel.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); - this.lblLevel.Name = "lblLevel"; - this.lblLevel.Size = new System.Drawing.Size(100, 31); - this.lblLevel.TabIndex = 6; - this.lblLevel.Text = "报警级别:"; - this.lblLevel.TextAlign = System.Drawing.ContentAlignment.MiddleRight; - // - // txtAlarmContent - // - this.txtAlarmContent.Font = new System.Drawing.Font("微软雅黑", 10F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - this.txtAlarmContent.Location = new System.Drawing.Point(130, 181); - this.txtAlarmContent.Margin = new System.Windows.Forms.Padding(4); - this.txtAlarmContent.Multiline = true; - this.txtAlarmContent.Name = "txtAlarmContent"; - this.txtAlarmContent.Size = new System.Drawing.Size(330, 50); - this.txtAlarmContent.TabIndex = 5; - // - // lblAlarmContent - // - this.lblAlarmContent.Font = new System.Drawing.Font("微软雅黑", 10F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - this.lblAlarmContent.Location = new System.Drawing.Point(27, 181); - this.lblAlarmContent.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); - this.lblAlarmContent.Name = "lblAlarmContent"; - this.lblAlarmContent.Size = new System.Drawing.Size(100, 31); - this.lblAlarmContent.TabIndex = 4; - this.lblAlarmContent.Text = "报警内容:"; - this.lblAlarmContent.TextAlign = System.Drawing.ContentAlignment.MiddleRight; - // - // numAlarmCode - // - this.numAlarmCode.Font = new System.Drawing.Font("微软雅黑", 10F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - this.numAlarmCode.Location = new System.Drawing.Point(130, 119); - this.numAlarmCode.Margin = new System.Windows.Forms.Padding(4); - this.numAlarmCode.Maximum = new decimal(new int[] { - 99999, - 0, - 0, - 0}); - this.numAlarmCode.Name = "numAlarmCode"; - this.numAlarmCode.Size = new System.Drawing.Size(330, 29); - this.numAlarmCode.TabIndex = 3; - // - // lblAlarmCode - // - this.lblAlarmCode.Font = new System.Drawing.Font("微软雅黑", 10F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - this.lblAlarmCode.Location = new System.Drawing.Point(27, 119); - this.lblAlarmCode.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); - this.lblAlarmCode.Name = "lblAlarmCode"; - this.lblAlarmCode.Size = new System.Drawing.Size(100, 31); - this.lblAlarmCode.TabIndex = 2; - this.lblAlarmCode.Text = "报警编码:"; - this.lblAlarmCode.TextAlign = System.Drawing.ContentAlignment.MiddleRight; - // - // txtAlarmId - // - this.txtAlarmId.BackColor = System.Drawing.Color.LightGray; - this.txtAlarmId.Font = new System.Drawing.Font("微软雅黑", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - this.txtAlarmId.Location = new System.Drawing.Point(130, 56); - this.txtAlarmId.Margin = new System.Windows.Forms.Padding(4); - this.txtAlarmId.Name = "txtAlarmId"; - this.txtAlarmId.ReadOnly = true; - this.txtAlarmId.Size = new System.Drawing.Size(330, 27); - this.txtAlarmId.TabIndex = 1; - this.txtAlarmId.Visible = false; - // - // lblAlarmId - // - this.lblAlarmId.Font = new System.Drawing.Font("微软雅黑", 10F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - this.lblAlarmId.Location = new System.Drawing.Point(27, 56); - this.lblAlarmId.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); - this.lblAlarmId.Name = "lblAlarmId"; - this.lblAlarmId.Size = new System.Drawing.Size(100, 31); - this.lblAlarmId.TabIndex = 0; - this.lblAlarmId.Text = "编号:"; - this.lblAlarmId.TextAlign = System.Drawing.ContentAlignment.MiddleRight; - this.lblAlarmId.Visible = false; - // - // pnlEditButtons - // - this.pnlEditButtons.Controls.Add(this.btnCancel); - this.pnlEditButtons.Controls.Add(this.btnDelete); - this.pnlEditButtons.Controls.Add(this.btnSave); - this.pnlEditButtons.Dock = System.Windows.Forms.DockStyle.Bottom; - this.pnlEditButtons.Location = new System.Drawing.Point(0, 625); - this.pnlEditButtons.Margin = new System.Windows.Forms.Padding(4); - this.pnlEditButtons.Name = "pnlEditButtons"; - this.pnlEditButtons.Padding = new System.Windows.Forms.Padding(13, 12, 13, 12); - this.pnlEditButtons.Size = new System.Drawing.Size(495, 125); - this.pnlEditButtons.TabIndex = 0; - // - // btnCancel - // - this.btnCancel.Font = new System.Drawing.Font("微软雅黑", 11F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - this.btnCancel.Location = new System.Drawing.Point(333, 25); - this.btnCancel.Margin = new System.Windows.Forms.Padding(4); - this.btnCancel.Name = "btnCancel"; - this.btnCancel.Size = new System.Drawing.Size(133, 62); - this.btnCancel.TabIndex = 2; - this.btnCancel.Text = "取消"; - this.btnCancel.UseVisualStyleBackColor = true; - this.btnCancel.Click += new System.EventHandler(this.btnCancel_Click); - // - // btnDelete - // - this.btnDelete.BackColor = System.Drawing.Color.LightCoral; - this.btnDelete.Font = new System.Drawing.Font("微软雅黑", 11F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - this.btnDelete.Location = new System.Drawing.Point(180, 25); - this.btnDelete.Margin = new System.Windows.Forms.Padding(4); - this.btnDelete.Name = "btnDelete"; - this.btnDelete.Size = new System.Drawing.Size(133, 62); - this.btnDelete.TabIndex = 1; - this.btnDelete.Text = "删除"; - this.btnDelete.UseVisualStyleBackColor = false; - this.btnDelete.Click += new System.EventHandler(this.btnDelete_Click); - // - // btnSave - // - this.btnSave.BackColor = System.Drawing.Color.LightBlue; - this.btnSave.Font = new System.Drawing.Font("微软雅黑", 11F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - this.btnSave.Location = new System.Drawing.Point(27, 25); - this.btnSave.Margin = new System.Windows.Forms.Padding(4); - this.btnSave.Name = "btnSave"; - this.btnSave.Size = new System.Drawing.Size(133, 62); - this.btnSave.TabIndex = 0; - this.btnSave.Text = "新增"; - this.btnSave.UseVisualStyleBackColor = false; - this.btnSave.Click += new System.EventHandler(this.btnSave_Click); - // - // colAlarmId - // - this.colAlarmId.HeaderText = "编号"; - this.colAlarmId.MinimumWidth = 6; - this.colAlarmId.Name = "colAlarmId"; - this.colAlarmId.ReadOnly = true; - this.colAlarmId.Visible = false; - // - // colAlarmCode - // - this.colAlarmCode.HeaderText = "报警编码"; - this.colAlarmCode.MinimumWidth = 6; - this.colAlarmCode.Name = "colAlarmCode"; - this.colAlarmCode.ReadOnly = true; - // - // colAlarmContent - // - this.colAlarmContent.HeaderText = "报警内容"; - this.colAlarmContent.MinimumWidth = 6; - this.colAlarmContent.Name = "colAlarmContent"; - this.colAlarmContent.ReadOnly = true; - // - // colLevel - // - this.colLevel.HeaderText = "级别"; - this.colLevel.MinimumWidth = 6; - this.colLevel.Name = "colLevel"; - this.colLevel.ReadOnly = true; - // - // colEnabled - // - this.colEnabled.HeaderText = "启用"; - this.colEnabled.MinimumWidth = 6; - this.colEnabled.Name = "colEnabled"; - this.colEnabled.ReadOnly = true; - // - // colRemarks - // - this.colRemarks.HeaderText = "备注"; - this.colRemarks.MinimumWidth = 6; - this.colRemarks.Name = "colRemarks"; - this.colRemarks.ReadOnly = true; - // - // AlarmConfigManagementForm - // - this.AutoScaleDimensions = new System.Drawing.SizeF(8F, 15F); - this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - this.ClientSize = new System.Drawing.Size(1400, 750); - this.Controls.Add(this.splitContainer); - this.Margin = new System.Windows.Forms.Padding(4); - this.MinimumSize = new System.Drawing.Size(1200, 600); - this.Name = "AlarmConfigManagementForm"; - this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen; - this.Text = "报警配置管理"; - this.splitContainer.Panel1.ResumeLayout(false); - this.splitContainer.Panel2.ResumeLayout(false); - ((System.ComponentModel.ISupportInitialize)(this.splitContainer)).EndInit(); - this.splitContainer.ResumeLayout(false); - this.pnlList.ResumeLayout(false); - ((System.ComponentModel.ISupportInitialize)(this.dgvAlarmConfigs)).EndInit(); - this.pnlListButtons.ResumeLayout(false); - this.pnlListButtons.PerformLayout(); - this.pnlSearch.ResumeLayout(false); - this.pnlSearch.PerformLayout(); - this.pnlEdit.ResumeLayout(false); - this.grpEditInfo.ResumeLayout(false); - this.grpEditInfo.PerformLayout(); - ((System.ComponentModel.ISupportInitialize)(this.numAlarmCode)).EndInit(); - this.pnlEditButtons.ResumeLayout(false); - this.ResumeLayout(false); - - } - - #endregion - - private System.Windows.Forms.SplitContainer splitContainer; - private System.Windows.Forms.Panel pnlList; - private System.Windows.Forms.DataGridView dgvAlarmConfigs; - private System.Windows.Forms.Panel pnlListButtons; - private System.Windows.Forms.Label lblStatistics; - private System.Windows.Forms.Button btnClose; - private System.Windows.Forms.Button btnRefresh; - private System.Windows.Forms.Panel pnlSearch; - private System.Windows.Forms.ComboBox cmbLevelFilter; - private System.Windows.Forms.Label lblLevelFilter; - private System.Windows.Forms.TextBox txtSearch; - private System.Windows.Forms.Label lblSearch; - private System.Windows.Forms.Panel pnlEdit; - private System.Windows.Forms.GroupBox grpEditInfo; - private System.Windows.Forms.TextBox txtRemarks; - private System.Windows.Forms.Label lblRemarks; - private System.Windows.Forms.CheckBox chkEnabled; - private System.Windows.Forms.ComboBox cmbLevel; - private System.Windows.Forms.Label lblLevel; - private System.Windows.Forms.TextBox txtAlarmContent; - private System.Windows.Forms.Label lblAlarmContent; - private System.Windows.Forms.NumericUpDown numAlarmCode; - private System.Windows.Forms.Label lblAlarmCode; - private System.Windows.Forms.TextBox txtAlarmId; - private System.Windows.Forms.Label lblAlarmId; - private System.Windows.Forms.Panel pnlEditButtons; - private System.Windows.Forms.Button btnCancel; - private System.Windows.Forms.Button btnDelete; - private System.Windows.Forms.Button btnSave; - private System.Windows.Forms.DataGridViewTextBoxColumn colAlarmId; - private System.Windows.Forms.DataGridViewTextBoxColumn colAlarmCode; - private System.Windows.Forms.DataGridViewTextBoxColumn colAlarmContent; - private System.Windows.Forms.DataGridViewTextBoxColumn colLevel; - private System.Windows.Forms.DataGridViewTextBoxColumn colEnabled; - private System.Windows.Forms.DataGridViewTextBoxColumn colRemarks; - } -} - diff --git a/StandardScene.Core/Charge/AlarmConfigManagementForm.cs b/StandardScene.Core/Charge/AlarmConfigManagementForm.cs index 9f50a2e..fc15792 100644 --- a/StandardScene.Core/Charge/AlarmConfigManagementForm.cs +++ b/StandardScene.Core/Charge/AlarmConfigManagementForm.cs @@ -1,240 +1,313 @@ using System; +using System.Collections.Generic; using System.Drawing; using System.Linq; -using System.Windows.Forms; +using CycleGUI; +using StandardScene.Utils; namespace StandardScene.Charge { /// - /// 报警配置管理窗体 + /// 报警配置管理界面(CycleGUI 版,替代原 WinForms 窗体)。 /// - public partial class AlarmConfigManagementForm : Form + public class AlarmConfigManagementForm { - private readonly AlarmConfigDataService dataService; - private AlarmConfig selectedAlarmConfig; + private const string TableId = "alarm-config-list"; - public AlarmConfigManagementForm() + private static readonly string[] LevelNames = { "无", "低", "中", "高", "严重" }; + private static readonly string[] LevelFilterNames = { "全部", "无", "低", "中", "高", "严重" }; + + private static readonly Color CriticalRowColor = Color.FromArgb(255, 235, 238); + private static readonly Color HighRowColor = Color.FromArgb(255, 243, 224); + private static readonly Color MediumRowColor = Color.FromArgb(255, 249, 196); + private static readonly Color LowRowColor = Color.FromArgb(232, 245, 233); + private static readonly Color DisabledRowColor = Color.FromArgb(238, 238, 238); + + private static readonly AlarmConfigDataService DataService = AlarmConfigDataService.Instance; + + private static Panel _panel; + private static Panel _dialog; + private static List _allAlarms = new List(); + private static int _levelFilterIdx; + private static string _status = ""; + + /// 打开(或置前)报警配置管理面板。兼容原 new AlarmConfigManagementForm().Show() 调用方式。 + public void Show() => Open(); + + /// 打开(或置前)报警配置管理面板。 + public static void Open() { - try + if (_panel != null) { - InitializeComponent(); - dataService = AlarmConfigDataService.Instance; - - // 订阅Load事件,确保所有控件都已初始化后再加载数据 - this.Load += AlarmConfigManagementForm_Load; + try + { + _panel.BringToFront(); + return; + } + catch + { + _panel = null; + } } - catch (Exception ex) + + LoadAlarms(); + + var panel = GUI.DeclarePanel() + .ShowTitle("报警配置管理") + .SetDefaultDocking(Panel.Docking.None) + .InitSize(1100, 680) + .InitPos(false, 0, 0, 0.5f, 0.5f, 0.5f, 0.5f); + _panel = panel; + panel.IfTerminalQuit(() => _panel = null); + + panel.Define(pb => { - MessageBox.Show($"初始化报警配置管理窗体失败: {ex.Message}\n\n详细信息:\n{ex.StackTrace}", - "错误", MessageBoxButtons.OK, MessageBoxIcon.Error); - } - } - - /// - /// 窗体加载事件 - /// - private void AlarmConfigManagementForm_Load(object sender, EventArgs e) - { - InitializeForm(); - } - - /// - /// 初始化窗体 - /// - private void InitializeForm() - { - try - { - // 初始化报警级别下拉框 - if (cmbLevel != null) - { - cmbLevel.Items.Clear(); - cmbLevel.Items.Add("无"); - cmbLevel.Items.Add("低"); - cmbLevel.Items.Add("中"); - cmbLevel.Items.Add("高"); - cmbLevel.Items.Add("严重"); - cmbLevel.SelectedIndex = 2; // 默认选择"中" - } - - // 初始化级别筛选下拉框 - if (cmbLevelFilter != null) - { - cmbLevelFilter.Items.Clear(); - cmbLevelFilter.Items.Add("全部"); - cmbLevelFilter.Items.Add("无"); - cmbLevelFilter.Items.Add("低"); - cmbLevelFilter.Items.Add("中"); - cmbLevelFilter.Items.Add("高"); - cmbLevelFilter.Items.Add("严重"); - cmbLevelFilter.SelectedIndex = 0; - } - - LoadAlarmConfigs(); - ClearEditFields(); - } - catch (Exception ex) - { - MessageBox.Show($"初始化窗体失败: {ex.Message}\n\n{ex.StackTrace}", "错误", - MessageBoxButtons.OK, MessageBoxIcon.Error); - } - } - - /// - /// 加载报警配置列表 - /// - private void LoadAlarmConfigs() - { - try - { - if (dgvAlarmConfigs == null) - { - return; // 控件还未初始化,直接返回 - } - - var alarmConfigs = dataService.GetAllAlarmConfigs(); - - if (alarmConfigs == null) - { - alarmConfigs = new System.Collections.Generic.List(); - } - - // 根据级别筛选 - if (cmbLevelFilter != null && cmbLevelFilter.SelectedIndex > 0) - { - var filterLevel = (AlarmLevel)(cmbLevelFilter.SelectedIndex - 1); - alarmConfigs = alarmConfigs.Where(a => a.Level == filterLevel).ToList(); - } - - // 根据搜索文本筛选 - if (txtSearch != null && !string.IsNullOrWhiteSpace(txtSearch.Text)) - { - var searchText = txtSearch.Text.Trim().ToLower(); - alarmConfigs = alarmConfigs.Where(a => - a.AlarmId.ToLower().Contains(searchText) || - a.AlarmCode.ToString().Contains(searchText) || - a.AlarmContent.ToLower().Contains(searchText) - ).ToList(); - } - - dgvAlarmConfigs.Rows.Clear(); - - foreach (var alarm in alarmConfigs) - { - var index = dgvAlarmConfigs.Rows.Add( - alarm.AlarmId, - alarm.AlarmCode, - alarm.AlarmContent, - GetLevelText(alarm.Level), - alarm.Enabled ? "是" : "否", - alarm.Remarks - ); - - // 根据级别设置行颜色 - var row = dgvAlarmConfigs.Rows[index]; - switch (alarm.Level) - { - case AlarmLevel.Critical: - row.DefaultCellStyle.BackColor = Color.FromArgb(255, 235, 238); // 浅红色 - row.DefaultCellStyle.ForeColor = Color.FromArgb(183, 28, 28); - // 安全地创建粗体字体 - var baseFont = row.DefaultCellStyle.Font ?? dgvAlarmConfigs.DefaultCellStyle.Font ?? new Font("微软雅黑", 9F); - row.DefaultCellStyle.Font = new Font(baseFont, FontStyle.Bold); - break; - case AlarmLevel.High: - row.DefaultCellStyle.BackColor = Color.FromArgb(255, 243, 224); // 浅橙色 - row.DefaultCellStyle.ForeColor = Color.FromArgb(230, 81, 0); - break; - case AlarmLevel.Medium: - row.DefaultCellStyle.BackColor = Color.FromArgb(255, 249, 196); // 浅黄色 - row.DefaultCellStyle.ForeColor = Color.FromArgb(245, 127, 23); - break; - case AlarmLevel.Low: - row.DefaultCellStyle.BackColor = Color.FromArgb(232, 245, 233); // 浅绿色 - row.DefaultCellStyle.ForeColor = Color.FromArgb(46, 125, 50); - break; - } - - // 如果未启用,显示为灰色 - if (!alarm.Enabled) - { - row.DefaultCellStyle.BackColor = Color.FromArgb(238, 238, 238); - row.DefaultCellStyle.ForeColor = Color.FromArgb(158, 158, 158); - } - } - - UpdateStatistics(); - UpdateTitleWithFilter(alarmConfigs.Count); - } - catch (Exception ex) - { - MessageBox.Show($"加载数据失败: {ex.Message}", "错误", - MessageBoxButtons.OK, MessageBoxIcon.Error); - } - } - - /// - /// 更新统计信息 - /// - private void UpdateStatistics() - { - try - { - if (lblStatistics == null) + if (pb.Closing()) { + panel.Exit(); + _panel = null; return; } - var alarmConfigs = dataService.GetAllAlarmConfigs(); - if (alarmConfigs == null) + if (pb.Button("新增", distinct: "alarm-add")) + OpenEditDialog(null); + pb.SameLine(12); + if (pb.Button("刷新", distinct: "alarm-refresh")) { - alarmConfigs = new System.Collections.Generic.List(); + DataService.Reload(); + LoadAlarms(); + _status = "数据已刷新"; + } + pb.SameLine(12); + if (pb.Button("关闭", distinct: "alarm-close")) + { + panel.Exit(); + _panel = null; + return; } - var total = alarmConfigs.Count; - var enabled = alarmConfigs.Count(a => a.Enabled); - var disabled = total - enabled; - var critical = alarmConfigs.Count(a => a.Level == AlarmLevel.Critical); - var high = alarmConfigs.Count(a => a.Level == AlarmLevel.High); + pb.Separator(); + pb.DropdownBox("级别筛选", LevelFilterNames, ref _levelFilterIdx); - lblStatistics.Text = $"总数: {total} | 启用: {enabled} | 禁用: {disabled} | 严重: {critical} | 高级: {high}"; - } - catch (Exception ex) - { - System.Diagnostics.Debug.WriteLine($"更新统计信息失败: {ex.Message}"); - } + var filtered = GetFilteredAlarms(); + var totalCount = _allAlarms.Count; + if (_levelFilterIdx > 0) + pb.Label($"显示: {filtered.Count}/{totalCount} ({LevelFilterNames[_levelFilterIdx]})"); + else + pb.Label($"总数: {totalCount} | {GetStatisticsText()}"); + + pb.Table(TableId, + new[] { "报警编码", "报警内容", "级别", "启用", "备注", "操作" }, + filtered.Count, (row, i) => + { + var alarm = filtered[i]; + if (!alarm.Enabled) + row.SetColor(DisabledRowColor); + else if (alarm.Level == AlarmLevel.Critical) + row.SetColor(CriticalRowColor); + else if (alarm.Level == AlarmLevel.High) + row.SetColor(HighRowColor); + else if (alarm.Level == AlarmLevel.Medium) + row.SetColor(MediumRowColor); + else if (alarm.Level == AlarmLevel.Low) + row.SetColor(LowRowColor); + + row.Label($"{alarm.AlarmCode}"); + row.Label(alarm.AlarmContent ?? ""); + row.Label(GetLevelText(alarm.Level)); + row.Label(alarm.Enabled ? "是" : "否"); + row.Label(alarm.Remarks ?? ""); + + if (row.ButtonGroup(new[] { "编辑" }, new[] { "编辑该报警配置" }) == 0) + OpenEditDialog(alarm); + }, height: 16, enableSearch: true); + + if (!string.IsNullOrEmpty(_status)) + { + pb.Separator(); + pb.Label(_status); + } + + pb.Panel.Repaint(repaintTimeMs: 500); + }); } - /// - /// 更新标题显示筛选信息 - /// - private void UpdateTitleWithFilter(int displayCount) + private static void OpenEditDialog(AlarmConfig existing) + { + if (_dialog != null) + { + try + { + _dialog.BringToFront(); + return; + } + catch + { + _dialog = null; + } + } + + bool isAdd = existing == null; + var draft = isAdd ? new AlarmConfig() : existing; + + int levelIdx = (int)draft.Level; + if (levelIdx < 0 || levelIdx >= LevelNames.Length) + levelIdx = 2; + + string codeText = draft.AlarmCode.ToString(); + string contentText = draft.AlarmContent ?? ""; + string remarksText = draft.Remarks ?? ""; + bool enabled = draft.Enabled; + string err = ""; + + var dlg = GUI.DeclarePanel() + .ShowTitle(isAdd ? "新增报警配置" : $"编辑报警配置 [{draft.AlarmCode}]") + .SetDefaultDocking(Panel.Docking.None) + .InitSize(460, 420) + .InitPos(false, 0, 0, 0.5f, 0.5f, 0.5f, 0.5f); + _dialog = dlg; + dlg.IfTerminalQuit(() => _dialog = null); + + dlg.Define(pb => + { + if (pb.Closing()) + { + dlg.Exit(); + _dialog = null; + return; + } + + if (isAdd) + pb.Label("编号: (新增时自动生成)"); + else + pb.Label($"编号: {draft.AlarmId}"); + + var (code, _) = pb.TextInput("1. 报警编码 (0~99999)", codeText, alwaysReturnString: true); + codeText = code; + var (content, _) = pb.TextInput("2. 报警内容", contentText, alwaysReturnString: true); + contentText = content; + pb.DropdownBox("3. 报警级别", LevelNames, ref levelIdx); + pb.CheckBox("4. 启用", ref enabled); + var (remarks, _) = pb.TextInput("5. 备注", remarksText, alwaysReturnString: true); + remarksText = remarks; + + if (!string.IsNullOrEmpty(err)) + { + pb.Separator(); + pb.Label(err); + } + + pb.Separator(); + if (pb.Button(isAdd ? "新增" : "保存", distinct: "alarm-edit-save")) + { + if (!int.TryParse(codeText?.Trim(), out var alarmCode) || alarmCode < 0 || alarmCode > 99999) + { + err = "报警编码需为 0~99999 的整数"; + return; + } + + if (string.IsNullOrWhiteSpace(contentText)) + { + err = "报警内容不能为空"; + return; + } + + draft.AlarmCode = alarmCode; + draft.AlarmContent = contentText.Trim(); + draft.Level = (AlarmLevel)levelIdx; + draft.Enabled = enabled; + draft.Remarks = remarksText?.Trim() ?? ""; + + bool success; + string errorMessage; + if (isAdd) + success = DataService.AddAlarmConfig(draft, out errorMessage); + else + success = DataService.UpdateAlarmConfig(draft, out errorMessage); + + if (success) + { + CycleUiHelper.Alert("成功", "保存成功!"); + LoadAlarms(); + _status = isAdd ? "已新增报警配置" : $"已保存报警配置 [{draft.AlarmCode}]"; + dlg.Exit(); + _dialog = null; + _panel?.Repaint(); + } + else + { + err = $"保存失败: {errorMessage}"; + } + } + + if (!isAdd) + { + pb.SameLine(8); + if (pb.Button("删除", distinct: "alarm-edit-delete")) + { + var toDelete = draft; + CycleUiHelper.ConfirmThen( + $"确定要删除报警配置 [{toDelete.AlarmCode}] {toDelete.AlarmContent} 吗?", + () => + { + if (DataService.DeleteAlarmConfig(toDelete.AlarmId, out string errorMessage)) + { + CycleUiHelper.Alert("成功", "删除成功!"); + LoadAlarms(); + _status = $"已删除报警配置 [{toDelete.AlarmCode}]"; + dlg.Exit(); + _dialog = null; + _panel?.Repaint(); + } + else + { + CycleUiHelper.Alert("错误", $"删除失败: {errorMessage}"); + } + }); + } + } + + pb.SameLine(8); + if (pb.Button("取消", distinct: "alarm-edit-cancel")) + { + dlg.Exit(); + _dialog = null; + } + }); + } + + private static void LoadAlarms() { try { - var allConfigs = dataService.GetAllAlarmConfigs(); - var totalCount = allConfigs != null ? allConfigs.Count : 0; - - if (cmbLevelFilter != null && cmbLevelFilter.SelectedIndex > 0) - { - this.Text = $"报警配置管理 - 显示: {displayCount}/{totalCount} ({cmbLevelFilter.Text})"; - } - else - { - this.Text = $"报警配置管理 - 总数: {totalCount}"; - } + _allAlarms = DataService.GetAllAlarmConfigs() ?? new List(); } catch (Exception ex) { - System.Diagnostics.Debug.WriteLine($"更新标题失败: {ex.Message}"); - this.Text = "报警配置管理"; + _allAlarms = new List(); + CycleUiHelper.Alert("错误", $"加载数据失败: {ex.Message}"); } } - /// - /// 获取级别文本 - /// - private string GetLevelText(AlarmLevel level) + private static List GetFilteredAlarms() + { + IEnumerable query = _allAlarms; + if (_levelFilterIdx > 0) + query = query.Where(a => a.Level == (AlarmLevel)(_levelFilterIdx - 1)); + return query.ToList(); + } + + private static string GetStatisticsText() + { + var total = _allAlarms.Count; + var enabled = _allAlarms.Count(a => a.Enabled); + var disabled = total - enabled; + var critical = _allAlarms.Count(a => a.Level == AlarmLevel.Critical); + var high = _allAlarms.Count(a => a.Level == AlarmLevel.High); + return $"启用: {enabled} | 禁用: {disabled} | 严重: {critical} | 高级: {high}"; + } + + private static string GetLevelText(AlarmLevel level) { switch (level) { @@ -247,301 +320,5 @@ namespace StandardScene.Charge } } - /// - /// 清空编辑字段 - /// - private void ClearEditFields() - { - try - { - selectedAlarmConfig = null; - - if (txtAlarmId != null) - { - txtAlarmId.Text = ""; - txtAlarmId.Enabled = false; // 新增时编号自动生成 - } - - if (numAlarmCode != null) - { - numAlarmCode.Value = 0; - numAlarmCode.Enabled = true; - numAlarmCode.ReadOnly = false; - } - - if (txtAlarmContent != null) - { - txtAlarmContent.Text = ""; - txtAlarmContent.Enabled = true; - txtAlarmContent.ReadOnly = false; - } - - if (cmbLevel != null) - { - cmbLevel.SelectedIndex = 2; // 中 - cmbLevel.Enabled = true; - } - - if (chkEnabled != null) - { - chkEnabled.Checked = true; - chkEnabled.Enabled = true; - } - - if (txtRemarks != null) - { - txtRemarks.Text = ""; - txtRemarks.Enabled = true; - txtRemarks.ReadOnly = false; - } - - if (btnSave != null) - { - btnSave.Text = "新增"; - btnSave.Enabled = true; - } - - if (btnDelete != null) - { - btnDelete.Enabled = false; - } - } - catch (Exception ex) - { - System.Diagnostics.Debug.WriteLine($"清空编辑字段失败: {ex.Message}"); - } - } - - /// - /// 从字段创建报警配置 - /// - private AlarmConfig CreateAlarmConfigFromFields() - { - var alarmConfig = selectedAlarmConfig ?? new AlarmConfig(); - - alarmConfig.AlarmCode = (int)numAlarmCode.Value; - alarmConfig.AlarmContent = txtAlarmContent.Text.Trim(); - alarmConfig.Level = (AlarmLevel)cmbLevel.SelectedIndex; - alarmConfig.Enabled = chkEnabled.Checked; - alarmConfig.Remarks = txtRemarks.Text.Trim(); - - return alarmConfig; - } - - /// - /// 加载报警配置到编辑区 - /// - private void LoadAlarmConfigToFields(AlarmConfig alarmConfig) - { - try - { - selectedAlarmConfig = alarmConfig; - - // 填充数据 - if (txtAlarmId != null) - { - txtAlarmId.Text = alarmConfig.AlarmId; - txtAlarmId.Enabled = false; // 编号不可修改 - } - - if (numAlarmCode != null) - { - numAlarmCode.Value = alarmConfig.AlarmCode; - numAlarmCode.Enabled = true; - numAlarmCode.ReadOnly = false; - } - - if (txtAlarmContent != null) - { - txtAlarmContent.Text = alarmConfig.AlarmContent; - txtAlarmContent.Enabled = true; - txtAlarmContent.ReadOnly = false; - } - - if (cmbLevel != null) - { - cmbLevel.SelectedIndex = (int)alarmConfig.Level; - cmbLevel.Enabled = true; - } - - if (chkEnabled != null) - { - chkEnabled.Checked = alarmConfig.Enabled; - chkEnabled.Enabled = true; - } - - if (txtRemarks != null) - { - txtRemarks.Text = alarmConfig.Remarks ?? ""; - txtRemarks.Enabled = true; - txtRemarks.ReadOnly = false; - } - - // 设置按钮状态 - if (btnSave != null) - { - btnSave.Text = "保存"; - btnSave.Enabled = true; - } - - if (btnDelete != null) - { - btnDelete.Enabled = true; - } - } - catch (Exception ex) - { - MessageBox.Show($"加载数据到编辑区失败: {ex.Message}\n\n{ex.StackTrace}", "错误", - MessageBoxButtons.OK, MessageBoxIcon.Error); - } - } - - // ==================== 事件处理 ==================== - - private void btnSave_Click(object sender, EventArgs e) - { - try - { - // 验证报警编码 - if (numAlarmCode.Value < 0) - { - MessageBox.Show("报警编码不能为负数", "验证失败", - MessageBoxButtons.OK, MessageBoxIcon.Warning); - numAlarmCode.Focus(); - return; - } - - // 验证报警内容 - if (string.IsNullOrWhiteSpace(txtAlarmContent.Text)) - { - MessageBox.Show("报警内容不能为空", "验证失败", - MessageBoxButtons.OK, MessageBoxIcon.Warning); - txtAlarmContent.Focus(); - return; - } - - var alarmConfig = CreateAlarmConfigFromFields(); - string errorMessage; - - bool success; - if (selectedAlarmConfig == null) - { - // 新增 - success = dataService.AddAlarmConfig(alarmConfig, out errorMessage); - } - else - { - // 更新 - success = dataService.UpdateAlarmConfig(alarmConfig, out errorMessage); - } - - if (success) - { - MessageBox.Show("保存成功!", "提示", - MessageBoxButtons.OK, MessageBoxIcon.Information); - LoadAlarmConfigs(); - ClearEditFields(); - } - else - { - MessageBox.Show($"保存失败: {errorMessage}", "错误", - MessageBoxButtons.OK, MessageBoxIcon.Error); - } - } - catch (Exception ex) - { - MessageBox.Show($"保存失败: {ex.Message}", "错误", - MessageBoxButtons.OK, MessageBoxIcon.Error); - } - } - - private void btnDelete_Click(object sender, EventArgs e) - { - if (selectedAlarmConfig == null) - { - MessageBox.Show("请先选择要删除的报警配置", "提示", - MessageBoxButtons.OK, MessageBoxIcon.Warning); - return; - } - - var result = MessageBox.Show( - $"确定要删除报警配置 [{selectedAlarmConfig.AlarmCode}] {selectedAlarmConfig.AlarmContent} 吗?", - "确认删除", - MessageBoxButtons.YesNo, - MessageBoxIcon.Question); - - if (result == DialogResult.Yes) - { - if (dataService.DeleteAlarmConfig(selectedAlarmConfig.AlarmId, out string errorMessage)) - { - MessageBox.Show("删除成功!", "提示", - MessageBoxButtons.OK, MessageBoxIcon.Information); - LoadAlarmConfigs(); - ClearEditFields(); - } - else - { - MessageBox.Show($"删除失败: {errorMessage}", "错误", - MessageBoxButtons.OK, MessageBoxIcon.Error); - } - } - } - - private void btnCancel_Click(object sender, EventArgs e) - { - ClearEditFields(); - } - - private void btnRefresh_Click(object sender, EventArgs e) - { - dataService.Reload(); - LoadAlarmConfigs(); - } - - private void btnClose_Click(object sender, EventArgs e) - { - this.Close(); - } - - private void dgvAlarmConfigs_CellDoubleClick(object sender, DataGridViewCellEventArgs e) - { - try - { - if (e.RowIndex >= 0 && e.RowIndex < dgvAlarmConfigs.Rows.Count) - { - var row = dgvAlarmConfigs.Rows[e.RowIndex]; - if (row.Cells[0].Value != null) - { - var alarmId = row.Cells[1].Value.ToString(); - var alarmConfig = dataService.GetAlarmConfigAlarmCode(int.Parse(alarmId)); - if (alarmConfig != null) - { - LoadAlarmConfigToFields(alarmConfig); - } - else - { - MessageBox.Show($"未找到报警配置: {alarmId}", "提示", - MessageBoxButtons.OK, MessageBoxIcon.Warning); - } - } - } - } - catch (Exception ex) - { - MessageBox.Show($"加载报警配置失败: {ex.Message}", "错误", - MessageBoxButtons.OK, MessageBoxIcon.Error); - } - } - - private void txtSearch_TextChanged(object sender, EventArgs e) - { - LoadAlarmConfigs(); - } - - private void cmbLevelFilter_SelectedIndexChanged(object sender, EventArgs e) - { - LoadAlarmConfigs(); - } } } - diff --git a/StandardScene.Core/Charge/AlarmConfigManagementForm.resx b/StandardScene.Core/Charge/AlarmConfigManagementForm.resx deleted file mode 100644 index 1af7de1..0000000 --- a/StandardScene.Core/Charge/AlarmConfigManagementForm.resx +++ /dev/null @@ -1,120 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - \ No newline at end of file diff --git a/StandardScene.Core/Charge/ChargeStationHelper.cs b/StandardScene.Core/Charge/ChargeStationHelper.cs index 1d91eb4..1fa87cd 100644 --- a/StandardScene.Core/Charge/ChargeStationHelper.cs +++ b/StandardScene.Core/Charge/ChargeStationHelper.cs @@ -1,9 +1,10 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Windows.Forms; +using CycleGUI; using SimpleCore; using SimpleCore.Library; +using StandardScene.Utils; namespace StandardScene.Charge { @@ -13,36 +14,11 @@ namespace StandardScene.Charge /// public static class ChargeStationHelper { - private static ChargeStationManagementForm _managementForm; + /// 打开充电桩管理面板(单实例)。 + public static void OpenManagementWindow() => ChargeStationManagementForm.Open(); - /// - /// 打开充电桩管理窗口(单例模式) - /// - public static void OpenManagementWindow() - { - if (_managementForm == null || _managementForm.IsDisposed) - { - _managementForm = new ChargeStationManagementForm(); - _managementForm.FormClosed += (s, e) => _managementForm = null; - _managementForm.Show(); - } - else - { - _managementForm.BringToFront(); - _managementForm.Activate(); - } - } - - /// - /// 打开充电桩管理窗口(对话框模式) - /// - public static DialogResult OpenManagementDialog() - { - using (var form = new ChargeStationManagementForm()) - { - return form.ShowDialog(); - } - } + /// 打开充电桩管理面板(兼容旧 API)。 + public static void OpenManagementDialog() => ChargeStationManagementForm.Open(); /// /// 获取指定站点的充电桩 @@ -297,65 +273,44 @@ namespace StandardScene.Charge return success; } - /// - /// 显示充电桩选择对话框 - /// - /// 按状态过滤(null表示显示全部) - /// 选中的充电桩,取消则返回null - public static ChargeStation ShowStationSelectionDialog(ChargeStationStatus? filterByStatus = null) + /// 显示充电桩选择面板(非阻塞;通过 回调返回结果)。 + public static void ShowStationSelectionDialog(ChargeStationStatus? filterByStatus, System.Action onSelected) { - var dataService = ChargeStationDataService.Instance; - var stations = dataService.GetAllStations(); - + var stations = ChargeStationDataService.Instance.GetAllStations(); if (filterByStatus.HasValue) - { stations = stations.Where(s => s.Status == filterByStatus.Value).ToList(); - } if (stations.Count == 0) { - MessageBox.Show("没有符合条件的充电桩", "提示", - MessageBoxButtons.OK, MessageBoxIcon.Information); - return null; + CycleUiHelper.Alert("提示", "没有符合条件的充电桩"); + onSelected?.Invoke(null); + return; } - // 创建简单的选择对话框 - using (var dialog = new Form()) + var labels = stations.Select(s => + $"[{s.StationId}] {s.Name} - {s.IpAddress}:{s.Port} - {GetStatusText(s.Status)}").ToArray(); + int sel = 0; + var dlg = GUI.DeclarePanel() + .ShowTitle("选择充电桩") + .TopMost(true) + .InitSize(520, 400) + .InitPos(false, 0, 0, 0.5f, 0.5f, 0.5f, 0.5f); + dlg.Define(pb => { - dialog.Text = "选择充电桩"; - dialog.Size = new System.Drawing.Size(500, 400); - dialog.StartPosition = FormStartPosition.CenterParent; - - var listBox = new ListBox + if (pb.Closing()) { dlg.Exit(); onSelected?.Invoke(null); return; } + sel = pb.ListBox("充电桩", labels, height: 12); + if (pb.Button("确定", distinct: "cs-pick-ok")) { - Dock = DockStyle.Fill, - Font = new System.Drawing.Font("微软雅黑", 10F) - }; - - foreach (var station in stations) - { - listBox.Items.Add($"[{station.StationId}] {station.Name} - {station.IpAddress}:{station.Port} - {GetStatusText(station.Status)}"); + dlg.Exit(); + onSelected?.Invoke(sel >= 0 && sel < stations.Count ? stations[sel] : null); } - - var btnOK = new Button + pb.SameLine(8); + if (pb.Button("取消", distinct: "cs-pick-cancel")) { - Text = "确定", - DialogResult = DialogResult.OK, - Dock = DockStyle.Bottom, - Height = 40 - }; - - dialog.Controls.Add(listBox); - dialog.Controls.Add(btnOK); - dialog.AcceptButton = btnOK; - - if (dialog.ShowDialog() == DialogResult.OK && listBox.SelectedIndex >= 0) - { - return stations[listBox.SelectedIndex]; + dlg.Exit(); + onSelected?.Invoke(null); } - - return null; - } + }); } /// diff --git a/StandardScene.Core/Charge/ChargeStationManagementForm.Designer.cs b/StandardScene.Core/Charge/ChargeStationManagementForm.Designer.cs deleted file mode 100644 index 0e77df8..0000000 --- a/StandardScene.Core/Charge/ChargeStationManagementForm.Designer.cs +++ /dev/null @@ -1,1335 +0,0 @@ -namespace StandardScene.Charge -{ - partial class ChargeStationManagementForm - { - /// - /// 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() - { - System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle1 = new System.Windows.Forms.DataGridViewCellStyle(); - System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle19 = new System.Windows.Forms.DataGridViewCellStyle(); - System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle2 = new System.Windows.Forms.DataGridViewCellStyle(); - System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle3 = new System.Windows.Forms.DataGridViewCellStyle(); - System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle4 = new System.Windows.Forms.DataGridViewCellStyle(); - System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle5 = new System.Windows.Forms.DataGridViewCellStyle(); - System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle6 = new System.Windows.Forms.DataGridViewCellStyle(); - System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle7 = new System.Windows.Forms.DataGridViewCellStyle(); - System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle8 = new System.Windows.Forms.DataGridViewCellStyle(); - System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle9 = new System.Windows.Forms.DataGridViewCellStyle(); - System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle10 = new System.Windows.Forms.DataGridViewCellStyle(); - System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle11 = new System.Windows.Forms.DataGridViewCellStyle(); - System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle12 = new System.Windows.Forms.DataGridViewCellStyle(); - System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle13 = new System.Windows.Forms.DataGridViewCellStyle(); - System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle14 = new System.Windows.Forms.DataGridViewCellStyle(); - System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle15 = new System.Windows.Forms.DataGridViewCellStyle(); - System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle16 = new System.Windows.Forms.DataGridViewCellStyle(); - System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle17 = new System.Windows.Forms.DataGridViewCellStyle(); - System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle18 = new System.Windows.Forms.DataGridViewCellStyle(); - this.splitContainer = new System.Windows.Forms.SplitContainer(); - this.pnlList = new System.Windows.Forms.Panel(); - this.dgvStations = new System.Windows.Forms.DataGridView(); - this.pnlListButtons = new System.Windows.Forms.Panel(); - this.lblStatistics = new System.Windows.Forms.Label(); - this.btnStrategyConfig = new System.Windows.Forms.Button(); - this.btnCommMonitor = new System.Windows.Forms.Button(); - this.btnAlarmConfig = new System.Windows.Forms.Button(); - this.btnExport = new System.Windows.Forms.Button(); - this.btnRefresh = new System.Windows.Forms.Button(); - this.pnlSearch = new System.Windows.Forms.Panel(); - this.cmbStatusFilter = new System.Windows.Forms.ComboBox(); - this.lblStatusFilter = new System.Windows.Forms.Label(); - this.txtSearch = new System.Windows.Forms.TextBox(); - this.lblSearch = new System.Windows.Forms.Label(); - this.pnlEdit = new System.Windows.Forms.Panel(); - this.grpRealTimeInfo = new System.Windows.Forms.GroupBox(); - this.lblAlarmValue = new System.Windows.Forms.Label(); - this.lblAlarm = new System.Windows.Forms.Label(); - this.lblMechanismStatusValue = new System.Windows.Forms.Label(); - this.lblMechanismStatus = new System.Windows.Forms.Label(); - this.lblChargeCommandStatusValue = new System.Windows.Forms.Label(); - this.lblChargeCommandStatus = new System.Windows.Forms.Label(); - this.lblCommStatusValue = new System.Windows.Forms.Label(); - this.lblCommStatus = new System.Windows.Forms.Label(); - this.lblRealTimeCurrentValue = new System.Windows.Forms.Label(); - this.lblRealTimeCurrent = new System.Windows.Forms.Label(); - this.lblRealTimeVoltageValue = new System.Windows.Forms.Label(); - this.lblRealTimeVoltage = new System.Windows.Forms.Label(); - this.lblBatteryLevelValue = new System.Windows.Forms.Label(); - this.lblBatteryLevel = new System.Windows.Forms.Label(); - this.lblCurrentVehicleValue = new System.Windows.Forms.Label(); - this.lblCurrentVehicle = new System.Windows.Forms.Label(); - this.grpEditInfo = new System.Windows.Forms.GroupBox(); - this.chargeCarType = new System.Windows.Forms.ComboBox(); - this.label1 = new System.Windows.Forms.Label(); - this.txtRemarks = new System.Windows.Forms.TextBox(); - this.lblRemarks = new System.Windows.Forms.Label(); - this.numSiteId = new System.Windows.Forms.NumericUpDown(); - this.lblSiteId = new System.Windows.Forms.Label(); - this.chkEnabled = new System.Windows.Forms.CheckBox(); - this.chkShieldSiteMechanismStatus = new System.Windows.Forms.CheckBox(); - this.numCurrent = new System.Windows.Forms.NumericUpDown(); - this.lblCurrent = new System.Windows.Forms.Label(); - this.numVoltage = new System.Windows.Forms.NumericUpDown(); - this.lblVoltage = new System.Windows.Forms.Label(); - this.numPort = new System.Windows.Forms.NumericUpDown(); - this.lblPort = new System.Windows.Forms.Label(); - this.txtIpAddress = new System.Windows.Forms.TextBox(); - this.lblIpAddress = new System.Windows.Forms.Label(); - this.cmbChargeMethod = new System.Windows.Forms.ComboBox(); - this.lblChargeMethod = new System.Windows.Forms.Label(); - this.cmbType = new System.Windows.Forms.ComboBox(); - this.lblType = new System.Windows.Forms.Label(); - this.txtName = new System.Windows.Forms.TextBox(); - this.lblName = new System.Windows.Forms.Label(); - this.txtStationId = new System.Windows.Forms.TextBox(); - this.lblStationId = new System.Windows.Forms.Label(); - this.pnlEditButtons = new System.Windows.Forms.Panel(); - this.btnCancel = new System.Windows.Forms.Button(); - this.btnDelete = new System.Windows.Forms.Button(); - this.btnSave = new System.Windows.Forms.Button(); - this.colStationId = new System.Windows.Forms.DataGridViewTextBoxColumn(); - this.colName = new System.Windows.Forms.DataGridViewTextBoxColumn(); - this.colType = new System.Windows.Forms.DataGridViewTextBoxColumn(); - this.colChargeMethod = new System.Windows.Forms.DataGridViewTextBoxColumn(); - this.colSiteId = new System.Windows.Forms.DataGridViewTextBoxColumn(); - this.colLastSendTime = new System.Windows.Forms.DataGridViewTextBoxColumn(); - this.colLastReceiveTime = new System.Windows.Forms.DataGridViewTextBoxColumn(); - this.colCommStatus = new System.Windows.Forms.DataGridViewTextBoxColumn(); - this.colChargeCommandStatus = new System.Windows.Forms.DataGridViewTextBoxColumn(); - this.colMechanismStatus = new System.Windows.Forms.DataGridViewTextBoxColumn(); - this.colCurrentVehicle = new System.Windows.Forms.DataGridViewTextBoxColumn(); - this.colBatteryLevel = new System.Windows.Forms.DataGridViewTextBoxColumn(); - this.colAlarm = new System.Windows.Forms.DataGridViewTextBoxColumn(); - this.colRealTimeVoltage = new System.Windows.Forms.DataGridViewTextBoxColumn(); - this.colRealTimeCurrent = new System.Windows.Forms.DataGridViewTextBoxColumn(); - this.colStatus = new System.Windows.Forms.DataGridViewTextBoxColumn(); - this.colEnabled = new System.Windows.Forms.DataGridViewTextBoxColumn(); - ((System.ComponentModel.ISupportInitialize)(this.splitContainer)).BeginInit(); - this.splitContainer.Panel1.SuspendLayout(); - this.splitContainer.Panel2.SuspendLayout(); - this.splitContainer.SuspendLayout(); - this.pnlList.SuspendLayout(); - ((System.ComponentModel.ISupportInitialize)(this.dgvStations)).BeginInit(); - this.pnlListButtons.SuspendLayout(); - this.pnlSearch.SuspendLayout(); - this.pnlEdit.SuspendLayout(); - this.grpRealTimeInfo.SuspendLayout(); - this.grpEditInfo.SuspendLayout(); - ((System.ComponentModel.ISupportInitialize)(this.numSiteId)).BeginInit(); - ((System.ComponentModel.ISupportInitialize)(this.numCurrent)).BeginInit(); - ((System.ComponentModel.ISupportInitialize)(this.numVoltage)).BeginInit(); - ((System.ComponentModel.ISupportInitialize)(this.numPort)).BeginInit(); - this.pnlEditButtons.SuspendLayout(); - this.SuspendLayout(); - // - // splitContainer - // - this.splitContainer.Dock = System.Windows.Forms.DockStyle.Fill; - this.splitContainer.Location = new System.Drawing.Point(0, 0); - this.splitContainer.Margin = new System.Windows.Forms.Padding(4); - this.splitContainer.Name = "splitContainer"; - // - // splitContainer.Panel1 - // - this.splitContainer.Panel1.Controls.Add(this.pnlList); - // - // splitContainer.Panel2 - // - this.splitContainer.Panel2.Controls.Add(this.pnlEdit); - this.splitContainer.Size = new System.Drawing.Size(1731, 875); - this.splitContainer.SplitterDistance = 1081; - this.splitContainer.SplitterWidth = 5; - this.splitContainer.TabIndex = 0; - // - // pnlList - // - this.pnlList.Controls.Add(this.dgvStations); - this.pnlList.Controls.Add(this.pnlListButtons); - this.pnlList.Controls.Add(this.pnlSearch); - this.pnlList.Dock = System.Windows.Forms.DockStyle.Fill; - this.pnlList.Location = new System.Drawing.Point(0, 0); - this.pnlList.Margin = new System.Windows.Forms.Padding(4); - this.pnlList.Name = "pnlList"; - this.pnlList.Size = new System.Drawing.Size(1081, 875); - this.pnlList.TabIndex = 0; - // - // dgvStations - // - this.dgvStations.AllowUserToAddRows = false; - this.dgvStations.AllowUserToDeleteRows = false; - this.dgvStations.AllowUserToResizeRows = false; - this.dgvStations.AutoSizeColumnsMode = System.Windows.Forms.DataGridViewAutoSizeColumnsMode.Fill; - this.dgvStations.BackgroundColor = System.Drawing.Color.White; - this.dgvStations.BorderStyle = System.Windows.Forms.BorderStyle.None; - this.dgvStations.CellBorderStyle = System.Windows.Forms.DataGridViewCellBorderStyle.SingleHorizontal; - dataGridViewCellStyle1.Alignment = System.Windows.Forms.DataGridViewContentAlignment.MiddleLeft; - dataGridViewCellStyle1.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(63)))), ((int)(((byte)(81)))), ((int)(((byte)(181))))); - dataGridViewCellStyle1.Font = new System.Drawing.Font("微软雅黑", 9F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - dataGridViewCellStyle1.ForeColor = System.Drawing.Color.White; - dataGridViewCellStyle1.SelectionBackColor = System.Drawing.Color.FromArgb(((int)(((byte)(63)))), ((int)(((byte)(81)))), ((int)(((byte)(181))))); - dataGridViewCellStyle1.SelectionForeColor = System.Drawing.SystemColors.HighlightText; - dataGridViewCellStyle1.WrapMode = System.Windows.Forms.DataGridViewTriState.True; - this.dgvStations.ColumnHeadersDefaultCellStyle = dataGridViewCellStyle1; - this.dgvStations.ColumnHeadersHeight = 100; - this.dgvStations.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.DisableResizing; - this.dgvStations.Columns.AddRange(new System.Windows.Forms.DataGridViewColumn[] { - this.colStationId, - this.colName, - this.colType, - this.colChargeMethod, - this.colSiteId, - this.colLastSendTime, - this.colLastReceiveTime, - this.colCommStatus, - this.colChargeCommandStatus, - this.colMechanismStatus, - this.colCurrentVehicle, - this.colBatteryLevel, - this.colAlarm, - this.colRealTimeVoltage, - this.colRealTimeCurrent, - this.colStatus, - this.colEnabled}); - dataGridViewCellStyle19.Alignment = System.Windows.Forms.DataGridViewContentAlignment.MiddleLeft; - dataGridViewCellStyle19.BackColor = System.Drawing.Color.White; - dataGridViewCellStyle19.Font = new System.Drawing.Font("微软雅黑", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - dataGridViewCellStyle19.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(64)))), ((int)(((byte)(64)))), ((int)(((byte)(64))))); - dataGridViewCellStyle19.SelectionBackColor = System.Drawing.Color.FromArgb(((int)(((byte)(197)))), ((int)(((byte)(202)))), ((int)(((byte)(233))))); - dataGridViewCellStyle19.SelectionForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(33)))), ((int)(((byte)(33)))), ((int)(((byte)(33))))); - dataGridViewCellStyle19.WrapMode = System.Windows.Forms.DataGridViewTriState.False; - this.dgvStations.DefaultCellStyle = dataGridViewCellStyle19; - this.dgvStations.Dock = System.Windows.Forms.DockStyle.Fill; - this.dgvStations.EnableHeadersVisualStyles = false; - this.dgvStations.GridColor = System.Drawing.Color.FromArgb(((int)(((byte)(224)))), ((int)(((byte)(224)))), ((int)(((byte)(224))))); - this.dgvStations.Location = new System.Drawing.Point(0, 62); - this.dgvStations.Margin = new System.Windows.Forms.Padding(4); - this.dgvStations.MultiSelect = false; - this.dgvStations.Name = "dgvStations"; - this.dgvStations.ReadOnly = true; - this.dgvStations.RowHeadersVisible = false; - this.dgvStations.RowHeadersWidth = 30; - this.dgvStations.RowHeadersWidthSizeMode = System.Windows.Forms.DataGridViewRowHeadersWidthSizeMode.DisableResizing; - this.dgvStations.RowTemplate.Height = 35; - this.dgvStations.SelectionMode = System.Windows.Forms.DataGridViewSelectionMode.FullRowSelect; - this.dgvStations.Size = new System.Drawing.Size(1081, 725); - this.dgvStations.TabIndex = 2; - this.dgvStations.CellDoubleClick += new System.Windows.Forms.DataGridViewCellEventHandler(this.dgvStations_CellDoubleClick); - // - // pnlListButtons - // - this.pnlListButtons.Controls.Add(this.lblStatistics); - this.pnlListButtons.Controls.Add(this.btnStrategyConfig); - this.pnlListButtons.Controls.Add(this.btnCommMonitor); - this.pnlListButtons.Controls.Add(this.btnAlarmConfig); - this.pnlListButtons.Controls.Add(this.btnExport); - this.pnlListButtons.Controls.Add(this.btnRefresh); - this.pnlListButtons.Dock = System.Windows.Forms.DockStyle.Bottom; - this.pnlListButtons.Location = new System.Drawing.Point(0, 749); - this.pnlListButtons.Margin = new System.Windows.Forms.Padding(4); - this.pnlListButtons.Name = "pnlListButtons"; - this.pnlListButtons.Padding = new System.Windows.Forms.Padding(13, 12, 13, 12); - this.pnlListButtons.Size = new System.Drawing.Size(1081, 126); - this.pnlListButtons.TabIndex = 1; - // - // lblStatistics - // - this.lblStatistics.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) - | System.Windows.Forms.AnchorStyles.Right))); - this.lblStatistics.AutoSize = false; - this.lblStatistics.Font = new System.Drawing.Font("微软雅黑", 10F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - this.lblStatistics.Location = new System.Drawing.Point(20, 12); - this.lblStatistics.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); - this.lblStatistics.Name = "lblStatistics"; - this.lblStatistics.Size = new System.Drawing.Size(1034, 24); - this.lblStatistics.TabIndex = 2; - this.lblStatistics.Text = "总数: 0 | 空闲: 0 | 充电中: 0 | 故障: 0"; - // - // btnStrategyConfig - // - this.btnStrategyConfig.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); - this.btnStrategyConfig.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(76)))), ((int)(((byte)(175)))), ((int)(((byte)(80))))); - this.btnStrategyConfig.Font = new System.Drawing.Font("微软雅黑", 10F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - this.btnStrategyConfig.ForeColor = System.Drawing.Color.White; - this.btnStrategyConfig.Location = new System.Drawing.Point(411, 52); - this.btnStrategyConfig.Margin = new System.Windows.Forms.Padding(4); - this.btnStrategyConfig.Name = "btnStrategyConfig"; - this.btnStrategyConfig.Size = new System.Drawing.Size(120, 50); - this.btnStrategyConfig.TabIndex = 5; - this.btnStrategyConfig.Text = "策略配置"; - this.btnStrategyConfig.UseVisualStyleBackColor = false; - this.btnStrategyConfig.Click += new System.EventHandler(this.btnStrategyConfig_Click); - // - // btnCommMonitor - // - this.btnCommMonitor.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); - this.btnCommMonitor.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(33)))), ((int)(((byte)(150)))), ((int)(((byte)(243))))); - this.btnCommMonitor.Font = new System.Drawing.Font("微软雅黑", 10F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - this.btnCommMonitor.ForeColor = System.Drawing.Color.White; - this.btnCommMonitor.Location = new System.Drawing.Point(541, 52); - this.btnCommMonitor.Margin = new System.Windows.Forms.Padding(4); - this.btnCommMonitor.Name = "btnCommMonitor"; - this.btnCommMonitor.Size = new System.Drawing.Size(120, 50); - this.btnCommMonitor.TabIndex = 4; - this.btnCommMonitor.Text = "通讯监控"; - this.btnCommMonitor.UseVisualStyleBackColor = false; - this.btnCommMonitor.Click += new System.EventHandler(this.btnCommMonitor_Click); - // - // btnAlarmConfig - // - this.btnAlarmConfig.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); - this.btnAlarmConfig.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(255)))), ((int)(((byte)(193)))), ((int)(((byte)(7))))); - this.btnAlarmConfig.Font = new System.Drawing.Font("微软雅黑", 10F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - this.btnAlarmConfig.ForeColor = System.Drawing.Color.White; - this.btnAlarmConfig.Location = new System.Drawing.Point(671, 52); - this.btnAlarmConfig.Margin = new System.Windows.Forms.Padding(4); - this.btnAlarmConfig.Name = "btnAlarmConfig"; - this.btnAlarmConfig.Size = new System.Drawing.Size(120, 50); - this.btnAlarmConfig.TabIndex = 3; - this.btnAlarmConfig.Text = "报警配置"; - this.btnAlarmConfig.UseVisualStyleBackColor = false; - this.btnAlarmConfig.Click += new System.EventHandler(this.btnAlarmConfig_Click); - // - // btnExport - // - this.btnExport.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); - this.btnExport.Font = new System.Drawing.Font("微软雅黑", 10F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - this.btnExport.Location = new System.Drawing.Point(934, 52); - this.btnExport.Margin = new System.Windows.Forms.Padding(4); - this.btnExport.Name = "btnExport"; - this.btnExport.Size = new System.Drawing.Size(120, 50); - this.btnExport.TabIndex = 1; - this.btnExport.Text = "导出"; - this.btnExport.UseVisualStyleBackColor = true; - this.btnExport.Click += new System.EventHandler(this.btnExport_Click); - // - // btnRefresh - // - this.btnRefresh.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); - this.btnRefresh.Font = new System.Drawing.Font("微软雅黑", 10F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - this.btnRefresh.Location = new System.Drawing.Point(801, 52); - this.btnRefresh.Margin = new System.Windows.Forms.Padding(4); - this.btnRefresh.Name = "btnRefresh"; - this.btnRefresh.Size = new System.Drawing.Size(120, 50); - this.btnRefresh.TabIndex = 0; - this.btnRefresh.Text = "刷新"; - this.btnRefresh.UseVisualStyleBackColor = true; - this.btnRefresh.Click += new System.EventHandler(this.btnRefresh_Click); - // - // pnlSearch - // - this.pnlSearch.Controls.Add(this.cmbStatusFilter); - this.pnlSearch.Controls.Add(this.lblStatusFilter); - this.pnlSearch.Controls.Add(this.txtSearch); - this.pnlSearch.Controls.Add(this.lblSearch); - this.pnlSearch.Dock = System.Windows.Forms.DockStyle.Top; - this.pnlSearch.Location = new System.Drawing.Point(0, 0); - this.pnlSearch.Margin = new System.Windows.Forms.Padding(4); - this.pnlSearch.Name = "pnlSearch"; - this.pnlSearch.Padding = new System.Windows.Forms.Padding(13, 12, 13, 12); - this.pnlSearch.Size = new System.Drawing.Size(1081, 62); - this.pnlSearch.TabIndex = 0; - // - // cmbStatusFilter - // - this.cmbStatusFilter.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; - this.cmbStatusFilter.Font = new System.Drawing.Font("微软雅黑", 10F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - this.cmbStatusFilter.FormattingEnabled = true; - this.cmbStatusFilter.Location = new System.Drawing.Point(680, 16); - this.cmbStatusFilter.Margin = new System.Windows.Forms.Padding(4); - this.cmbStatusFilter.Name = "cmbStatusFilter"; - this.cmbStatusFilter.Size = new System.Drawing.Size(199, 31); - this.cmbStatusFilter.TabIndex = 3; - this.cmbStatusFilter.SelectedIndexChanged += new System.EventHandler(this.cmbStatusFilter_SelectedIndexChanged); - // - // lblStatusFilter - // - this.lblStatusFilter.AutoSize = true; - this.lblStatusFilter.Font = new System.Drawing.Font("微软雅黑", 10F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - this.lblStatusFilter.Location = new System.Drawing.Point(593, 21); - this.lblStatusFilter.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); - this.lblStatusFilter.Name = "lblStatusFilter"; - this.lblStatusFilter.Size = new System.Drawing.Size(61, 23); - this.lblStatusFilter.TabIndex = 2; - this.lblStatusFilter.Text = "状态:"; - // - // txtSearch - // - this.txtSearch.Font = new System.Drawing.Font("微软雅黑", 10F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - this.txtSearch.Location = new System.Drawing.Point(100, 16); - this.txtSearch.Margin = new System.Windows.Forms.Padding(4); - this.txtSearch.Name = "txtSearch"; - this.txtSearch.Size = new System.Drawing.Size(399, 29); - this.txtSearch.TabIndex = 1; - this.txtSearch.TextChanged += new System.EventHandler(this.txtSearch_TextChanged); - // - // lblSearch - // - this.lblSearch.AutoSize = true; - this.lblSearch.Font = new System.Drawing.Font("微软雅黑", 10F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - this.lblSearch.Location = new System.Drawing.Point(13, 21); - this.lblSearch.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); - this.lblSearch.Name = "lblSearch"; - this.lblSearch.Size = new System.Drawing.Size(61, 23); - this.lblSearch.TabIndex = 0; - this.lblSearch.Text = "搜索:"; - // - // pnlEdit - // - this.pnlEdit.Controls.Add(this.grpRealTimeInfo); - this.pnlEdit.Controls.Add(this.grpEditInfo); - this.pnlEdit.Controls.Add(this.pnlEditButtons); - this.pnlEdit.Dock = System.Windows.Forms.DockStyle.Fill; - this.pnlEdit.Location = new System.Drawing.Point(0, 0); - this.pnlEdit.Margin = new System.Windows.Forms.Padding(4); - this.pnlEdit.Name = "pnlEdit"; - this.pnlEdit.Size = new System.Drawing.Size(645, 875); - this.pnlEdit.TabIndex = 0; - // - // grpRealTimeInfo - // - this.grpRealTimeInfo.Controls.Add(this.lblAlarmValue); - this.grpRealTimeInfo.Controls.Add(this.lblAlarm); - this.grpRealTimeInfo.Controls.Add(this.lblMechanismStatusValue); - this.grpRealTimeInfo.Controls.Add(this.lblMechanismStatus); - this.grpRealTimeInfo.Controls.Add(this.lblChargeCommandStatusValue); - this.grpRealTimeInfo.Controls.Add(this.lblChargeCommandStatus); - this.grpRealTimeInfo.Controls.Add(this.lblCommStatusValue); - this.grpRealTimeInfo.Controls.Add(this.lblCommStatus); - this.grpRealTimeInfo.Controls.Add(this.lblRealTimeCurrentValue); - this.grpRealTimeInfo.Controls.Add(this.lblRealTimeCurrent); - this.grpRealTimeInfo.Controls.Add(this.lblRealTimeVoltageValue); - this.grpRealTimeInfo.Controls.Add(this.lblRealTimeVoltage); - this.grpRealTimeInfo.Controls.Add(this.lblBatteryLevelValue); - this.grpRealTimeInfo.Controls.Add(this.lblBatteryLevel); - this.grpRealTimeInfo.Controls.Add(this.lblCurrentVehicleValue); - this.grpRealTimeInfo.Controls.Add(this.lblCurrentVehicle); - this.grpRealTimeInfo.Dock = System.Windows.Forms.DockStyle.Fill; - this.grpRealTimeInfo.Font = new System.Drawing.Font("微软雅黑", 10F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - this.grpRealTimeInfo.Location = new System.Drawing.Point(0, 500); - this.grpRealTimeInfo.Margin = new System.Windows.Forms.Padding(4); - this.grpRealTimeInfo.Name = "grpRealTimeInfo"; - this.grpRealTimeInfo.Padding = new System.Windows.Forms.Padding(20, 10, 20, 10); - this.grpRealTimeInfo.Size = new System.Drawing.Size(645, 250); - this.grpRealTimeInfo.TabIndex = 2; - this.grpRealTimeInfo.TabStop = false; - this.grpRealTimeInfo.Text = "实时状态(只读)"; - // - // lblAlarmValue - // - this.lblAlarmValue.Font = new System.Drawing.Font("微软雅黑", 8F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - this.lblAlarmValue.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(76)))), ((int)(((byte)(175)))), ((int)(((byte)(80))))); - this.lblAlarmValue.Location = new System.Drawing.Point(451, 96); - this.lblAlarmValue.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); - this.lblAlarmValue.Name = "lblAlarmValue"; - this.lblAlarmValue.Size = new System.Drawing.Size(76, 20); - this.lblAlarmValue.TabIndex = 15; - this.lblAlarmValue.Text = "正常"; - this.lblAlarmValue.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; - // - // lblAlarm - // - this.lblAlarm.Font = new System.Drawing.Font("微软雅黑", 9F); - this.lblAlarm.Location = new System.Drawing.Point(343, 97); - this.lblAlarm.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); - this.lblAlarm.Name = "lblAlarm"; - this.lblAlarm.Size = new System.Drawing.Size(100, 18); - this.lblAlarm.TabIndex = 14; - this.lblAlarm.Text = "报警状态:"; - this.lblAlarm.TextAlign = System.Drawing.ContentAlignment.MiddleRight; - // - // lblMechanismStatusValue - // - this.lblMechanismStatusValue.Font = new System.Drawing.Font("微软雅黑", 8F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - this.lblMechanismStatusValue.Location = new System.Drawing.Point(155, 98); - this.lblMechanismStatusValue.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); - this.lblMechanismStatusValue.Name = "lblMechanismStatusValue"; - this.lblMechanismStatusValue.Size = new System.Drawing.Size(81, 18); - this.lblMechanismStatusValue.TabIndex = 13; - this.lblMechanismStatusValue.Text = "? 未知"; - this.lblMechanismStatusValue.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; - // - // lblMechanismStatus - // - this.lblMechanismStatus.Font = new System.Drawing.Font("微软雅黑", 9F); - this.lblMechanismStatus.Location = new System.Drawing.Point(50, 95); - this.lblMechanismStatus.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); - this.lblMechanismStatus.Name = "lblMechanismStatus"; - this.lblMechanismStatus.Size = new System.Drawing.Size(97, 30); - this.lblMechanismStatus.TabIndex = 12; - this.lblMechanismStatus.Text = "机构状态:"; - this.lblMechanismStatus.TextAlign = System.Drawing.ContentAlignment.MiddleRight; - // - // lblChargeCommandStatusValue - // - this.lblChargeCommandStatusValue.Font = new System.Drawing.Font("微软雅黑", 9F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - this.lblChargeCommandStatusValue.Location = new System.Drawing.Point(451, 75); - this.lblChargeCommandStatusValue.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); - this.lblChargeCommandStatusValue.Name = "lblChargeCommandStatusValue"; - this.lblChargeCommandStatusValue.Size = new System.Drawing.Size(150, 20); - this.lblChargeCommandStatusValue.TabIndex = 11; - this.lblChargeCommandStatusValue.Text = "◯ 停止"; - this.lblChargeCommandStatusValue.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; - // - // lblChargeCommandStatus - // - this.lblChargeCommandStatus.Font = new System.Drawing.Font("微软雅黑", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - this.lblChargeCommandStatus.Location = new System.Drawing.Point(343, 75); - this.lblChargeCommandStatus.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); - this.lblChargeCommandStatus.Name = "lblChargeCommandStatus"; - this.lblChargeCommandStatus.Size = new System.Drawing.Size(100, 20); - this.lblChargeCommandStatus.TabIndex = 10; - this.lblChargeCommandStatus.Text = "充电指令:"; - this.lblChargeCommandStatus.TextAlign = System.Drawing.ContentAlignment.MiddleRight; - // - // lblCommStatusValue - // - this.lblCommStatusValue.Font = new System.Drawing.Font("微软雅黑", 9F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - this.lblCommStatusValue.Location = new System.Drawing.Point(155, 75); - this.lblCommStatusValue.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); - this.lblCommStatusValue.Name = "lblCommStatusValue"; - this.lblCommStatusValue.Size = new System.Drawing.Size(200, 20); - this.lblCommStatusValue.TabIndex = 9; - this.lblCommStatusValue.Text = "? 未知"; - this.lblCommStatusValue.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; - // - // lblCommStatus - // - this.lblCommStatus.Font = new System.Drawing.Font("微软雅黑", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - this.lblCommStatus.Location = new System.Drawing.Point(27, 75); - this.lblCommStatus.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); - this.lblCommStatus.Name = "lblCommStatus"; - this.lblCommStatus.Size = new System.Drawing.Size(120, 20); - this.lblCommStatus.TabIndex = 8; - this.lblCommStatus.Text = "通讯状态:"; - this.lblCommStatus.TextAlign = System.Drawing.ContentAlignment.MiddleRight; - // - // lblRealTimeCurrentValue - // - this.lblRealTimeCurrentValue.Font = new System.Drawing.Font("微软雅黑", 9F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - this.lblRealTimeCurrentValue.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(33)))), ((int)(((byte)(150)))), ((int)(((byte)(243))))); - this.lblRealTimeCurrentValue.Location = new System.Drawing.Point(451, 50); - this.lblRealTimeCurrentValue.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); - this.lblRealTimeCurrentValue.Name = "lblRealTimeCurrentValue"; - this.lblRealTimeCurrentValue.Size = new System.Drawing.Size(150, 25); - this.lblRealTimeCurrentValue.TabIndex = 7; - this.lblRealTimeCurrentValue.Text = "0.0 A"; - this.lblRealTimeCurrentValue.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; - // - // lblRealTimeCurrent - // - this.lblRealTimeCurrent.Font = new System.Drawing.Font("微软雅黑", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - this.lblRealTimeCurrent.Location = new System.Drawing.Point(327, 50); - this.lblRealTimeCurrent.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); - this.lblRealTimeCurrent.Name = "lblRealTimeCurrent"; - this.lblRealTimeCurrent.Size = new System.Drawing.Size(116, 25); - this.lblRealTimeCurrent.TabIndex = 6; - this.lblRealTimeCurrent.Text = "实时电流:"; - this.lblRealTimeCurrent.TextAlign = System.Drawing.ContentAlignment.MiddleRight; - // - // lblRealTimeVoltageValue - // - this.lblRealTimeVoltageValue.Font = new System.Drawing.Font("微软雅黑", 9F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - this.lblRealTimeVoltageValue.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(33)))), ((int)(((byte)(150)))), ((int)(((byte)(243))))); - this.lblRealTimeVoltageValue.Location = new System.Drawing.Point(155, 50); - this.lblRealTimeVoltageValue.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); - this.lblRealTimeVoltageValue.Name = "lblRealTimeVoltageValue"; - this.lblRealTimeVoltageValue.Size = new System.Drawing.Size(200, 25); - this.lblRealTimeVoltageValue.TabIndex = 5; - this.lblRealTimeVoltageValue.Text = "0.0 V"; - this.lblRealTimeVoltageValue.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; - // - // lblRealTimeVoltage - // - this.lblRealTimeVoltage.Font = new System.Drawing.Font("微软雅黑", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - this.lblRealTimeVoltage.Location = new System.Drawing.Point(27, 50); - this.lblRealTimeVoltage.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); - this.lblRealTimeVoltage.Name = "lblRealTimeVoltage"; - this.lblRealTimeVoltage.Size = new System.Drawing.Size(120, 25); - this.lblRealTimeVoltage.TabIndex = 4; - this.lblRealTimeVoltage.Text = "实时电压:"; - this.lblRealTimeVoltage.TextAlign = System.Drawing.ContentAlignment.MiddleRight; - // - // lblBatteryLevelValue - // - this.lblBatteryLevelValue.Font = new System.Drawing.Font("微软雅黑", 9F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - this.lblBatteryLevelValue.Location = new System.Drawing.Point(451, 25); - this.lblBatteryLevelValue.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); - this.lblBatteryLevelValue.Name = "lblBatteryLevelValue"; - this.lblBatteryLevelValue.Size = new System.Drawing.Size(150, 25); - this.lblBatteryLevelValue.TabIndex = 3; - this.lblBatteryLevelValue.Text = "-"; - this.lblBatteryLevelValue.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; - // - // lblBatteryLevel - // - this.lblBatteryLevel.Font = new System.Drawing.Font("微软雅黑", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - this.lblBatteryLevel.Location = new System.Drawing.Point(350, 25); - this.lblBatteryLevel.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); - this.lblBatteryLevel.Name = "lblBatteryLevel"; - this.lblBatteryLevel.Size = new System.Drawing.Size(93, 25); - this.lblBatteryLevel.TabIndex = 2; - this.lblBatteryLevel.Text = "实时电量:"; - this.lblBatteryLevel.TextAlign = System.Drawing.ContentAlignment.MiddleRight; - // - // lblCurrentVehicleValue - // - this.lblCurrentVehicleValue.Font = new System.Drawing.Font("微软雅黑", 9F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - this.lblCurrentVehicleValue.Location = new System.Drawing.Point(155, 25); - this.lblCurrentVehicleValue.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); - this.lblCurrentVehicleValue.Name = "lblCurrentVehicleValue"; - this.lblCurrentVehicleValue.Size = new System.Drawing.Size(200, 25); - this.lblCurrentVehicleValue.TabIndex = 1; - this.lblCurrentVehicleValue.Text = "-"; - this.lblCurrentVehicleValue.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; - // - // lblCurrentVehicle - // - this.lblCurrentVehicle.Font = new System.Drawing.Font("微软雅黑", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - this.lblCurrentVehicle.Location = new System.Drawing.Point(27, 25); - this.lblCurrentVehicle.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); - this.lblCurrentVehicle.Name = "lblCurrentVehicle"; - this.lblCurrentVehicle.Size = new System.Drawing.Size(120, 25); - this.lblCurrentVehicle.TabIndex = 0; - this.lblCurrentVehicle.Text = "当前车辆:"; - this.lblCurrentVehicle.TextAlign = System.Drawing.ContentAlignment.MiddleRight; - // - // grpEditInfo - // - this.grpEditInfo.Controls.Add(this.chargeCarType); - this.grpEditInfo.Controls.Add(this.label1); - this.grpEditInfo.Controls.Add(this.txtRemarks); - this.grpEditInfo.Controls.Add(this.lblRemarks); - this.grpEditInfo.Controls.Add(this.numSiteId); - this.grpEditInfo.Controls.Add(this.lblSiteId); - this.grpEditInfo.Controls.Add(this.chkEnabled); - this.grpEditInfo.Controls.Add(this.chkShieldSiteMechanismStatus); - this.grpEditInfo.Controls.Add(this.numCurrent); - this.grpEditInfo.Controls.Add(this.lblCurrent); - this.grpEditInfo.Controls.Add(this.numVoltage); - this.grpEditInfo.Controls.Add(this.lblVoltage); - this.grpEditInfo.Controls.Add(this.numPort); - this.grpEditInfo.Controls.Add(this.lblPort); - this.grpEditInfo.Controls.Add(this.txtIpAddress); - this.grpEditInfo.Controls.Add(this.lblIpAddress); - this.grpEditInfo.Controls.Add(this.cmbChargeMethod); - this.grpEditInfo.Controls.Add(this.lblChargeMethod); - this.grpEditInfo.Controls.Add(this.cmbType); - this.grpEditInfo.Controls.Add(this.lblType); - this.grpEditInfo.Controls.Add(this.txtName); - this.grpEditInfo.Controls.Add(this.lblName); - this.grpEditInfo.Controls.Add(this.txtStationId); - this.grpEditInfo.Controls.Add(this.lblStationId); - this.grpEditInfo.Dock = System.Windows.Forms.DockStyle.Top; - this.grpEditInfo.Font = new System.Drawing.Font("微软雅黑", 10F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - this.grpEditInfo.Location = new System.Drawing.Point(0, 0); - this.grpEditInfo.Margin = new System.Windows.Forms.Padding(4); - this.grpEditInfo.Name = "grpEditInfo"; - this.grpEditInfo.Padding = new System.Windows.Forms.Padding(20, 19, 20, 19); - this.grpEditInfo.Size = new System.Drawing.Size(645, 500); - this.grpEditInfo.TabIndex = 1; - this.grpEditInfo.TabStop = false; - this.grpEditInfo.Text = "基本信息(可编辑)"; - // - // chargeCarType - // - this.chargeCarType.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; - this.chargeCarType.Font = new System.Drawing.Font("微软雅黑", 10F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - this.chargeCarType.FormattingEnabled = true; - this.chargeCarType.Items.AddRange(new object[] { - "FRLD", - "MuXing"}); - this.chargeCarType.Location = new System.Drawing.Point(426, 332); - this.chargeCarType.Margin = new System.Windows.Forms.Padding(4); - this.chargeCarType.Name = "chargeCarType"; - this.chargeCarType.Size = new System.Drawing.Size(140, 31); - this.chargeCarType.TabIndex = 25; - // - // label1 - // - this.label1.Font = new System.Drawing.Font("微软雅黑", 10F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - this.label1.Location = new System.Drawing.Point(292, 332); - this.label1.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); - this.label1.Name = "label1"; - this.label1.Size = new System.Drawing.Size(137, 31); - this.label1.TabIndex = 24; - this.label1.Text = "停靠车辆类型:"; - this.label1.TextAlign = System.Drawing.ContentAlignment.MiddleRight; - // - // txtRemarks - // - this.txtRemarks.Font = new System.Drawing.Font("微软雅黑", 10F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - this.txtRemarks.Location = new System.Drawing.Point(167, 408); - this.txtRemarks.Margin = new System.Windows.Forms.Padding(4); - this.txtRemarks.Multiline = true; - this.txtRemarks.Name = "txtRemarks"; - this.txtRemarks.ScrollBars = System.Windows.Forms.ScrollBars.Vertical; - this.txtRemarks.Size = new System.Drawing.Size(399, 62); - this.txtRemarks.TabIndex = 21; - // - // lblRemarks - // - this.lblRemarks.Font = new System.Drawing.Font("微软雅黑", 10F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - this.lblRemarks.Location = new System.Drawing.Point(27, 408); - this.lblRemarks.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); - this.lblRemarks.Name = "lblRemarks"; - this.lblRemarks.Size = new System.Drawing.Size(133, 31); - this.lblRemarks.TabIndex = 20; - this.lblRemarks.Text = "备注:"; - this.lblRemarks.TextAlign = System.Drawing.ContentAlignment.TopRight; - // - // numSiteId - // - this.numSiteId.Font = new System.Drawing.Font("微软雅黑", 10F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - this.numSiteId.Location = new System.Drawing.Point(167, 370); - this.numSiteId.Margin = new System.Windows.Forms.Padding(4); - this.numSiteId.Maximum = new decimal(new int[] { - 99999, - 0, - 0, - 0}); - this.numSiteId.Name = "numSiteId"; - this.numSiteId.Size = new System.Drawing.Size(400, 29); - this.numSiteId.TabIndex = 19; - // - // lblSiteId - // - this.lblSiteId.Font = new System.Drawing.Font("微软雅黑", 10F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - this.lblSiteId.Location = new System.Drawing.Point(27, 370); - this.lblSiteId.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); - this.lblSiteId.Name = "lblSiteId"; - this.lblSiteId.Size = new System.Drawing.Size(133, 31); - this.lblSiteId.TabIndex = 18; - this.lblSiteId.Text = "站点ID:"; - this.lblSiteId.TextAlign = System.Drawing.ContentAlignment.MiddleRight; - // - // chkEnabled - // - this.chkEnabled.AutoSize = true; - this.chkEnabled.Checked = true; - this.chkEnabled.CheckState = System.Windows.Forms.CheckState.Checked; - this.chkEnabled.Font = new System.Drawing.Font("微软雅黑", 10F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - this.chkEnabled.Location = new System.Drawing.Point(167, 334); - this.chkEnabled.Margin = new System.Windows.Forms.Padding(4); - this.chkEnabled.Name = "chkEnabled"; - this.chkEnabled.Size = new System.Drawing.Size(117, 27); - this.chkEnabled.TabIndex = 17; - this.chkEnabled.Text = "启用充电桩"; - this.chkEnabled.UseVisualStyleBackColor = true; - // - // chkShieldSiteMechanismStatus - // - this.chkShieldSiteMechanismStatus.AutoSize = true; - this.chkShieldSiteMechanismStatus.Font = new System.Drawing.Font("微软雅黑", 10F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - this.chkShieldSiteMechanismStatus.Location = new System.Drawing.Point(167, 473); - this.chkShieldSiteMechanismStatus.Margin = new System.Windows.Forms.Padding(4); - this.chkShieldSiteMechanismStatus.Name = "chkShieldSiteMechanismStatus"; - this.chkShieldSiteMechanismStatus.Size = new System.Drawing.Size(168, 27); - this.chkShieldSiteMechanismStatus.TabIndex = 18; - this.chkShieldSiteMechanismStatus.Text = "屏蔽机构状态交互"; - this.chkShieldSiteMechanismStatus.UseVisualStyleBackColor = true; - // - // numCurrent - // - this.numCurrent.DecimalPlaces = 1; - this.numCurrent.Font = new System.Drawing.Font("微软雅黑", 10F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - this.numCurrent.Increment = new decimal(new int[] { - 1, - 0, - 0, - 65536}); - this.numCurrent.Location = new System.Drawing.Point(167, 296); - this.numCurrent.Margin = new System.Windows.Forms.Padding(4); - this.numCurrent.Maximum = new decimal(new int[] { - 110, - 0, - 0, - 0}); - this.numCurrent.Name = "numCurrent"; - this.numCurrent.Size = new System.Drawing.Size(400, 29); - this.numCurrent.TabIndex = 13; - this.numCurrent.Value = new decimal(new int[] { - 10, - 0, - 0, - 0}); - // - // lblCurrent - // - this.lblCurrent.Font = new System.Drawing.Font("微软雅黑", 10F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - this.lblCurrent.Location = new System.Drawing.Point(27, 296); - this.lblCurrent.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); - this.lblCurrent.Name = "lblCurrent"; - this.lblCurrent.Size = new System.Drawing.Size(133, 31); - this.lblCurrent.TabIndex = 12; - this.lblCurrent.Text = "电流(A):"; - this.lblCurrent.TextAlign = System.Drawing.ContentAlignment.MiddleRight; - // - // numVoltage - // - this.numVoltage.DecimalPlaces = 1; - this.numVoltage.Font = new System.Drawing.Font("微软雅黑", 10F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - this.numVoltage.Increment = new decimal(new int[] { - 1, - 0, - 0, - 65536}); - this.numVoltage.Location = new System.Drawing.Point(167, 258); - this.numVoltage.Margin = new System.Windows.Forms.Padding(4); - this.numVoltage.Maximum = new decimal(new int[] { - 68, - 0, - 0, - 0}); - this.numVoltage.Name = "numVoltage"; - this.numVoltage.Size = new System.Drawing.Size(400, 29); - this.numVoltage.TabIndex = 11; - // - // lblVoltage - // - this.lblVoltage.Font = new System.Drawing.Font("微软雅黑", 10F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - this.lblVoltage.Location = new System.Drawing.Point(27, 258); - this.lblVoltage.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); - this.lblVoltage.Name = "lblVoltage"; - this.lblVoltage.Size = new System.Drawing.Size(133, 31); - this.lblVoltage.TabIndex = 10; - this.lblVoltage.Text = "电压(V):"; - this.lblVoltage.TextAlign = System.Drawing.ContentAlignment.MiddleRight; - // - // numPort - // - this.numPort.Font = new System.Drawing.Font("微软雅黑", 10F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - this.numPort.Location = new System.Drawing.Point(167, 220); - this.numPort.Margin = new System.Windows.Forms.Padding(4); - this.numPort.Maximum = new decimal(new int[] { - 65535, - 0, - 0, - 0}); - this.numPort.Minimum = new decimal(new int[] { - 1, - 0, - 0, - 0}); - this.numPort.Name = "numPort"; - this.numPort.Size = new System.Drawing.Size(400, 29); - this.numPort.TabIndex = 9; - this.numPort.Value = new decimal(new int[] { - 1, - 0, - 0, - 0}); - // - // lblPort - // - this.lblPort.Font = new System.Drawing.Font("微软雅黑", 10F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - this.lblPort.Location = new System.Drawing.Point(27, 220); - this.lblPort.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); - this.lblPort.Name = "lblPort"; - this.lblPort.Size = new System.Drawing.Size(133, 31); - this.lblPort.TabIndex = 8; - this.lblPort.Text = "端口:"; - this.lblPort.TextAlign = System.Drawing.ContentAlignment.MiddleRight; - // - // txtIpAddress - // - this.txtIpAddress.Font = new System.Drawing.Font("微软雅黑", 10F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - this.txtIpAddress.Location = new System.Drawing.Point(167, 182); - this.txtIpAddress.Margin = new System.Windows.Forms.Padding(4); - this.txtIpAddress.Name = "txtIpAddress"; - this.txtIpAddress.Size = new System.Drawing.Size(399, 29); - this.txtIpAddress.TabIndex = 7; - // - // lblIpAddress - // - this.lblIpAddress.Font = new System.Drawing.Font("微软雅黑", 10F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - this.lblIpAddress.Location = new System.Drawing.Point(27, 182); - this.lblIpAddress.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); - this.lblIpAddress.Name = "lblIpAddress"; - this.lblIpAddress.Size = new System.Drawing.Size(133, 31); - this.lblIpAddress.TabIndex = 6; - this.lblIpAddress.Text = "IP地址:"; - this.lblIpAddress.TextAlign = System.Drawing.ContentAlignment.MiddleRight; - // - // cmbChargeMethod - // - this.cmbChargeMethod.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; - this.cmbChargeMethod.Font = new System.Drawing.Font("微软雅黑", 10F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - this.cmbChargeMethod.FormattingEnabled = true; - this.cmbChargeMethod.Items.AddRange(new object[] { - "地充", - "尾充", - "侧充"}); - this.cmbChargeMethod.Location = new System.Drawing.Point(167, 144); - this.cmbChargeMethod.Margin = new System.Windows.Forms.Padding(4); - this.cmbChargeMethod.Name = "cmbChargeMethod"; - this.cmbChargeMethod.Size = new System.Drawing.Size(399, 31); - this.cmbChargeMethod.TabIndex = 23; - this.cmbChargeMethod.SelectedIndexChanged += new System.EventHandler(this.cmbChargeMethod_SelectedIndexChanged); - // - // lblChargeMethod - // - this.lblChargeMethod.Font = new System.Drawing.Font("微软雅黑", 10F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - this.lblChargeMethod.Location = new System.Drawing.Point(27, 144); - this.lblChargeMethod.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); - this.lblChargeMethod.Name = "lblChargeMethod"; - this.lblChargeMethod.Size = new System.Drawing.Size(133, 31); - this.lblChargeMethod.TabIndex = 22; - this.lblChargeMethod.Text = "充电方式:"; - this.lblChargeMethod.TextAlign = System.Drawing.ContentAlignment.MiddleRight; - // - // cmbType - // - this.cmbType.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; - this.cmbType.Font = new System.Drawing.Font("微软雅黑", 10F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - this.cmbType.FormattingEnabled = true; - this.cmbType.Items.AddRange(new object[] { - "FRLD高款充电桩", - "FRLD矮款充电桩", - "牧星充电桩"}); - this.cmbType.Location = new System.Drawing.Point(167, 106); - this.cmbType.Margin = new System.Windows.Forms.Padding(4); - this.cmbType.Name = "cmbType"; - this.cmbType.Size = new System.Drawing.Size(399, 31); - this.cmbType.TabIndex = 5; - // - // lblType - // - this.lblType.Font = new System.Drawing.Font("微软雅黑", 10F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - this.lblType.Location = new System.Drawing.Point(27, 106); - this.lblType.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); - this.lblType.Name = "lblType"; - this.lblType.Size = new System.Drawing.Size(133, 31); - this.lblType.TabIndex = 4; - this.lblType.Text = "类型:"; - this.lblType.TextAlign = System.Drawing.ContentAlignment.MiddleRight; - // - // txtName - // - this.txtName.Font = new System.Drawing.Font("微软雅黑", 10F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - this.txtName.Location = new System.Drawing.Point(167, 64); - this.txtName.Margin = new System.Windows.Forms.Padding(4); - this.txtName.Name = "txtName"; - this.txtName.Size = new System.Drawing.Size(192, 29); - this.txtName.TabIndex = 3; - // - // lblName - // - this.lblName.Font = new System.Drawing.Font("微软雅黑", 10F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - this.lblName.Location = new System.Drawing.Point(26, 62); - this.lblName.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); - this.lblName.Name = "lblName"; - this.lblName.Size = new System.Drawing.Size(133, 31); - this.lblName.TabIndex = 2; - this.lblName.Text = "充电桩名称:"; - this.lblName.TextAlign = System.Drawing.ContentAlignment.MiddleRight; - // - // txtStationId - // - this.txtStationId.Font = new System.Drawing.Font("微软雅黑", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - this.txtStationId.Location = new System.Drawing.Point(167, 29); - this.txtStationId.Margin = new System.Windows.Forms.Padding(4); - this.txtStationId.Name = "txtStationId"; - this.txtStationId.Size = new System.Drawing.Size(192, 27); - this.txtStationId.TabIndex = 1; - // - // lblStationId - // - this.lblStationId.Font = new System.Drawing.Font("微软雅黑", 10F); - this.lblStationId.Location = new System.Drawing.Point(49, 31); - this.lblStationId.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); - this.lblStationId.Name = "lblStationId"; - this.lblStationId.Size = new System.Drawing.Size(127, 25); - this.lblStationId.TabIndex = 0; - this.lblStationId.Text = "充电桩编号:"; - this.lblStationId.TextAlign = System.Drawing.ContentAlignment.BottomLeft; - // - // pnlEditButtons - // - this.pnlEditButtons.Controls.Add(this.btnCancel); - this.pnlEditButtons.Controls.Add(this.btnDelete); - this.pnlEditButtons.Controls.Add(this.btnSave); - this.pnlEditButtons.Dock = System.Windows.Forms.DockStyle.Bottom; - this.pnlEditButtons.Location = new System.Drawing.Point(0, 750); - this.pnlEditButtons.Margin = new System.Windows.Forms.Padding(4); - this.pnlEditButtons.Name = "pnlEditButtons"; - this.pnlEditButtons.Padding = new System.Windows.Forms.Padding(13, 12, 13, 12); - this.pnlEditButtons.Size = new System.Drawing.Size(645, 125); - this.pnlEditButtons.TabIndex = 0; - // - // btnCancel - // - this.btnCancel.Font = new System.Drawing.Font("微软雅黑", 11F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - this.btnCancel.Location = new System.Drawing.Point(373, 25); - this.btnCancel.Margin = new System.Windows.Forms.Padding(4); - this.btnCancel.Name = "btnCancel"; - this.btnCancel.Size = new System.Drawing.Size(133, 62); - this.btnCancel.TabIndex = 2; - this.btnCancel.Text = "取消"; - this.btnCancel.UseVisualStyleBackColor = true; - this.btnCancel.Click += new System.EventHandler(this.btnCancel_Click); - // - // btnDelete - // - this.btnDelete.BackColor = System.Drawing.Color.LightCoral; - this.btnDelete.Font = new System.Drawing.Font("微软雅黑", 11F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - this.btnDelete.Location = new System.Drawing.Point(200, 25); - this.btnDelete.Margin = new System.Windows.Forms.Padding(4); - this.btnDelete.Name = "btnDelete"; - this.btnDelete.Size = new System.Drawing.Size(133, 62); - this.btnDelete.TabIndex = 1; - this.btnDelete.Text = "删除"; - this.btnDelete.UseVisualStyleBackColor = false; - this.btnDelete.Click += new System.EventHandler(this.btnDelete_Click); - // - // btnSave - // - this.btnSave.BackColor = System.Drawing.Color.LightBlue; - this.btnSave.Font = new System.Drawing.Font("微软雅黑", 11F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - this.btnSave.Location = new System.Drawing.Point(27, 25); - this.btnSave.Margin = new System.Windows.Forms.Padding(4); - this.btnSave.Name = "btnSave"; - this.btnSave.Size = new System.Drawing.Size(133, 62); - this.btnSave.TabIndex = 0; - this.btnSave.Text = "保存"; - this.btnSave.UseVisualStyleBackColor = false; - this.btnSave.Click += new System.EventHandler(this.btnSave_Click); - // - // colStationId - // - this.colStationId.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.None; - dataGridViewCellStyle2.Alignment = System.Windows.Forms.DataGridViewContentAlignment.MiddleCenter; - this.colStationId.DefaultCellStyle = dataGridViewCellStyle2; - this.colStationId.HeaderText = "编号"; - this.colStationId.MinimumWidth = 6; - this.colStationId.Name = "colStationId"; - this.colStationId.ReadOnly = true; - this.colStationId.Resizable = System.Windows.Forms.DataGridViewTriState.False; - this.colStationId.Width = 30; - // - // colName - // - this.colName.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.None; - dataGridViewCellStyle3.Alignment = System.Windows.Forms.DataGridViewContentAlignment.MiddleCenter; - this.colName.DefaultCellStyle = dataGridViewCellStyle3; - this.colName.HeaderText = "名称"; - this.colName.MinimumWidth = 6; - this.colName.Name = "colName"; - this.colName.ReadOnly = true; - this.colName.Resizable = System.Windows.Forms.DataGridViewTriState.False; - this.colName.Width = 30; - // - // colType - // - this.colType.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.None; - dataGridViewCellStyle4.Alignment = System.Windows.Forms.DataGridViewContentAlignment.MiddleCenter; - this.colType.DefaultCellStyle = dataGridViewCellStyle4; - this.colType.FillWeight = 189.1357F; - this.colType.HeaderText = "类型"; - this.colType.MinimumWidth = 6; - this.colType.Name = "colType"; - this.colType.ReadOnly = true; - this.colType.Resizable = System.Windows.Forms.DataGridViewTriState.False; - this.colType.Width = 110; - // - // colChargeMethod - // - this.colChargeMethod.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.None; - dataGridViewCellStyle5.Alignment = System.Windows.Forms.DataGridViewContentAlignment.MiddleCenter; - this.colChargeMethod.DefaultCellStyle = dataGridViewCellStyle5; - this.colChargeMethod.FillWeight = 98.90109F; - this.colChargeMethod.HeaderText = "充电方式"; - this.colChargeMethod.MinimumWidth = 6; - this.colChargeMethod.Name = "colChargeMethod"; - this.colChargeMethod.ReadOnly = true; - this.colChargeMethod.Resizable = System.Windows.Forms.DataGridViewTriState.False; - this.colChargeMethod.Width = 50; - // - // colSiteId - // - dataGridViewCellStyle6.Alignment = System.Windows.Forms.DataGridViewContentAlignment.MiddleCenter; - this.colSiteId.DefaultCellStyle = dataGridViewCellStyle6; - this.colSiteId.FillWeight = 59.72706F; - this.colSiteId.HeaderText = "站点编号"; - this.colSiteId.MinimumWidth = 6; - this.colSiteId.Name = "colSiteId"; - this.colSiteId.ReadOnly = true; - this.colSiteId.Resizable = System.Windows.Forms.DataGridViewTriState.False; - // - // colLastSendTime - // - dataGridViewCellStyle7.Alignment = System.Windows.Forms.DataGridViewContentAlignment.MiddleCenter; - this.colLastSendTime.DefaultCellStyle = dataGridViewCellStyle7; - this.colLastSendTime.FillWeight = 59.72706F; - this.colLastSendTime.HeaderText = "发送时间"; - this.colLastSendTime.MinimumWidth = 6; - this.colLastSendTime.Name = "colLastSendTime"; - this.colLastSendTime.ReadOnly = true; - this.colLastSendTime.Resizable = System.Windows.Forms.DataGridViewTriState.False; - // - // colLastReceiveTime - // - dataGridViewCellStyle8.Alignment = System.Windows.Forms.DataGridViewContentAlignment.MiddleCenter; - this.colLastReceiveTime.DefaultCellStyle = dataGridViewCellStyle8; - this.colLastReceiveTime.FillWeight = 59.72706F; - this.colLastReceiveTime.HeaderText = "接收时间"; - this.colLastReceiveTime.MinimumWidth = 6; - this.colLastReceiveTime.Name = "colLastReceiveTime"; - this.colLastReceiveTime.ReadOnly = true; - this.colLastReceiveTime.Resizable = System.Windows.Forms.DataGridViewTriState.False; - // - // colCommStatus - // - dataGridViewCellStyle9.Alignment = System.Windows.Forms.DataGridViewContentAlignment.MiddleCenter; - this.colCommStatus.DefaultCellStyle = dataGridViewCellStyle9; - this.colCommStatus.FillWeight = 59.72706F; - this.colCommStatus.HeaderText = "通讯"; - this.colCommStatus.MinimumWidth = 6; - this.colCommStatus.Name = "colCommStatus"; - this.colCommStatus.ReadOnly = true; - this.colCommStatus.Resizable = System.Windows.Forms.DataGridViewTriState.False; - // - // colChargeCommandStatus - // - dataGridViewCellStyle10.Alignment = System.Windows.Forms.DataGridViewContentAlignment.MiddleCenter; - this.colChargeCommandStatus.DefaultCellStyle = dataGridViewCellStyle10; - this.colChargeCommandStatus.FillWeight = 59.72706F; - this.colChargeCommandStatus.HeaderText = "充电指令"; - this.colChargeCommandStatus.MinimumWidth = 6; - this.colChargeCommandStatus.Name = "colChargeCommandStatus"; - this.colChargeCommandStatus.ReadOnly = true; - this.colChargeCommandStatus.Resizable = System.Windows.Forms.DataGridViewTriState.False; - // - // colMechanismStatus - // - dataGridViewCellStyle11.Alignment = System.Windows.Forms.DataGridViewContentAlignment.MiddleCenter; - this.colMechanismStatus.DefaultCellStyle = dataGridViewCellStyle11; - this.colMechanismStatus.FillWeight = 59.72706F; - this.colMechanismStatus.HeaderText = "机构状态"; - this.colMechanismStatus.MinimumWidth = 6; - this.colMechanismStatus.Name = "colMechanismStatus"; - this.colMechanismStatus.ReadOnly = true; - this.colMechanismStatus.Resizable = System.Windows.Forms.DataGridViewTriState.False; - // - // colCurrentVehicle - // - dataGridViewCellStyle12.Alignment = System.Windows.Forms.DataGridViewContentAlignment.MiddleCenter; - this.colCurrentVehicle.DefaultCellStyle = dataGridViewCellStyle12; - this.colCurrentVehicle.FillWeight = 59.72706F; - this.colCurrentVehicle.HeaderText = "当前车辆"; - this.colCurrentVehicle.MinimumWidth = 6; - this.colCurrentVehicle.Name = "colCurrentVehicle"; - this.colCurrentVehicle.ReadOnly = true; - this.colCurrentVehicle.Resizable = System.Windows.Forms.DataGridViewTriState.False; - // - // colBatteryLevel - // - dataGridViewCellStyle13.Alignment = System.Windows.Forms.DataGridViewContentAlignment.MiddleCenter; - dataGridViewCellStyle13.Format = "%"; - dataGridViewCellStyle13.NullValue = "0%"; - this.colBatteryLevel.DefaultCellStyle = dataGridViewCellStyle13; - this.colBatteryLevel.FillWeight = 59.72706F; - this.colBatteryLevel.HeaderText = "电量"; - this.colBatteryLevel.MinimumWidth = 6; - this.colBatteryLevel.Name = "colBatteryLevel"; - this.colBatteryLevel.ReadOnly = true; - this.colBatteryLevel.Resizable = System.Windows.Forms.DataGridViewTriState.False; - // - // colAlarm - // - this.colAlarm.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.None; - dataGridViewCellStyle14.Alignment = System.Windows.Forms.DataGridViewContentAlignment.MiddleCenter; - this.colAlarm.DefaultCellStyle = dataGridViewCellStyle14; - this.colAlarm.FillWeight = 120F; - this.colAlarm.HeaderText = "报警 "; - this.colAlarm.MinimumWidth = 6; - this.colAlarm.Name = "colAlarm"; - this.colAlarm.ReadOnly = true; - this.colAlarm.Resizable = System.Windows.Forms.DataGridViewTriState.False; - this.colAlarm.Width = 110; - // - // colRealTimeVoltage - // - dataGridViewCellStyle15.Alignment = System.Windows.Forms.DataGridViewContentAlignment.MiddleCenter; - dataGridViewCellStyle15.Format = "V"; - dataGridViewCellStyle15.NullValue = "V"; - this.colRealTimeVoltage.DefaultCellStyle = dataGridViewCellStyle15; - this.colRealTimeVoltage.FillWeight = 59.72706F; - this.colRealTimeVoltage.HeaderText = "实时电压"; - this.colRealTimeVoltage.MinimumWidth = 6; - this.colRealTimeVoltage.Name = "colRealTimeVoltage"; - this.colRealTimeVoltage.ReadOnly = true; - this.colRealTimeVoltage.Resizable = System.Windows.Forms.DataGridViewTriState.False; - // - // colRealTimeCurrent - // - dataGridViewCellStyle16.Alignment = System.Windows.Forms.DataGridViewContentAlignment.MiddleCenter; - this.colRealTimeCurrent.DefaultCellStyle = dataGridViewCellStyle16; - this.colRealTimeCurrent.FillWeight = 59.72706F; - this.colRealTimeCurrent.HeaderText = "实时电流"; - this.colRealTimeCurrent.MinimumWidth = 6; - this.colRealTimeCurrent.Name = "colRealTimeCurrent"; - this.colRealTimeCurrent.ReadOnly = true; - this.colRealTimeCurrent.Resizable = System.Windows.Forms.DataGridViewTriState.False; - // - // colStatus - // - dataGridViewCellStyle17.Alignment = System.Windows.Forms.DataGridViewContentAlignment.MiddleCenter; - this.colStatus.DefaultCellStyle = dataGridViewCellStyle17; - this.colStatus.FillWeight = 59.72706F; - this.colStatus.HeaderText = "充电状态"; - this.colStatus.MinimumWidth = 6; - this.colStatus.Name = "colStatus"; - this.colStatus.ReadOnly = true; - this.colStatus.Resizable = System.Windows.Forms.DataGridViewTriState.False; - // - // colEnabled - // - dataGridViewCellStyle18.Alignment = System.Windows.Forms.DataGridViewContentAlignment.MiddleCenter; - this.colEnabled.DefaultCellStyle = dataGridViewCellStyle18; - this.colEnabled.FillWeight = 59.72706F; - this.colEnabled.HeaderText = "启用"; - this.colEnabled.MinimumWidth = 6; - this.colEnabled.Name = "colEnabled"; - this.colEnabled.ReadOnly = true; - this.colEnabled.Resizable = System.Windows.Forms.DataGridViewTriState.False; - // - // ChargeStationManagementForm - // - this.AutoScaleDimensions = new System.Drawing.SizeF(8F, 15F); - this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - this.ClientSize = new System.Drawing.Size(1731, 875); - this.Controls.Add(this.splitContainer); - this.Margin = new System.Windows.Forms.Padding(4); - this.MinimumSize = new System.Drawing.Size(1327, 738); - this.Name = "ChargeStationManagementForm"; - this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen; - this.Text = "充电桩管理系统"; - this.splitContainer.Panel1.ResumeLayout(false); - this.splitContainer.Panel2.ResumeLayout(false); - ((System.ComponentModel.ISupportInitialize)(this.splitContainer)).EndInit(); - this.splitContainer.ResumeLayout(false); - this.pnlList.ResumeLayout(false); - ((System.ComponentModel.ISupportInitialize)(this.dgvStations)).EndInit(); - this.pnlListButtons.ResumeLayout(false); - this.pnlListButtons.PerformLayout(); - this.pnlSearch.ResumeLayout(false); - this.pnlSearch.PerformLayout(); - this.pnlEdit.ResumeLayout(false); - this.grpRealTimeInfo.ResumeLayout(false); - this.grpEditInfo.ResumeLayout(false); - this.grpEditInfo.PerformLayout(); - ((System.ComponentModel.ISupportInitialize)(this.numSiteId)).EndInit(); - ((System.ComponentModel.ISupportInitialize)(this.numCurrent)).EndInit(); - ((System.ComponentModel.ISupportInitialize)(this.numVoltage)).EndInit(); - ((System.ComponentModel.ISupportInitialize)(this.numPort)).EndInit(); - this.pnlEditButtons.ResumeLayout(false); - this.ResumeLayout(false); - - } - - #endregion - - private System.Windows.Forms.SplitContainer splitContainer; - private System.Windows.Forms.Panel pnlList; - private System.Windows.Forms.DataGridView dgvStations; - private System.Windows.Forms.Panel pnlListButtons; - private System.Windows.Forms.Label lblStatistics; - private System.Windows.Forms.Button btnStrategyConfig; - private System.Windows.Forms.Button btnCommMonitor; - private System.Windows.Forms.Button btnAlarmConfig; - private System.Windows.Forms.Button btnExport; - private System.Windows.Forms.Button btnRefresh; - private System.Windows.Forms.Panel pnlSearch; - private System.Windows.Forms.ComboBox cmbStatusFilter; - private System.Windows.Forms.Label lblStatusFilter; - private System.Windows.Forms.TextBox txtSearch; - private System.Windows.Forms.Label lblSearch; - private System.Windows.Forms.Panel pnlEdit; - private System.Windows.Forms.GroupBox grpEditInfo; - private System.Windows.Forms.TextBox txtRemarks; - private System.Windows.Forms.Label lblRemarks; - private System.Windows.Forms.NumericUpDown numSiteId; - private System.Windows.Forms.Label lblSiteId; - private System.Windows.Forms.CheckBox chkEnabled; - private System.Windows.Forms.CheckBox chkShieldSiteMechanismStatus; - private System.Windows.Forms.NumericUpDown numCurrent; - private System.Windows.Forms.Label lblCurrent; - private System.Windows.Forms.NumericUpDown numVoltage; - private System.Windows.Forms.Label lblVoltage; - private System.Windows.Forms.NumericUpDown numPort; - private System.Windows.Forms.Label lblPort; - private System.Windows.Forms.TextBox txtIpAddress; - private System.Windows.Forms.Label lblIpAddress; - private System.Windows.Forms.ComboBox cmbType; - private System.Windows.Forms.Label lblType; - private System.Windows.Forms.ComboBox cmbChargeMethod; - private System.Windows.Forms.Label lblChargeMethod; - private System.Windows.Forms.TextBox txtName; - private System.Windows.Forms.Label lblName; - private System.Windows.Forms.TextBox txtStationId; - private System.Windows.Forms.Label lblStationId; - private System.Windows.Forms.Panel pnlEditButtons; - private System.Windows.Forms.Button btnCancel; - private System.Windows.Forms.Button btnDelete; - private System.Windows.Forms.Button btnSave; - private System.Windows.Forms.GroupBox grpRealTimeInfo; - private System.Windows.Forms.Label lblCurrentVehicleValue; - private System.Windows.Forms.Label lblCurrentVehicle; - private System.Windows.Forms.Label lblBatteryLevelValue; - private System.Windows.Forms.Label lblBatteryLevel; - private System.Windows.Forms.Label lblRealTimeVoltageValue; - private System.Windows.Forms.Label lblRealTimeVoltage; - private System.Windows.Forms.Label lblRealTimeCurrentValue; - private System.Windows.Forms.Label lblRealTimeCurrent; - private System.Windows.Forms.Label lblCommStatusValue; - private System.Windows.Forms.Label lblCommStatus; - private System.Windows.Forms.Label lblChargeCommandStatusValue; - private System.Windows.Forms.Label lblChargeCommandStatus; - private System.Windows.Forms.Label lblMechanismStatusValue; - private System.Windows.Forms.Label lblMechanismStatus; - private System.Windows.Forms.Label lblAlarmValue; - private System.Windows.Forms.Label lblAlarm; - private System.Windows.Forms.ComboBox chargeCarType; - private System.Windows.Forms.Label label1; - private System.Windows.Forms.DataGridViewTextBoxColumn colStationId; - private System.Windows.Forms.DataGridViewTextBoxColumn colName; - private System.Windows.Forms.DataGridViewTextBoxColumn colType; - private System.Windows.Forms.DataGridViewTextBoxColumn colChargeMethod; - private System.Windows.Forms.DataGridViewTextBoxColumn colSiteId; - private System.Windows.Forms.DataGridViewTextBoxColumn colLastSendTime; - private System.Windows.Forms.DataGridViewTextBoxColumn colLastReceiveTime; - private System.Windows.Forms.DataGridViewTextBoxColumn colCommStatus; - private System.Windows.Forms.DataGridViewTextBoxColumn colChargeCommandStatus; - private System.Windows.Forms.DataGridViewTextBoxColumn colMechanismStatus; - private System.Windows.Forms.DataGridViewTextBoxColumn colCurrentVehicle; - private System.Windows.Forms.DataGridViewTextBoxColumn colBatteryLevel; - private System.Windows.Forms.DataGridViewTextBoxColumn colAlarm; - private System.Windows.Forms.DataGridViewTextBoxColumn colRealTimeVoltage; - private System.Windows.Forms.DataGridViewTextBoxColumn colRealTimeCurrent; - private System.Windows.Forms.DataGridViewTextBoxColumn colStatus; - private System.Windows.Forms.DataGridViewTextBoxColumn colEnabled; - } -} - diff --git a/StandardScene.Core/Charge/ChargeStationManagementForm.cs b/StandardScene.Core/Charge/ChargeStationManagementForm.cs index bd1b27d..1f26c4c 100644 --- a/StandardScene.Core/Charge/ChargeStationManagementForm.cs +++ b/StandardScene.Core/Charge/ChargeStationManagementForm.cs @@ -1,1389 +1,455 @@ -using SimpleCore; using System; +using System.Collections.Generic; using System.Drawing; +using System.IO; using System.Linq; using System.Net.NetworkInformation; -using System.Windows.Forms; +using System.Text; +using System.Threading.Tasks; +using CycleGUI; +using SimpleCore; +using StandardScene.Utils; namespace StandardScene.Charge { /// - /// 充电桩管理窗口 + /// 充电桩管理界面(CycleGUI 版,替代原 WinForms 窗体)。 /// - public partial class ChargeStationManagementForm : Form + public class ChargeStationManagementForm { - private ChargeStationDataService dataService; - private ChargeStation selectedStation; - private System.Windows.Forms.Timer autoRefreshTimer; - private Ping Ping = new Ping(); - private CommunicationMonitorForm communicationMonitorForm; + private const string TableId = "charge-station-list"; + private static readonly string[] StatusFilterNames = { "全部状态", "空闲", "充电中", "故障", "离线" }; + private static readonly string[] TypeNames = { "FRLD高款充电桩", "FRLD矮款充电桩", "牧星充电桩" }; + private static readonly string[] MethodNames = { "地充", "尾充", "侧充" }; + private static readonly string[] CarTypeNames = { "FRLD充电", "牧星充电桩充电" }; - public ChargeStationManagementForm() - { - InitializeComponent(); - dataService = ChargeStationDataService.Instance; - InitializeForm(); - InitializeAutoRefresh(); - } + private static readonly ChargeStationDataService DataService = ChargeStationDataService.Instance; + private static readonly Ping Ping = new Ping(); - /// - /// 初始化自动刷新定时器 - /// - private void InitializeAutoRefresh() - { - autoRefreshTimer = new System.Windows.Forms.Timer(); - autoRefreshTimer.Interval = 3000; // 每3秒刷新一次 - autoRefreshTimer.Tick += AutoRefreshTimer_Tick; - autoRefreshTimer.Start(); - } + private static Panel _panel; + private static Panel _editDialog; + private static List _snapshot = new List(); + private static volatile bool _refreshing; + private static DateTime _lastFlush = DateTime.MinValue; + private static readonly TimeSpan FlushInterval = TimeSpan.FromSeconds(3); - /// - /// 自动刷新事件 - /// - private void AutoRefreshTimer_Tick(object sender, EventArgs e) + private static int _statusFilterIdx; + private static string _searchText = ""; + private static string _statsText = ""; + private static string _status = ""; + + public void Show() => Open(); + + public static void Open() { - // 保存当前选中的充电桩ID - string selectedStationId = null; - if (dgvStations.SelectedRows.Count > 0) + if (_panel != null) { - selectedStationId = dgvStations.SelectedRows[0].Cells[0].Value?.ToString(); + try { _panel.BringToFront(); return; } + catch { _panel = null; } } - // 刷新列表 - LoadStations(); + _statusFilterIdx = 0; + _searchText = ""; + _lastFlush = DateTime.MinValue; - // 恢复选中状态 - if (!string.IsNullOrEmpty(selectedStationId)) + var panel = GUI.DeclarePanel() + .ShowTitle("充电桩管理系统") + .SetDefaultDocking(Panel.Docking.None) + .InitSize(1400, 760) + .InitPos(false, 0, 0, 0.5f, 0.5f, 0.5f, 0.5f); + _panel = panel; + panel.IfTerminalQuit(() => _panel = null); + + panel.Define(pb => { - foreach (DataGridViewRow row in dgvStations.Rows) + if (pb.Closing()) { - if (row.Cells[0].Value?.ToString() == selectedStationId) - { - row.Selected = true; - dgvStations.CurrentCell = row.Cells[0]; - break; - } + panel.Exit(); + _panel = null; + return; } - } + + if (pb.Button("新增", distinct: "cs-add")) OpenEditDialog(null); + pb.SameLine(8); + if (pb.Button("刷新", distinct: "cs-refresh")) + { + DataService.Reload(); + _lastFlush = DateTime.MinValue; + _status = "数据已刷新"; + } + pb.SameLine(8); + if (pb.Button("策略配置", distinct: "cs-strategy")) ChargeStrategyConfigForm.Open(); + pb.SameLine(8); + if (pb.Button("通讯监控", distinct: "cs-comm")) CommunicationMonitorForm.Open(); + pb.SameLine(8); + if (pb.Button("报警配置", distinct: "cs-alarm")) AlarmConfigManagementForm.Open(); + pb.SameLine(8); + if (pb.Button("导出", distinct: "cs-export")) ExportData(pb); + + pb.Separator(); + if (pb.DropdownBox("状态筛选", StatusFilterNames, ref _statusFilterIdx)) + _lastFlush = DateTime.MinValue; + pb.SameLine(12); + var (search, _) = pb.TextInput("搜索", _searchText, alwaysReturnString: true, hintText: "编号/名称/IP"); + if (search != _searchText) { _searchText = search; _lastFlush = DateTime.MinValue; } + + EnsureSnapshotFresh(); + var items = FilterSnapshot(_snapshot); + UpdateStats(_snapshot); + + pb.Label(_statsText); + pb.Label($"显示 {items.Count} / 共 {_snapshot.Count} 个充电桩"); + + pb.Table(TableId, + new[] + { + "编号", "名称", "类型", "充电方式", "站点", "通讯", "状态", + "车辆", "电量", "电压", "电流", "启用", "操作" + }, + items.Count, (row, i) => + { + var s = items[i]; + ApplyRowColor(row, s); + row.Label(s.StationId); + row.Label(s.Name); + row.Label(GetTypeText(s.Type)); + row.Label(GetMethodText(s.ChargeMethod)); + row.Label(s.SiteId?.ToString() ?? ""); + row.Label(FormatComm(s.CommStatus)); + row.Label(GetStatusText(s.Status)); + row.Label(string.IsNullOrWhiteSpace(s.CurrentVehicle) ? "-" : s.CurrentVehicle); + row.Label(s.BatteryLevel > 0 ? $"{s.BatteryLevel:F1}%" : "-"); + row.Label($"{s.RealTimeVoltage:F1}"); + row.Label($"{s.RealTimeCurrent:F1}"); + row.Label(s.Enabled ? "是" : "否"); + var op = row.ButtonGroup(new[] { "编辑", "删除" }, new[] { "编辑充电桩", "删除充电桩" }); + if (op == 0) OpenEditDialog(s); + else if (op == 1) ConfirmDelete(s); + }, height: 16, enableSearch: true); + + if (!string.IsNullOrEmpty(_status)) + { + pb.Separator(); + pb.Label(_status); + } + + pb.Panel.Repaint(repaintTimeMs: 1000); + }); } - /// - /// 窗体关闭时停止定时器 - /// - protected override void OnFormClosing(FormClosingEventArgs e) + private static void EnsureSnapshotFresh() { - if (autoRefreshTimer != null) - { - autoRefreshTimer.Stop(); - autoRefreshTimer.Dispose(); - } - base.OnFormClosing(e); - } - - private void InitializeForm() - { - // 设置窗口属性 - this.Text = "充电桩管理系统"; - this.Size = new Size(1200, 700); - this.StartPosition = FormStartPosition.CenterScreen; - this.MinimumSize = new Size(1000, 600); - - // 设置表头文字垂直排列 - SetupVerticalHeaderText(); - - // 初始化状态筛选下拉框 - InitializeStatusFilter(); - - // 加载数据 - LoadStations(); - - // 设置默认状态为新增模式 - ClearEditFields(); - } - - /// - /// 设置表头文字垂直排列 - /// - private void SetupVerticalHeaderText() - { - if (dgvStations == null) - return; - - // 增加列标题高度以容纳垂直文字 - dgvStations.ColumnHeadersHeight = 100; - - // 订阅列标题绘制事件 - dgvStations.ColumnHeadersHeightSizeMode = DataGridViewColumnHeadersHeightSizeMode.DisableResizing; - dgvStations.CellPainting += DgvStations_CellPainting; - - // 固定行高度 - dgvStations.RowTemplate.Height = 35; - dgvStations.AllowUserToResizeRows = false; - dgvStations.RowHeadersWidthSizeMode = DataGridViewRowHeadersWidthSizeMode.DisableResizing; - dgvStations.AutoSizeRowsMode = DataGridViewAutoSizeRowsMode.None; - } - - /// - /// 自定义绘制列标题(垂直文字) - /// - private void DgvStations_CellPainting(object sender, DataGridViewCellPaintingEventArgs e) - { - // 只处理列标题行 - if (e.RowIndex == -1 && e.ColumnIndex >= 0) + if (_refreshing) return; + if (DateTime.Now - _lastFlush < FlushInterval) return; + _lastFlush = DateTime.Now; + _refreshing = true; + Task.Run(() => { try { - // 绘制背景 - e.PaintBackground(e.CellBounds, true); - - // 获取列标题文本 - string headerText = dgvStations.Columns[e.ColumnIndex].HeaderText; - - // 设置文字格式(垂直排列,从上到下) - using (var brush = new SolidBrush(dgvStations.ColumnHeadersDefaultCellStyle.ForeColor)) - using (var format = new StringFormat()) + var list = DataService.GetAllStations().ToList(); + foreach (var s in list) { - format.Alignment = StringAlignment.Center; - format.LineAlignment = StringAlignment.Near; - format.FormatFlags = StringFormatFlags.DirectionVertical; // 垂直文字 - - // 计算绘制位置(居中) - float x = e.CellBounds.Left + (e.CellBounds.Width - e.Graphics.MeasureString("测", dgvStations.ColumnHeadersDefaultCellStyle.Font).Width) / 2; - float y = e.CellBounds.Top + 5; - - // 绘制垂直文字 - e.Graphics.DrawString( - headerText, - dgvStations.ColumnHeadersDefaultCellStyle.Font, - brush, - new RectangleF(x, y, e.CellBounds.Width, e.CellBounds.Height - 10), - format); - } - - // 绘制边框 - e.Paint(e.CellBounds, DataGridViewPaintParts.Border); - - // 标记为已处理 - e.Handled = true; - } - catch (Exception ex) - { - System.Diagnostics.Debug.WriteLine($"绘制列标题失败: {ex.Message}"); - } - } - } - - /// - /// 初始化状态筛选下拉框 - /// - private void InitializeStatusFilter() - { - if (cmbStatusFilter != null) - { - cmbStatusFilter.Items.Clear(); - cmbStatusFilter.Items.Add("全部状态"); - cmbStatusFilter.Items.Add("空闲"); - cmbStatusFilter.Items.Add("充电中"); - cmbStatusFilter.Items.Add("故障"); - cmbStatusFilter.Items.Add("离线"); - cmbStatusFilter.SelectedIndex = 0; // 默认显示全部 - } - } - - /// - /// 加载充电桩列表 - /// - private void LoadStations() - { - try - { - var stations = dataService.GetAllStations(); - - // 根据状态筛选 - if (cmbStatusFilter != null && cmbStatusFilter.SelectedIndex > 0) - { - var filterStatus = GetStatusFromFilterIndex(cmbStatusFilter.SelectedIndex); - stations = stations.Where(s => s.Status == filterStatus).ToList(); - } - - dgvStations.Rows.Clear(); - - foreach (var station in stations) - { - - try - { - if (Ping.Send(station.IpAddress, 1000).Status == IPStatus.Success) + try { - station.CommStatus = CommunicationStatus.Normal; - } - else - { - station.CommStatus = CommunicationStatus.Error; + s.CommStatus = Ping.Send(s.IpAddress, 1000).Status == IPStatus.Success + ? CommunicationStatus.Normal : CommunicationStatus.Error; } + catch { s.CommStatus = CommunicationStatus.Error; } } - catch (Exception) - { + _snapshot = list; + } + catch (Exception ex) { _status = $"加载失败: {ex.Message}"; } + finally { _refreshing = false; _panel?.Repaint(); } + }); + } - station.CommStatus = CommunicationStatus.Error; - } + private static List FilterSnapshot(List src) + { + IEnumerable q = src; + if (_statusFilterIdx > 0) + { + var st = GetStatusFromFilterIndex(_statusFilterIdx); + q = q.Where(s => s.Status == st); + } + var text = (_searchText ?? "").Trim().ToLower(); + if (!string.IsNullOrEmpty(text)) + { + q = q.Where(s => s.StationId.ToLower().Contains(text) + || s.Name.ToLower().Contains(text) + || (s.IpAddress ?? "").Contains(text) + || GetTypeText(s.Type).Contains(text)); + } + return q.ToList(); + } + private static void UpdateStats(List all) + { + _statsText = $"总数: {all.Count} | 空闲: {all.Count(s => s.Status == ChargeStationStatus.Idle)} | " + + $"充电中: {all.Count(s => s.Status == ChargeStationStatus.Charging)} | " + + $"故障: {all.Count(s => s.Status == ChargeStationStatus.Fault)} | " + + $"AGV电池已接入: {all.Count(s => s.Status == ChargeStationStatus.Battery)}"; + } - var index = dgvStations.Rows.Add( - station.StationId, - station.Name, - GetTypeText(station.Type), - GetChargeMethodText(station.ChargeMethod), - //station.IpAddress, - //station.Port, - station.SiteId?.ToString() ?? "", - FormatTimeToMinuteSecond(station.LastSendTime), - FormatTimeToMinuteSecond(station.LastReceiveTime), - FormatCommStatusDisplay(station.CommStatus), - FormatChargeCommandStatusDisplay(station.ChargeCommandStatus), - FormatMechanismStatusDisplay(station.MechanismStatus), - string.IsNullOrWhiteSpace(station.CurrentVehicle) ? "-" : station.CurrentVehicle, - station.BatteryLevel > 0 ? $"{station.BatteryLevel:F1}%" : "-", - FormatAlarmDisplay(station), - //station.SetVoltage, - //station.SetElectricCurrent, - station.RealTimeVoltage.ToString("F1"), - station.RealTimeCurrent.ToString("F1"), - GetStatusText(station.Status), - station.Enabled ? "是" : "否", - station.Remarks - ); + private static void OpenEditDialog(ChargeStation existing) + { + if (_editDialog != null) + { + try { _editDialog.BringToFront(); return; } + catch { _editDialog = null; } + } - // 根据状态设置行颜色(扁平化设计) - var row = dgvStations.Rows[index]; + bool isAdd = existing == null; + string stationId = isAdd ? "" : existing.StationId; + string name = isAdd ? "" : existing.Name; + int typeIdx = isAdd ? 1 : (int)existing.Type; + int methodIdx = isAdd ? 0 : (int)existing.ChargeMethod; + int carTypeIdx = isAdd ? 0 : (int)existing.GroupCarType; + string ip = isAdd ? "192.168." : existing.IpAddress; + string port = isAdd ? "2000" : existing.Port.ToString(); + string voltage = isAdd ? "48" : existing.SetVoltage.ToString("F1"); + string current = isAdd ? "32" : existing.SetElectricCurrent.ToString("F1"); + string siteId = isAdd ? "0" : (existing.SiteId?.ToString() ?? "0"); + string remarks = isAdd ? "" : (existing.Remarks ?? ""); + bool enabled = isAdd || existing.Enabled; + bool shield = !isAdd && existing.ShieldSiteMechanismStatus; + string err = ""; - // 通讯状态列样式设置(第9列,因为增加了充电方式列) - var commCell = row.Cells[9]; - switch (station.CommStatus) - { - case CommunicationStatus.Normal: - commCell.Style.ForeColor = Color.FromArgb(76, 175, 80); // 绿色 - // commCell.Style.Font = new Font(commCell.Style.Font ?? row.DefaultCellStyle.Font, FontStyle.Bold); - break; - case CommunicationStatus.Delayed: - commCell.Style.ForeColor = Color.FromArgb(255, 152, 0); // 橙色 - // commCell.Style.Font = new Font(commCell.Style.Font ?? row.DefaultCellStyle.Font, FontStyle.Bold); - break; - case CommunicationStatus.Timeout: - case CommunicationStatus.Disconnected: - case CommunicationStatus.Error: - commCell.Style.ForeColor = Color.FromArgb(244, 67, 54); // 红色 - // commCell.Style.Font = new Font(commCell.Style.Font ?? row.DefaultCellStyle.Font, FontStyle.Bold); - break; - case CommunicationStatus.Unknown: - commCell.Style.ForeColor = Color.FromArgb(158, 158, 158); // 灰色 - break; - } + var dlg = GUI.DeclarePanel() + .ShowTitle(isAdd ? "新增充电桩" : $"编辑充电桩 [{existing.StationId}]") + .SetDefaultDocking(Panel.Docking.None) + .InitSize(480, 520) + .InitPos(false, 0, 0, 0.5f, 0.5f, 0.5f, 0.5f); + _editDialog = dlg; + dlg.IfTerminalQuit(() => _editDialog = null); - // 充电指令状态列样式设置(第10列) - var chargeCommandCell = row.Cells[10]; - switch (station.ChargeCommandStatus) - { - case ChargeCommandStatus.Stopped: - chargeCommandCell.Style.ForeColor = Color.FromArgb(158, 158, 158); // 灰色 - break; - case ChargeCommandStatus.Started: - chargeCommandCell.Style.ForeColor = Color.FromArgb(76, 175, 80); // 绿色 - // chargeCommandCell.Style.Font = new Font(chargeCommandCell.Style.Font ?? row.DefaultCellStyle.Font, FontStyle.Bold); - break; - } + dlg.Define(pb => + { + if (pb.Closing()) { dlg.Exit(); _editDialog = null; return; } - // 机构状态列样式设置(第11列) - var mechanismCell = row.Cells[11]; - switch (station.MechanismStatus) - { + var (sid, _) = pb.TextInput("1. 编号 (1-99)", stationId, alwaysReturnString: true); + stationId = sid; + var (nm, _) = pb.TextInput("2. 名称", name, alwaysReturnString: true); + name = nm; + pb.DropdownBox("3. 类型", TypeNames, ref typeIdx); + pb.DropdownBox("4. 充电方式", MethodNames, ref methodIdx); + pb.DropdownBox("5. 停靠车型", CarTypeNames, ref carTypeIdx); + var (ipT, _) = pb.TextInput("6. IP", ip, alwaysReturnString: true); + ip = ipT; + var (pt, _) = pb.TextInput("7. 端口", port, alwaysReturnString: true); + port = pt; + var (vt, _) = pb.TextInput("8. 电压", voltage, alwaysReturnString: true); + voltage = vt; + var (ct, _) = pb.TextInput("9. 电流", current, alwaysReturnString: true); + current = ct; + var (site, _) = pb.TextInput("10. 站点ID", siteId, alwaysReturnString: true); + siteId = site; + var (rm, _) = pb.TextInput("11. 备注", remarks, alwaysReturnString: true); + remarks = rm; + pb.CheckBox("12. 启用", ref enabled); + if (methodIdx == (int)ChargeMethodType.Side) + pb.CheckBox("13. 屏蔽机构状态交互", ref shield); - case MechanismStatus.Retracted: - mechanismCell.Style.ForeColor = Color.FromArgb(76, 175, 80); // 绿色 - //mechanismCell.Style.Font = new Font(mechanismCell.Style.Font ?? row.DefaultCellStyle.Font, FontStyle.Bold); - break; - case MechanismStatus.Extending: - mechanismCell.Style.ForeColor = Color.FromArgb(33, 150, 243); // 蓝色 - //mechanismCell.Style.Font = new Font(mechanismCell.Style.Font ?? row.DefaultCellStyle.Font, FontStyle.Bold); - break; - case MechanismStatus.Extended: - mechanismCell.Style.ForeColor = Color.FromArgb(255, 0, 0); // - //mechanismCell.Style.Font = new Font(mechanismCell.Style.Font ?? row.DefaultCellStyle.Font, FontStyle.Bold); - break; - break; - } - - // 电量列样式设置(第13列) - if (station.BatteryLevel > 0) - { - var batteryCell = row.Cells[13]; - if (station.BatteryLevel >= 80) - { - batteryCell.Style.ForeColor = Color.FromArgb(76, 175, 80); // 绿色 - 电量充足 - //batteryCell.Style.Font = new Font(batteryCell.Style.Font ?? row.DefaultCellStyle.Font, FontStyle.Bold); - } - else if (station.BatteryLevel >= 50) - { - batteryCell.Style.ForeColor = Color.FromArgb(33, 150, 243); // 蓝色 - 电量中等 - //batteryCell.Style.Font = new Font(batteryCell.Style.Font ?? row.DefaultCellStyle.Font, FontStyle.Bold); - } - else if (station.BatteryLevel >= 20) - { - batteryCell.Style.ForeColor = Color.FromArgb(255, 152, 0); // 橙色 - 电量偏低 - //batteryCell.Style.Font = new Font(batteryCell.Style.Font ?? row.DefaultCellStyle.Font, FontStyle.Bold); - } - else - { - batteryCell.Style.ForeColor = Color.FromArgb(244, 67, 54); // 红色 - 电量低 - //batteryCell.Style.Font = new Font(batteryCell.Style.Font ?? row.DefaultCellStyle.Font, FontStyle.Bold); - } - } - - // 如果有报警,整行显示红色 - if (station.HasAlarm) - { - row.DefaultCellStyle.BackColor = Color.FromArgb(255, 205, 210); // 浅红色背景 #FFCDD2 - row.DefaultCellStyle.ForeColor = Color.FromArgb(198, 40, 40); // 深红色文字 - var baseFont = row.DefaultCellStyle.Font ?? dgvStations.DefaultCellStyle.Font ?? new Font("微软雅黑", 9F); - row.DefaultCellStyle.Font = new Font(baseFont, FontStyle.Bold); - } - else - { - // 根据状态设置颜色 - switch (station.Status) - { - case ChargeStationStatus.Idle: - row.DefaultCellStyle.BackColor = Color.FromArgb(232, 245, 233); // 浅绿色 #E8F5E9 - row.DefaultCellStyle.ForeColor = Color.FromArgb(46, 125, 50); // 深绿色文字 - break; - case ChargeStationStatus.Charging: - row.DefaultCellStyle.BackColor = Color.FromArgb(200, 230, 201); // 亮绿色 #C8E6C9 - row.DefaultCellStyle.ForeColor = Color.FromArgb(27, 94, 32); // 深绿色文字 - break; - case ChargeStationStatus.Fault: - row.DefaultCellStyle.BackColor = Color.FromArgb(255, 205, 210); // 浅红色 #FFCDD2 - row.DefaultCellStyle.ForeColor = Color.FromArgb(198, 40, 40); // 深红色文字 - break; - case ChargeStationStatus.Battery: - row.DefaultCellStyle.BackColor = Color.FromArgb(238, 238, 238); // 浅灰色 #EEEEEE - row.DefaultCellStyle.ForeColor = Color.FromArgb(97, 97, 97); // 深灰色文字 - break; - } - } + if (!isAdd) + { + pb.SeparatorText("实时状态(只读)"); + pb.Label($"车辆: {(string.IsNullOrWhiteSpace(existing.CurrentVehicle) ? "-" : existing.CurrentVehicle)}"); + pb.Label($"电量: {(existing.BatteryLevel > 0 ? $"{existing.BatteryLevel:F1}%" : "-")}"); + pb.Label($"通讯: {FormatComm(existing.CommStatus)} | 机构: {FormatMech(existing.MechanismStatus)}"); + pb.Label($"报警: {FormatAlarm(existing)}"); } - // 更新统计信息 - UpdateStatistics(); + if (!string.IsNullOrEmpty(err)) { pb.Separator(); pb.Label(err); } - // 更新标题显示筛选状态 - UpdateTitleWithFilter(stations.Count); - } - catch (Exception ex) - { - MessageBox.Show($"加载数据失败: {ex.Message}\n\n堆栈:\n{ex.StackTrace}", "错误", - MessageBoxButtons.OK, MessageBoxIcon.Error); - } - } - - /// - /// 根据筛选器索引获取状态 - /// - private ChargeStationStatus GetStatusFromFilterIndex(int index) - { - switch (index) - { - case 1: return ChargeStationStatus.Idle; // 空闲 - case 2: return ChargeStationStatus.Charging; // 充电中 - case 3: return ChargeStationStatus.Fault; // 故障 - case 4: return ChargeStationStatus.Battery; // 离线 - default: return ChargeStationStatus.Idle; - } - } - - /// - /// 更新标题显示筛选信息 - /// - private void UpdateTitleWithFilter(int displayCount) - { - var totalCount = dataService.GetAllStations().Count; - if (cmbStatusFilter != null && cmbStatusFilter.SelectedIndex > 0) - { - this.Text = $"充电桩管理系统 - 显示: {displayCount}/{totalCount} ({cmbStatusFilter.Text})"; - } - else - { - this.Text = $"充电桩管理系统 - 总数: {totalCount}"; - } - } - - /// - /// 更新统计信息 - /// - private void UpdateStatistics() - { - var stations = dataService.GetAllStations(); - var total = stations.Count; - var idle = stations.Count(s => s.Status == ChargeStationStatus.Idle); - var charging = stations.Count(s => s.Status == ChargeStationStatus.Charging); - var fault = stations.Count(s => s.Status == ChargeStationStatus.Fault); - var offline = stations.Count(s => s.Status == ChargeStationStatus.Battery); - - lblStatistics.Text = $"总数: {total} | 空闲: {idle} | 充电中: {charging} | 故障: {fault} | AGV电池已接入: {offline}"; - } - - /// - /// 获取状态文本 - /// - private string GetStatusText(ChargeStationStatus status) - { - switch (status) - { - case ChargeStationStatus.Idle: return "空闲"; - case ChargeStationStatus.Charging: return "充电中"; - case ChargeStationStatus.Fault: return "故障"; - case ChargeStationStatus.Battery: return "AGV电池已接入"; - default: return "未知"; - } - } - - /// - /// 格式化时间为 mm:ss 格式 - /// - private string FormatTimeToMinuteSecond(DateTime? dateTime) - { - if (dateTime == null) - { - return "--:--"; - } - return dateTime.Value.ToString("mm:ss"); - } - - /// - /// 格式化机构伸缩状态显示 - /// - private string FormatMechanismStatusDisplay(MechanismStatus status) - { - switch (status) - { - case MechanismStatus.Extended: - return "◆ 伸出"; - case MechanismStatus.Retracted: - return "◇ 缩回"; - case MechanismStatus.Extending: - return "▶ 运动中"; - default: - return "? 未知"; - } - } - - /// - /// 格式化充电指令状态显示 - /// - private string FormatChargeCommandStatusDisplay(ChargeCommandStatus status) - { - switch (status) - { - case ChargeCommandStatus.Stopped: - return "◯ 停止"; - case ChargeCommandStatus.Started: - return "▶ 启动"; - default: - return "◯ 停止"; - } - } - - /// - /// 格式化通讯状态显示 - /// - private string FormatCommStatusDisplay(CommunicationStatus status) - { - switch (status) - { - case CommunicationStatus.Normal: - return "✓ 正常"; - case CommunicationStatus.Delayed: - return "⚠ 延迟"; - case CommunicationStatus.Timeout: - return "✗ 超时"; - case CommunicationStatus.Disconnected: - return "✗ 断开"; - case CommunicationStatus.Error: - return "✗ 错误"; - case CommunicationStatus.Unknown: - default: - return "? 未知"; - } - } - - /// - /// 格式化报警信息显示 - /// - private string FormatAlarmDisplay(ChargeStation station) - { - if (!station.HasAlarm) - { - return "正常"; - } - - string levelText = GetAlarmLevelText(station.AlarmLevel); - if (string.IsNullOrWhiteSpace(station.AlarmMessage)) - { - return $"【{levelText}】"; - } - - return $"{station.AlarmMessage}"; - } - - /// - /// 获取报警级别文本 - /// - private string GetAlarmLevelText(AlarmLevel level) - { - switch (level) - { - case AlarmLevel.None: return "无"; - case AlarmLevel.Low: return "低"; - case AlarmLevel.Medium: return "中"; - case AlarmLevel.High: return "高"; - case AlarmLevel.Critical: return "严重"; - default: return "未知"; - } - } - - /// - /// 获取类型文本 - /// - private string GetTypeText(ChargeStationType type) - { - switch (type) - { - case ChargeStationType.FRLDTall: return "FRLD高款充电桩"; - case ChargeStationType.FRLDShort: return "FRLD矮款充电桩"; - case ChargeStationType.MuXing: return "牧星充电桩"; - default: return "未知"; - } - } - - /// - /// 获取充电方式文本 - /// - private string GetChargeMethodText(ChargeMethodType method) - { - switch (method) - { - case ChargeMethodType.Ground: return "地充"; - case ChargeMethodType.Rear: return "尾充"; - case ChargeMethodType.Side: return "侧充"; - default: return "未知"; - } - } - - /// - /// 设置编辑模式 - /// - /// - /// 清空编辑区 - /// - private void ClearEditFields() - { - selectedStation = null; - txtStationId.Text = ""; // 手动输入编号 - txtName.Text = ""; - cmbType.SelectedIndex = 1; - cmbChargeMethod.SelectedIndex = 0; // 默认地充 - chargeCarType.SelectedIndex = 0; - // 触发充电方式改变事件,更新"屏蔽机构状态交互"的可见性 - cmbChargeMethod_SelectedIndexChanged(null, null); - - txtIpAddress.Text = "192.168."; - numPort.Value = 2000; - numVoltage.Value = 48; - numCurrent.Value = 32; - chkEnabled.Checked = true; - chkShieldSiteMechanismStatus.Checked = false; - numSiteId.Value = 0; - txtRemarks.Text = ""; - - // 清空实时状态显示 - ClearRealTimeInfo(); - - // 新增模式:所有字段可编辑 - SetEditMode(true); - btnSave.Text = "保存"; - btnDelete.Enabled = false; - } - - /// - /// 清空实时状态显示区域 - /// - private void ClearRealTimeInfo() - { - lblCurrentVehicleValue.Text = "-"; - lblCurrentVehicleValue.ForeColor = Color.Gray; - - lblBatteryLevelValue.Text = "-"; - lblBatteryLevelValue.ForeColor = Color.Gray; - - lblRealTimeVoltageValue.Text = "0.0 V"; - lblRealTimeVoltageValue.ForeColor = Color.Gray; - - lblRealTimeCurrentValue.Text = "0.0 A"; - lblRealTimeCurrentValue.ForeColor = Color.Gray; - - lblCommStatusValue.Text = "? 未知"; - lblCommStatusValue.ForeColor = Color.Gray; - - lblChargeCommandStatusValue.Text = "◯ 停止"; - lblChargeCommandStatusValue.ForeColor = Color.Gray; - - lblMechanismStatusValue.Text = "? 未知"; - lblMechanismStatusValue.ForeColor = Color.Gray; - - lblAlarmValue.Text = "-"; - lblAlarmValue.ForeColor = Color.Gray; - } - - /// - /// 设置编辑模式 - /// - /// true=可编辑,false=只读 - private void SetEditMode(bool editable, bool isList = false) - { - // 编号在新增时可编辑,编辑时只读 - if (selectedStation == null) - { - // 新增模式:编号可编辑 - txtStationId.ReadOnly = false; - txtStationId.BackColor = Color.White; - } - else - { - // 编辑模式:编号只读 - txtStationId.ReadOnly = true; - txtStationId.BackColor = Color.LightGray; - } - - // 其他字段根据参数设置 - txtName.ReadOnly = !editable; - cmbType.Enabled = isList ? false : editable; - cmbChargeMethod.Enabled = editable; - txtIpAddress.ReadOnly = !editable; - numPort.ReadOnly = !editable; - numVoltage.ReadOnly = !editable; - numCurrent.ReadOnly = !editable; - chkEnabled.Enabled = editable; - chkShieldSiteMechanismStatus.Enabled = editable; - numSiteId.ReadOnly = !editable; - txtRemarks.ReadOnly = !editable; - - // 设置背景颜色 - if (!editable) - { - txtName.BackColor = Color.WhiteSmoke; - txtIpAddress.BackColor = Color.WhiteSmoke; - txtRemarks.BackColor = Color.WhiteSmoke; - } - else - { - txtName.BackColor = Color.White; - txtIpAddress.BackColor = Color.White; - txtRemarks.BackColor = Color.White; - } - - // 控制按钮状态 - btnSave.Enabled = editable; - } - - /// - /// 从编辑区创建充电桩对象 - /// - private ChargeStation CreateStationFromFields() - { - //var station = selectedStation ?? new ChargeStation(); - var station = new ChargeStation(); - station.StationId = txtStationId.Text.Trim(); - station.Name = txtName.Text.Trim(); - station.Type = (ChargeStationType)cmbType.SelectedIndex; - station.ChargeMethod = (ChargeMethodType)cmbChargeMethod.SelectedIndex; - station.IpAddress = txtIpAddress.Text.Trim(); - station.Port = (int)numPort.Value; - station.SetVoltage = (double)numVoltage.Value; - station.SetElectricCurrent = (double)numCurrent.Value; - station.Enabled = chkEnabled.Checked; - station.ShieldSiteMechanismStatus = chkShieldSiteMechanismStatus.Checked; - station.GroupCarType = (ChargeStationCarType)chargeCarType.SelectedIndex; - station.SiteId = numSiteId.Value > 0 ? (int?)numSiteId.Value : null; - station.Remarks = txtRemarks.Text.Trim(); - - return station; - } - - /// - /// 加载充电桩到编辑区 - /// - private void LoadStationToFields(ChargeStation station) - { - selectedStation = station; - - // 加载基本信息(可编辑部分) - txtStationId.Text = station.StationId; - txtName.Text = station.Name; - cmbType.SelectedIndex = (int)station.Type; - cmbChargeMethod.SelectedIndex = (int)station.ChargeMethod; - - // 触发充电方式改变事件,更新"屏蔽机构状态交互"的可见性 - cmbChargeMethod_SelectedIndexChanged(null, null); - - txtIpAddress.Text = station.IpAddress; - numPort.Value = station.Port; - numVoltage.Value = (decimal)station.SetVoltage; - numCurrent.Value = (decimal)station.SetElectricCurrent; - chkEnabled.Checked = station.Enabled; - chkShieldSiteMechanismStatus.Checked = station.ShieldSiteMechanismStatus; - chargeCarType.SelectedIndex = (int)station.GroupCarType; - numSiteId.Value = station.SiteId ?? 0; - txtRemarks.Text = station.Remarks ?? ""; - - // 加载实时状态信息(只读部分) - LoadRealTimeInfo(station); - - // 查看模式:所有字段只读 - SetEditMode(false); - btnSave.Text = "修改"; - btnDelete.Enabled = true; - } - - /// - /// 加载实时状态信息到显示区域 - /// - private void LoadRealTimeInfo(ChargeStation station) - { - // 当前车辆 - lblCurrentVehicleValue.Text = string.IsNullOrWhiteSpace(station.CurrentVehicle) ? "-" : station.CurrentVehicle; - - // 电量 - if (station.BatteryLevel > 0) - { - lblBatteryLevelValue.Text = $"{station.BatteryLevel:F1}%"; - if (station.BatteryLevel >= 80) + pb.Separator(); + if (pb.Button("保存", distinct: "cs-edit-save")) { - lblBatteryLevelValue.ForeColor = Color.FromArgb(76, 175, 80); // 绿色 - } - else if (station.BatteryLevel >= 50) - { - lblBatteryLevelValue.ForeColor = Color.FromArgb(33, 150, 243); // 蓝色 - } - else if (station.BatteryLevel >= 20) - { - lblBatteryLevelValue.ForeColor = Color.FromArgb(255, 152, 0); // 橙色 - } - else - { - lblBatteryLevelValue.ForeColor = Color.FromArgb(244, 67, 54); // 红色 - } - } - else - { - lblBatteryLevelValue.Text = "-"; - lblBatteryLevelValue.ForeColor = Color.Gray; - } - - // 实时电压 - lblRealTimeVoltageValue.Text = $"{station.RealTimeVoltage:F1} V"; - lblRealTimeVoltageValue.ForeColor = station.RealTimeVoltage > 0 - ? Color.FromArgb(33, 150, 243) // 蓝色 - : Color.Gray; - - // 实时电流 - lblRealTimeCurrentValue.Text = $"{station.RealTimeCurrent:F1} A"; - lblRealTimeCurrentValue.ForeColor = station.RealTimeCurrent > 0 - ? Color.FromArgb(33, 150, 243) // 蓝色 - : Color.Gray; - - // 通讯状态 - lblCommStatusValue.Text = FormatCommStatusDisplay(station.CommStatus); - switch (station.CommStatus) - { - case CommunicationStatus.Normal: - lblCommStatusValue.ForeColor = Color.FromArgb(76, 175, 80); // 绿色 - break; - case CommunicationStatus.Delayed: - lblCommStatusValue.ForeColor = Color.FromArgb(255, 193, 7); // 黄色 - break; - case CommunicationStatus.Timeout: - case CommunicationStatus.Disconnected: - case CommunicationStatus.Error: - lblCommStatusValue.ForeColor = Color.FromArgb(244, 67, 54); // 红色 - break; - default: - lblCommStatusValue.ForeColor = Color.Gray; - break; - } - - // 充电指令状态 - lblChargeCommandStatusValue.Text = FormatChargeCommandStatusDisplay(station.ChargeCommandStatus); - lblChargeCommandStatusValue.ForeColor = station.ChargeCommandStatus == ChargeCommandStatus.Started - ? Color.FromArgb(76, 175, 80) // 绿色 - : Color.Gray; - - // 机构状态 - lblMechanismStatusValue.Text = FormatMechanismStatusDisplay(station.MechanismStatus); - switch (station.MechanismStatus) - { - - case MechanismStatus.Retracted: - lblMechanismStatusValue.ForeColor = Color.FromArgb(76, 175, 80); // 绿色 - break; - case MechanismStatus.Extending: - lblMechanismStatusValue.ForeColor = Color.FromArgb(33, 150, 243); // 蓝色 - break; - case MechanismStatus.Extended: - lblMechanismStatusValue.ForeColor = Color.FromArgb(255, 0, 0); // 蓝色 - break; - default: - lblMechanismStatusValue.ForeColor = Color.Gray; - break; - } - - // 报警信息 - if (station.HasAlarm) - { - string levelText = ""; - switch (station.AlarmLevel) - { - case AlarmLevel.Critical: - levelText = "严重"; - lblAlarmValue.ForeColor = Color.FromArgb(183, 28, 28); // 深红色 - break; - case AlarmLevel.High: - levelText = "高"; - lblAlarmValue.ForeColor = Color.FromArgb(244, 67, 54); // 红色 - break; - case AlarmLevel.Medium: - levelText = "中"; - lblAlarmValue.ForeColor = Color.FromArgb(255, 152, 0); // 橙色 - break; - case AlarmLevel.Low: - levelText = "低"; - lblAlarmValue.ForeColor = Color.FromArgb(255, 193, 7); // 黄色 - break; - default: - levelText = "未知"; - lblAlarmValue.ForeColor = Color.Gray; - break; - } - lblAlarmValue.Text = $"【{levelText}】{station.AlarmMessage}"; - } - else - { - lblAlarmValue.Text = "正常"; - lblAlarmValue.ForeColor = Color.FromArgb(76, 175, 80); // 绿色 - } - } - - - // ==================== 事件处理 ==================== - - /// - /// 充电方式改变事件 - 控制"屏蔽机构状态交互"选项的显示 - /// - private void cmbChargeMethod_SelectedIndexChanged(object sender, EventArgs e) - { - // 只有侧充(Side=2)时才显示"屏蔽机构状态交互"选项 - bool isSideCharge = cmbChargeMethod.SelectedIndex == (int)ChargeMethodType.Side; - chkShieldSiteMechanismStatus.Visible = isSideCharge; - - // 如果不是侧充,自动取消勾选 - if (!isSideCharge) - { - chkShieldSiteMechanismStatus.Checked = false; - } - } - - private void btnSave_Click(object sender, EventArgs e) - { - try - { - // 如果当前是只读模式(查看模式),点击"修改"按钮切换到编辑模式 - if (btnSave.Text == "修改") - { - SetEditMode(true); - btnSave.Text = "保存"; - return; - } - - // 以下是保存逻辑 - // 充电桩编号验证:1-99之间的数字 - string stationId = txtStationId.Text.Trim(); - if (string.IsNullOrEmpty(stationId)) - { - MessageBox.Show("请输入充电桩编号(1-99)", "验证失败", - MessageBoxButtons.OK, MessageBoxIcon.Warning); - txtStationId.Focus(); - return; - } - - // 验证是否为数字且在1-99范围内 - if (!int.TryParse(stationId, out int stationNumber) || stationNumber < 1 || stationNumber > 99) - { - MessageBox.Show("充电桩编号必须是1-99之间的数字", "验证失败", - MessageBoxButtons.OK, MessageBoxIcon.Warning); - txtStationId.Focus(); - return; - } - - // 检查编号是否重复(新增时) - if (selectedStation == null) - { - var existingStations = dataService.GetAllStations(); - if (existingStations.Any(s => s.StationId == stationId)) - { - MessageBox.Show($"充电桩编号 {stationId} 已存在,请输入其他编号", "验证失败", - MessageBoxButtons.OK, MessageBoxIcon.Warning); - txtStationId.Focus(); + if (!TryBuildStation(isAdd, existing, stationId, name, typeIdx, methodIdx, carTypeIdx, + ip, port, voltage, current, siteId, remarks, enabled, shield, out var station, out err)) return; + + string errorMessage; + bool ok = isAdd + ? DataService.AddStation(station, out errorMessage) + : DataService.UpdateStation(station, out errorMessage, true); + + if (!ok) { err = errorMessage; return; } + + var mapSite = SimpleLib.GetSite(station.SiteId ?? 0); + if (mapSite != null) + { + mapSite.name = station.Name; + mapSite.fields["setVoltage"] = station.SetVoltage.ToString(); + mapSite.fields["setElectricCurrent"] = station.SetElectricCurrent.ToString(); + mapSite.fields["group"] = station.Enabled ? station.GroupCarType.ToString() : "禁用"; } + + _lastFlush = DateTime.MinValue; + _status = "保存成功"; + CycleUiHelper.Alert("提示", "保存成功"); + dlg.Exit(); + _editDialog = null; + _panel?.Repaint(); } + pb.SameLine(8); + if (pb.Button("取消", distinct: "cs-edit-cancel")) { dlg.Exit(); _editDialog = null; } + }); + } + private static bool TryBuildStation(bool isAdd, ChargeStation existing, string stationId, string name, + int typeIdx, int methodIdx, int carTypeIdx, string ip, string port, string voltage, string current, + string siteIdText, string remarks, bool enabled, bool shield, + out ChargeStation station, out string err) + { + station = new ChargeStation(); + err = ""; + stationId = (stationId ?? "").Trim(); + if (string.IsNullOrEmpty(stationId) || !int.TryParse(stationId, out var num) || num < 1 || num > 99) + { err = "充电桩编号必须是 1-99 之间的数字"; return false; } - var Site = SimpleLib.GetSite((int)numSiteId.Value); - if (Site == null) //判断站点是否在S里。 + if (isAdd && DataService.GetAllStations().Any(s => s.StationId == stationId)) + { err = $"充电桩编号 {stationId} 已存在"; return false; } + + if (!int.TryParse((siteIdText ?? "").Trim(), out var siteId)) + { err = "站点ID必须是数字"; return false; } + var site = SimpleLib.GetSite(siteId); + if (site == null) { err = $"站点ID {siteId} 未在调度系统上"; return false; } + + if (!int.TryParse((port ?? "").Trim(), out var portNum)) { err = "端口必须是数字"; return false; } + if (!double.TryParse((voltage ?? "").Trim(), out var volt)) { err = "电压格式无效"; return false; } + if (!double.TryParse((current ?? "").Trim(), out var amp)) { err = "电流格式无效"; return false; } + + station.StationId = stationId; + station.Name = (name ?? "").Trim(); + station.Type = (ChargeStationType)typeIdx; + station.ChargeMethod = (ChargeMethodType)methodIdx; + station.GroupCarType = (ChargeStationCarType)carTypeIdx; + station.IpAddress = (ip ?? "").Trim(); + station.Port = portNum; + station.SetVoltage = volt; + station.SetElectricCurrent = amp; + station.Enabled = enabled; + station.ShieldSiteMechanismStatus = shield; + station.SiteId = siteId > 0 ? siteId : (int?)null; + station.Remarks = remarks ?? ""; + return true; + } + + private static void ConfirmDelete(ChargeStation station) + { + CycleUiHelper.ConfirmThen($"确定删除充电桩 [{station.StationId}] {station.Name}?", () => + { + if (!DataService.DeleteStation(station.StationId, out var err)) { - MessageBox.Show($"站点ID{numSiteId.Value} 未在调度系统上", "验证失败", - MessageBoxButtons.OK, MessageBoxIcon.Warning); - numSiteId.Focus(); + CycleUiHelper.Alert("错误", $"删除失败: {err}"); return; } - - var station = CreateStationFromFields(); - string errorMessage; - - bool success; - if (selectedStation == null) + var site = SimpleLib.GetSite(station.SiteId ?? 0); + if (site != null) { - // 新增 - success = dataService.AddStation(station, out errorMessage); + site.name = "NoName"; + site.fields.Remove("setVoltage"); + site.fields.Remove("setElectricCurrent"); + site.fields.Remove("Charge"); + site.fields.Remove("group"); } - else - { - // 更新 - success = dataService.UpdateStation(station, out errorMessage, true); - } - - if (success) - { - Site.name = station.Name; - //Site.fields["chargeNum"] = station.StationId; - //Site.fields["stationIP"] = station.IpAddress; - //Site.fields["stationPort"] = station.Port.ToString(); - //switch (station.Type) - //{ - // case ChargeStationType.FRLDTall: - // Site.fields["Charge"] = "FLChargeStation"; - // break; - // case ChargeStationType.FRLDShort: - // Site.fields["Charge"] = "PCBChargeStation"; - // Site.fields["CommunicationType"] = "UDP"; - // break; - // case ChargeStationType.MuXing: - // Site.fields["Charge"] = "MuXingChargeStation"; - // break; - // default: - // break; - //} - Site.fields["setVoltage"] = station.SetVoltage.ToString(); - Site.fields["setElectricCurrent"] = station.SetElectricCurrent.ToString(); - if (!station.Enabled) - { - Site.fields["group"] = "禁用"; - } - else - { - Site.fields["group"] = station.GroupCarType.ToString(); - } - - LoadStations(); - ClearEditFields(); - MessageBox.Show("保存成功", "提示", - MessageBoxButtons.OK, MessageBoxIcon.Information); - } - else - { - MessageBox.Show($"保存失败: {errorMessage}", "错误", - MessageBoxButtons.OK, MessageBoxIcon.Error); - } - } - catch (Exception ex) - { - MessageBox.Show($"保存失败: {ex.Message}", "错误", - MessageBoxButtons.OK, MessageBoxIcon.Error); - } + _lastFlush = DateTime.MinValue; + _status = "删除成功"; + _panel?.Repaint(); + }); } - private void btnDelete_Click(object sender, EventArgs e) - { - if (dgvStations.SelectedRows.Count == 0) - { - MessageBox.Show("请先选择要删除的充电桩", "提示", - MessageBoxButtons.OK, MessageBoxIcon.Warning); - return; - } - - var stationId = dgvStations.SelectedRows[0].Cells[0].Value.ToString(); - var stationName = dgvStations.SelectedRows[0].Cells[1].Value.ToString(); - - var result = MessageBox.Show( - $"确定要删除充电桩 [{stationId}] {stationName} 吗?", - "确认删除", - MessageBoxButtons.YesNo, - MessageBoxIcon.Question); - - if (result == DialogResult.Yes) - { - if (dataService.DeleteStation(stationId, out string errorMessage)) - { - - var Site = SimpleLib.GetSite((int)numSiteId.Value); - if (Site != null) //判断站点是否在S里。 - { - Site.name = "NoName"; - Site.fields.Remove("setVoltage"); - Site.fields.Remove("setElectricCurrent"); - Site.fields.Remove("Charge"); - Site.fields.Remove("group"); - - - } - MessageBox.Show("删除成功!", "提示", - MessageBoxButtons.OK, MessageBoxIcon.Information); - LoadStations(); - ClearEditFields(); - } - else - { - MessageBox.Show($"删除失败: {errorMessage}", "错误", - MessageBoxButtons.OK, MessageBoxIcon.Error); - } - - } - } - - private void btnCancel_Click(object sender, EventArgs e) - { - // 如果有选中的充电桩,恢复到只读模式 - //if (selectedStation != null) - //{ - // LoadStationToFields(selectedStation); - //} - //else - { - // 否则清空为新增模式 - ClearEditFields(); - } - } - - private void btnRefresh_Click(object sender, EventArgs e) - { - dataService.Reload(); - LoadStations(); - MessageBox.Show("刷新成功!", "提示", - MessageBoxButtons.OK, MessageBoxIcon.Information); - } - - private void dgvStations_CellDoubleClick(object sender, DataGridViewCellEventArgs e) - { - if (e.RowIndex >= 0) - { - var stationId = dgvStations.Rows[e.RowIndex].Cells[0].Value.ToString(); - var station = dataService.GetStationById(stationId); - if (station != null) - { - LoadStationToFields(station); - SetEditMode(true, true); - } - } - } - - - - private void txtSearch_TextChanged(object sender, EventArgs e) - { - ApplyFilters(); - } - - private void cmbStatusFilter_SelectedIndexChanged(object sender, EventArgs e) - { - ApplyFilters(); - } - - /// - /// 应用搜索和状态筛选 - /// - private void ApplyFilters() - { - var stations = dataService.GetAllStations(); - - // 应用搜索筛选 - var searchText = txtSearch.Text.Trim().ToLower(); - if (!string.IsNullOrEmpty(searchText)) - { - stations = stations - .Where(s => s.StationId.ToLower().Contains(searchText) || - s.Name.ToLower().Contains(searchText) || - s.IpAddress.Contains(searchText) || - GetTypeText(s.Type).Contains(searchText)) - .ToList(); - } - - // 应用状态筛选 - if (cmbStatusFilter != null && cmbStatusFilter.SelectedIndex > 0) - { - var filterStatus = GetStatusFromFilterIndex(cmbStatusFilter.SelectedIndex); - stations = stations.Where(s => s.Status == filterStatus).ToList(); - } - - // 显示结果 - dgvStations.Rows.Clear(); - foreach (var station in stations) - { - var index = dgvStations.Rows.Add( - station.StationId, - station.Name, - GetTypeText(station.Type), - GetChargeMethodText(station.ChargeMethod), - //station.IpAddress, - //station.Port, - station.SiteId?.ToString() ?? "", - FormatTimeToMinuteSecond(station.LastSendTime), - FormatTimeToMinuteSecond(station.LastReceiveTime), - FormatCommStatusDisplay(station.CommStatus), - FormatChargeCommandStatusDisplay(station.ChargeCommandStatus), - FormatMechanismStatusDisplay(station.MechanismStatus), - string.IsNullOrWhiteSpace(station.CurrentVehicle) ? "-" : station.CurrentVehicle, - station.BatteryLevel > 0 ? $"{station.BatteryLevel:F1}%" : "-", - FormatAlarmDisplay(station), - //station.SetVoltage, - //station.SetElectricCurrent, - station.RealTimeVoltage.ToString("F1"), - station.RealTimeCurrent.ToString("F1"), - GetStatusText(station.Status), - station.Enabled ? "是" : "否", - station.Remarks - ); - - // 根据状态设置行颜色(扁平化设计) - var row = dgvStations.Rows[index]; - - // 通讯状态列样式设置(第9列) - var commCell = row.Cells[9]; - switch (station.CommStatus) - { - case CommunicationStatus.Normal: - commCell.Style.ForeColor = Color.FromArgb(76, 175, 80); // 绿色 - // commCell.Style.Font = new Font(commCell.Style.Font ?? row.DefaultCellStyle.Font, FontStyle.Bold); - break; - case CommunicationStatus.Delayed: - commCell.Style.ForeColor = Color.FromArgb(255, 152, 0); // 橙色 - // commCell.Style.Font = new Font(commCell.Style.Font ?? row.DefaultCellStyle.Font, FontStyle.Bold); - break; - case CommunicationStatus.Timeout: - case CommunicationStatus.Disconnected: - commCell.Style.ForeColor = Color.FromArgb(244, 67, 54); // 红色 - // commCell.Style.Font = new Font(commCell.Style.Font ?? row.DefaultCellStyle.Font, FontStyle.Bold); - break; - case CommunicationStatus.Unknown: - commCell.Style.ForeColor = Color.FromArgb(158, 158, 158); // 灰色 - break; - } - - // 充电指令状态列样式设置(第10列) - var chargeCommandCell = row.Cells[10]; - switch (station.ChargeCommandStatus) - { - case ChargeCommandStatus.Stopped: - chargeCommandCell.Style.ForeColor = Color.FromArgb(158, 158, 158); // 灰色 - break; - case ChargeCommandStatus.Started: - chargeCommandCell.Style.ForeColor = Color.FromArgb(76, 175, 80); // 绿色 - //chargeCommandCell.Style.Font = new Font(chargeCommandCell.Style.Font ?? row.DefaultCellStyle.Font, FontStyle.Bold); - break; - } - - // 机构状态列样式设置(第11列) - var mechanismCell = row.Cells[11]; - switch (station.MechanismStatus) - { - - case MechanismStatus.Retracted: - mechanismCell.Style.ForeColor = Color.FromArgb(76, 175, 80); // 绿色 - // mechanismCell.Style.Font = new Font(mechanismCell.Style.Font ?? mechanismCell.Style.Font, FontStyle.Bold); - break; - case MechanismStatus.Extending: - mechanismCell.Style.ForeColor = Color.FromArgb(33, 150, 243); // 蓝色 - // mechanismCell.Style.Font = new Font(mechanismCell.Style.Font ?? row.DefaultCellStyle.Font, FontStyle.Bold); - break; - } - - // 电量列样式设置(第13列) - if (station.BatteryLevel > 0) - { - var batteryCell = row.Cells[13]; - if (station.BatteryLevel >= 80) - { - batteryCell.Style.ForeColor = Color.FromArgb(76, 175, 80); // 绿色 - 电量充足 - //batteryCell.Style.Font = new Font(batteryCell.Style.Font ?? batteryCell.Style.Font, FontStyle.Bold); - } - else if (station.BatteryLevel >= 50) - { - batteryCell.Style.ForeColor = Color.FromArgb(33, 150, 243); // 蓝色 - 电量中等 - //batteryCell.Style.Font = new Font(batteryCell.Style.Font ?? batteryCell.Style.Font, FontStyle.Bold); - } - else if (station.BatteryLevel >= 20) - { - batteryCell.Style.ForeColor = Color.FromArgb(255, 152, 0); // 橙色 - 电量偏低 - //batteryCell.Style.Font = new Font(batteryCell.Style.Font ?? batteryCell.Style.Font, FontStyle.Bold); - } - else - { - batteryCell.Style.ForeColor = Color.FromArgb(244, 67, 54); // 红色 - 电量低 - //batteryCell.Style.Font = new Font(batteryCell.Style.Font ?? batteryCell.Style.Font, FontStyle.Bold); - } - } - - // 如果有报警,整行显示红色 - if (station.HasAlarm) - { - row.DefaultCellStyle.BackColor = Color.FromArgb(255, 205, 210); // 浅红色背景 #FFCDD2 - row.DefaultCellStyle.ForeColor = Color.FromArgb(198, 40, 40); // 深红色文字 - // row.DefaultCellStyle.Font = new Font(row.DefaultCellStyle.Font, FontStyle.Bold); - } - else - { - // 根据状态设置颜色 - switch (station.Status) - { - case ChargeStationStatus.Idle: - row.DefaultCellStyle.BackColor = Color.FromArgb(232, 245, 233); // 浅绿色 #E8F5E9 - // row.DefaultCellStyle.ForeColor = Color.FromArgb(46, 125, 50); // 深绿色文字 - break; - case ChargeStationStatus.Charging: - row.DefaultCellStyle.BackColor = Color.FromArgb(200, 230, 201); // 亮绿色 #C8E6C9 - // row.DefaultCellStyle.ForeColor = Color.FromArgb(27, 94, 32); // 深绿色文字 - break; - case ChargeStationStatus.Fault: - row.DefaultCellStyle.BackColor = Color.FromArgb(255, 205, 210); // 浅红色 #FFCDD2 - // row.DefaultCellStyle.ForeColor = Color.FromArgb(198, 40, 40); // 深红色文字 - break; - case ChargeStationStatus.Battery: - row.DefaultCellStyle.BackColor = Color.FromArgb(238, 238, 238); // 浅灰色 #EEEEEE - // row.DefaultCellStyle.ForeColor = Color.FromArgb(97, 97, 97); // 深灰色文字 - break; - } - } - } - - // 更新标题和统计 - UpdateTitleWithFilter(stations.Count); - } - - private void btnStrategyConfig_Click(object sender, EventArgs e) + private static void ExportData(PanelBuilder pb) { + if (!pb.SaveFile("导出充电桩", "*.json;*.csv", out var path) || string.IsNullOrEmpty(path)) return; try { - // 打开充电策略配置界面 - var strategyConfigForm = new ChargeStrategyConfigForm(); - strategyConfigForm.ShowDialog(); - } - catch (Exception ex) - { - MessageBox.Show($"打开策略配置界面失败: {ex.Message}", "错误", - MessageBoxButtons.OK, MessageBoxIcon.Error); - } - } - - private void btnCommMonitor_Click(object sender, EventArgs e) - { - try - { - // 通讯监控窗口仅允许单实例 - if (communicationMonitorForm == null || communicationMonitorForm.IsDisposed) + var stations = DataService.GetAllStations(); + if (path.EndsWith(".csv", StringComparison.OrdinalIgnoreCase)) { - communicationMonitorForm = new CommunicationMonitorForm(); - communicationMonitorForm.FormClosed += (s, args) => communicationMonitorForm = null; - communicationMonitorForm.Show(); + var csv = new StringBuilder("编号,名称,类型,IP,端口,电压,电流,状态,启用,车型,站点ID,备注\n"); + foreach (var s in stations) + { + csv.Append($"{s.StationId},{s.Name},{GetTypeText(s.Type)},{s.IpAddress},{s.Port}," + + $"{s.SetVoltage},{s.SetElectricCurrent},{GetStatusText(s.Status)}," + + $"{(s.Enabled ? "是" : "否")},{s.GroupCarType},{s.SiteId},{s.Remarks}\n"); + } + File.WriteAllText(path, csv.ToString(), Encoding.UTF8); } else { - if (communicationMonitorForm.WindowState == FormWindowState.Minimized) - { - communicationMonitorForm.WindowState = FormWindowState.Normal; - } - communicationMonitorForm.BringToFront(); - communicationMonitorForm.Activate(); + File.WriteAllText(path, Newtonsoft.Json.JsonConvert.SerializeObject(stations, Newtonsoft.Json.Formatting.Indented)); } + CycleUiHelper.Alert("提示", "导出成功"); } - catch (Exception ex) - { - MessageBox.Show($"打开通讯监控界面失败: {ex.Message}", "错误", - MessageBoxButtons.OK, MessageBoxIcon.Error); - } + catch (Exception ex) { CycleUiHelper.Alert("错误", $"导出失败: {ex.Message}"); } } - private void btnAlarmConfig_Click(object sender, EventArgs e) + private static void ApplyRowColor(PanelBuilder.Row row, ChargeStation s) { - try - { - // 打开报警配置界面 - var alarmConfigForm = new AlarmConfigManagementForm(); - alarmConfigForm.ShowDialog(); - } - catch (Exception ex) - { - MessageBox.Show($"打开报警配置失败: {ex.Message}", "错误", - MessageBoxButtons.OK, MessageBoxIcon.Error); - } + if (s.HasAlarm) row.SetColor(Color.FromArgb(255, 205, 210)); + else if (s.Status == ChargeStationStatus.Idle) row.SetColor(Color.FromArgb(232, 245, 233)); + else if (s.Status == ChargeStationStatus.Charging) row.SetColor(Color.FromArgb(200, 230, 201)); + else if (s.Status == ChargeStationStatus.Fault) row.SetColor(Color.FromArgb(255, 205, 210)); + else if (s.Status == ChargeStationStatus.Battery) row.SetColor(Color.FromArgb(238, 238, 238)); } - private void btnExport_Click(object sender, EventArgs e) + private static ChargeStationStatus GetStatusFromFilterIndex(int idx) => idx switch { - try - { - var saveDialog = new SaveFileDialog - { - Filter = "JSON文件|*.json|CSV文件|*.csv", - FileName = $"ChargeStations_{DateTime.Now:yyyyMMddHHmmss}" - }; + 1 => ChargeStationStatus.Idle, + 2 => ChargeStationStatus.Charging, + 3 => ChargeStationStatus.Fault, + 4 => ChargeStationStatus.Battery, + _ => ChargeStationStatus.Idle + }; - if (saveDialog.ShowDialog() == DialogResult.OK) - { - var stations = dataService.GetAllStations(); - if (saveDialog.FilterIndex == 1) // JSON - { - var json = Newtonsoft.Json.JsonConvert.SerializeObject(stations, Newtonsoft.Json.Formatting.Indented); - System.IO.File.WriteAllText(saveDialog.FileName, json); - } - else // CSV - { - var csv = "编号,名称,类型,IP地址,端口,电压,电流,功率,状态,启用,停靠车的类型,站点ID,备注\n"; - foreach (var s in stations) - { - csv += $"{s.StationId},{s.Name},{GetTypeText(s.Type)},{s.IpAddress},{s.Port},{s.SetVoltage},{s.SetElectricCurrent},{s.Power},{GetStatusText(s.Status)},{(s.Enabled ? "是" : "否")},{s.GroupCarType},{s.SiteId},{s.Remarks}\n"; - } - System.IO.File.WriteAllText(saveDialog.FileName, csv, System.Text.Encoding.UTF8); - } + private static string GetStatusText(ChargeStationStatus status) => status switch + { + ChargeStationStatus.Idle => "空闲", + ChargeStationStatus.Charging => "充电中", + ChargeStationStatus.Fault => "故障", + ChargeStationStatus.Battery => "AGV电池已接入", + _ => "未知" + }; - MessageBox.Show("导出成功!", "提示", - MessageBoxButtons.OK, MessageBoxIcon.Information); - } - } - catch (Exception ex) - { - MessageBox.Show($"导出失败: {ex.Message}", "错误", - MessageBoxButtons.OK, MessageBoxIcon.Error); - } + private static string GetTypeText(ChargeStationType type) => type switch + { + ChargeStationType.FRLDTall => "FRLD高款充电桩", + ChargeStationType.FRLDShort => "FRLD矮款充电桩", + ChargeStationType.MuXing => "牧星充电桩", + _ => "未知" + }; + + private static string GetMethodText(ChargeMethodType m) => m switch + { + ChargeMethodType.Ground => "地充", + ChargeMethodType.Rear => "尾充", + ChargeMethodType.Side => "侧充", + _ => "未知" + }; + + private static string FormatComm(CommunicationStatus s) => s switch + { + CommunicationStatus.Normal => "✓ 正常", + CommunicationStatus.Delayed => "⚠ 延迟", + CommunicationStatus.Timeout => "✗ 超时", + CommunicationStatus.Disconnected => "✗ 断开", + CommunicationStatus.Error => "✗ 错误", + _ => "? 未知" + }; + + private static string FormatMech(MechanismStatus s) => s switch + { + MechanismStatus.Extended => "◆ 伸出", + MechanismStatus.Retracted => "◇ 缩回", + MechanismStatus.Extending => "▶ 运动中", + _ => "? 未知" + }; + + private static string FormatAlarm(ChargeStation s) + { + if (!s.HasAlarm) return "正常"; + return string.IsNullOrWhiteSpace(s.AlarmMessage) ? $"【{s.AlarmLevel}】" : s.AlarmMessage; } } } - - - diff --git a/StandardScene.Core/Charge/ChargeStationManagementForm.resx b/StandardScene.Core/Charge/ChargeStationManagementForm.resx deleted file mode 100644 index 1af7de1..0000000 --- a/StandardScene.Core/Charge/ChargeStationManagementForm.resx +++ /dev/null @@ -1,120 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - \ No newline at end of file diff --git a/StandardScene.Core/Charge/ChargeStrategyConfigForm.Designer.cs b/StandardScene.Core/Charge/ChargeStrategyConfigForm.Designer.cs deleted file mode 100644 index 62ad18f..0000000 --- a/StandardScene.Core/Charge/ChargeStrategyConfigForm.Designer.cs +++ /dev/null @@ -1,572 +0,0 @@ -namespace StandardScene.Charge -{ - partial class ChargeStrategyConfigForm - { - private System.ComponentModel.IContainer components = null; - - protected override void Dispose(bool disposing) - { - if (disposing && (components != null)) - { - components.Dispose(); - } - base.Dispose(disposing); - } - - #region Windows Form Designer generated code - - private void InitializeComponent() - { - // 创建所有控件实例 - this.pnlMain = new System.Windows.Forms.Panel(); - this.pnlBottom = new System.Windows.Forms.Panel(); - this.grpSocParams = new System.Windows.Forms.GroupBox(); - this.grpTimeParams = new System.Windows.Forms.GroupBox(); - this.grpTaskParams = new System.Windows.Forms.GroupBox(); - this.grpSwitchParams = new System.Windows.Forms.GroupBox(); - - // SOC 参数控件 - this.lblMustChargeSoc = new System.Windows.Forms.Label(); - this.numMustChargeSoc = new System.Windows.Forms.NumericUpDown(); - this.lblIdleChargeSoc = new System.Windows.Forms.Label(); - this.numIdleChargeSoc = new System.Windows.Forms.NumericUpDown(); - this.lblTaskAvailableSoc = new System.Windows.Forms.Label(); - this.numTaskAvailableSoc = new System.Windows.Forms.NumericUpDown(); - this.lblFullChargeSoc = new System.Windows.Forms.Label(); - this.numFullChargeSoc = new System.Windows.Forms.NumericUpDown(); - this.lblAllowInterruptSoc = new System.Windows.Forms.Label(); - this.numAllowInterruptSoc = new System.Windows.Forms.NumericUpDown(); - - // 时间参数控件 - this.lblIdleChargeSeconds = new System.Windows.Forms.Label(); - this.numIdleChargeSeconds = new System.Windows.Forms.NumericUpDown(); - this.lblIdleSeconds = new System.Windows.Forms.Label(); - this.numIdleSeconds = new System.Windows.Forms.NumericUpDown(); - this.lblMustChargeSeconds = new System.Windows.Forms.Label(); - this.numMustChargeSeconds = new System.Windows.Forms.NumericUpDown(); - this.lblTopUpMinutes = new System.Windows.Forms.Label(); - this.numTopUpMinutes = new System.Windows.Forms.NumericUpDown(); - - // 任务参数控件 - this.lblMinAllowFreeCarToChargeTaskCnt = new System.Windows.Forms.Label(); - this.numMinAllowFreeCarToChargeTaskCnt = new System.Windows.Forms.NumericUpDown(); - - // 开关参数控件 - this.chkAllowInterruptTask = new System.Windows.Forms.CheckBox(); - this.chkUseLowerSocForCharge = new System.Windows.Forms.CheckBox(); - this.chkEnableErrorChargeDetection = new System.Windows.Forms.CheckBox(); - this.chkUseChargeSiteFilter = new System.Windows.Forms.CheckBox(); - - // 底部控件 - this.lblStatus = new System.Windows.Forms.Label(); - this.btnSave = new System.Windows.Forms.Button(); - this.btnApply = new System.Windows.Forms.Button(); - this.btnRestoreDefaults = new System.Windows.Forms.Button(); - this.btnCancel = new System.Windows.Forms.Button(); - this.pnlMain.SuspendLayout(); - this.grpSwitchParams.SuspendLayout(); - this.grpTaskParams.SuspendLayout(); - ((System.ComponentModel.ISupportInitialize)(this.numMinAllowFreeCarToChargeTaskCnt)).BeginInit(); - this.grpTimeParams.SuspendLayout(); - ((System.ComponentModel.ISupportInitialize)(this.numTopUpMinutes)).BeginInit(); - ((System.ComponentModel.ISupportInitialize)(this.numMustChargeSeconds)).BeginInit(); - ((System.ComponentModel.ISupportInitialize)(this.numIdleSeconds)).BeginInit(); - ((System.ComponentModel.ISupportInitialize)(this.numIdleChargeSeconds)).BeginInit(); - this.grpSocParams.SuspendLayout(); - ((System.ComponentModel.ISupportInitialize)(this.numAllowInterruptSoc)).BeginInit(); - ((System.ComponentModel.ISupportInitialize)(this.numFullChargeSoc)).BeginInit(); - ((System.ComponentModel.ISupportInitialize)(this.numTaskAvailableSoc)).BeginInit(); - ((System.ComponentModel.ISupportInitialize)(this.numIdleChargeSoc)).BeginInit(); - ((System.ComponentModel.ISupportInitialize)(this.numMustChargeSoc)).BeginInit(); - this.pnlBottom.SuspendLayout(); - this.SuspendLayout(); - // - // pnlMain - // - this.pnlMain.AutoScroll = true; - this.pnlMain.Controls.Add(this.grpSwitchParams); - this.pnlMain.Controls.Add(this.grpTaskParams); - this.pnlMain.Controls.Add(this.grpTimeParams); - this.pnlMain.Controls.Add(this.grpSocParams); - this.pnlMain.Dock = System.Windows.Forms.DockStyle.Fill; - this.pnlMain.Location = new System.Drawing.Point(0, 0); - this.pnlMain.Name = "pnlMain"; - this.pnlMain.Padding = new System.Windows.Forms.Padding(10); - this.pnlMain.Size = new System.Drawing.Size(784, 631); - this.pnlMain.TabIndex = 0; - // - // grpSwitchParams - // - this.grpSwitchParams.Controls.Add(this.chkUseChargeSiteFilter); - this.grpSwitchParams.Controls.Add(this.chkEnableErrorChargeDetection); - this.grpSwitchParams.Controls.Add(this.chkUseLowerSocForCharge); - this.grpSwitchParams.Controls.Add(this.chkAllowInterruptTask); - this.grpSwitchParams.Dock = System.Windows.Forms.DockStyle.Top; - this.grpSwitchParams.Font = new System.Drawing.Font("微软雅黑", 9F, System.Drawing.FontStyle.Bold); - this.grpSwitchParams.Location = new System.Drawing.Point(10, 460); - this.grpSwitchParams.Name = "grpSwitchParams"; - this.grpSwitchParams.Padding = new System.Windows.Forms.Padding(10); - this.grpSwitchParams.Size = new System.Drawing.Size(764, 150); - this.grpSwitchParams.TabIndex = 3; - this.grpSwitchParams.TabStop = false; - this.grpSwitchParams.Text = "开关参数"; - // - // chkUseChargeSiteFilter - // - this.chkUseChargeSiteFilter.AutoSize = true; - this.chkUseChargeSiteFilter.Font = new System.Drawing.Font("微软雅黑", 9F); - this.chkUseChargeSiteFilter.Location = new System.Drawing.Point(400, 80); - this.chkUseChargeSiteFilter.Name = "chkUseChargeSiteFilter"; - this.chkUseChargeSiteFilter.Size = new System.Drawing.Size(147, 24); - this.chkUseChargeSiteFilter.TabIndex = 3; - this.chkUseChargeSiteFilter.Text = "使用充电站点筛选"; - this.chkUseChargeSiteFilter.UseVisualStyleBackColor = true; - // - // chkEnableErrorChargeDetection - // - this.chkEnableErrorChargeDetection.AutoSize = true; - this.chkEnableErrorChargeDetection.Font = new System.Drawing.Font("微软雅黑", 9F); - this.chkEnableErrorChargeDetection.Location = new System.Drawing.Point(30, 80); - this.chkEnableErrorChargeDetection.Name = "chkEnableErrorChargeDetection"; - this.chkEnableErrorChargeDetection.Size = new System.Drawing.Size(147, 24); - this.chkEnableErrorChargeDetection.TabIndex = 2; - this.chkEnableErrorChargeDetection.Text = "启用充电错误检测"; - this.chkEnableErrorChargeDetection.UseVisualStyleBackColor = true; - // - // chkUseLowerSocForCharge - // - this.chkUseLowerSocForCharge.AutoSize = true; - this.chkUseLowerSocForCharge.Font = new System.Drawing.Font("微软雅黑", 9F); - this.chkUseLowerSocForCharge.Location = new System.Drawing.Point(400, 40); - this.chkUseLowerSocForCharge.Name = "chkUseLowerSocForCharge"; - this.chkUseLowerSocForCharge.Size = new System.Drawing.Size(195, 24); - this.chkUseLowerSocForCharge.TabIndex = 1; - this.chkUseLowerSocForCharge.Text = "优先使用低电量车辆充电"; - this.chkUseLowerSocForCharge.UseVisualStyleBackColor = true; - // - // chkAllowInterruptTask - // - this.chkAllowInterruptTask.AutoSize = true; - this.chkAllowInterruptTask.Font = new System.Drawing.Font("微软雅黑", 9F); - this.chkAllowInterruptTask.Location = new System.Drawing.Point(30, 40); - this.chkAllowInterruptTask.Name = "chkAllowInterruptTask"; - this.chkAllowInterruptTask.Size = new System.Drawing.Size(147, 24); - this.chkAllowInterruptTask.TabIndex = 0; - this.chkAllowInterruptTask.Text = "允许中断充电任务"; - this.chkAllowInterruptTask.UseVisualStyleBackColor = true; - // - // grpTaskParams - // - this.grpTaskParams.Controls.Add(this.numMinAllowFreeCarToChargeTaskCnt); - this.grpTaskParams.Controls.Add(this.lblMinAllowFreeCarToChargeTaskCnt); - this.grpTaskParams.Dock = System.Windows.Forms.DockStyle.Top; - this.grpTaskParams.Font = new System.Drawing.Font("微软雅黑", 9F, System.Drawing.FontStyle.Bold); - this.grpTaskParams.Location = new System.Drawing.Point(10, 370); - this.grpTaskParams.Name = "grpTaskParams"; - this.grpTaskParams.Padding = new System.Windows.Forms.Padding(10); - this.grpTaskParams.Size = new System.Drawing.Size(764, 90); - this.grpTaskParams.TabIndex = 2; - this.grpTaskParams.TabStop = false; - this.grpTaskParams.Text = "任务参数"; - // - // numMinAllowFreeCarToChargeTaskCnt - // - this.numMinAllowFreeCarToChargeTaskCnt.Font = new System.Drawing.Font("微软雅黑", 9F); - this.numMinAllowFreeCarToChargeTaskCnt.Location = new System.Drawing.Point(250, 40); - this.numMinAllowFreeCarToChargeTaskCnt.Maximum = new decimal(new int[] { - 100, - 0, - 0, - 0}); - this.numMinAllowFreeCarToChargeTaskCnt.Name = "numMinAllowFreeCarToChargeTaskCnt"; - this.numMinAllowFreeCarToChargeTaskCnt.Size = new System.Drawing.Size(120, 27); - this.numMinAllowFreeCarToChargeTaskCnt.TabIndex = 1; - // - // lblMinAllowFreeCarToChargeTaskCnt - // - this.lblMinAllowFreeCarToChargeTaskCnt.AutoSize = true; - this.lblMinAllowFreeCarToChargeTaskCnt.Font = new System.Drawing.Font("微软雅黑", 9F); - this.lblMinAllowFreeCarToChargeTaskCnt.Location = new System.Drawing.Point(30, 42); - this.lblMinAllowFreeCarToChargeTaskCnt.Name = "lblMinAllowFreeCarToChargeTaskCnt"; - this.lblMinAllowFreeCarToChargeTaskCnt.Size = new System.Drawing.Size(207, 20); - this.lblMinAllowFreeCarToChargeTaskCnt.TabIndex = 0; - this.lblMinAllowFreeCarToChargeTaskCnt.Text = "允许空闲车充电的最小任务数:"; - // - // grpTimeParams - // - this.grpTimeParams.Controls.Add(this.numTopUpMinutes); - this.grpTimeParams.Controls.Add(this.lblTopUpMinutes); - this.grpTimeParams.Controls.Add(this.numMustChargeSeconds); - this.grpTimeParams.Controls.Add(this.lblMustChargeSeconds); - this.grpTimeParams.Controls.Add(this.numIdleSeconds); - this.grpTimeParams.Controls.Add(this.lblIdleSeconds); - this.grpTimeParams.Controls.Add(this.numIdleChargeSeconds); - this.grpTimeParams.Controls.Add(this.lblIdleChargeSeconds); - this.grpTimeParams.Dock = System.Windows.Forms.DockStyle.Top; - this.grpTimeParams.Font = new System.Drawing.Font("微软雅黑", 9F, System.Drawing.FontStyle.Bold); - this.grpTimeParams.Location = new System.Drawing.Point(10, 210); - this.grpTimeParams.Name = "grpTimeParams"; - this.grpTimeParams.Padding = new System.Windows.Forms.Padding(10); - this.grpTimeParams.Size = new System.Drawing.Size(764, 160); - this.grpTimeParams.TabIndex = 1; - this.grpTimeParams.TabStop = false; - this.grpTimeParams.Text = "时间参数"; - // - // numTopUpMinutes - // - this.numTopUpMinutes.DecimalPlaces = 1; - this.numTopUpMinutes.Font = new System.Drawing.Font("微软雅黑", 9F); - this.numTopUpMinutes.Location = new System.Drawing.Point(580, 100); - this.numTopUpMinutes.Maximum = new decimal(new int[] { - 1000, - 0, - 0, - 0}); - this.numTopUpMinutes.Name = "numTopUpMinutes"; - this.numTopUpMinutes.Size = new System.Drawing.Size(120, 27); - this.numTopUpMinutes.TabIndex = 7; - // - // lblTopUpMinutes - // - this.lblTopUpMinutes.AutoSize = true; - this.lblTopUpMinutes.Font = new System.Drawing.Font("微软雅黑", 9F); - this.lblTopUpMinutes.Location = new System.Drawing.Point(400, 102); - this.lblTopUpMinutes.Name = "lblTopUpMinutes"; - this.lblTopUpMinutes.Size = new System.Drawing.Size(159, 20); - this.lblTopUpMinutes.TabIndex = 6; - this.lblTopUpMinutes.Text = "补电时间 (分钟,min):"; - // - // numMustChargeSeconds - // - this.numMustChargeSeconds.DecimalPlaces = 1; - this.numMustChargeSeconds.Font = new System.Drawing.Font("微软雅黑", 9F); - this.numMustChargeSeconds.Location = new System.Drawing.Point(250, 100); - this.numMustChargeSeconds.Maximum = new decimal(new int[] { - 10000, - 0, - 0, - 0}); - this.numMustChargeSeconds.Name = "numMustChargeSeconds"; - this.numMustChargeSeconds.Size = new System.Drawing.Size(120, 27); - this.numMustChargeSeconds.TabIndex = 5; - // - // lblMustChargeSeconds - // - this.lblMustChargeSeconds.AutoSize = true; - this.lblMustChargeSeconds.Font = new System.Drawing.Font("微软雅黑", 9F); - this.lblMustChargeSeconds.Location = new System.Drawing.Point(30, 102); - this.lblMustChargeSeconds.Name = "lblMustChargeSeconds"; - this.lblMustChargeSeconds.Size = new System.Drawing.Size(147, 20); - this.lblMustChargeSeconds.TabIndex = 4; - this.lblMustChargeSeconds.Text = "必充时间 (秒,sec):"; - // - // numIdleSeconds - // - this.numIdleSeconds.DecimalPlaces = 1; - this.numIdleSeconds.Font = new System.Drawing.Font("微软雅黑", 9F); - this.numIdleSeconds.Location = new System.Drawing.Point(580, 40); - this.numIdleSeconds.Maximum = new decimal(new int[] { - 10000, - 0, - 0, - 0}); - this.numIdleSeconds.Name = "numIdleSeconds"; - this.numIdleSeconds.Size = new System.Drawing.Size(120, 27); - this.numIdleSeconds.TabIndex = 3; - // - // lblIdleSeconds - // - this.lblIdleSeconds.AutoSize = true; - this.lblIdleSeconds.Font = new System.Drawing.Font("微软雅黑", 9F); - this.lblIdleSeconds.Location = new System.Drawing.Point(400, 42); - this.lblIdleSeconds.Name = "lblIdleSeconds"; - this.lblIdleSeconds.Size = new System.Drawing.Size(147, 20); - this.lblIdleSeconds.TabIndex = 2; - this.lblIdleSeconds.Text = "空闲时间 (秒,sec):"; - // - // numIdleChargeSeconds - // - this.numIdleChargeSeconds.DecimalPlaces = 1; - this.numIdleChargeSeconds.Font = new System.Drawing.Font("微软雅黑", 9F); - this.numIdleChargeSeconds.Location = new System.Drawing.Point(250, 40); - this.numIdleChargeSeconds.Maximum = new decimal(new int[] { - 10000, - 0, - 0, - 0}); - this.numIdleChargeSeconds.Name = "numIdleChargeSeconds"; - this.numIdleChargeSeconds.Size = new System.Drawing.Size(120, 27); - this.numIdleChargeSeconds.TabIndex = 1; - // - // lblIdleChargeSeconds - // - this.lblIdleChargeSeconds.AutoSize = true; - this.lblIdleChargeSeconds.Font = new System.Drawing.Font("微软雅黑", 9F); - this.lblIdleChargeSeconds.Location = new System.Drawing.Point(30, 42); - this.lblIdleChargeSeconds.Name = "lblIdleChargeSeconds"; - this.lblIdleChargeSeconds.Size = new System.Drawing.Size(171, 20); - this.lblIdleChargeSeconds.TabIndex = 0; - this.lblIdleChargeSeconds.Text = "空闲充电时间 (秒,sec):"; - // - // grpSocParams - // - this.grpSocParams.Controls.Add(this.numAllowInterruptSoc); - this.grpSocParams.Controls.Add(this.lblAllowInterruptSoc); - this.grpSocParams.Controls.Add(this.numFullChargeSoc); - this.grpSocParams.Controls.Add(this.lblFullChargeSoc); - this.grpSocParams.Controls.Add(this.numTaskAvailableSoc); - this.grpSocParams.Controls.Add(this.lblTaskAvailableSoc); - this.grpSocParams.Controls.Add(this.numIdleChargeSoc); - this.grpSocParams.Controls.Add(this.lblIdleChargeSoc); - this.grpSocParams.Controls.Add(this.numMustChargeSoc); - this.grpSocParams.Controls.Add(this.lblMustChargeSoc); - this.grpSocParams.Dock = System.Windows.Forms.DockStyle.Top; - this.grpSocParams.Font = new System.Drawing.Font("微软雅黑", 9F, System.Drawing.FontStyle.Bold); - this.grpSocParams.Location = new System.Drawing.Point(10, 10); - this.grpSocParams.Name = "grpSocParams"; - this.grpSocParams.Padding = new System.Windows.Forms.Padding(10); - this.grpSocParams.Size = new System.Drawing.Size(764, 200); - this.grpSocParams.TabIndex = 0; - this.grpSocParams.TabStop = false; - this.grpSocParams.Text = "SOC 参数 (电量百分比)"; - // - // numAllowInterruptSoc - // - this.numAllowInterruptSoc.DecimalPlaces = 1; - this.numAllowInterruptSoc.Font = new System.Drawing.Font("微软雅黑", 9F); - this.numAllowInterruptSoc.Location = new System.Drawing.Point(250, 150); - this.numAllowInterruptSoc.Name = "numAllowInterruptSoc"; - this.numAllowInterruptSoc.Size = new System.Drawing.Size(120, 27); - this.numAllowInterruptSoc.TabIndex = 9; - // - // lblAllowInterruptSoc - // - this.lblAllowInterruptSoc.AutoSize = true; - this.lblAllowInterruptSoc.Font = new System.Drawing.Font("微软雅黑", 9F); - this.lblAllowInterruptSoc.Location = new System.Drawing.Point(30, 152); - this.lblAllowInterruptSoc.Name = "lblAllowInterruptSoc"; - this.lblAllowInterruptSoc.Size = new System.Drawing.Size(135, 20); - this.lblAllowInterruptSoc.TabIndex = 8; - this.lblAllowInterruptSoc.Text = "允许中断电量 (%):"; - // - // numFullChargeSoc - // - this.numFullChargeSoc.DecimalPlaces = 1; - this.numFullChargeSoc.Font = new System.Drawing.Font("微软雅黑", 9F); - this.numFullChargeSoc.Location = new System.Drawing.Point(580, 95); - this.numFullChargeSoc.Name = "numFullChargeSoc"; - this.numFullChargeSoc.Size = new System.Drawing.Size(120, 27); - this.numFullChargeSoc.TabIndex = 7; - // - // lblFullChargeSoc - // - this.lblFullChargeSoc.AutoSize = true; - this.lblFullChargeSoc.Font = new System.Drawing.Font("微软雅黑", 9F); - this.lblFullChargeSoc.Location = new System.Drawing.Point(400, 97); - this.lblFullChargeSoc.Name = "lblFullChargeSoc"; - this.lblFullChargeSoc.Size = new System.Drawing.Size(99, 20); - this.lblFullChargeSoc.TabIndex = 6; - this.lblFullChargeSoc.Text = "满电电量 (%):"; - // - // numTaskAvailableSoc - // - this.numTaskAvailableSoc.DecimalPlaces = 1; - this.numTaskAvailableSoc.Font = new System.Drawing.Font("微软雅黑", 9F); - this.numTaskAvailableSoc.Location = new System.Drawing.Point(250, 95); - this.numTaskAvailableSoc.Name = "numTaskAvailableSoc"; - this.numTaskAvailableSoc.Size = new System.Drawing.Size(120, 27); - this.numTaskAvailableSoc.TabIndex = 5; - // - // lblTaskAvailableSoc - // - this.lblTaskAvailableSoc.AutoSize = true; - this.lblTaskAvailableSoc.Font = new System.Drawing.Font("微软雅黑", 9F); - this.lblTaskAvailableSoc.Location = new System.Drawing.Point(30, 97); - this.lblTaskAvailableSoc.Name = "lblTaskAvailableSoc"; - this.lblTaskAvailableSoc.Size = new System.Drawing.Size(135, 20); - this.lblTaskAvailableSoc.TabIndex = 4; - this.lblTaskAvailableSoc.Text = "任务可用电量 (%):"; - // - // numIdleChargeSoc - // - this.numIdleChargeSoc.DecimalPlaces = 1; - this.numIdleChargeSoc.Font = new System.Drawing.Font("微软雅黑", 9F); - this.numIdleChargeSoc.Location = new System.Drawing.Point(580, 40); - this.numIdleChargeSoc.Name = "numIdleChargeSoc"; - this.numIdleChargeSoc.Size = new System.Drawing.Size(120, 27); - this.numIdleChargeSoc.TabIndex = 3; - // - // lblIdleChargeSoc - // - this.lblIdleChargeSoc.AutoSize = true; - this.lblIdleChargeSoc.Font = new System.Drawing.Font("微软雅黑", 9F); - this.lblIdleChargeSoc.Location = new System.Drawing.Point(400, 42); - this.lblIdleChargeSoc.Name = "lblIdleChargeSoc"; - this.lblIdleChargeSoc.Size = new System.Drawing.Size(135, 20); - this.lblIdleChargeSoc.TabIndex = 2; - this.lblIdleChargeSoc.Text = "空闲充电电量 (%):"; - // - // numMustChargeSoc - // - this.numMustChargeSoc.DecimalPlaces = 1; - this.numMustChargeSoc.Font = new System.Drawing.Font("微软雅黑", 9F); - this.numMustChargeSoc.Location = new System.Drawing.Point(250, 40); - this.numMustChargeSoc.Name = "numMustChargeSoc"; - this.numMustChargeSoc.Size = new System.Drawing.Size(120, 27); - this.numMustChargeSoc.TabIndex = 1; - // - // lblMustChargeSoc - // - this.lblMustChargeSoc.AutoSize = true; - this.lblMustChargeSoc.Font = new System.Drawing.Font("微软雅黑", 9F); - this.lblMustChargeSoc.Location = new System.Drawing.Point(30, 42); - this.lblMustChargeSoc.Name = "lblMustChargeSoc"; - this.lblMustChargeSoc.Size = new System.Drawing.Size(99, 20); - this.lblMustChargeSoc.TabIndex = 0; - this.lblMustChargeSoc.Text = "必充电量 (%):"; - // - // pnlBottom - // - this.pnlBottom.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(250)))), ((int)(((byte)(250)))), ((int)(((byte)(250))))); - this.pnlBottom.Controls.Add(this.lblStatus); - this.pnlBottom.Controls.Add(this.btnApply); - this.pnlBottom.Controls.Add(this.btnRestoreDefaults); - this.pnlBottom.Controls.Add(this.btnCancel); - this.pnlBottom.Controls.Add(this.btnSave); - this.pnlBottom.Dock = System.Windows.Forms.DockStyle.Bottom; - this.pnlBottom.Location = new System.Drawing.Point(0, 631); - this.pnlBottom.Name = "pnlBottom"; - this.pnlBottom.Size = new System.Drawing.Size(784, 70); - this.pnlBottom.TabIndex = 1; - // - // lblStatus - // - this.lblStatus.AutoSize = true; - this.lblStatus.Font = new System.Drawing.Font("微软雅黑", 9F); - this.lblStatus.Location = new System.Drawing.Point(20, 25); - this.lblStatus.Name = "lblStatus"; - this.lblStatus.Size = new System.Drawing.Size(54, 20); - this.lblStatus.TabIndex = 4; - this.lblStatus.Text = "就绪..."; - // - // btnApply - // - this.btnApply.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); - this.btnApply.Font = new System.Drawing.Font("微软雅黑", 9F); - this.btnApply.Location = new System.Drawing.Point(564, 18); - this.btnApply.Name = "btnApply"; - this.btnApply.Size = new System.Drawing.Size(100, 35); - this.btnApply.TabIndex = 3; - this.btnApply.Text = "应用"; - this.btnApply.UseVisualStyleBackColor = true; - this.btnApply.Click += new System.EventHandler(this.btnApply_Click); - // - // btnRestoreDefaults - // - this.btnRestoreDefaults.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); - this.btnRestoreDefaults.Font = new System.Drawing.Font("微软雅黑", 9F); - this.btnRestoreDefaults.Location = new System.Drawing.Point(344, 18); - this.btnRestoreDefaults.Name = "btnRestoreDefaults"; - this.btnRestoreDefaults.Size = new System.Drawing.Size(100, 35); - this.btnRestoreDefaults.TabIndex = 2; - this.btnRestoreDefaults.Text = "恢复默认"; - this.btnRestoreDefaults.UseVisualStyleBackColor = true; - this.btnRestoreDefaults.Click += new System.EventHandler(this.btnRestoreDefaults_Click); - // - // btnCancel - // - this.btnCancel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); - this.btnCancel.Font = new System.Drawing.Font("微软雅黑", 9F); - this.btnCancel.Location = new System.Drawing.Point(674, 18); - this.btnCancel.Name = "btnCancel"; - this.btnCancel.Size = new System.Drawing.Size(100, 35); - this.btnCancel.TabIndex = 1; - this.btnCancel.Text = "取消"; - this.btnCancel.UseVisualStyleBackColor = true; - this.btnCancel.Click += new System.EventHandler(this.btnCancel_Click); - // - // btnSave - // - this.btnSave.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); - this.btnSave.Font = new System.Drawing.Font("微软雅黑", 9F); - this.btnSave.Location = new System.Drawing.Point(454, 18); - this.btnSave.Name = "btnSave"; - this.btnSave.Size = new System.Drawing.Size(100, 35); - this.btnSave.TabIndex = 0; - this.btnSave.Text = "保存"; - this.btnSave.UseVisualStyleBackColor = true; - this.btnSave.Click += new System.EventHandler(this.btnSave_Click); - // - // ChargeStrategyConfigForm - // - this.AutoScaleDimensions = new System.Drawing.SizeF(8F, 15F); - this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - this.ClientSize = new System.Drawing.Size(784, 701); - this.Controls.Add(this.pnlMain); - this.Controls.Add(this.pnlBottom); - this.Name = "ChargeStrategyConfigForm"; - this.Text = "充电策略配置"; - this.pnlMain.ResumeLayout(false); - this.grpSwitchParams.ResumeLayout(false); - this.grpSwitchParams.PerformLayout(); - this.grpTaskParams.ResumeLayout(false); - this.grpTaskParams.PerformLayout(); - ((System.ComponentModel.ISupportInitialize)(this.numMinAllowFreeCarToChargeTaskCnt)).EndInit(); - this.grpTimeParams.ResumeLayout(false); - this.grpTimeParams.PerformLayout(); - ((System.ComponentModel.ISupportInitialize)(this.numTopUpMinutes)).EndInit(); - ((System.ComponentModel.ISupportInitialize)(this.numMustChargeSeconds)).EndInit(); - ((System.ComponentModel.ISupportInitialize)(this.numIdleSeconds)).EndInit(); - ((System.ComponentModel.ISupportInitialize)(this.numIdleChargeSeconds)).EndInit(); - this.grpSocParams.ResumeLayout(false); - this.grpSocParams.PerformLayout(); - ((System.ComponentModel.ISupportInitialize)(this.numAllowInterruptSoc)).EndInit(); - ((System.ComponentModel.ISupportInitialize)(this.numFullChargeSoc)).EndInit(); - ((System.ComponentModel.ISupportInitialize)(this.numTaskAvailableSoc)).EndInit(); - ((System.ComponentModel.ISupportInitialize)(this.numIdleChargeSoc)).EndInit(); - ((System.ComponentModel.ISupportInitialize)(this.numMustChargeSoc)).EndInit(); - this.pnlBottom.ResumeLayout(false); - this.pnlBottom.PerformLayout(); - this.ResumeLayout(false); - - } - - #endregion - - private System.Windows.Forms.Panel pnlMain; - private System.Windows.Forms.GroupBox grpSocParams; - private System.Windows.Forms.NumericUpDown numMustChargeSoc; - private System.Windows.Forms.Label lblMustChargeSoc; - private System.Windows.Forms.NumericUpDown numIdleChargeSoc; - private System.Windows.Forms.Label lblIdleChargeSoc; - private System.Windows.Forms.NumericUpDown numTaskAvailableSoc; - private System.Windows.Forms.Label lblTaskAvailableSoc; - private System.Windows.Forms.NumericUpDown numFullChargeSoc; - private System.Windows.Forms.Label lblFullChargeSoc; - private System.Windows.Forms.NumericUpDown numAllowInterruptSoc; - private System.Windows.Forms.Label lblAllowInterruptSoc; - private System.Windows.Forms.GroupBox grpTimeParams; - private System.Windows.Forms.NumericUpDown numIdleChargeSeconds; - private System.Windows.Forms.Label lblIdleChargeSeconds; - private System.Windows.Forms.NumericUpDown numIdleSeconds; - private System.Windows.Forms.Label lblIdleSeconds; - private System.Windows.Forms.NumericUpDown numMustChargeSeconds; - private System.Windows.Forms.Label lblMustChargeSeconds; - private System.Windows.Forms.NumericUpDown numTopUpMinutes; - private System.Windows.Forms.Label lblTopUpMinutes; - private System.Windows.Forms.GroupBox grpTaskParams; - private System.Windows.Forms.NumericUpDown numMinAllowFreeCarToChargeTaskCnt; - private System.Windows.Forms.Label lblMinAllowFreeCarToChargeTaskCnt; - private System.Windows.Forms.GroupBox grpSwitchParams; - private System.Windows.Forms.CheckBox chkAllowInterruptTask; - private System.Windows.Forms.CheckBox chkUseLowerSocForCharge; - private System.Windows.Forms.CheckBox chkEnableErrorChargeDetection; - private System.Windows.Forms.CheckBox chkUseChargeSiteFilter; - private System.Windows.Forms.Panel pnlBottom; - private System.Windows.Forms.Button btnSave; - private System.Windows.Forms.Button btnCancel; - private System.Windows.Forms.Button btnRestoreDefaults; - private System.Windows.Forms.Button btnApply; - private System.Windows.Forms.Label lblStatus; - } -} diff --git a/StandardScene.Core/Charge/ChargeStrategyConfigForm.cs b/StandardScene.Core/Charge/ChargeStrategyConfigForm.cs index 53f84a7..a8b18f8 100644 --- a/StandardScene.Core/Charge/ChargeStrategyConfigForm.cs +++ b/StandardScene.Core/Charge/ChargeStrategyConfigForm.cs @@ -1,215 +1,259 @@ using System; -using System.Drawing; -using System.Windows.Forms; +using CycleGUI; +using StandardScene.Utils; namespace StandardScene.Charge { /// - /// 充电策略配置窗体 + /// 充电策略配置界面(CycleGUI 版,替代原 WinForms 窗体)。 /// - public partial class ChargeStrategyConfigForm : Form + public class ChargeStrategyConfigForm { - private ChargeStrategyConfig config; - private ChargeStrategyConfigService configService; + private static Panel _panel; + private static readonly ChargeStrategyConfigService ConfigService = ChargeStrategyConfigService.Instance; - public ChargeStrategyConfigForm() + private static ChargeStrategyConfig _config; + private static string _status = ""; + + // SOC 参数 + private static float _mustChargeSoc; + private static float _idleChargeSoc; + private static float _taskAvailableSoc; + private static float _fullChargeSoc; + private static float _allowInterruptSoc; + + // 时间参数 + private static float _idleChargeSeconds; + private static float _idleSeconds; + private static float _mustChargeSeconds; + private static float _topUpMinutes; + + // 任务参数 + private static int _minAllowFreeCarToChargeTaskCnt; + + // 开关参数 + private static bool _allowInterruptTask; + private static bool _useLowerSocForCharge; + private static bool _enableErrorChargeDetection; + private static bool _useChargeSiteFilter; + + /// 打开(或置前)充电策略配置面板。兼容原 new ChargeStrategyConfigForm().Show() 调用方式。 + public void Show() => Open(); + + /// 打开(或置前)充电策略配置面板。 + public static void Open() { - InitializeComponent(); - configService = ChargeStrategyConfigService.Instance; - InitializeForm(); - } - - private void InitializeForm() - { - this.Text = "充电策略配置"; - this.Size = new Size(800, 700); - this.StartPosition = FormStartPosition.CenterScreen; - this.MinimumSize = new Size(700, 600); - this.FormBorderStyle = FormBorderStyle.FixedDialog; - this.MaximizeBox = false; - - // 加载配置 - LoadConfig(); - } - - /// - /// 加载配置到界面 - /// - private void LoadConfig(bool isDef = false) - { - try + if (_panel != null) { - if (!isDef) + try { - config = configService.LoadConfig(); + _panel.BringToFront(); + return; + } + catch + { + _panel = null; } - - - // SOC 相关参数 - numMustChargeSoc.Value = (decimal)config.MustChargeSoc; - numIdleChargeSoc.Value = (decimal)config.IdleChargeSoc; - numTaskAvailableSoc.Value = (decimal)config.TaskAvailableSoc; - numFullChargeSoc.Value = (decimal)config.FullChargeSoc; - numAllowInterruptSoc.Value = (decimal)config.AllowInterruptSoc; - - // 时间相关参数 - numIdleChargeSeconds.Value = (decimal)config.IdleChargeSeconds; - numIdleSeconds.Value = (decimal)config.IdleSeconds; - numMustChargeSeconds.Value = (decimal)config.MustChargeSeconds; - numTopUpMinutes.Value = (decimal)config.TopUpMinutes; - - // 任务相关参数 - numMinAllowFreeCarToChargeTaskCnt.Value = config.MinAllowFreeCarToChargeTaskCnt; - - // 开关参数 - chkAllowInterruptTask.Checked = config.AllowInterruptTask; - chkUseLowerSocForCharge.Checked = config.UseLowerSocForCharge; - chkEnableErrorChargeDetection.Checked = config.EnableErrorChargeDetection; - chkUseChargeSiteFilter.Checked = config.UseChargeSiteFilter; - - lblStatus.Text = "配置加载成功"; - lblStatus.ForeColor = Color.Green; } - catch (Exception ex) + + if (!TryLoadConfig()) + return; + + var panel = GUI.DeclarePanel() + .ShowTitle("充电策略配置") + .SetDefaultDocking(Panel.Docking.None) + .InitSize(780, 680) + .InitPos(false, 0, 0, 0.5f, 0.5f, 0.5f, 0.5f); + _panel = panel; + panel.IfTerminalQuit(() => _panel = null); + + panel.Define(pb => { - MessageBox.Show($"加载配置失败: {ex.Message}", "错误", - MessageBoxButtons.OK, MessageBoxIcon.Error); - lblStatus.Text = "配置加载失败"; - lblStatus.ForeColor = Color.Red; - } + if (pb.Closing()) + { + panel.Exit(); + _panel = null; + return; + } + + pb.SeparatorText("SOC 参数 (电量百分比)"); + pb.DragFloat("1. 必充电量 (%)", ref _mustChargeSoc, step: 0.1f, min: 0, max: 100); + pb.DragFloat("2. 空闲充电电量 (%)", ref _idleChargeSoc, step: 0.1f, min: 0, max: 100); + pb.DragFloat("3. 任务可用电量 (%)", ref _taskAvailableSoc, step: 0.1f, min: 0, max: 100); + pb.DragFloat("4. 满电电量 (%)", ref _fullChargeSoc, step: 0.1f, min: 0, max: 100); + pb.DragFloat("5. 允许中断电量 (%)", ref _allowInterruptSoc, step: 0.1f, min: 0, max: 100); + + pb.SeparatorText("时间参数"); + pb.DragFloat("6. 空闲充电时间 (秒)", ref _idleChargeSeconds, step: 0.1f, min: 0, max: 10000); + pb.DragFloat("7. 空闲时间 (秒)", ref _idleSeconds, step: 0.1f, min: 0, max: 10000); + pb.DragFloat("8. 必充时间 (秒)", ref _mustChargeSeconds, step: 0.1f, min: 0, max: 10000); + pb.DragFloat("9. 补电时间 (分钟)", ref _topUpMinutes, step: 0.1f, min: 0, max: 1000); + + pb.SeparatorText("任务参数"); + pb.SliderInt("10. 允许空闲车充电的最小任务数", ref _minAllowFreeCarToChargeTaskCnt, min: 0, max: 100); + + pb.SeparatorText("开关参数"); + pb.CheckBox("11. 允许中断充电任务", ref _allowInterruptTask); + pb.CheckBox("12. 优先使用低电量车辆充电", ref _useLowerSocForCharge); + pb.CheckBox("13. 启用充电错误检测", ref _enableErrorChargeDetection); + pb.CheckBox("14. 使用充电站点筛选", ref _useChargeSiteFilter); + + pb.Separator(); + if (!string.IsNullOrEmpty(_status)) + pb.Label(_status); + + pb.Separator(); + if (pb.Button("保存", distinct: "charge-strategy-save")) + TrySave(closeAfterSave: false); + pb.SameLine(8); + if (pb.Button("应用", distinct: "charge-strategy-apply")) + TrySave(closeAfterSave: false); + pb.SameLine(8); + if (pb.Button("恢复默认", distinct: "charge-strategy-restore")) + RestoreDefaults(); + pb.SameLine(8); + if (pb.Button("取消", distinct: "charge-strategy-cancel")) + { + panel.Exit(); + _panel = null; + } + }); } - /// - /// 从界面保存配置 - /// - private void SaveConfig() + private static bool TryLoadConfig() { try { - // SOC 相关参数 - config.MustChargeSoc = (double)numMustChargeSoc.Value; - config.IdleChargeSoc = (double)numIdleChargeSoc.Value; - config.TaskAvailableSoc = (double)numTaskAvailableSoc.Value; - config.FullChargeSoc = (double)numFullChargeSoc.Value; - config.AllowInterruptSoc = (double)numAllowInterruptSoc.Value; - - // 时间相关参数 - config.IdleChargeSeconds = (double)numIdleChargeSeconds.Value; - config.IdleSeconds = (double)numIdleSeconds.Value; - config.MustChargeSeconds = (double)numMustChargeSeconds.Value; - config.TopUpMinutes = (double)numTopUpMinutes.Value; - - // 任务相关参数 - config.MinAllowFreeCarToChargeTaskCnt = (int)numMinAllowFreeCarToChargeTaskCnt.Value; - - // 开关参数 - config.AllowInterruptTask = chkAllowInterruptTask.Checked; - config.UseLowerSocForCharge = chkUseLowerSocForCharge.Checked; - config.EnableErrorChargeDetection = chkEnableErrorChargeDetection.Checked; - config.UseChargeSiteFilter = chkUseChargeSiteFilter.Checked; - - // 保存到文件 - configService.SaveConfig(config); - - lblStatus.Text = "配置保存成功"; - lblStatus.ForeColor = Color.Green; - - MessageBox.Show("充电策略配置保存成功!", "成功", - MessageBoxButtons.OK, MessageBoxIcon.Information); + _config = ConfigService.LoadConfig(); + ApplyConfigToUi(_config); + _status = "配置加载成功"; + return true; } catch (Exception ex) { - MessageBox.Show($"保存配置失败: {ex.Message}", "错误", - MessageBoxButtons.OK, MessageBoxIcon.Error); - lblStatus.Text = "配置保存失败"; - lblStatus.ForeColor = Color.Red; + CycleUiHelper.Alert("错误", $"加载配置失败: {ex.Message}"); + _status = "配置加载失败"; + return false; } } - /// - /// 恢复默认配置 - /// - private void RestoreDefaults() + private static void ApplyConfigToUi(ChargeStrategyConfig config) { - var result = MessageBox.Show( - "确定要恢复默认配置吗?当前配置将被覆盖。", - "确认恢复", - MessageBoxButtons.YesNo, - MessageBoxIcon.Question); + _mustChargeSoc = (float)config.MustChargeSoc; + _idleChargeSoc = (float)config.IdleChargeSoc; + _taskAvailableSoc = (float)config.TaskAvailableSoc; + _fullChargeSoc = (float)config.FullChargeSoc; + _allowInterruptSoc = (float)config.AllowInterruptSoc; - if (result == DialogResult.Yes) - { - config = ChargeStrategyConfig.CreateDefault(); - LoadConfig(true); - lblStatus.Text = "已恢复默认配置(未保存)"; - lblStatus.ForeColor = Color.Blue; - } + _idleChargeSeconds = (float)config.IdleChargeSeconds; + _idleSeconds = (float)config.IdleSeconds; + _mustChargeSeconds = (float)config.MustChargeSeconds; + _topUpMinutes = (float)config.TopUpMinutes; + + _minAllowFreeCarToChargeTaskCnt = config.MinAllowFreeCarToChargeTaskCnt; + + _allowInterruptTask = config.AllowInterruptTask; + _useLowerSocForCharge = config.UseLowerSocForCharge; + _enableErrorChargeDetection = config.EnableErrorChargeDetection; + _useChargeSiteFilter = config.UseChargeSiteFilter; } - /// - /// 验证配置参数 - /// - private bool ValidateConfig() + private static void ApplyUiToConfig() { - // 验证 SOC 范围 - if (numMustChargeSoc.Value >= numIdleChargeSoc.Value) + _config.MustChargeSoc = _mustChargeSoc; + _config.IdleChargeSoc = _idleChargeSoc; + _config.TaskAvailableSoc = _taskAvailableSoc; + _config.FullChargeSoc = _fullChargeSoc; + _config.AllowInterruptSoc = _allowInterruptSoc; + + _config.IdleChargeSeconds = _idleChargeSeconds; + _config.IdleSeconds = _idleSeconds; + _config.MustChargeSeconds = _mustChargeSeconds; + _config.TopUpMinutes = _topUpMinutes; + + _config.MinAllowFreeCarToChargeTaskCnt = _minAllowFreeCarToChargeTaskCnt; + + _config.AllowInterruptTask = _allowInterruptTask; + _config.UseLowerSocForCharge = _useLowerSocForCharge; + _config.EnableErrorChargeDetection = _enableErrorChargeDetection; + _config.UseChargeSiteFilter = _useChargeSiteFilter; + } + + /// 验证 SOC 阈值之间的逻辑关系(保存前)。 + private static bool ValidateSocRanges(out string errorMessage) + { + if (_mustChargeSoc >= _idleChargeSoc) { - MessageBox.Show("必充电量必须小于空闲充电电量", "验证失败", - MessageBoxButtons.OK, MessageBoxIcon.Warning); + errorMessage = "必充电量必须小于空闲充电电量"; return false; } - if (numTaskAvailableSoc.Value <= numMustChargeSoc.Value) + if (_taskAvailableSoc <= _mustChargeSoc) { - MessageBox.Show("任务可用电量必须大于必充电量", "验证失败", - MessageBoxButtons.OK, MessageBoxIcon.Warning); + errorMessage = "任务可用电量必须大于必充电量"; return false; } - if (numFullChargeSoc.Value < numIdleChargeSoc.Value) + if (_fullChargeSoc < _idleChargeSoc) { - MessageBox.Show("满电电量必须大于等于空闲充电电量", "验证失败", - MessageBoxButtons.OK, MessageBoxIcon.Warning); + errorMessage = "满电电量必须大于等于空闲充电电量"; return false; } - if (numAllowInterruptSoc.Value <= numMustChargeSoc.Value) + if (_allowInterruptSoc <= _mustChargeSoc) { - MessageBox.Show("允许中断电量必须大于必充电量", "验证失败", - MessageBoxButtons.OK, MessageBoxIcon.Warning); + errorMessage = "允许中断电量必须大于必充电量"; return false; } + errorMessage = string.Empty; return true; } - // ==================== 事件处理 ==================== - - private void btnSave_Click(object sender, EventArgs e) + private static void TrySave(bool closeAfterSave) { - if (ValidateConfig()) + ApplyUiToConfig(); + + if (!ValidateSocRanges(out var socError)) { - SaveConfig(); + CycleUiHelper.Alert("验证失败", socError); + _status = "配置保存失败"; + _panel?.Repaint(); + return; + } + + try + { + ConfigService.SaveConfig(_config); + _status = "配置保存成功"; + CycleUiHelper.Alert("成功", "充电策略配置保存成功!"); + if (closeAfterSave) + { + _panel?.Exit(); + _panel = null; + } + else + { + _panel?.Repaint(); + } + } + catch (Exception ex) + { + CycleUiHelper.Alert("错误", $"保存配置失败: {ex.Message}"); + _status = "配置保存失败"; + _panel?.Repaint(); } } - private void btnCancel_Click(object sender, EventArgs e) + private static void RestoreDefaults() { - this.Close(); - } - - private void btnRestoreDefaults_Click(object sender, EventArgs e) - { - RestoreDefaults(); - } - - private void btnApply_Click(object sender, EventArgs e) - { - if (ValidateConfig()) + CycleUiHelper.ConfirmThen("确定要恢复默认配置吗?当前配置将被覆盖。", () => { - SaveConfig(); - } + _config = ChargeStrategyConfig.CreateDefault(); + ApplyConfigToUi(_config); + _status = "已恢复默认配置(未保存)"; + _panel?.Repaint(); + }); } } } - diff --git a/StandardScene.Core/Charge/CommunicationMonitorForm.Designer.cs b/StandardScene.Core/Charge/CommunicationMonitorForm.Designer.cs deleted file mode 100644 index f83e9b4..0000000 --- a/StandardScene.Core/Charge/CommunicationMonitorForm.Designer.cs +++ /dev/null @@ -1,376 +0,0 @@ -namespace StandardScene.Charge -{ - partial class CommunicationMonitorForm - { - private System.ComponentModel.IContainer components = null; - - protected override void Dispose(bool disposing) - { - if (disposing && (components != null)) - { - components.Dispose(); - } - base.Dispose(disposing); - } - - #region Windows Form Designer generated code - - private void InitializeComponent() - { - System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle1 = new System.Windows.Forms.DataGridViewCellStyle(); - this.splitContainer = new System.Windows.Forms.SplitContainer(); - this.pnlLeft = new System.Windows.Forms.Panel(); - this.dgvMessages = new System.Windows.Forms.DataGridView(); - this.colTime = new System.Windows.Forms.DataGridViewTextBoxColumn(); - this.colDirection = new System.Windows.Forms.DataGridViewTextBoxColumn(); - this.colIpAddress = new System.Windows.Forms.DataGridViewTextBoxColumn(); - this.colPort = new System.Windows.Forms.DataGridViewTextBoxColumn(); - this.colLength = new System.Windows.Forms.DataGridViewTextBoxColumn(); - this.colRawData = new System.Windows.Forms.DataGridViewTextBoxColumn(); - this.colStationId = new System.Windows.Forms.DataGridViewTextBoxColumn(); - this.type = new System.Windows.Forms.DataGridViewTextBoxColumn(); - this.pnlLeftTop = new System.Windows.Forms.Panel(); - this.button1 = new System.Windows.Forms.Button(); - this.btnClear = new System.Windows.Forms.Button(); - this.btnRefresh = new System.Windows.Forms.Button(); - this.lblStatistics = new System.Windows.Forms.Label(); - this.cmbIpFilter = new System.Windows.Forms.ComboBox(); - this.lblIpFilter = new System.Windows.Forms.Label(); - this.pnlRight = new System.Windows.Forms.Panel(); - this.txtParsedData = new System.Windows.Forms.TextBox(); - this.pnlRightTop = new System.Windows.Forms.Panel(); - this.btnClose = new System.Windows.Forms.Button(); - this.lblParsedTitle = new System.Windows.Forms.Label(); - ((System.ComponentModel.ISupportInitialize)(this.splitContainer)).BeginInit(); - this.splitContainer.Panel1.SuspendLayout(); - this.splitContainer.Panel2.SuspendLayout(); - this.splitContainer.SuspendLayout(); - this.pnlLeft.SuspendLayout(); - ((System.ComponentModel.ISupportInitialize)(this.dgvMessages)).BeginInit(); - this.pnlLeftTop.SuspendLayout(); - this.pnlRight.SuspendLayout(); - this.pnlRightTop.SuspendLayout(); - this.SuspendLayout(); - // - // splitContainer - // - this.splitContainer.Dock = System.Windows.Forms.DockStyle.Fill; - this.splitContainer.Location = new System.Drawing.Point(0, 0); - this.splitContainer.Name = "splitContainer"; - // - // splitContainer.Panel1 - // - this.splitContainer.Panel1.Controls.Add(this.pnlLeft); - // - // splitContainer.Panel2 - // - this.splitContainer.Panel2.Controls.Add(this.pnlRight); - this.splitContainer.Size = new System.Drawing.Size(1400, 800); - this.splitContainer.SplitterDistance = 850; - this.splitContainer.TabIndex = 0; - // - // pnlLeft - // - this.pnlLeft.Controls.Add(this.dgvMessages); - this.pnlLeft.Controls.Add(this.pnlLeftTop); - this.pnlLeft.Dock = System.Windows.Forms.DockStyle.Fill; - this.pnlLeft.Location = new System.Drawing.Point(0, 0); - this.pnlLeft.Name = "pnlLeft"; - this.pnlLeft.Size = new System.Drawing.Size(850, 800); - this.pnlLeft.TabIndex = 0; - // - // dgvMessages - // - this.dgvMessages.AllowUserToAddRows = false; - this.dgvMessages.AllowUserToDeleteRows = false; - this.dgvMessages.AutoSizeColumnsMode = System.Windows.Forms.DataGridViewAutoSizeColumnsMode.Fill; - this.dgvMessages.BackgroundColor = System.Drawing.Color.White; - this.dgvMessages.BorderStyle = System.Windows.Forms.BorderStyle.None; - dataGridViewCellStyle1.Alignment = System.Windows.Forms.DataGridViewContentAlignment.MiddleLeft; - dataGridViewCellStyle1.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(63)))), ((int)(((byte)(81)))), ((int)(((byte)(181))))); - dataGridViewCellStyle1.Font = new System.Drawing.Font("微软雅黑", 9F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - dataGridViewCellStyle1.ForeColor = System.Drawing.Color.White; - dataGridViewCellStyle1.SelectionBackColor = System.Drawing.SystemColors.Highlight; - dataGridViewCellStyle1.SelectionForeColor = System.Drawing.SystemColors.HighlightText; - dataGridViewCellStyle1.WrapMode = System.Windows.Forms.DataGridViewTriState.True; - this.dgvMessages.ColumnHeadersDefaultCellStyle = dataGridViewCellStyle1; - this.dgvMessages.ColumnHeadersHeight = 35; - this.dgvMessages.Columns.AddRange(new System.Windows.Forms.DataGridViewColumn[] { - this.colTime, - this.colDirection, - this.colIpAddress, - this.colPort, - this.colLength, - this.colRawData, - this.colStationId, - this.type}); - this.dgvMessages.Dock = System.Windows.Forms.DockStyle.Fill; - this.dgvMessages.EnableHeadersVisualStyles = false; - this.dgvMessages.GridColor = System.Drawing.Color.LightGray; - this.dgvMessages.Location = new System.Drawing.Point(0, 80); - this.dgvMessages.MultiSelect = false; - this.dgvMessages.Name = "dgvMessages"; - this.dgvMessages.ReadOnly = true; - this.dgvMessages.RowHeadersVisible = false; - this.dgvMessages.RowHeadersWidth = 51; - this.dgvMessages.RowTemplate.Height = 30; - this.dgvMessages.SelectionMode = System.Windows.Forms.DataGridViewSelectionMode.FullRowSelect; - this.dgvMessages.Size = new System.Drawing.Size(850, 720); - this.dgvMessages.TabIndex = 1; - this.dgvMessages.SelectionChanged += new System.EventHandler(this.dgvMessages_SelectionChanged); - // - // colTime - // - this.colTime.FillWeight = 80F; - this.colTime.HeaderText = "时间"; - this.colTime.MinimumWidth = 6; - this.colTime.Name = "colTime"; - this.colTime.ReadOnly = true; - // - // colDirection - // - this.colDirection.FillWeight = 50F; - this.colDirection.HeaderText = "方向"; - this.colDirection.MinimumWidth = 6; - this.colDirection.Name = "colDirection"; - this.colDirection.ReadOnly = true; - // - // colIpAddress - // - this.colIpAddress.FillWeight = 80F; - this.colIpAddress.HeaderText = "IP地址"; - this.colIpAddress.MinimumWidth = 6; - this.colIpAddress.Name = "colIpAddress"; - this.colIpAddress.ReadOnly = true; - // - // colPort - // - this.colPort.FillWeight = 50F; - this.colPort.HeaderText = "端口"; - this.colPort.MinimumWidth = 6; - this.colPort.Name = "colPort"; - this.colPort.ReadOnly = true; - // - // colLength - // - this.colLength.FillWeight = 50F; - this.colLength.HeaderText = "长度"; - this.colLength.MinimumWidth = 6; - this.colLength.Name = "colLength"; - this.colLength.ReadOnly = true; - // - // colRawData - // - this.colRawData.FillWeight = 200F; - this.colRawData.HeaderText = "原始数据"; - this.colRawData.MinimumWidth = 6; - this.colRawData.Name = "colRawData"; - this.colRawData.ReadOnly = true; - // - // colStationId - // - this.colStationId.FillWeight = 80F; - this.colStationId.HeaderText = "充电桩"; - this.colStationId.MinimumWidth = 6; - this.colStationId.Name = "colStationId"; - this.colStationId.ReadOnly = true; - // - // type - // - this.type.HeaderText = "协议类型"; - this.type.MinimumWidth = 6; - this.type.Name = "type"; - this.type.ReadOnly = true; - // - // pnlLeftTop - // - this.pnlLeftTop.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(250)))), ((int)(((byte)(250)))), ((int)(((byte)(250))))); - this.pnlLeftTop.Controls.Add(this.button1); - this.pnlLeftTop.Controls.Add(this.btnClear); - this.pnlLeftTop.Controls.Add(this.btnRefresh); - this.pnlLeftTop.Controls.Add(this.lblStatistics); - this.pnlLeftTop.Controls.Add(this.cmbIpFilter); - this.pnlLeftTop.Controls.Add(this.lblIpFilter); - this.pnlLeftTop.Dock = System.Windows.Forms.DockStyle.Top; - this.pnlLeftTop.Location = new System.Drawing.Point(0, 0); - this.pnlLeftTop.Name = "pnlLeftTop"; - this.pnlLeftTop.Padding = new System.Windows.Forms.Padding(10); - this.pnlLeftTop.Size = new System.Drawing.Size(850, 80); - this.pnlLeftTop.TabIndex = 0; - // - // button1 - // - this.button1.Font = new System.Drawing.Font("微软雅黑", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - this.button1.Location = new System.Drawing.Point(546, 16); - this.button1.Name = "button1"; - this.button1.Size = new System.Drawing.Size(80, 32); - this.button1.TabIndex = 5; - this.button1.Text = "暂停"; - this.button1.UseVisualStyleBackColor = true; - this.button1.Click += new System.EventHandler(this.button1_Click); - // - // btnClear - // - this.btnClear.Font = new System.Drawing.Font("微软雅黑", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - this.btnClear.Location = new System.Drawing.Point(460, 15); - this.btnClear.Name = "btnClear"; - this.btnClear.Size = new System.Drawing.Size(80, 32); - this.btnClear.TabIndex = 4; - this.btnClear.Text = "清空"; - this.btnClear.UseVisualStyleBackColor = true; - this.btnClear.Click += new System.EventHandler(this.btnClear_Click); - // - // btnRefresh - // - this.btnRefresh.Font = new System.Drawing.Font("微软雅黑", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - this.btnRefresh.Location = new System.Drawing.Point(370, 15); - this.btnRefresh.Name = "btnRefresh"; - this.btnRefresh.Size = new System.Drawing.Size(80, 32); - this.btnRefresh.TabIndex = 3; - this.btnRefresh.Text = "刷新"; - this.btnRefresh.UseVisualStyleBackColor = true; - this.btnRefresh.Click += new System.EventHandler(this.btnRefresh_Click); - // - // lblStatistics - // - this.lblStatistics.AutoSize = true; - this.lblStatistics.Font = new System.Drawing.Font("微软雅黑", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - this.lblStatistics.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(100)))), ((int)(((byte)(100)))), ((int)(((byte)(100))))); - this.lblStatistics.Location = new System.Drawing.Point(13, 52); - this.lblStatistics.Name = "lblStatistics"; - this.lblStatistics.Size = new System.Drawing.Size(115, 20); - this.lblStatistics.TabIndex = 2; - this.lblStatistics.Text = "显示: 0 | 总数: 0"; - // - // cmbIpFilter - // - this.cmbIpFilter.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; - this.cmbIpFilter.Font = new System.Drawing.Font("微软雅黑", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - this.cmbIpFilter.FormattingEnabled = true; - this.cmbIpFilter.Location = new System.Drawing.Point(100, 17); - this.cmbIpFilter.Name = "cmbIpFilter"; - this.cmbIpFilter.Size = new System.Drawing.Size(250, 28); - this.cmbIpFilter.TabIndex = 1; - this.cmbIpFilter.SelectedIndexChanged += new System.EventHandler(this.cmbIpFilter_SelectedIndexChanged); - // - // lblIpFilter - // - this.lblIpFilter.AutoSize = true; - this.lblIpFilter.Font = new System.Drawing.Font("微软雅黑", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - this.lblIpFilter.Location = new System.Drawing.Point(13, 21); - this.lblIpFilter.Name = "lblIpFilter"; - this.lblIpFilter.Size = new System.Drawing.Size(67, 20); - this.lblIpFilter.TabIndex = 0; - this.lblIpFilter.Text = "IP筛选:"; - // - // pnlRight - // - this.pnlRight.Controls.Add(this.txtParsedData); - this.pnlRight.Controls.Add(this.pnlRightTop); - this.pnlRight.Dock = System.Windows.Forms.DockStyle.Fill; - this.pnlRight.Location = new System.Drawing.Point(0, 0); - this.pnlRight.Name = "pnlRight"; - this.pnlRight.Size = new System.Drawing.Size(546, 800); - this.pnlRight.TabIndex = 0; - // - // txtParsedData - // - this.txtParsedData.BackColor = System.Drawing.Color.White; - this.txtParsedData.Dock = System.Windows.Forms.DockStyle.Fill; - this.txtParsedData.Font = new System.Drawing.Font("Consolas", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.txtParsedData.Location = new System.Drawing.Point(0, 60); - this.txtParsedData.Multiline = true; - this.txtParsedData.Name = "txtParsedData"; - this.txtParsedData.ReadOnly = true; - this.txtParsedData.ScrollBars = System.Windows.Forms.ScrollBars.Both; - this.txtParsedData.Size = new System.Drawing.Size(546, 740); - this.txtParsedData.TabIndex = 1; - this.txtParsedData.WordWrap = false; - // - // pnlRightTop - // - this.pnlRightTop.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(250)))), ((int)(((byte)(250)))), ((int)(((byte)(250))))); - this.pnlRightTop.Controls.Add(this.btnClose); - this.pnlRightTop.Controls.Add(this.lblParsedTitle); - this.pnlRightTop.Dock = System.Windows.Forms.DockStyle.Top; - this.pnlRightTop.Location = new System.Drawing.Point(0, 0); - this.pnlRightTop.Name = "pnlRightTop"; - this.pnlRightTop.Padding = new System.Windows.Forms.Padding(10); - this.pnlRightTop.Size = new System.Drawing.Size(546, 60); - this.pnlRightTop.TabIndex = 0; - // - // btnClose - // - this.btnClose.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); - this.btnClose.Font = new System.Drawing.Font("微软雅黑", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - this.btnClose.Location = new System.Drawing.Point(446, 15); - this.btnClose.Name = "btnClose"; - this.btnClose.Size = new System.Drawing.Size(80, 32); - this.btnClose.TabIndex = 1; - this.btnClose.Text = "关闭"; - this.btnClose.UseVisualStyleBackColor = true; - this.btnClose.Click += new System.EventHandler(this.btnClose_Click); - // - // lblParsedTitle - // - this.lblParsedTitle.AutoSize = true; - this.lblParsedTitle.Font = new System.Drawing.Font("微软雅黑", 10F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - this.lblParsedTitle.Location = new System.Drawing.Point(13, 20); - this.lblParsedTitle.Name = "lblParsedTitle"; - this.lblParsedTitle.Size = new System.Drawing.Size(112, 24); - this.lblParsedTitle.TabIndex = 0; - this.lblParsedTitle.Text = "报文数据解析"; - // - // CommunicationMonitorForm - // - this.AutoScaleDimensions = new System.Drawing.SizeF(8F, 15F); - this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - this.ClientSize = new System.Drawing.Size(1400, 800); - this.Controls.Add(this.splitContainer); - this.Name = "CommunicationMonitorForm"; - this.Text = "通讯监控"; - this.Load += new System.EventHandler(this.CommunicationMonitorForm_Load); - this.splitContainer.Panel1.ResumeLayout(false); - this.splitContainer.Panel2.ResumeLayout(false); - ((System.ComponentModel.ISupportInitialize)(this.splitContainer)).EndInit(); - this.splitContainer.ResumeLayout(false); - this.pnlLeft.ResumeLayout(false); - ((System.ComponentModel.ISupportInitialize)(this.dgvMessages)).EndInit(); - this.pnlLeftTop.ResumeLayout(false); - this.pnlLeftTop.PerformLayout(); - this.pnlRight.ResumeLayout(false); - this.pnlRight.PerformLayout(); - this.pnlRightTop.ResumeLayout(false); - this.pnlRightTop.PerformLayout(); - this.ResumeLayout(false); - - } - - #endregion - - private System.Windows.Forms.SplitContainer splitContainer; - private System.Windows.Forms.Panel pnlLeft; - private System.Windows.Forms.DataGridView dgvMessages; - private System.Windows.Forms.Panel pnlLeftTop; - private System.Windows.Forms.ComboBox cmbIpFilter; - private System.Windows.Forms.Label lblIpFilter; - private System.Windows.Forms.Panel pnlRight; - private System.Windows.Forms.TextBox txtParsedData; - private System.Windows.Forms.Panel pnlRightTop; - private System.Windows.Forms.Label lblParsedTitle; - private System.Windows.Forms.Label lblStatistics; - private System.Windows.Forms.Button btnRefresh; - private System.Windows.Forms.Button btnClear; - private System.Windows.Forms.Button btnClose; - private System.Windows.Forms.DataGridViewTextBoxColumn colTime; - private System.Windows.Forms.DataGridViewTextBoxColumn colDirection; - private System.Windows.Forms.DataGridViewTextBoxColumn colIpAddress; - private System.Windows.Forms.DataGridViewTextBoxColumn colPort; - private System.Windows.Forms.DataGridViewTextBoxColumn colLength; - private System.Windows.Forms.DataGridViewTextBoxColumn colRawData; - private System.Windows.Forms.DataGridViewTextBoxColumn colStationId; - private System.Windows.Forms.DataGridViewTextBoxColumn type; - private System.Windows.Forms.Button button1; - } -} - diff --git a/StandardScene.Core/Charge/CommunicationMonitorForm.cs b/StandardScene.Core/Charge/CommunicationMonitorForm.cs index bc9db7f..e9da876 100644 --- a/StandardScene.Core/Charge/CommunicationMonitorForm.cs +++ b/StandardScene.Core/Charge/CommunicationMonitorForm.cs @@ -2,108 +2,298 @@ using System; using System.Collections.Generic; using System.Drawing; using System.Linq; -using System.Windows.Forms; +using System.Text; +using CycleGUI; +using StandardScene.Utils; namespace StandardScene.Charge { /// - /// 通讯监控窗体 + /// 通讯监控面板(CycleGUI 版,替代原 WinForms 窗体)。 + /// + /// 单实例:再次打开则把已有面板置前。 + /// 订阅 ,批量刷新 UI(500ms 节流),最多显示 100 行。 + /// 支持 IP 筛选、暂停/继续、清空(二次确认)、选中报文解析详情。 + /// + /// 保留可实例化 + 以兼容既有调用。 /// - public partial class CommunicationMonitorForm : Form + public class CommunicationMonitorForm { - private readonly CommunicationMessageService messageService; - private bool isFormLoaded = false; - private bool isFormMessageStop = false; private const int MaxDisplayRows = 100; private const int UiBatchSize = 20; private const int StatsRefreshMs = 500; - private readonly Queue pendingMessages = new Queue(); - private readonly object pendingMessagesLock = new object(); - private readonly Timer uiFlushTimer; - private readonly Timer statsRefreshTimer; - private bool pendingStatsRefresh = false; - private int lastDisplayCountForStats = 0; - public CommunicationMonitorForm() - { - InitializeComponent(); - messageService = CommunicationMessageService.Instance; - uiFlushTimer = new Timer { Interval = 500 }; - uiFlushTimer.Tick += UiFlushTimer_Tick; - statsRefreshTimer = new Timer { Interval = StatsRefreshMs }; - statsRefreshTimer.Tick += StatsRefreshTimer_Tick; + private const string TableId = "comm-monitor-msgs"; - // 订阅窗体关闭事件 - this.FormClosing += CommunicationMonitorForm_FormClosing; + private static readonly Color SendRowColor = Color.FromArgb(232, 245, 233); + private static readonly Color ReceiveRowColor = Color.FromArgb(227, 242, 253); + private static readonly Color SelectedRowColor = Color.FromArgb(255, 249, 196); + + private static readonly CommunicationMessageService MessageService = CommunicationMessageService.Instance; + + private static Panel _panel; + private static bool _subscribed; + private static bool _paused; + private static int _selectedIpIndex; + private static string[] _ipOptions = { "全部" }; + private static int _selectedRowIndex = -1; + private static string _parsedText = ""; + private static string _statsText = ""; + + private static List _displayMessages = new List(); + private static readonly Queue PendingMessages = new Queue(); + private static readonly object PendingLock = new object(); + + private static DateTime _lastStatsRefresh = DateTime.MinValue; + private static bool _pendingStatsRefresh; + + /// 打开(或置前)通讯监控面板。兼容原 new CommunicationMonitorForm().Show() 调用方式。 + public void Show() => Open(); + + /// 打开(或置前)通讯监控面板。 + public static void Open() + { + if (_panel != null) + { + try + { + _panel.BringToFront(); + return; + } + catch + { + _panel = null; + } + } + + _paused = false; + _selectedRowIndex = -1; + _parsedText = ""; + RefreshIpFilter(); + ReloadFromService(); + + var panel = GUI.DeclarePanel() + .ShowTitle("通讯监控") + .SetDefaultDocking(Panel.Docking.None) + .InitSize(1400, 800) + .InitPos(false, 0, 0, 0.5f, 0.5f, 0.5f, 0.5f); + _panel = panel; + panel.IfTerminalQuit(() => + { + Unsubscribe(); + _panel = null; + }); + + Subscribe(); + + panel.Define(pb => + { + if (pb.Closing()) + { + Unsubscribe(); + panel.Exit(); + _panel = null; + return; + } + + FlushPendingBatch(); + + if (pb.DropdownBox("IP筛选", _ipOptions, ref _selectedIpIndex)) + ReloadFromService(); + + pb.SameLine(16); + if (pb.Button(_paused ? "继续" : "暂停", distinct: "comm-pause")) + _paused = !_paused; + pb.SameLine(8); + if (pb.Button("刷新", distinct: "comm-refresh")) + { + RefreshIpFilter(); + ReloadFromService(); + RequestStatisticsRefresh(); + } + pb.SameLine(8); + if (pb.Button("清空", distinct: "comm-clear")) + { + CycleUiHelper.ConfirmThen("确定要清空所有报文记录吗?", () => + { + MessageService.Clear(); + lock (PendingLock) + PendingMessages.Clear(); + RefreshIpFilter(); + _displayMessages.Clear(); + _selectedRowIndex = -1; + _parsedText = ""; + RequestStatisticsRefresh(); + }); + } + pb.SameLine(8); + if (pb.Button("关闭", distinct: "comm-close")) + { + Unsubscribe(); + panel.Exit(); + _panel = null; + return; + } + + MaybeRefreshStatistics(); + pb.Label(_statsText); + + pb.Table(TableId, + new[] { "时间", "方向", "IP地址", "端口", "长度", "原始数据", "站点", "类型", "操作" }, + _displayMessages.Count, (row, i) => + { + var msg = _displayMessages[i]; + row.SetColor(_selectedRowIndex == i + ? SelectedRowColor + : msg.Direction == MessageDirection.Send ? SendRowColor : ReceiveRowColor); + + row.Label($"{msg.Timestamp:HH:mm:ss.fff}"); + row.Label(msg.Direction == MessageDirection.Send ? "发送" : "接收"); + row.Label(msg.IpAddress ?? ""); + row.Label($"{msg.Port}"); + row.Label($"{msg.Length}"); + row.Label(TruncateRawData(msg.RawData)); + row.Label(string.IsNullOrEmpty(msg.StationId) ? "-" : msg.StationId); + row.Label(msg.Type ?? ""); + + if (row.ButtonGroup(new[] { "解析" }, new[] { "解析该报文" }) == 0) + { + _selectedRowIndex = i; + _parsedText = BuildParsedText(msg); + } + }, height: 20, enableSearch: true); + + pb.SeparatorText("报文解析"); + pb.SelectableText(null, _parsedText ?? "", copyButton: true); + + pb.Panel.Repaint(repaintTimeMs: 500); + }); } - private void CommunicationMonitorForm_Load(object sender, EventArgs e) + private static void Subscribe() + { + if (_subscribed) + return; + MessageService.MessageAdded += OnMessageAdded; + _subscribed = true; + } + + private static void Unsubscribe() + { + if (!_subscribed) + return; + MessageService.MessageAdded -= OnMessageAdded; + _subscribed = false; + lock (PendingLock) + PendingMessages.Clear(); + } + + private static void OnMessageAdded(object sender, CommunicationMessage message) + { + if (!_subscribed || message == null) + return; + + lock (PendingLock) + PendingMessages.Enqueue(message); + + _panel?.Repaint(); + } + + /// 定时批量刷新 UI,避免每条报文都抢占渲染线程。 + private static void FlushPendingBatch() + { + if (_paused) + return; + + List batch = null; + lock (PendingLock) + { + if (PendingMessages.Count == 0) + return; + + int count = Math.Min(UiBatchSize, PendingMessages.Count); + batch = new List(count); + for (int i = 0; i < count; i++) + batch.Add(PendingMessages.Dequeue()); + } + + if (batch == null || batch.Count == 0) + return; + + var filter = SelectedIpFilter(); + bool displayChanged = false; + + foreach (var message in batch) + { + EnsureIpInFilter(message.IpAddress); + if (string.IsNullOrEmpty(filter) || filter == "全部" || filter == message.IpAddress) + { + InsertMessageAtTop(message); + displayChanged = true; + } + } + + if (displayChanged) + RequestStatisticsRefresh(); + } + + private static void InsertMessageAtTop(CommunicationMessage msg) + { + _displayMessages.Insert(0, msg); + while (_displayMessages.Count > MaxDisplayRows) + _displayMessages.RemoveAt(_displayMessages.Count - 1); + + if (_selectedRowIndex >= 0) + _selectedRowIndex++; + } + + private static void ReloadFromService() { try { - InitializeForm(); - LoadMessages(); + var filter = SelectedIpFilter(); + var messages = string.IsNullOrEmpty(filter) || filter == "全部" + ? MessageService.GetAllMessages() + : MessageService.GetMessagesByIp(filter); - // 标记窗体已加载完成 - isFormLoaded = true; - uiFlushTimer.Start(); - statsRefreshTimer.Start(); - - // 在窗体加载完成后再订阅报文添加事件(避免在初始化期间触发) - messageService.MessageAdded += OnMessageAdded; + _displayMessages = messages.Take(MaxDisplayRows).ToList(); + _selectedRowIndex = -1; + _parsedText = ""; + _pendingStatsRefresh = false; + UpdateStatistics(_displayMessages.Count); } catch (Exception ex) { - MessageBox.Show($"窗体加载失败: {ex.Message}\r\n{ex.StackTrace}", "错误", - MessageBoxButtons.OK, MessageBoxIcon.Error); + _statsText = $"加载报文失败: {ex.Message}"; } } - private void InitializeForm() - { - this.Text = "通讯监控"; - this.Size = new Size(1400, 800); - this.StartPosition = FormStartPosition.CenterScreen; - this.MinimumSize = new Size(1200, 600); - - // 初始化IP筛选下拉框 - RefreshIpFilter(); - } - - /// - /// 刷新IP筛选下拉框 - /// - private void RefreshIpFilter() + private static void RefreshIpFilter() { try { - if (cmbIpFilter == null || messageService == null) - return; + var selectedIp = SelectedIpFilter(); + var options = new List { "全部" }; - var selectedIp = cmbIpFilter.SelectedItem?.ToString(); - - cmbIpFilter.Items.Clear(); - cmbIpFilter.Items.Add("全部"); - - var ipAddresses = messageService.GetUniqueIpAddresses(); + var ipAddresses = MessageService.GetUniqueIpAddresses(); if (ipAddresses != null) { foreach (var ip in ipAddresses) { if (!string.IsNullOrEmpty(ip)) - { - cmbIpFilter.Items.Add(ip); - } + options.Add(ip); } } - // 恢复选中项 - if (!string.IsNullOrEmpty(selectedIp) && cmbIpFilter.Items.Contains(selectedIp)) + _ipOptions = options.ToArray(); + + if (!string.IsNullOrEmpty(selectedIp)) { - cmbIpFilter.SelectedItem = selectedIp; + var idx = Array.IndexOf(_ipOptions, selectedIp); + _selectedIpIndex = idx >= 0 ? idx : 0; } - else if (cmbIpFilter.Items.Count > 0) + else { - cmbIpFilter.SelectedIndex = 0; + _selectedIpIndex = 0; } } catch (Exception ex) @@ -112,248 +302,83 @@ namespace StandardScene.Charge } } - /// - /// 加载报文列表 - /// - private void LoadMessages() + private static void EnsureIpInFilter(string ipAddress) { - var layoutSuspended = false; - try - { - if (dgvMessages == null|| isFormMessageStop) - return; - - var selectedIp = cmbIpFilter?.SelectedItem?.ToString(); - var messages = string.IsNullOrEmpty(selectedIp) || selectedIp == "全部" - ? messageService.GetAllMessages() - : messageService.GetMessagesByIp(selectedIp); - - dgvMessages.SuspendLayout(); - layoutSuspended = true; - dgvMessages.Rows.Clear(); - - foreach (var msg in messages) - { - AddMessageRow(msg, false); - } - - UpdateStatistics(messages.Count); - pendingStatsRefresh = false; - } - catch (Exception ex) - { - MessageBox.Show($"加载报文失败: {ex.Message}", "错误", - MessageBoxButtons.OK, MessageBoxIcon.Error); - } - finally - { - if (layoutSuspended && dgvMessages != null) - { - dgvMessages.ResumeLayout(); - } - } - } - - /// - /// 定时批量刷新UI,避免每条报文都抢占UI线程 - /// - private void UiFlushTimer_Tick(object sender, EventArgs e) - { - if (!isFormLoaded || isFormMessageStop) + if (string.IsNullOrWhiteSpace(ipAddress)) return; - List batch = null; - lock (pendingMessagesLock) - { - if (pendingMessages.Count == 0) - return; - - int count = Math.Min(UiBatchSize, pendingMessages.Count); - batch = new List(count); - for (int i = 0; i < count; i++) - { - batch.Add(pendingMessages.Dequeue()); - } - } - - if (batch == null || batch.Count == 0) + if (_ipOptions.Contains(ipAddress)) return; - dgvMessages.SuspendLayout(); - try - { - var selectedIp = cmbIpFilter?.SelectedItem?.ToString(); - bool displayChanged = false; - - foreach (var message in batch) - { - EnsureIpInFilter(message.IpAddress); - if (string.IsNullOrEmpty(selectedIp) || selectedIp == "全部" || selectedIp == message.IpAddress) - { - AddMessageRow(message, true); - displayChanged = true; - } - } - - if (displayChanged) - { - RequestStatisticsRefresh(dgvMessages.Rows.Count); - } - } - finally - { - dgvMessages.ResumeLayout(); - } + var list = _ipOptions.ToList(); + list.Add(ipAddress); + _ipOptions = list.ToArray(); } - /// - /// 统计信息低频刷新(500ms) - /// - private void StatsRefreshTimer_Tick(object sender, EventArgs e) + private static string SelectedIpFilter() { - if (!isFormLoaded || isFormMessageStop || !pendingStatsRefresh) + if (_ipOptions == null || _ipOptions.Length == 0) + return "全部"; + if (_selectedIpIndex < 0 || _selectedIpIndex >= _ipOptions.Length) + return "全部"; + return _ipOptions[_selectedIpIndex]; + } + + private static void RequestStatisticsRefresh() + { + _pendingStatsRefresh = true; + } + + /// 统计信息低频刷新(500ms)。 + private static void MaybeRefreshStatistics() + { + if (!_pendingStatsRefresh) + return; + if (DateTime.Now - _lastStatsRefresh < TimeSpan.FromMilliseconds(StatsRefreshMs)) return; - pendingStatsRefresh = false; - UpdateStatistics(lastDisplayCountForStats); + _pendingStatsRefresh = false; + _lastStatsRefresh = DateTime.Now; + UpdateStatistics(_displayMessages.Count); } - private void RequestStatisticsRefresh(int displayCount) - { - lastDisplayCountForStats = displayCount; - pendingStatsRefresh = true; - } - - private void EnsureIpInFilter(string ipAddress) - { - if (cmbIpFilter == null || string.IsNullOrWhiteSpace(ipAddress)) - return; - - if (!cmbIpFilter.Items.Contains(ipAddress)) - { - cmbIpFilter.Items.Add(ipAddress); - } - } - - /// - /// 向表格新增一条报文行(支持头部插入) - /// - private void AddMessageRow(CommunicationMessage msg, bool insertAtTop = true) - { - if (msg == null || dgvMessages == null) - return; - - DataGridViewRow row; - if (insertAtTop) - { - dgvMessages.Rows.Insert(0, - msg.Timestamp.ToString("HH:mm:ss.fff"), - msg.Direction == MessageDirection.Send ? "发送" : "接收", - msg.IpAddress, - msg.Port, - msg.Length, - msg.RawData, - msg.StationId ?? "-", - msg.Type); - row = dgvMessages.Rows[0]; - } - else - { - var index = dgvMessages.Rows.Add( - msg.Timestamp.ToString("HH:mm:ss.fff"), - msg.Direction == MessageDirection.Send ? "发送" : "接收", - msg.IpAddress, - msg.Port, - msg.Length, - msg.RawData, - msg.StationId ?? "-", - msg.Type); - row = dgvMessages.Rows[index]; - } - - if (msg.Direction == MessageDirection.Send) - { - row.DefaultCellStyle.BackColor = Color.FromArgb(232, 245, 233); - row.DefaultCellStyle.ForeColor = Color.FromArgb(46, 125, 50); - } - else - { - row.DefaultCellStyle.BackColor = Color.FromArgb(227, 242, 253); - row.DefaultCellStyle.ForeColor = Color.FromArgb(13, 71, 161); - } - - while (dgvMessages.Rows.Count > MaxDisplayRows) - { - dgvMessages.Rows.RemoveAt(dgvMessages.Rows.Count - 1); - } - } - - /// - /// 更新统计信息 - /// - private void UpdateStatistics(int displayCount) + private static void UpdateStatistics(int displayCount) { try { - if (lblStatistics == null || messageService == null) - return; - - var allMessages = messageService.GetAllMessages(); + var allMessages = MessageService.GetAllMessages(); if (allMessages == null) + { + _statsText = "统计信息加载失败"; return; + } var sendCount = allMessages.Count(m => m.Direction == MessageDirection.Send); var receiveCount = allMessages.Count(m => m.Direction == MessageDirection.Receive); - - lblStatistics.Text = $"显示: {displayCount} | 总数: {allMessages.Count} | 发送: {sendCount} | 接收: {receiveCount}"; + _statsText = $"显示: {displayCount} | 总数: {allMessages.Count} | 发送: {sendCount} | 接收: {receiveCount}"; } catch (Exception ex) { System.Diagnostics.Debug.WriteLine($"更新统计信息失败: {ex.Message}"); - if (lblStatistics != null) - { - lblStatistics.Text = "统计信息加载失败"; - } + _statsText = "统计信息加载失败"; } } - /// - /// 新报文添加事件处理(线程安全) - /// - private void OnMessageAdded(object sender, CommunicationMessage message) + private static string TruncateRawData(string rawData, int maxLen = 48) { - // 如果窗体还未加载完成,忽略此事件 - if (!isFormLoaded || isFormMessageStop) - return; - - try - { - if (message == null) - { - return; - } - lock (pendingMessagesLock) - { - pendingMessages.Enqueue(message); - } - } - catch (Exception ex) - { - System.Diagnostics.Debug.WriteLine($"处理新报文失败: {ex.Message}"); - } + if (string.IsNullOrEmpty(rawData)) + return ""; + return rawData.Length <= maxLen ? rawData : rawData.Substring(0, maxLen) + "…"; } - /// - /// 解析报文数据 - /// - private void ParseMessage(CommunicationMessage message) + private static string BuildParsedText(CommunicationMessage message) { - if (message == null || txtParsedData == null) - return; + if (message == null) + return ""; try { - var parsed = new System.Text.StringBuilder(); + var parsed = new StringBuilder(); parsed.AppendLine("=== 报文解析 ==="); parsed.AppendLine($"时间: {message.Timestamp:yyyy-MM-dd HH:mm:ss.fff}"); parsed.AppendLine($"方向: {(message.Direction == MessageDirection.Send ? "发送" : "接收")}"); @@ -363,199 +388,43 @@ namespace StandardScene.Charge parsed.AppendLine(); parsed.AppendLine("=== 原始数据 (HEX) ==="); parsed.AppendLine(message.RawData); - // parsed.AppendLine(FormatHexString(message.RawData)); parsed.AppendLine(); parsed.AppendLine("=== 数据解析 ==="); - // TODO: 根据实际协议进行解析 - parsed.AppendLine(); - if (message.Direction== MessageDirection.Send) + if (message.Direction == MessageDirection.Send) { - var sendDate = messageService.ParseSendRawData(message.RawData, message.Type); + var sendData = MessageService.ParseSendRawData(message.RawData, message.Type); parsed.AppendLine("示例解析:"); - parsed.AppendLine($"充电指令:{sendDate.ChargeCommand}"); - parsed.AppendLine($"发送电压:{sendDate.SetVoltage}"); - parsed.AppendLine($"发送电流:{sendDate.SetCurrent}"); - parsed.AppendLine($"车辆ID:{sendDate.CurrentVehicleId}"); - parsed.AppendLine($"车辆电量:{sendDate.BatteryLevel}"); - parsed.AppendLine($"车辆电压:{sendDate.CarVoltage}"); - parsed.AppendLine($"车辆电流:{sendDate.CarCurrent}"); - + parsed.AppendLine($"充电指令:{sendData.ChargeCommand}"); + parsed.AppendLine($"发送电压:{sendData.SetVoltage}"); + parsed.AppendLine($"发送电流:{sendData.SetCurrent}"); + parsed.AppendLine($"车辆ID:{sendData.CurrentVehicleId}"); + parsed.AppendLine($"车辆电量:{sendData.BatteryLevel}"); + parsed.AppendLine($"车辆电压:{sendData.CarVoltage}"); + parsed.AppendLine($"车辆电流:{sendData.CarCurrent}"); } else { - - var recDate = messageService.ParseReceiveRawData(message.RawData, message.Type); - string mechanismStatus = (int)recDate.MechanismStatus == 1 ? "伸出" : (int)recDate.MechanismStatus == 2 ? "缩回" : (int)recDate.MechanismStatus == 3 ? "运动中" : recDate.MechanismStatus.ToString(); + var recData = MessageService.ParseReceiveRawData(message.RawData, message.Type); + string mechanismStatus = (int)recData.MechanismStatus == 1 ? "伸出" + : (int)recData.MechanismStatus == 2 ? "缩回" + : (int)recData.MechanismStatus == 3 ? "运动中" + : recData.MechanismStatus.ToString(); parsed.AppendLine("示例解析:"); parsed.AppendLine($"机构状态:{mechanismStatus}"); - parsed.AppendLine($"实时电压:{recDate.RealTimeVoltage}"); - parsed.AppendLine($"实时电流:{recDate.RealTimeCurrent}"); - parsed.AppendLine($"充电量: {recDate.BatteryAH}"); - parsed.AppendLine($"是否报警:{recDate.HasAlarm}"); - parsed.AppendLine($"充电状态:{recDate.Status.ToString()}"); - + parsed.AppendLine($"实时电压:{recData.RealTimeVoltage}"); + parsed.AppendLine($"实时电流:{recData.RealTimeCurrent}"); + parsed.AppendLine($"充电量: {recData.BatteryAH}"); + parsed.AppendLine($"是否报警:{recData.HasAlarm}"); + parsed.AppendLine($"充电状态:{recData.Status}"); } - - - txtParsedData.Text = parsed.ToString(); + return parsed.ToString(); } catch (Exception ex) { - txtParsedData.Text = $"解析失败: {ex.Message}"; - } - } - - /// - /// 格式化十六进制字符串 - /// - private string FormatHexString(string hexData) - { - if (string.IsNullOrEmpty(hexData)) - return string.Empty; - - var formatted = new System.Text.StringBuilder(); - for (int i = 0; i < hexData.Length; i += 2) - { - if (i > 0 && i % 32 == 0) - formatted.AppendLine(); - else if (i > 0) - formatted.Append(" "); - - if (i + 1 < hexData.Length) - formatted.Append(hexData.Substring(i, 2)); - else - formatted.Append(hexData[i]); - } - return formatted.ToString(); - } - - // ==================== 事件处理 ==================== - - private void cmbIpFilter_SelectedIndexChanged(object sender, EventArgs e) - { - try - { - LoadMessages(); - } - catch (Exception ex) - { - System.Diagnostics.Debug.WriteLine($"筛选改变失败: {ex.Message}"); - } - } - - private void dgvMessages_SelectionChanged(object sender, EventArgs e) - { - try - { - if (dgvMessages.SelectedRows.Count > 0) - { - var row = dgvMessages.SelectedRows[0]; - var rawData = row.Cells[5].Value?.ToString(); - var ipAddress = row.Cells[2].Value?.ToString(); - var port = int.Parse(row.Cells[3].Value?.ToString() ?? "0"); - var timeStr = row.Cells[0].Value?.ToString(); - var directionStr = row.Cells[1].Value?.ToString(); - var stationId = row.Cells[6].Value?.ToString(); - var type = row.Cells[7].Value?.ToString(); - - // 构造消息对象用于解析 - var message = new CommunicationMessage - { - RawData = rawData, - IpAddress = ipAddress, - Port = port, - Direction = directionStr == "发送" ? MessageDirection.Send : MessageDirection.Receive, - StationId = stationId == "-" ? null : stationId, - Length = rawData.Split(' ')?.Length ?? 0, - Type=type, - - }; - - if (DateTime.TryParse(timeStr, out DateTime timestamp)) - { - message.Timestamp = timestamp; - } - - ParseMessage(message); - } - } - catch (Exception ex) - { - System.Diagnostics.Debug.WriteLine($"选择报文失败: {ex.Message}"); - } - } - - private void btnRefresh_Click(object sender, EventArgs e) - { - try - { - RefreshIpFilter(); - LoadMessages(); - RequestStatisticsRefresh(dgvMessages?.Rows.Count ?? 0); - } - catch (Exception ex) - { - MessageBox.Show($"刷新失败: {ex.Message}", "错误", - MessageBoxButtons.OK, MessageBoxIcon.Error); - } - } - - private void btnClear_Click(object sender, EventArgs e) - { - try - { - var result = MessageBox.Show( - "确定要清空所有报文记录吗?", - "确认清空", - MessageBoxButtons.YesNo, - MessageBoxIcon.Question); - - if (result == DialogResult.Yes) - { - messageService.Clear(); - RefreshIpFilter(); - LoadMessages(); - if (txtParsedData != null) - { - txtParsedData.Clear(); - } - dgvMessages.Rows.Clear(); - RequestStatisticsRefresh(0); - } - - } - catch (Exception ex) - { - MessageBox.Show($"清空报文失败: {ex.Message}", "错误", - MessageBoxButtons.OK, MessageBoxIcon.Error); - } - } - - private void btnClose_Click(object sender, EventArgs e) - { - this.Close(); - } - - private void CommunicationMonitorForm_FormClosing(object sender, FormClosingEventArgs e) - { - // 取消订阅事件 - messageService.MessageAdded -= OnMessageAdded; - uiFlushTimer.Stop(); - uiFlushTimer.Dispose(); - statsRefreshTimer.Stop(); - statsRefreshTimer.Dispose(); - } - - private void button1_Click(object sender, EventArgs e) - { - isFormMessageStop = !isFormMessageStop; - if (sender is Button pauseButton) - { - pauseButton.Text = isFormMessageStop ? "继续" : "暂停"; + return $"解析失败: {ex.Message}"; } } } } - diff --git a/StandardScene.Core/Charge/CommunicationMonitorForm.resx b/StandardScene.Core/Charge/CommunicationMonitorForm.resx deleted file mode 100644 index a41e003..0000000 --- a/StandardScene.Core/Charge/CommunicationMonitorForm.resx +++ /dev/null @@ -1,123 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - True - - \ No newline at end of file diff --git a/StandardScene.Core/Charge/StandardChargeMission.cs b/StandardScene.Core/Charge/StandardChargeMission.cs index 22d8820..f786717 100644 --- a/StandardScene.Core/Charge/StandardChargeMission.cs +++ b/StandardScene.Core/Charge/StandardChargeMission.cs @@ -5,7 +5,7 @@ using System.Net; using System.Text; using System.Threading; using System.Threading.Tasks; -using System.Windows.Forms; +using StandardScene.Utils; using CommonUsage; using LessokajiWeaverUtilities.Utilities; using Newtonsoft.Json; @@ -278,7 +278,7 @@ namespace StandardScene.Charge // 防止重复启动 if (myStarted) { - MessageBox.Show("充电进程已启动,不可重复启动"); + CycleUiHelper.Alert("提示", "充电进程已启动,不可重复启动"); return; } status.status = "已启动"; diff --git a/StandardScene.Core/Commons.cs b/StandardScene.Core/Commons.cs index ebb43a4..33b51fe 100644 --- a/StandardScene.Core/Commons.cs +++ b/StandardScene.Core/Commons.cs @@ -7,7 +7,6 @@ using System.Runtime.InteropServices; using System.Text; using System.Threading; using System.Threading.Tasks; -using System.Windows.Forms; using SimpleLite.RCS; using SimpleLite.RCS.CarTypes; using SimpleLite.CADTools; @@ -20,6 +19,7 @@ using SimpleCore.PropType; using SimpleCore.Traffic; using StandardScene.InterLock; using StandardScene.Model; +using StandardScene.Utils; using static StandardScene.Chained.ChainedDeliveryMission; namespace StandardScene @@ -39,7 +39,7 @@ namespace StandardScene var cars = string.Join(",", loopingCar.Select(p => $"{p.id}")); var msg = $"小车:{cars}间发生死锁" + $"请及时人工介入处理!!!!"; - MessageBox.Show(msg); + CycleUiHelper.Alert("死锁提醒", msg); } catch { } }; diff --git a/StandardScene.Core/Docs/Charge/完整文件清单.md b/StandardScene.Core/Docs/Charge/完整文件清单.md index 0f1204f..f611f8b 100644 --- a/StandardScene.Core/Docs/Charge/完整文件清单.md +++ b/StandardScene.Core/Docs/Charge/完整文件清单.md @@ -417,8 +417,8 @@ ChargeStationManagementExample.InitializeTestData(); ## 📊 系统要求 ### 软件要求 -- .NET Framework 4.5 或更高版本 -- Windows Forms +- .NET 8(net8.0-windows),宿主 `SimpleLite.exe` +- Windows Forms(充电模块界面尚未迁移到 CycleGUI,过渡期仍依赖) - Newtonsoft.Json(NuGet) ### 硬件要求 diff --git a/StandardScene.Core/ExtendDevice/ButtonBox/ButtonBoxManager.Designer.cs b/StandardScene.Core/ExtendDevice/ButtonBox/ButtonBoxManager.Designer.cs deleted file mode 100644 index bf7a199..0000000 --- a/StandardScene.Core/ExtendDevice/ButtonBox/ButtonBoxManager.Designer.cs +++ /dev/null @@ -1,608 +0,0 @@ -namespace StandardScene.ExtendDevice.ButtonBox -{ - partial class ButtonBoxManager - { - /// - /// 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.buttonBoxListView = new System.Windows.Forms.ListView(); - this.columnHeaderBoxIndex = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); - this.columnHeaderIp = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); - this.columnHeaderPort = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); - this.columnHeaderType = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); - this.groupBoxButtonBox = new System.Windows.Forms.GroupBox(); - this.btnSaveButtonBox = new System.Windows.Forms.Button(); - this.btnDeleteButtonBox = new System.Windows.Forms.Button(); - this.btnAddButtonBox = new System.Windows.Forms.Button(); - this.labelType = new System.Windows.Forms.Label(); - this.comboBoxType = new System.Windows.Forms.ComboBox(); - this.labelBoxIndex = new System.Windows.Forms.Label(); - this.textBoxBoxIndex = new System.Windows.Forms.TextBox(); - this.labelPort = new System.Windows.Forms.Label(); - this.textBoxPort = new System.Windows.Forms.TextBox(); - this.labelIp = new System.Windows.Forms.Label(); - this.textBoxIp = new System.Windows.Forms.TextBox(); - this.buttonListView = new System.Windows.Forms.ListView(); - this.columnHeaderButtonIndex = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); - this.columnHeaderTriggerMission = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); - this.columnHeaderTriggerMethod = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); - this.columnHeaderTriggerMethodParams = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); - this.columnHeaderTriggerState = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); - this.columnHeaderTriggerDelay = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); - this.groupBoxButton = new System.Windows.Forms.GroupBox(); - this.btnSaveButton = new System.Windows.Forms.Button(); - this.btnDeleteButton = new System.Windows.Forms.Button(); - this.btnAddButton = new System.Windows.Forms.Button(); - this.labelTriggerMethodParams = new System.Windows.Forms.Label(); - this.textBoxTriggerMethodParams = new System.Windows.Forms.TextBox(); - this.labelTriggerMethod = new System.Windows.Forms.Label(); - this.textBoxTriggerMethod = new System.Windows.Forms.TextBox(); - this.labelTriggerMission = new System.Windows.Forms.Label(); - this.textBoxTriggerMission = new System.Windows.Forms.TextBox(); - this.labelButtonIndex = new System.Windows.Forms.Label(); - this.textBoxButtonIndex = new System.Windows.Forms.TextBox(); - this.labelTriggerState = new System.Windows.Forms.Label(); - this.comboBoxTriggerState = new System.Windows.Forms.ComboBox(); - this.labelTriggerDelay = new System.Windows.Forms.Label(); - this.textBoxTriggerDelay = new System.Windows.Forms.TextBox(); - this.labelTitle = new System.Windows.Forms.Label(); - this.groupBoxButtonBox.SuspendLayout(); - this.groupBoxButton.SuspendLayout(); - this.SuspendLayout(); - // - // buttonBoxListView - // - this.buttonBoxListView.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) - | System.Windows.Forms.AnchorStyles.Left))); - this.buttonBoxListView.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; - this.buttonBoxListView.Columns.AddRange(new System.Windows.Forms.ColumnHeader[] { - this.columnHeaderBoxIndex, - this.columnHeaderIp, - this.columnHeaderPort, - this.columnHeaderType}); - this.buttonBoxListView.Font = new System.Drawing.Font("微软雅黑", 10.5F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - this.buttonBoxListView.FullRowSelect = true; - this.buttonBoxListView.GridLines = true; - this.buttonBoxListView.HeaderStyle = System.Windows.Forms.ColumnHeaderStyle.Nonclickable; - this.buttonBoxListView.HideSelection = false; - this.buttonBoxListView.Location = new System.Drawing.Point(15, 55); - this.buttonBoxListView.MultiSelect = false; - this.buttonBoxListView.Name = "buttonBoxListView"; - this.buttonBoxListView.OwnerDraw = true; - this.buttonBoxListView.Size = new System.Drawing.Size(450, 290); - this.buttonBoxListView.TabIndex = 0; - this.buttonBoxListView.UseCompatibleStateImageBehavior = false; - this.buttonBoxListView.View = System.Windows.Forms.View.Details; - this.buttonBoxListView.SelectedIndexChanged += new System.EventHandler(this.buttonBoxListView_SelectedIndexChanged); - // - // columnHeaderBoxIndex - // - this.columnHeaderBoxIndex.Text = "编码"; - this.columnHeaderBoxIndex.Width = 70; - // - // columnHeaderIp - // - this.columnHeaderIp.Text = "IP地址"; - this.columnHeaderIp.Width = 130; - // - // columnHeaderPort - // - this.columnHeaderPort.Text = "端口"; - this.columnHeaderPort.Width = 90; - // - // columnHeaderType - // - this.columnHeaderType.Text = "类型"; - this.columnHeaderType.Width = 140; - // - // groupBoxButtonBox - // - this.groupBoxButtonBox.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); - this.groupBoxButtonBox.Controls.Add(this.btnSaveButtonBox); - this.groupBoxButtonBox.Controls.Add(this.btnDeleteButtonBox); - this.groupBoxButtonBox.Controls.Add(this.btnAddButtonBox); - this.groupBoxButtonBox.Controls.Add(this.labelType); - this.groupBoxButtonBox.Controls.Add(this.comboBoxType); - this.groupBoxButtonBox.Controls.Add(this.labelBoxIndex); - this.groupBoxButtonBox.Controls.Add(this.textBoxBoxIndex); - this.groupBoxButtonBox.Controls.Add(this.labelPort); - this.groupBoxButtonBox.Controls.Add(this.textBoxPort); - this.groupBoxButtonBox.Controls.Add(this.labelIp); - this.groupBoxButtonBox.Controls.Add(this.textBoxIp); - this.groupBoxButtonBox.Font = new System.Drawing.Font("微软雅黑", 10.5F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - this.groupBoxButtonBox.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(68)))), ((int)(((byte)(68)))), ((int)(((byte)(68))))); - this.groupBoxButtonBox.Location = new System.Drawing.Point(15, 360); - this.groupBoxButtonBox.Name = "groupBoxButtonBox"; - this.groupBoxButtonBox.Padding = new System.Windows.Forms.Padding(12, 10, 12, 12); - this.groupBoxButtonBox.Size = new System.Drawing.Size(450, 250); - this.groupBoxButtonBox.TabIndex = 1; - this.groupBoxButtonBox.TabStop = false; - this.groupBoxButtonBox.Text = "按钮盒信息"; - // - // btnSaveButtonBox - // - this.btnSaveButtonBox.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(0)))), ((int)(((byte)(122)))), ((int)(((byte)(204))))); - this.btnSaveButtonBox.FlatAppearance.BorderSize = 0; - this.btnSaveButtonBox.FlatAppearance.MouseDownBackColor = System.Drawing.Color.FromArgb(((int)(((byte)(0)))), ((int)(((byte)(92)))), ((int)(((byte)(153))))); - this.btnSaveButtonBox.FlatAppearance.MouseOverBackColor = System.Drawing.Color.FromArgb(((int)(((byte)(0)))), ((int)(((byte)(102)))), ((int)(((byte)(170))))); - this.btnSaveButtonBox.FlatStyle = System.Windows.Forms.FlatStyle.Flat; - this.btnSaveButtonBox.Font = new System.Drawing.Font("微软雅黑", 10.5F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - this.btnSaveButtonBox.ForeColor = System.Drawing.Color.White; - this.btnSaveButtonBox.Location = new System.Drawing.Point(330, 200); - this.btnSaveButtonBox.Name = "btnSaveButtonBox"; - this.btnSaveButtonBox.Size = new System.Drawing.Size(100, 38); - this.btnSaveButtonBox.TabIndex = 10; - this.btnSaveButtonBox.Text = "保存"; - this.btnSaveButtonBox.UseVisualStyleBackColor = false; - this.btnSaveButtonBox.Click += new System.EventHandler(this.btnSaveButtonBox_Click); - // - // btnDeleteButtonBox - // - this.btnDeleteButtonBox.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(220)))), ((int)(((byte)(53)))), ((int)(((byte)(69))))); - this.btnDeleteButtonBox.FlatAppearance.BorderSize = 0; - this.btnDeleteButtonBox.FlatAppearance.MouseDownBackColor = System.Drawing.Color.FromArgb(((int)(((byte)(165)))), ((int)(((byte)(40)))), ((int)(((byte)(52))))); - this.btnDeleteButtonBox.FlatAppearance.MouseOverBackColor = System.Drawing.Color.FromArgb(((int)(((byte)(187)))), ((int)(((byte)(45)))), ((int)(((byte)(59))))); - this.btnDeleteButtonBox.FlatStyle = System.Windows.Forms.FlatStyle.Flat; - this.btnDeleteButtonBox.Font = new System.Drawing.Font("微软雅黑", 10.5F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - this.btnDeleteButtonBox.ForeColor = System.Drawing.Color.White; - this.btnDeleteButtonBox.Location = new System.Drawing.Point(220, 200); - this.btnDeleteButtonBox.Name = "btnDeleteButtonBox"; - this.btnDeleteButtonBox.Size = new System.Drawing.Size(100, 38); - this.btnDeleteButtonBox.TabIndex = 9; - this.btnDeleteButtonBox.Text = "删除"; - this.btnDeleteButtonBox.UseVisualStyleBackColor = false; - this.btnDeleteButtonBox.Click += new System.EventHandler(this.btnDeleteButtonBox_Click); - // - // btnAddButtonBox - // - this.btnAddButtonBox.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(40)))), ((int)(((byte)(167)))), ((int)(((byte)(69))))); - this.btnAddButtonBox.FlatAppearance.BorderSize = 0; - this.btnAddButtonBox.FlatAppearance.MouseDownBackColor = System.Drawing.Color.FromArgb(((int)(((byte)(30)))), ((int)(((byte)(125)))), ((int)(((byte)(52))))); - this.btnAddButtonBox.FlatAppearance.MouseOverBackColor = System.Drawing.Color.FromArgb(((int)(((byte)(33)))), ((int)(((byte)(136)))), ((int)(((byte)(56))))); - this.btnAddButtonBox.FlatStyle = System.Windows.Forms.FlatStyle.Flat; - this.btnAddButtonBox.Font = new System.Drawing.Font("微软雅黑", 10.5F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - this.btnAddButtonBox.ForeColor = System.Drawing.Color.White; - this.btnAddButtonBox.Location = new System.Drawing.Point(110, 200); - this.btnAddButtonBox.Name = "btnAddButtonBox"; - this.btnAddButtonBox.Size = new System.Drawing.Size(100, 38); - this.btnAddButtonBox.TabIndex = 8; - this.btnAddButtonBox.Text = "添加"; - this.btnAddButtonBox.UseVisualStyleBackColor = false; - this.btnAddButtonBox.Click += new System.EventHandler(this.btnAddButtonBox_Click); - // - // labelType - // - this.labelType.AutoSize = true; - this.labelType.Font = new System.Drawing.Font("微软雅黑", 10.5F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - this.labelType.Location = new System.Drawing.Point(28, 168); - this.labelType.Name = "labelType"; - this.labelType.Size = new System.Drawing.Size(65, 24); - this.labelType.TabIndex = 7; - this.labelType.Text = "类型:"; - // - // comboBoxType - // - this.comboBoxType.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; - this.comboBoxType.Font = new System.Drawing.Font("微软雅黑", 10.5F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - this.comboBoxType.FormattingEnabled = true; - this.comboBoxType.Location = new System.Drawing.Point(110, 165); - this.comboBoxType.Name = "comboBoxType"; - this.comboBoxType.Size = new System.Drawing.Size(320, 32); - this.comboBoxType.TabIndex = 6; - // - // labelBoxIndex - // - this.labelBoxIndex.AutoSize = true; - this.labelBoxIndex.Font = new System.Drawing.Font("微软雅黑", 10.5F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - this.labelBoxIndex.Location = new System.Drawing.Point(28, 48); - this.labelBoxIndex.Name = "labelBoxIndex"; - this.labelBoxIndex.Size = new System.Drawing.Size(65, 24); - this.labelBoxIndex.TabIndex = 1; - this.labelBoxIndex.Text = "编码:"; - // - // textBoxBoxIndex - // - this.textBoxBoxIndex.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; - this.textBoxBoxIndex.Font = new System.Drawing.Font("微软雅黑", 10.5F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - this.textBoxBoxIndex.Location = new System.Drawing.Point(110, 45); - this.textBoxBoxIndex.Name = "textBoxBoxIndex"; - this.textBoxBoxIndex.Size = new System.Drawing.Size(320, 30); - this.textBoxBoxIndex.TabIndex = 0; - // - // labelIp - // - this.labelIp.AutoSize = true; - this.labelIp.Font = new System.Drawing.Font("微软雅黑", 10.5F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - this.labelIp.Location = new System.Drawing.Point(18, 88); - this.labelIp.Name = "labelIp"; - this.labelIp.Size = new System.Drawing.Size(85, 24); - this.labelIp.TabIndex = 3; - this.labelIp.Text = "IP地址:"; - // - // textBoxIp - // - this.textBoxIp.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; - this.textBoxIp.Font = new System.Drawing.Font("微软雅黑", 10.5F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - this.textBoxIp.Location = new System.Drawing.Point(110, 85); - this.textBoxIp.Name = "textBoxIp"; - this.textBoxIp.Size = new System.Drawing.Size(320, 30); - this.textBoxIp.TabIndex = 2; - this.textBoxIp.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(255)))), ((int)(((byte)(255)))), ((int)(((byte)(255))))); - // - // labelPort - // - this.labelPort.AutoSize = true; - this.labelPort.Font = new System.Drawing.Font("微软雅黑", 10.5F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - this.labelPort.Location = new System.Drawing.Point(28, 128); - this.labelPort.Name = "labelPort"; - this.labelPort.Size = new System.Drawing.Size(65, 24); - this.labelPort.TabIndex = 5; - this.labelPort.Text = "端口:"; - // - // textBoxPort - // - this.textBoxPort.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; - this.textBoxPort.Font = new System.Drawing.Font("微软雅黑", 10.5F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - this.textBoxPort.Location = new System.Drawing.Point(110, 125); - this.textBoxPort.Name = "textBoxPort"; - this.textBoxPort.Size = new System.Drawing.Size(320, 30); - this.textBoxPort.TabIndex = 4; - // - // buttonListView - // - this.buttonListView.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) - | System.Windows.Forms.AnchorStyles.Left))); - this.buttonListView.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; - this.buttonListView.Columns.AddRange(new System.Windows.Forms.ColumnHeader[] { - this.columnHeaderButtonIndex, - this.columnHeaderTriggerState, - this.columnHeaderTriggerDelay, - this.columnHeaderTriggerMission, - this.columnHeaderTriggerMethod, - this.columnHeaderTriggerMethodParams}); - this.buttonListView.Font = new System.Drawing.Font("微软雅黑", 10.5F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - this.buttonListView.FullRowSelect = true; - this.buttonListView.GridLines = true; - this.buttonListView.HeaderStyle = System.Windows.Forms.ColumnHeaderStyle.Nonclickable; - this.buttonListView.HideSelection = false; - this.buttonListView.Location = new System.Drawing.Point(483, 55); - this.buttonListView.MultiSelect = false; - this.buttonListView.Name = "buttonListView"; - this.buttonListView.OwnerDraw = true; - this.buttonListView.Size = new System.Drawing.Size(700, 290); - this.buttonListView.TabIndex = 2; - this.buttonListView.UseCompatibleStateImageBehavior = false; - this.buttonListView.View = System.Windows.Forms.View.Details; - this.buttonListView.SelectedIndexChanged += new System.EventHandler(this.buttonListView_SelectedIndexChanged); - // - // columnHeaderButtonIndex - // - this.columnHeaderButtonIndex.Text = "编码"; - this.columnHeaderButtonIndex.Width = 70; - // - // columnHeaderTriggerState - // - this.columnHeaderTriggerState.Text = "触发状态"; - this.columnHeaderTriggerState.Width = 100; - // - // columnHeaderTriggerDelay - // - this.columnHeaderTriggerDelay.Text = "触发延迟"; - this.columnHeaderTriggerDelay.Width = 90; - // - // columnHeaderTriggerMission - // - this.columnHeaderTriggerMission.Text = "触发任务"; - this.columnHeaderTriggerMission.Width = 140; - // - // columnHeaderTriggerMethod - // - this.columnHeaderTriggerMethod.Text = "触发方法"; - this.columnHeaderTriggerMethod.Width = 140; - // - // columnHeaderTriggerMethodParams - // - this.columnHeaderTriggerMethodParams.Text = "方法参数"; - this.columnHeaderTriggerMethodParams.Width = 160; - // - // groupBoxButton - // - this.groupBoxButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); - this.groupBoxButton.Controls.Add(this.btnSaveButton); - this.groupBoxButton.Controls.Add(this.btnDeleteButton); - this.groupBoxButton.Controls.Add(this.btnAddButton); - this.groupBoxButton.Controls.Add(this.labelTriggerMethodParams); - this.groupBoxButton.Controls.Add(this.textBoxTriggerMethodParams); - this.groupBoxButton.Controls.Add(this.labelTriggerMethod); - this.groupBoxButton.Controls.Add(this.textBoxTriggerMethod); - this.groupBoxButton.Controls.Add(this.labelTriggerMission); - this.groupBoxButton.Controls.Add(this.textBoxTriggerMission); - this.groupBoxButton.Controls.Add(this.labelButtonIndex); - this.groupBoxButton.Controls.Add(this.textBoxButtonIndex); - this.groupBoxButton.Controls.Add(this.labelTriggerState); - this.groupBoxButton.Controls.Add(this.comboBoxTriggerState); - this.groupBoxButton.Controls.Add(this.labelTriggerDelay); - this.groupBoxButton.Controls.Add(this.textBoxTriggerDelay); - this.groupBoxButton.Font = new System.Drawing.Font("微软雅黑", 10.5F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - this.groupBoxButton.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(68)))), ((int)(((byte)(68)))), ((int)(((byte)(68))))); - this.groupBoxButton.Location = new System.Drawing.Point(483, 360); - this.groupBoxButton.Name = "groupBoxButton"; - this.groupBoxButton.Padding = new System.Windows.Forms.Padding(12, 10, 12, 12); - this.groupBoxButton.Size = new System.Drawing.Size(700, 250); - this.groupBoxButton.TabIndex = 3; - this.groupBoxButton.TabStop = false; - this.groupBoxButton.Text = "按钮信息"; - // - // btnSaveButton - // - this.btnSaveButton.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(0)))), ((int)(((byte)(122)))), ((int)(((byte)(204))))); - this.btnSaveButton.FlatAppearance.BorderSize = 0; - this.btnSaveButton.FlatAppearance.MouseDownBackColor = System.Drawing.Color.FromArgb(((int)(((byte)(0)))), ((int)(((byte)(92)))), ((int)(((byte)(153))))); - this.btnSaveButton.FlatAppearance.MouseOverBackColor = System.Drawing.Color.FromArgb(((int)(((byte)(0)))), ((int)(((byte)(102)))), ((int)(((byte)(170))))); - this.btnSaveButton.FlatStyle = System.Windows.Forms.FlatStyle.Flat; - this.btnSaveButton.Font = new System.Drawing.Font("微软雅黑", 10.5F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - this.btnSaveButton.ForeColor = System.Drawing.Color.White; - this.btnSaveButton.Location = new System.Drawing.Point(580, 180); - this.btnSaveButton.Name = "btnSaveButton"; - this.btnSaveButton.Size = new System.Drawing.Size(100, 38); - this.btnSaveButton.TabIndex = 13; - this.btnSaveButton.Text = "保存"; - this.btnSaveButton.UseVisualStyleBackColor = false; - this.btnSaveButton.Click += new System.EventHandler(this.btnSaveButton_Click); - // - // btnDeleteButton - // - this.btnDeleteButton.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(220)))), ((int)(((byte)(53)))), ((int)(((byte)(69))))); - this.btnDeleteButton.FlatAppearance.BorderSize = 0; - this.btnDeleteButton.FlatAppearance.MouseDownBackColor = System.Drawing.Color.FromArgb(((int)(((byte)(165)))), ((int)(((byte)(40)))), ((int)(((byte)(52))))); - this.btnDeleteButton.FlatAppearance.MouseOverBackColor = System.Drawing.Color.FromArgb(((int)(((byte)(187)))), ((int)(((byte)(45)))), ((int)(((byte)(59))))); - this.btnDeleteButton.FlatStyle = System.Windows.Forms.FlatStyle.Flat; - this.btnDeleteButton.Font = new System.Drawing.Font("微软雅黑", 10.5F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - this.btnDeleteButton.ForeColor = System.Drawing.Color.White; - this.btnDeleteButton.Location = new System.Drawing.Point(470, 180); - this.btnDeleteButton.Name = "btnDeleteButton"; - this.btnDeleteButton.Size = new System.Drawing.Size(100, 38); - this.btnDeleteButton.TabIndex = 12; - this.btnDeleteButton.Text = "删除"; - this.btnDeleteButton.UseVisualStyleBackColor = false; - this.btnDeleteButton.Click += new System.EventHandler(this.btnDeleteButton_Click); - // - // btnAddButton - // - this.btnAddButton.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(40)))), ((int)(((byte)(167)))), ((int)(((byte)(69))))); - this.btnAddButton.FlatAppearance.BorderSize = 0; - this.btnAddButton.FlatAppearance.MouseDownBackColor = System.Drawing.Color.FromArgb(((int)(((byte)(30)))), ((int)(((byte)(125)))), ((int)(((byte)(52))))); - this.btnAddButton.FlatAppearance.MouseOverBackColor = System.Drawing.Color.FromArgb(((int)(((byte)(33)))), ((int)(((byte)(136)))), ((int)(((byte)(56))))); - this.btnAddButton.FlatStyle = System.Windows.Forms.FlatStyle.Flat; - this.btnAddButton.Font = new System.Drawing.Font("微软雅黑", 10.5F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - this.btnAddButton.ForeColor = System.Drawing.Color.White; - this.btnAddButton.Location = new System.Drawing.Point(360, 180); - this.btnAddButton.Name = "btnAddButton"; - this.btnAddButton.Size = new System.Drawing.Size(100, 38); - this.btnAddButton.TabIndex = 11; - this.btnAddButton.Text = "添加"; - this.btnAddButton.UseVisualStyleBackColor = false; - this.btnAddButton.Click += new System.EventHandler(this.btnAddButton_Click); - // - // labelTriggerMethodParams - // - this.labelTriggerMethodParams.AutoSize = true; - this.labelTriggerMethodParams.Font = new System.Drawing.Font("微软雅黑", 10.5F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - this.labelTriggerMethodParams.Location = new System.Drawing.Point(370, 128); - this.labelTriggerMethodParams.Name = "labelTriggerMethodParams"; - this.labelTriggerMethodParams.Size = new System.Drawing.Size(103, 24); - this.labelTriggerMethodParams.TabIndex = 11; - this.labelTriggerMethodParams.Text = "方法参数:"; - // - // textBoxTriggerMethodParams - // - this.textBoxTriggerMethodParams.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; - this.textBoxTriggerMethodParams.Font = new System.Drawing.Font("微软雅黑", 10.5F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - this.textBoxTriggerMethodParams.Location = new System.Drawing.Point(490, 125); - this.textBoxTriggerMethodParams.Name = "textBoxTriggerMethodParams"; - this.textBoxTriggerMethodParams.Size = new System.Drawing.Size(190, 30); - this.textBoxTriggerMethodParams.TabIndex = 10; - // - // labelTriggerState - // - this.labelTriggerState.AutoSize = true; - this.labelTriggerState.Font = new System.Drawing.Font("微软雅黑", 10.5F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - this.labelTriggerState.Location = new System.Drawing.Point(370, 48); - this.labelTriggerState.Name = "labelTriggerState"; - this.labelTriggerState.Size = new System.Drawing.Size(103, 24); - this.labelTriggerState.TabIndex = 3; - this.labelTriggerState.Text = "触发状态:"; - // - // comboBoxTriggerState - // - this.comboBoxTriggerState.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; - this.comboBoxTriggerState.Font = new System.Drawing.Font("微软雅黑", 10.5F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - this.comboBoxTriggerState.FormattingEnabled = true; - this.comboBoxTriggerState.Location = new System.Drawing.Point(490, 45); - this.comboBoxTriggerState.Name = "comboBoxTriggerState"; - this.comboBoxTriggerState.Size = new System.Drawing.Size(190, 32); - this.comboBoxTriggerState.TabIndex = 2; - // - // labelTriggerDelay - // - this.labelTriggerDelay.AutoSize = true; - this.labelTriggerDelay.Font = new System.Drawing.Font("微软雅黑", 10.5F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - this.labelTriggerDelay.Location = new System.Drawing.Point(28, 88); - this.labelTriggerDelay.Name = "labelTriggerDelay"; - this.labelTriggerDelay.Size = new System.Drawing.Size(103, 24); - this.labelTriggerDelay.TabIndex = 5; - this.labelTriggerDelay.Text = "触发延迟:"; - // - // textBoxTriggerDelay - // - this.textBoxTriggerDelay.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; - this.textBoxTriggerDelay.Font = new System.Drawing.Font("微软雅黑", 10.5F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - this.textBoxTriggerDelay.Location = new System.Drawing.Point(150, 85); - this.textBoxTriggerDelay.Name = "textBoxTriggerDelay"; - this.textBoxTriggerDelay.Size = new System.Drawing.Size(200, 30); - this.textBoxTriggerDelay.TabIndex = 4; - // - // labelTriggerMethod - // - this.labelTriggerMethod.AutoSize = true; - this.labelTriggerMethod.Font = new System.Drawing.Font("微软雅黑", 10.5F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - this.labelTriggerMethod.Location = new System.Drawing.Point(28, 128); - this.labelTriggerMethod.Name = "labelTriggerMethod"; - this.labelTriggerMethod.Size = new System.Drawing.Size(103, 24); - this.labelTriggerMethod.TabIndex = 9; - this.labelTriggerMethod.Text = "触发方法:"; - // - // textBoxTriggerMethod - // - this.textBoxTriggerMethod.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; - this.textBoxTriggerMethod.Font = new System.Drawing.Font("微软雅黑", 10.5F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - this.textBoxTriggerMethod.Location = new System.Drawing.Point(150, 125); - this.textBoxTriggerMethod.Name = "textBoxTriggerMethod"; - this.textBoxTriggerMethod.Size = new System.Drawing.Size(200, 30); - this.textBoxTriggerMethod.TabIndex = 8; - // - // labelTriggerMission - // - this.labelTriggerMission.AutoSize = true; - this.labelTriggerMission.Font = new System.Drawing.Font("微软雅黑", 10.5F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - this.labelTriggerMission.Location = new System.Drawing.Point(370, 88); - this.labelTriggerMission.Name = "labelTriggerMission"; - this.labelTriggerMission.Size = new System.Drawing.Size(103, 24); - this.labelTriggerMission.TabIndex = 7; - this.labelTriggerMission.Text = "触发任务:"; - // - // textBoxTriggerMission - // - this.textBoxTriggerMission.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; - this.textBoxTriggerMission.Font = new System.Drawing.Font("微软雅黑", 10.5F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - this.textBoxTriggerMission.Location = new System.Drawing.Point(490, 85); - this.textBoxTriggerMission.Name = "textBoxTriggerMission"; - this.textBoxTriggerMission.Size = new System.Drawing.Size(190, 30); - this.textBoxTriggerMission.TabIndex = 6; - // - // labelButtonIndex - // - this.labelButtonIndex.AutoSize = true; - this.labelButtonIndex.Font = new System.Drawing.Font("微软雅黑", 10.5F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - this.labelButtonIndex.Location = new System.Drawing.Point(28, 48); - this.labelButtonIndex.Name = "labelButtonIndex"; - this.labelButtonIndex.Size = new System.Drawing.Size(65, 24); - this.labelButtonIndex.TabIndex = 1; - this.labelButtonIndex.Text = "编码:"; - // - // textBoxButtonIndex - // - this.textBoxButtonIndex.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; - this.textBoxButtonIndex.Font = new System.Drawing.Font("微软雅黑", 10.5F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - this.textBoxButtonIndex.Location = new System.Drawing.Point(150, 45); - this.textBoxButtonIndex.Name = "textBoxButtonIndex"; - this.textBoxButtonIndex.Size = new System.Drawing.Size(200, 30); - this.textBoxButtonIndex.TabIndex = 0; - // - // labelTitle - // - this.labelTitle.AutoSize = true; - this.labelTitle.Font = new System.Drawing.Font("微软雅黑", 16F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - this.labelTitle.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(51)))), ((int)(((byte)(51)))), ((int)(((byte)(51))))); - this.labelTitle.Location = new System.Drawing.Point(15, 12); - this.labelTitle.Name = "labelTitle"; - this.labelTitle.Size = new System.Drawing.Size(150, 42); - this.labelTitle.TabIndex = 4; - this.labelTitle.Text = "按钮盒管理"; - // - // ButtonBoxManager - // - this.AutoScaleDimensions = new System.Drawing.SizeF(9F, 18F); - this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - this.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(245)))), ((int)(((byte)(245)))), ((int)(((byte)(247))))); - this.ClientSize = new System.Drawing.Size(1200, 620); - this.Controls.Add(this.labelTitle); - this.Controls.Add(this.groupBoxButton); - this.Controls.Add(this.buttonListView); - this.Controls.Add(this.groupBoxButtonBox); - this.Controls.Add(this.buttonBoxListView); - this.Font = new System.Drawing.Font("微软雅黑", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - this.MinimumSize = new System.Drawing.Size(1200, 620); - this.Name = "ButtonBoxManager"; - this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen; - this.Text = "按钮盒管理"; - this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.ButtonBoxManager_FormClosing); - this.Load += new System.EventHandler(this.ButtonBoxManager_Load); - this.groupBoxButtonBox.ResumeLayout(false); - this.groupBoxButtonBox.PerformLayout(); - this.groupBoxButton.ResumeLayout(false); - this.groupBoxButton.PerformLayout(); - this.ResumeLayout(false); - this.PerformLayout(); - - } - - #endregion - - private System.Windows.Forms.ListView buttonBoxListView; - private System.Windows.Forms.ColumnHeader columnHeaderBoxIndex; - private System.Windows.Forms.ColumnHeader columnHeaderIp; - private System.Windows.Forms.ColumnHeader columnHeaderPort; - private System.Windows.Forms.ColumnHeader columnHeaderType; - private System.Windows.Forms.GroupBox groupBoxButtonBox; - private System.Windows.Forms.TextBox textBoxIp; - private System.Windows.Forms.Label labelIp; - private System.Windows.Forms.Label labelPort; - private System.Windows.Forms.TextBox textBoxPort; - private System.Windows.Forms.Label labelBoxIndex; - private System.Windows.Forms.TextBox textBoxBoxIndex; - private System.Windows.Forms.Label labelType; - private System.Windows.Forms.ComboBox comboBoxType; - private System.Windows.Forms.Button btnAddButtonBox; - private System.Windows.Forms.Button btnDeleteButtonBox; - private System.Windows.Forms.Button btnSaveButtonBox; - private System.Windows.Forms.ListView buttonListView; - private System.Windows.Forms.ColumnHeader columnHeaderButtonIndex; - private System.Windows.Forms.ColumnHeader columnHeaderTriggerMission; - private System.Windows.Forms.ColumnHeader columnHeaderTriggerMethod; - private System.Windows.Forms.ColumnHeader columnHeaderTriggerMethodParams; - private System.Windows.Forms.GroupBox groupBoxButton; - private System.Windows.Forms.Label labelButtonIndex; - private System.Windows.Forms.TextBox textBoxButtonIndex; - private System.Windows.Forms.Label labelTriggerMission; - private System.Windows.Forms.TextBox textBoxTriggerMission; - private System.Windows.Forms.Label labelTriggerMethod; - private System.Windows.Forms.TextBox textBoxTriggerMethod; - private System.Windows.Forms.Label labelTriggerMethodParams; - private System.Windows.Forms.TextBox textBoxTriggerMethodParams; - private System.Windows.Forms.Label labelTriggerState; - private System.Windows.Forms.ComboBox comboBoxTriggerState; - private System.Windows.Forms.Label labelTriggerDelay; - private System.Windows.Forms.TextBox textBoxTriggerDelay; - private System.Windows.Forms.ColumnHeader columnHeaderTriggerState; - private System.Windows.Forms.ColumnHeader columnHeaderTriggerDelay; - private System.Windows.Forms.Button btnAddButton; - private System.Windows.Forms.Button btnDeleteButton; - private System.Windows.Forms.Button btnSaveButton; - private System.Windows.Forms.Label labelTitle; - } -} - diff --git a/StandardScene.Core/ExtendDevice/ButtonBox/ButtonBoxManager.cs b/StandardScene.Core/ExtendDevice/ButtonBox/ButtonBoxManager.cs index 17f591a..92aa326 100644 --- a/StandardScene.Core/ExtendDevice/ButtonBox/ButtonBoxManager.cs +++ b/StandardScene.Core/ExtendDevice/ButtonBox/ButtonBoxManager.cs @@ -1,1000 +1,404 @@ using System; using System.Collections.Generic; -using System.ComponentModel; -using System.Data; -using System.Drawing; using System.IO; using System.Linq; using System.Net; -using System.Reflection; using System.Text; using System.Text.RegularExpressions; using System.Threading.Tasks; -using System.Windows.Forms; +using CycleGUI; using StandardScene.Utils; namespace StandardScene.ExtendDevice.ButtonBox { - public partial class ButtonBoxManager : Form + /// + /// 按钮盒配置管理(CycleGUI 版,替代原 WinForms ButtonBoxManager 窗体)。 + /// + public class ButtonBoxManager { - private static ButtonBoxManager _instance = null; - private static readonly object _lock = new object(); - private const string DataFileName = "ButtonBoxConfig.json"; - private string _dataFilePath; + private static string DataFilePath => Path.Combine(AppDomain.CurrentDomain.BaseDirectory, DataFileName); + private static readonly object SaveLock = new object(); - private List _buttonBoxes = new List(); - private ButtonBoxModel _currentButtonBox = null; - private ButtonModel _currentButton = null; + private static Panel _panel; + private static List _boxes = new List(); + private static int _selectedBoxIdx = -1; + private static int _selectedBtnIdx = -1; + private static string _status = ""; - /// - /// 获取单例实例 - /// - public static ButtonBoxManager Instance + private static string _boxIp = ""; + private static string _boxPort = "502"; + private static string _boxIndex = ""; + private static int _typeIdx; + private static string[] _typeNames = Array.Empty(); + + private static string _btnIndex = ""; + private static string _triggerMission = ""; + private static string _triggerMethod = ""; + private static string _triggerParams = ""; + private static string _triggerDelay = "0"; + private static int _triggerStateIdx; + private static readonly string[] _triggerStateNames = Enum.GetNames(typeof(ButtonState)); + + public static void OpenViewer() => Open(); + + public static void Open() { - get + if (_panel != null) { - if (_instance == null || _instance.IsDisposed) - { - lock (_lock) - { - if (_instance == null || _instance.IsDisposed) - { - _instance = new ButtonBoxManager(); - } - } - } - return _instance; + try { _panel.BringToFront(); return; } + catch { _panel = null; } } - } - /// - /// 私有构造函数,确保单例模式 - /// - private ButtonBoxManager() - { - InitializeComponent(); - // 设置数据文件路径 - _dataFilePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, DataFileName); - } - - private void ButtonBoxManager_Load(object sender, EventArgs e) - { - // 设置ListView的视觉样式 - SetupListViewStyles(); - - // 设置按钮的鼠标悬停效果 - SetupButtonHoverEffects(); - - // 初始化类型下拉框 - InitializeTypeComboBox(); - - // 初始化触发状态下拉框 - InitializeTriggerStateComboBox(); - + _typeNames = DiscoverTypes(); LoadData(); - RefreshButtonBoxList(); + _selectedBoxIdx = -1; + _selectedBtnIdx = -1; + ClearBoxFields(); + ClearButtonFields(); + + var panel = GUI.DeclarePanel() + .ShowTitle("按钮盒管理") + .SetDefaultDocking(Panel.Docking.None) + .InitSize(1200, 760) + .InitPos(false, 0, 0, 0.5f, 0.5f, 0.5f, 0.5f); + _panel = panel; + panel.IfTerminalQuit(() => { if (_panel == panel) _panel = null; }); + + panel.Define(pb => + { + if (pb.Closing()) + { + SaveData(); + panel.Exit(); + _panel = null; + return; + } + + pb.SeparatorText("按钮盒"); + pb.Table("btnbox-list", new[] { "盒编码", "IP", "端口", "类型", "盒操作" }, + _boxes.Count, (row, i) => + { + var b = _boxes[i]; + row.Label($"{b.Index}"); + row.Label(b.Ip); + row.Label($"{b.Port}"); + row.Label(b.Type); + if (row.ButtonGroup(new[] { "选盒" }, new[] { "选择该按钮盒" }) == 0) + { + _selectedBoxIdx = i; + _selectedBtnIdx = -1; + LoadBoxFields(b); + ClearButtonFields(); + } + }, height: 8, enableSearch: true); + + pb.SeparatorText("按钮盒编辑"); + var (ip, _) = pb.TextInput("1. IP", _boxIp, alwaysReturnString: true); + _boxIp = ip; + pb.SameLine(12); + var (port, _) = pb.TextInput("2. 端口", _boxPort, alwaysReturnString: true); + _boxPort = port; + var (idx, _) = pb.TextInput("3. 盒编码", _boxIndex, alwaysReturnString: true); + _boxIndex = idx; + if (_typeNames.Length > 0) + pb.DropdownBox("4. 类型", _typeNames, ref _typeIdx); + + if (pb.Button("添加按钮盒", distinct: "bb-add-box")) AddBox(); + pb.SameLine(8); + if (pb.Button("保存按钮盒", distinct: "bb-save-box")) SaveBox(); + pb.SameLine(8); + if (pb.Button("删除按钮盒", distinct: "bb-del-box")) ConfirmDeleteBox(); + + var buttons = GetSelectedBox()?.Buttons ?? new List(); + pb.SeparatorText($"按钮列表(盒 #{GetSelectedBox()?.Index ?? 0},共 {buttons.Count} 个)"); + pb.Table("btn-list", new[] { "按钮编码", "触发状态", "延迟", "Mission", "Method", "按钮操作" }, + buttons.Count, (row, i) => + { + var btn = buttons[i]; + row.Label($"{btn.Index}"); + row.Label(btn.TriggerState); + row.Label($"{btn.TriggerDelay}"); + row.Label(btn.TriggerMission); + row.Label(btn.TriggerMethod); + if (row.ButtonGroup(new[] { "选按钮" }, new[] { "选择该按钮" }) == 0) + { + _selectedBtnIdx = i; + LoadButtonFields(btn); + } + }, height: 6, enableSearch: true); + + pb.SeparatorText("按钮编辑"); + var (bIdx, _) = pb.TextInput("5. 按钮编码", _btnIndex, alwaysReturnString: true); + _btnIndex = bIdx; + pb.DropdownBox("6. 触发状态", _triggerStateNames, ref _triggerStateIdx); + var (delay, _) = pb.TextInput("7. 触发延迟", _triggerDelay, alwaysReturnString: true); + _triggerDelay = delay; + var (mission, _) = pb.TextInput("8. TriggerMission", _triggerMission, alwaysReturnString: true); + _triggerMission = mission; + var (method, _) = pb.TextInput("9. TriggerMethod", _triggerMethod, alwaysReturnString: true); + _triggerMethod = method; + var (parms, _) = pb.TextInput("10. TriggerMethodParams", _triggerParams, alwaysReturnString: true); + _triggerParams = parms; + + if (pb.Button("添加按钮", distinct: "bb-add-btn")) AddButton(); + pb.SameLine(8); + if (pb.Button("保存按钮", distinct: "bb-save-btn")) SaveButton(); + pb.SameLine(8); + if (pb.Button("删除按钮", distinct: "bb-del-btn")) ConfirmDeleteButton(); + + if (!string.IsNullOrEmpty(_status)) + { + pb.Separator(); + pb.Label(_status); + } + }); } - /// - /// 初始化触发状态下拉框 - /// - private void InitializeTriggerStateComboBox() + private static ButtonBoxModel GetSelectedBox() => + _selectedBoxIdx >= 0 && _selectedBoxIdx < _boxes.Count ? _boxes[_selectedBoxIdx] : null; + + private static ButtonModel GetSelectedButton() { - comboBoxTriggerState.Items.Clear(); - - // 添加ButtonState枚举的所有值 - foreach (ButtonState state in Enum.GetValues(typeof(ButtonState))) - { - comboBoxTriggerState.Items.Add(state.ToString()); - } - - // 如果没有选中项,默认选择第一个 - if (comboBoxTriggerState.Items.Count > 0 && comboBoxTriggerState.SelectedIndex == -1) - { - comboBoxTriggerState.SelectedIndex = 0; - } + var box = GetSelectedBox(); + return box != null && _selectedBtnIdx >= 0 && _selectedBtnIdx < box.Buttons.Count + ? box.Buttons[_selectedBtnIdx] : null; } - /// - /// 初始化类型下拉框 - /// - private void InitializeTypeComboBox() + private static string[] DiscoverTypes() { - comboBoxType.Items.Clear(); - try { - // 获取当前命名空间下所有继承自BasicButtonBox的类 - // 跨程序集发现:按钮盒具体类型可能位于卫星插件 dll(StandardScene.Devices.ButtonBox), - // 用内核同款全域类型发现替代仅扫当前程序集的 GetExecutingAssembly。 - var buttonBoxTypes = SimpleLite.Utils.UiTypeDiscovery.AllTypes() - .Where(t => t.IsClass - && !t.IsAbstract + var names = SimpleLite.Utils.UiTypeDiscovery.AllTypes() + .Where(t => t.IsClass && !t.IsAbstract && t.Namespace == typeof(BasicButtonBox).Namespace && t.IsSubclassOf(typeof(BasicButtonBox))) - .OrderBy(t => t.Name) - .ToList(); + .Select(t => t.Name) + .OrderBy(n => n) + .ToArray(); + return names.Length > 0 ? names : new[] { "BasicButtonBox" }; + } + catch + { + return new[] { "BasicButtonBox" }; + } + } - foreach (var type in buttonBoxTypes) - { - comboBoxType.Items.Add(type.Name); - } - - // 如果没有找到任何类型,添加默认选项 - if (comboBoxType.Items.Count == 0) - { - comboBoxType.Items.Add("BasicButtonBox"); - } + private static void LoadData() + { + try + { + if (!File.Exists(DataFilePath)) { _boxes = new List(); return; } + var json = File.ReadAllText(DataFilePath, Encoding.UTF8); + _boxes = string.IsNullOrWhiteSpace(json) + ? new List() + : json.JsonTo>() ?? new List(); } catch (Exception ex) { - MessageBox.Show($"Failed to init type dropdown: {ex.Message}", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); - comboBoxType.Items.Add("BasicButtonBox"); + _boxes = new List(); + _status = $"加载失败: {ex.Message}"; } } - private int _buttonBoxHoverIndex = -1; - private int _buttonHoverIndex = -1; - - /// - /// 设置ListView的视觉样式 - /// - private void SetupListViewStyles() + private static void SaveData() { - SetupListView(buttonBoxListView, - ButtonBoxListView_DrawItem, - ButtonBoxListView_DrawSubItem, - ButtonBoxListView_DrawColumnHeader, - ButtonBoxListView_MouseMove, - ButtonBoxListView_MouseLeave); - - SetupListView(buttonListView, - ButtonListView_DrawItem, - ButtonListView_DrawSubItem, - ButtonListView_DrawColumnHeader, - ButtonListView_MouseMove, - ButtonListView_MouseLeave); - } - - private void SetupListView(ListView listView, - DrawListViewItemEventHandler itemHandler, - DrawListViewSubItemEventHandler subItemHandler, - DrawListViewColumnHeaderEventHandler headerHandler, - MouseEventHandler mouseMoveHandler, - EventHandler mouseLeaveHandler) - { - listView.OwnerDraw = true; - listView.BackColor = Color.White; - listView.DrawItem += itemHandler; - listView.DrawSubItem += subItemHandler; - listView.DrawColumnHeader += headerHandler; - listView.MouseMove += mouseMoveHandler; - listView.MouseLeave += mouseLeaveHandler; - - // 启用双缓冲,防止重绘时的灰色覆盖 - typeof(Control)?.GetProperty("DoubleBuffered", BindingFlags.Instance | BindingFlags.NonPublic)? - .SetValue(listView, true, null); - } - - private static readonly Color RowEvenColor = Color.FromArgb(250, 250, 252); - private static readonly Color RowOddColor = Color.White; - private static readonly Color RowHighlightColor = Color.FromArgb(230, 240, 255); - private static readonly Color TextRegularColor = Color.FromArgb(68, 68, 68); - private static readonly Color TextHighlightColor = Color.FromArgb(51, 51, 51); - - private void ButtonBoxListView_MouseMove(object sender, MouseEventArgs e) - { - UpdateHoverIndex(buttonBoxListView, e, true); - } - - private void ButtonBoxListView_MouseLeave(object sender, EventArgs e) - { - ResetHoverIndex(buttonBoxListView, true); - } - - private void ButtonListView_MouseMove(object sender, MouseEventArgs e) - { - UpdateHoverIndex(buttonListView, e, false); - } - - private void ButtonListView_MouseLeave(object sender, EventArgs e) - { - ResetHoverIndex(buttonListView, false); - } - - private void UpdateHoverIndex(ListView listView, MouseEventArgs e, bool isButtonBoxList) - { - var hoveredItem = listView.GetItemAt(e.X, e.Y); - int newIndex = hoveredItem?.Index ?? -1; - - if (isButtonBoxList) + var json = _boxes.ToJson(); + var path = DataFilePath; + Task.Run(() => { - if (_buttonBoxHoverIndex != newIndex) + try { - _buttonBoxHoverIndex = newIndex; - listView.Invalidate(); + lock (SaveLock) + File.WriteAllText(path, json, Encoding.UTF8); } - } - else - { - if (_buttonHoverIndex != newIndex) + catch (Exception ex) { - _buttonHoverIndex = newIndex; - listView.Invalidate(); + _status = $"保存失败: {ex.Message}"; + _panel?.Repaint(); } - } + }); } - private void ResetHoverIndex(ListView listView, bool isButtonBoxList) + private static void LoadBoxFields(ButtonBoxModel b) { - if (isButtonBoxList) - { - if (_buttonBoxHoverIndex != -1) - { - _buttonBoxHoverIndex = -1; - listView.Invalidate(); - } - } - else - { - if (_buttonHoverIndex != -1) - { - _buttonHoverIndex = -1; - listView.Invalidate(); - } - } + _boxIp = b.Ip; + _boxPort = b.Port.ToString(); + _boxIndex = b.Index.ToString(); + _typeIdx = Math.Max(0, Array.IndexOf(_typeNames, b.Type)); + if (_typeIdx < 0) _typeIdx = 0; } - /// - /// 按钮盒ListView绘制项 - /// - private void ButtonBoxListView_DrawItem(object sender, DrawListViewItemEventArgs e) + private static void ClearBoxFields() { - var isHighlighted = e.Item.Selected - || e.ItemIndex == _buttonBoxHoverIndex - || (e.State & ListViewItemStates.Focused) != 0; - - var backColor = isHighlighted - ? RowHighlightColor - : (e.ItemIndex % 2 == 0 ? RowEvenColor : RowOddColor); - - using (var brush = new SolidBrush(backColor)) - { - e.Graphics.FillRectangle(brush, e.Bounds); - } - - var textColor = isHighlighted ? TextHighlightColor : TextRegularColor; - - TextRenderer.DrawText(e.Graphics, e.Item.Text, e.Item.Font, e.Bounds, - textColor, - TextFormatFlags.VerticalCenter | TextFormatFlags.Left | TextFormatFlags.EndEllipsis); - - e.DrawFocusRectangle(); + _boxIp = ""; + _boxPort = "502"; + _boxIndex = ""; + _typeIdx = 0; } - /// - /// 按钮盒ListView绘制子项 - /// - private void ButtonBoxListView_DrawSubItem(object sender, DrawListViewSubItemEventArgs e) + private static void LoadButtonFields(ButtonModel b) { - var isHighlighted = e.Item.Selected - || e.ItemIndex == _buttonBoxHoverIndex - || (e.ItemState & ListViewItemStates.Focused) != 0; - - var backColor = isHighlighted - ? RowHighlightColor - : (e.ItemIndex % 2 == 0 ? RowEvenColor : RowOddColor); - - using (var brush = new SolidBrush(backColor)) - { - e.Graphics.FillRectangle(brush, e.Bounds); - } - - var textColor = isHighlighted ? TextHighlightColor : TextRegularColor; - - TextRenderer.DrawText(e.Graphics, e.SubItem.Text, e.SubItem.Font, e.Bounds, - textColor, - TextFormatFlags.VerticalCenter | TextFormatFlags.Left | TextFormatFlags.EndEllipsis); + _btnIndex = b.Index.ToString(); + _triggerMission = b.TriggerMission ?? ""; + _triggerMethod = b.TriggerMethod ?? ""; + _triggerParams = b.TriggerMethodParams ?? ""; + _triggerDelay = b.TriggerDelay.ToString(); + _triggerStateIdx = Math.Max(0, Array.IndexOf(_triggerStateNames, b.TriggerState)); } - /// - /// 按钮盒ListView绘制列标题 - /// - private void ButtonBoxListView_DrawColumnHeader(object sender, DrawListViewColumnHeaderEventArgs e) + private static void ClearButtonFields() { - // 绘制列标题背景 - e.Graphics.FillRectangle(new SolidBrush(Color.FromArgb(245, 247, 250)), e.Bounds); - - // 绘制边框 - e.Graphics.DrawLine(new Pen(Color.FromArgb(220, 220, 220)), - e.Bounds.Left, e.Bounds.Bottom - 1, e.Bounds.Right, e.Bounds.Bottom - 1); - - // 绘制文本 - TextRenderer.DrawText(e.Graphics, e.Header.Text, - new Font("微软雅黑", 10.5F, FontStyle.Bold), - e.Bounds, Color.FromArgb(68, 68, 68), - TextFormatFlags.VerticalCenter | TextFormatFlags.Left | TextFormatFlags.HorizontalCenter); + _btnIndex = ""; + _triggerMission = ""; + _triggerMethod = ""; + _triggerParams = ""; + _triggerDelay = "0"; + _triggerStateIdx = 0; } - /// - /// 按钮ListView绘制项 - /// - private void ButtonListView_DrawItem(object sender, DrawListViewItemEventArgs e) + private static void AddBox() { - var isHighlighted = e.Item.Selected - || e.ItemIndex == _buttonHoverIndex - || (e.State & ListViewItemStates.Focused) != 0; + if (!TryParseBoxInput(out var index, out var ip, out var port, out var type, false, out var err)) + { CycleUiHelper.Alert("错误", err); return; } + if (_boxes.Any(b => b.Index == index)) { CycleUiHelper.Alert("错误", $"编码 {index} 已存在"); return; } + if (_boxes.Any(b => b.Ip == ip)) { CycleUiHelper.Alert("错误", $"IP {ip} 已存在"); return; } - var backColor = isHighlighted - ? RowHighlightColor - : (e.ItemIndex % 2 == 0 ? RowEvenColor : RowOddColor); - - using (var brush = new SolidBrush(backColor)) - { - e.Graphics.FillRectangle(brush, e.Bounds); - } - - var textColor = isHighlighted ? TextHighlightColor : TextRegularColor; - - TextRenderer.DrawText(e.Graphics, e.Item.Text, e.Item.Font, e.Bounds, - textColor, - TextFormatFlags.VerticalCenter | TextFormatFlags.Left | TextFormatFlags.EndEllipsis); - - e.DrawFocusRectangle(); + var box = new ButtonBoxModel { Index = index, Ip = ip, Port = port, Type = type }; + _boxes.Add(box); + _selectedBoxIdx = _boxes.Count - 1; + SaveData(); + _status = "已添加按钮盒"; + _panel?.Repaint(); } - /// - /// 按钮ListView绘制子项 - /// - private void ButtonListView_DrawSubItem(object sender, DrawListViewSubItemEventArgs e) + private static void SaveBox() { - var isHighlighted = e.Item.Selected - || e.ItemIndex == _buttonHoverIndex - || (e.ItemState & ListViewItemStates.Focused) != 0; + var cur = GetSelectedBox(); + if (cur == null) { CycleUiHelper.Alert("提示", "请先选择按钮盒"); return; } + if (!TryParseBoxInput(out var index, out var ip, out var port, out var type, true, out var err)) + { CycleUiHelper.Alert("错误", err); return; } + if (_boxes.Any(b => b.Index == index && b != cur)) { CycleUiHelper.Alert("错误", $"编码 {index} 已存在"); return; } + if (_boxes.Any(b => b.Ip == ip && b != cur)) { CycleUiHelper.Alert("错误", $"IP {ip} 已存在"); return; } - var backColor = isHighlighted - ? RowHighlightColor - : (e.ItemIndex % 2 == 0 ? RowEvenColor : RowOddColor); - - using (var brush = new SolidBrush(backColor)) - { - e.Graphics.FillRectangle(brush, e.Bounds); - } - - var textColor = isHighlighted ? TextHighlightColor : TextRegularColor; - - TextRenderer.DrawText(e.Graphics, e.SubItem.Text, e.SubItem.Font, e.Bounds, - textColor, - TextFormatFlags.VerticalCenter | TextFormatFlags.Left | TextFormatFlags.EndEllipsis); + cur.Index = index; cur.Ip = ip; cur.Port = port; cur.Type = type; + SaveData(); + CycleUiHelper.Alert("提示", "保存成功"); + _panel?.Repaint(); } - /// - /// 按钮ListView绘制列标题 - /// - private void ButtonListView_DrawColumnHeader(object sender, DrawListViewColumnHeaderEventArgs e) + private static void ConfirmDeleteBox() { - // 绘制列标题背景 - e.Graphics.FillRectangle(new SolidBrush(Color.FromArgb(245, 247, 250)), e.Bounds); - - // 绘制边框 - e.Graphics.DrawLine(new Pen(Color.FromArgb(220, 220, 220)), - e.Bounds.Left, e.Bounds.Bottom - 1, e.Bounds.Right, e.Bounds.Bottom - 1); - - // 绘制文本 - TextRenderer.DrawText(e.Graphics, e.Header.Text, - new Font("微软雅黑", 10.5F, FontStyle.Bold), - e.Bounds, Color.FromArgb(68, 68, 68), - TextFormatFlags.VerticalCenter | TextFormatFlags.Left | TextFormatFlags.HorizontalCenter); - } - - /// - /// 设置按钮的鼠标悬停效果 - /// - private void SetupButtonHoverEffects() - { - // 按钮的悬停效果现在通过FlatAppearance属性在Designer中设置 - // 这里可以添加其他额外的效果,如工具提示等 - } - - /// - /// 刷新按钮盒列表 - /// - private void RefreshButtonBoxList() - { - buttonBoxListView.Items.Clear(); - foreach (var box in _buttonBoxes) + var cur = GetSelectedBox(); + if (cur == null) { CycleUiHelper.Alert("提示", "请选择要删除的按钮盒"); return; } + CycleUiHelper.ConfirmThen($"删除编码为 {cur.Index} 的按钮盒?", () => { - var item = new ListViewItem(box.Index.ToString()); - item.SubItems.Add(box.Ip); - item.SubItems.Add(box.Port.ToString()); - item.SubItems.Add(box.Type); - item.Tag = box; - item.UseItemStyleForSubItems = false; - buttonBoxListView.Items.Add(item); - } - } - - /// - /// 刷新按钮列表 - /// - private void RefreshButtonList() - { - buttonListView.Items.Clear(); - if (_currentButtonBox != null) - { - foreach (var button in _currentButtonBox.Buttons) - { - var item = new ListViewItem(button.Index.ToString()); - item.SubItems.Add(button.TriggerState); - item.SubItems.Add(button.TriggerDelay.ToString()); - item.SubItems.Add(button.TriggerMission); - item.SubItems.Add(button.TriggerMethod); - item.SubItems.Add(button.TriggerMethodParams); - item.Tag = button; - item.UseItemStyleForSubItems = false; - buttonListView.Items.Add(item); - } - } - } - - /// - /// 按钮盒列表选择改变 - /// - private void buttonBoxListView_SelectedIndexChanged(object sender, EventArgs e) - { - if (buttonBoxListView.SelectedItems.Count > 0) - { - _currentButtonBox = buttonBoxListView.SelectedItems[0].Tag as ButtonBoxModel; - if (_currentButtonBox != null) - { - // 填充按钮盒编辑区域 - textBoxIp.Text = _currentButtonBox.Ip; - textBoxPort.Text = _currentButtonBox.Port.ToString(); - textBoxBoxIndex.Text = _currentButtonBox.Index.ToString(); - // 设置类型下拉框 - if (comboBoxType.Items.Contains(_currentButtonBox.Type)) - { - comboBoxType.SelectedItem = _currentButtonBox.Type; - } - else - { - comboBoxType.SelectedIndex = comboBoxType.Items.Count > 0 ? 0 : -1; - } - - // 刷新按钮列表 - RefreshButtonList(); - } - } - else - { - _currentButtonBox = null; - ClearButtonBoxFields(); - buttonListView.Items.Clear(); - } - } - - /// - /// 按钮列表选择改变 - /// - private void buttonListView_SelectedIndexChanged(object sender, EventArgs e) - { - if (buttonListView.SelectedItems.Count > 0) - { - _currentButton = buttonListView.SelectedItems[0].Tag as ButtonModel; - if (_currentButton != null) - { - // 填充按钮编辑区域 - textBoxButtonIndex.Text = _currentButton.Index.ToString(); - textBoxTriggerMission.Text = _currentButton.TriggerMission; - textBoxTriggerMethod.Text = _currentButton.TriggerMethod; - textBoxTriggerMethodParams.Text = _currentButton.TriggerMethodParams; - - // 设置触发状态下拉框 - if (comboBoxTriggerState.Items.Contains(_currentButton.TriggerState)) - { - comboBoxTriggerState.SelectedItem = _currentButton.TriggerState; - } - else - { - comboBoxTriggerState.SelectedIndex = comboBoxTriggerState.Items.Count > 0 ? 0 : -1; - } - - textBoxTriggerDelay.Text = _currentButton.TriggerDelay.ToString(); - } - } - else - { - _currentButton = null; + _boxes.Remove(cur); + _selectedBoxIdx = -1; + _selectedBtnIdx = -1; + ClearBoxFields(); ClearButtonFields(); - } - } - - /// - /// 添加按钮盒 - /// - private void btnAddButtonBox_Click(object sender, EventArgs e) - { - try - { - // 获取输入框的值 - string ip = textBoxIp.Text.Trim(); - string portText = textBoxPort.Text.Trim(); - string indexText = textBoxBoxIndex.Text.Trim(); - string type = comboBoxType.SelectedItem?.ToString() ?? string.Empty; - - // 确定要使用的值:如果输入框不为空则使用输入值,否则使用默认值 - int newIndex; - if (!string.IsNullOrWhiteSpace(indexText)) - { - if (!int.TryParse(indexText, out newIndex)) - { - MessageBox.Show("Index must be numeric", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); - return; - } - } - else - { - newIndex = _buttonBoxes.Count > 0 ? _buttonBoxes.Max(b => b.Index) + 1 : 1; - } - - string newIp; - if (!string.IsNullOrWhiteSpace(ip)) - { - // 验证IP地址格式 - if (!IsValidIpAddress(ip)) - { - MessageBox.Show("Invalid IP address, e.g. 192.168.1.100", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); - return; - } - newIp = ip; - } - else - { - newIp = "192.168.1.100"; - } - - int newPort; - if (!string.IsNullOrWhiteSpace(portText)) - { - if (!int.TryParse(portText, out newPort)) - { - MessageBox.Show("Port must be numeric", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); - return; - } - } - else - { - newPort = 502; - } - - string newType; - if (!string.IsNullOrWhiteSpace(type)) - { - newType = type; - } - else - { - // 如果下拉框有选项,使用第一个选项作为默认值 - newType = comboBoxType.Items.Count > 0 ? comboBoxType.Items[0].ToString() : "BasicButtonBox"; - } - - // 检查编码是否重复 - if (_buttonBoxes.Any(b => b.Index == newIndex)) - { - MessageBox.Show($"Index {newIndex} already exists, use another", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); - return; - } - - // 检查IP地址是否重复 - if (_buttonBoxes.Any(b => b.Ip == newIp)) - { - MessageBox.Show($"IP {newIp} already exists, use another", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); - return; - } - - var newBox = new ButtonBoxModel - { - Index = newIndex, - Ip = newIp, - Port = newPort, - Type = newType - }; - - _buttonBoxes.Add(newBox); - RefreshButtonBoxList(); SaveData(); - - // 选中新添加的按钮盒 - foreach (ListViewItem item in buttonBoxListView.Items) - { - if (item.Tag == newBox) - { - item.Selected = true; - item.EnsureVisible(); - break; - } - } - } - catch (Exception ex) - { - MessageBox.Show($"Failed to add button box: {ex.Message}", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); - } + _status = "已删除按钮盒"; + _panel?.Repaint(); + }); } - /// - /// 删除按钮盒 - /// - private void btnDeleteButtonBox_Click(object sender, EventArgs e) + private static void AddButton() { - if (_currentButtonBox == null) - { - MessageBox.Show("Please select a button box to delete", "Info", MessageBoxButtons.OK, MessageBoxIcon.Information); - return; - } + var box = GetSelectedBox(); + if (box == null) { CycleUiHelper.Alert("提示", "请先选择按钮盒"); return; } + if (!TryParseButtonInput(out var index, out var delay, out var err)) + { CycleUiHelper.Alert("错误", err); return; } + if (box.Buttons.Any(b => b.Index == index)) { CycleUiHelper.Alert("错误", $"按钮编码 {index} 已存在"); return; } -var result = MessageBox.Show($"Delete button box with index {_currentButtonBox.Index}?", "Confirm delete", - MessageBoxButtons.YesNo, MessageBoxIcon.Question); - - if (result == DialogResult.Yes) + box.Buttons.Add(new ButtonModel { - _buttonBoxes.Remove(_currentButtonBox); - _currentButtonBox = null; - ClearButtonBoxFields(); - RefreshButtonBoxList(); - buttonListView.Items.Clear(); - SaveData(); - } + Index = index, + TriggerMission = _triggerMission.Trim(), + TriggerMethod = _triggerMethod.Trim(), + TriggerMethodParams = _triggerParams.Trim(), + TriggerState = _triggerStateNames[_triggerStateIdx], + TriggerDelay = delay + }); + _selectedBtnIdx = box.Buttons.Count - 1; + SaveData(); + _status = "已添加按钮"; + _panel?.Repaint(); } - /// - /// 保存按钮盒 - /// - private void btnSaveButtonBox_Click(object sender, EventArgs e) + private static void SaveButton() { - if (_currentButtonBox == null) - { - MessageBox.Show("Please select a button box to save", "Info", MessageBoxButtons.OK, MessageBoxIcon.Information); - return; - } + var box = GetSelectedBox(); + var btn = GetSelectedButton(); + if (box == null || btn == null) { CycleUiHelper.Alert("提示", "请先选择按钮"); return; } + if (!TryParseButtonInput(out var index, out var delay, out var err)) + { CycleUiHelper.Alert("错误", err); return; } + if (box.Buttons.Any(b => b.Index == index && b != btn)) { CycleUiHelper.Alert("错误", $"按钮编码 {index} 已存在"); return; } - try - { - string newIp = textBoxIp.Text.Trim(); - - // 验证IP地址格式 - if (!IsValidIpAddress(newIp)) - { - MessageBox.Show("Invalid IP address, e.g. 192.168.1.100", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); - return; - } - - if (!int.TryParse(textBoxPort.Text, out int port)) - { - MessageBox.Show("Port must be numeric", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); - return; - } - _currentButtonBox.Port = port; - - if (!int.TryParse(textBoxBoxIndex.Text, out int index)) - { - MessageBox.Show("Index must be numeric", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); - return; - } - - // 检查编码是否重复(排除当前项) - if (_buttonBoxes.Any(b => b.Index == index && b != _currentButtonBox)) - { - MessageBox.Show($"Index {index} already exists, use another", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); - return; - } - - // 检查IP地址是否重复(排除当前项) - if (_buttonBoxes.Any(b => b.Ip == newIp && b != _currentButtonBox)) - { - MessageBox.Show($"IP {newIp} already exists, use another", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); - return; - } - - _currentButtonBox.Index = index; - _currentButtonBox.Type = comboBoxType.SelectedItem?.ToString() ?? string.Empty; - _currentButtonBox.Ip = newIp; - - RefreshButtonBoxList(); - SaveData(); - MessageBox.Show("Saved", "Info", MessageBoxButtons.OK, MessageBoxIcon.Information); - } - catch (Exception ex) - { - MessageBox.Show($"Save failed: {ex.Message}", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); - } + btn.Index = index; + btn.TriggerMission = _triggerMission.Trim(); + btn.TriggerMethod = _triggerMethod.Trim(); + btn.TriggerMethodParams = _triggerParams.Trim(); + btn.TriggerState = _triggerStateNames[_triggerStateIdx]; + btn.TriggerDelay = delay; + SaveData(); + CycleUiHelper.Alert("提示", "保存成功"); + _panel?.Repaint(); } - /// - /// 添加按钮 - /// - private void btnAddButton_Click(object sender, EventArgs e) + private static void ConfirmDeleteButton() { - if (_currentButtonBox == null) + var box = GetSelectedBox(); + var btn = GetSelectedButton(); + if (box == null || btn == null) { CycleUiHelper.Alert("提示", "请选择要删除的按钮"); return; } + CycleUiHelper.ConfirmThen($"删除编码为 {btn.Index} 的按钮?", () => { - MessageBox.Show("Please select a button box first", "Info", MessageBoxButtons.OK, MessageBoxIcon.Information); - return; - } - - try - { - // 获取输入框的值 - string indexText = textBoxButtonIndex.Text.Trim(); - string triggerMission = textBoxTriggerMission.Text.Trim(); - string triggerMethod = textBoxTriggerMethod.Text.Trim(); - string triggerMethodParams = textBoxTriggerMethodParams.Text.Trim(); - string triggerState = comboBoxTriggerState.SelectedItem?.ToString() ?? string.Empty; - string triggerDelayText = textBoxTriggerDelay.Text.Trim(); - - // 确定要使用的值:如果输入框不为空则使用输入值,否则使用默认值 - int newIndex; - if (!string.IsNullOrWhiteSpace(indexText)) - { - if (!int.TryParse(indexText, out newIndex)) - { - MessageBox.Show("Button index must be numeric", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); - return; - } - } - else - { - newIndex = _currentButtonBox.Buttons.Count > 0 - ? _currentButtonBox.Buttons.Max(b => b.Index) + 1 - : 1; - } - - // 检查按钮编码是否重复 - if (_currentButtonBox.Buttons.Any(b => b.Index == newIndex)) - { - MessageBox.Show($"Button index {newIndex} already exists, use another", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); - return; - } - - // 解析触发延迟(必须是ushort类型,范围0-65535) - ushort triggerDelay = 0; - if (!string.IsNullOrWhiteSpace(triggerDelayText)) - { - if (!ushort.TryParse(triggerDelayText, out triggerDelay)) - { - MessageBox.Show("Trigger delay must be 0-65535", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); - return; - } - } - - var newButton = new ButtonModel - { - Index = newIndex, - TriggerMission = triggerMission, - TriggerMethod = triggerMethod, - TriggerMethodParams = triggerMethodParams, - TriggerState = triggerState, - TriggerDelay = triggerDelay - }; - - _currentButtonBox.Buttons.Add(newButton); - RefreshButtonList(); - SaveData(); - - // 选中新添加的按钮 - foreach (ListViewItem item in buttonListView.Items) - { - if (item.Tag == newButton) - { - item.Selected = true; - item.EnsureVisible(); - break; - } - } - } - catch (Exception ex) - { - MessageBox.Show($"Failed to add button: {ex.Message}", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); - } - } - - /// - /// 删除按钮 - /// - private void btnDeleteButton_Click(object sender, EventArgs e) - { - if (_currentButtonBox == null) - { - MessageBox.Show("Please select a button box first", "Info", MessageBoxButtons.OK, MessageBoxIcon.Information); - return; - } - - if (_currentButton == null) - { - MessageBox.Show("Please select a button to delete", "Info", MessageBoxButtons.OK, MessageBoxIcon.Information); - return; - } - -var result = MessageBox.Show($"Delete button with index {_currentButton.Index}?", "Confirm delete", - MessageBoxButtons.YesNo, MessageBoxIcon.Question); - - if (result == DialogResult.Yes) - { - _currentButtonBox.Buttons.Remove(_currentButton); - _currentButton = null; + box.Buttons.Remove(btn); + _selectedBtnIdx = -1; ClearButtonFields(); - RefreshButtonList(); SaveData(); - } + _status = "已删除按钮"; + _panel?.Repaint(); + }); } - /// - /// 保存按钮 - /// - private void btnSaveButton_Click(object sender, EventArgs e) + private static bool TryParseBoxInput(out int index, out string ip, out int port, out string type, bool requireSelection, out string err) { - if (_currentButtonBox == null) + index = 0; ip = ""; port = 502; type = ""; err = ""; + ip = string.IsNullOrWhiteSpace(_boxIp) ? "192.168.1.100" : _boxIp.Trim(); + if (!IsValidIp(ip)) { err = "无效的 IP 地址"; return false; } + if (!int.TryParse(_boxPort.Trim(), out port)) { err = "端口必须是数字"; return false; } + if (!int.TryParse(_boxIndex.Trim(), out index)) { - MessageBox.Show("Please select a button box first", "Info", MessageBoxButtons.OK, MessageBoxIcon.Information); - return; - } - - if (_currentButton == null) - { - MessageBox.Show("Please select a button to save", "Info", MessageBoxButtons.OK, MessageBoxIcon.Information); - return; - } - - try - { - if (!int.TryParse(textBoxButtonIndex.Text, out int index)) - { - MessageBox.Show("Button index must be numeric", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); - return; - } - - // 检查按钮编码是否重复(排除当前按钮) - if (_currentButtonBox.Buttons.Any(b => b.Index == index && b != _currentButton)) - { - MessageBox.Show($"Button index {index} already exists, use another", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); - return; - } - - // 验证触发延迟(必须是ushort类型,范围0-65535) - if (!ushort.TryParse(textBoxTriggerDelay.Text, out ushort triggerDelay)) - { - MessageBox.Show("Trigger delay must be 0-65535", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); - return; - } - - _currentButton.Index = index; - _currentButton.TriggerMission = textBoxTriggerMission.Text; - _currentButton.TriggerMethod = textBoxTriggerMethod.Text; - _currentButton.TriggerMethodParams = textBoxTriggerMethodParams.Text; - _currentButton.TriggerState = comboBoxTriggerState.SelectedItem?.ToString() ?? string.Empty; - _currentButton.TriggerDelay = triggerDelay; - - RefreshButtonList(); - SaveData(); - MessageBox.Show("Saved", "Info", MessageBoxButtons.OK, MessageBoxIcon.Information); - } - catch (Exception ex) - { - MessageBox.Show($"Save failed: {ex.Message}", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); + index = _boxes.Count > 0 ? _boxes.Max(b => b.Index) + 1 : 1; + if (!string.IsNullOrWhiteSpace(_boxIndex.Trim())) { err = "编码必须是数字"; return false; } } + type = _typeNames.Length > 0 ? _typeNames[_typeIdx] : "BasicButtonBox"; + if (requireSelection && GetSelectedBox() == null) { err = "请先选择按钮盒"; return false; } + return true; } - /// - /// 清空按钮盒字段 - /// - private void ClearButtonBoxFields() + private static bool TryParseButtonInput(out int index, out ushort delay, out string err) { - textBoxIp.Text = string.Empty; - textBoxPort.Text = string.Empty; - textBoxBoxIndex.Text = string.Empty; - comboBoxType.SelectedIndex = -1; + index = 0; delay = 0; err = ""; + var box = GetSelectedBox(); + if (!int.TryParse(_btnIndex.Trim(), out index)) + { + index = box?.Buttons.Count > 0 ? box.Buttons.Max(b => b.Index) + 1 : 1; + if (!string.IsNullOrWhiteSpace(_btnIndex.Trim())) { err = "按钮编码必须是数字"; return false; } + } + if (!ushort.TryParse(string.IsNullOrWhiteSpace(_triggerDelay) ? "0" : _triggerDelay.Trim(), out delay)) + { err = "触发延迟必须是 0-65535"; return false; } + return true; } - /// - /// 清空按钮字段 - /// - private void ClearButtonFields() + private static bool IsValidIp(string ip) { - textBoxButtonIndex.Text = string.Empty; - textBoxTriggerMission.Text = string.Empty; - textBoxTriggerMethod.Text = string.Empty; - textBoxTriggerMethodParams.Text = string.Empty; - comboBoxTriggerState.SelectedIndex = comboBoxTriggerState.Items.Count > 0 ? 0 : -1; - textBoxTriggerDelay.Text = string.Empty; - } - - /// - /// 加载数据 - /// - private void LoadData() - { - try - { - if (File.Exists(_dataFilePath)) - { - var jsonContent = File.ReadAllText(_dataFilePath, Encoding.UTF8); - if (!string.IsNullOrWhiteSpace(jsonContent)) - { - _buttonBoxes = jsonContent.JsonTo>(); - if (_buttonBoxes == null) - { - _buttonBoxes = new List(); - } - } - else - { - _buttonBoxes = new List(); - } - } - else - { - _buttonBoxes = new List(); - } - } - catch (Exception ex) - { - MessageBox.Show($"Load data failed: {ex.Message}", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); - _buttonBoxes = new List(); - } - } - - /// - /// 保存数据 - /// - private void SaveData() - { - try - { - var jsonContent = _buttonBoxes.ToJson(); - File.WriteAllText(_dataFilePath, jsonContent, Encoding.UTF8); - } - catch (Exception ex) - { - MessageBox.Show($"Save data failed: {ex.Message}", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); - } - } - - /// - /// 验证IP地址格式 - /// - /// IP地址字符串 - /// 如果格式正确返回true,否则返回false - private bool IsValidIpAddress(string ipAddress) - { - if (string.IsNullOrWhiteSpace(ipAddress)) - { - return false; - } - - // 使用正则表达式验证IP地址格式(IPv4) - string pattern = @"^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$"; - if (Regex.IsMatch(ipAddress, pattern)) - { - // 使用System.Net.IPAddress.TryParse进行二次验证 - IPAddress address; - return IPAddress.TryParse(ipAddress, out address) && address.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork; - } - - return false; - } - - /// - /// 窗体关闭事件 - /// - private void ButtonBoxManager_FormClosing(object sender, FormClosingEventArgs e) - { - if (e.CloseReason == CloseReason.UserClosing) - { - // 关闭前保存数据 - SaveData(); - e.Cancel = true; - this.Visible = false; - } + if (string.IsNullOrWhiteSpace(ip)) return false; + var pattern = @"^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$"; + return Regex.IsMatch(ip, pattern) && IPAddress.TryParse(ip, out var addr) + && addr.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork; } } } - diff --git a/StandardScene.Core/ExtendDevice/ButtonBox/ButtonBoxManager.resx b/StandardScene.Core/ExtendDevice/ButtonBox/ButtonBoxManager.resx deleted file mode 100644 index 44e9f97..0000000 --- a/StandardScene.Core/ExtendDevice/ButtonBox/ButtonBoxManager.resx +++ /dev/null @@ -1,120 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - diff --git a/StandardScene.Core/ExtendDevice/ButtonBox/ButtonMission.cs b/StandardScene.Core/ExtendDevice/ButtonBox/ButtonMission.cs index f6e64e7..a66340a 100644 --- a/StandardScene.Core/ExtendDevice/ButtonBox/ButtonMission.cs +++ b/StandardScene.Core/ExtendDevice/ButtonBox/ButtonMission.cs @@ -6,7 +6,6 @@ using System.Reflection; using System.Text; using System.Threading; using System.Threading.Tasks; -using System.Windows.Forms; using LessokajiWeaverUtilities.MagicAttributes; using LessokajiWeaverUtilities.Utilities; using SimpleLite; @@ -768,39 +767,8 @@ namespace StandardScene.ExtendDevice.ButtonBox [I18N.DocumentTranslation(Name = "Open Manager", locale = "en")] public static void OpenViewer() { - try - { - var manager = ButtonBoxManager.Instance; - - // 确保窗体没有被销毁 - if (manager.IsDisposed) - { - // 如果窗体被销毁,单例会自动重新创建 - manager = ButtonBoxManager.Instance; - } - - if (manager.Visible) - { - // 如果界面已经可见,将其激活并置于最前 - if (manager.WindowState == FormWindowState.Minimized) - { - manager.WindowState = FormWindowState.Normal; - } - manager.Activate(); - manager.BringToFront(); - } - else - { - // 如果界面不可见,显示它 - manager.Show(); - manager.Activate(); - } - } - catch (Exception ex) - { - MessageBox.Show($"打开按钮盒管理界面失败: {ex.Message}", "错误", - MessageBoxButtons.OK, MessageBoxIcon.Error); - } + try { ButtonBoxManager.Open(); } + catch (Exception ex) { CycleUiHelper.Alert("错误", $"打开按钮盒管理界面失败: {ex.Message}"); } } } } diff --git a/StandardScene.Core/ExtendDevice/Door/DoorManager.Designer.cs b/StandardScene.Core/ExtendDevice/Door/DoorManager.Designer.cs deleted file mode 100644 index 94f48c0..0000000 --- a/StandardScene.Core/ExtendDevice/Door/DoorManager.Designer.cs +++ /dev/null @@ -1,521 +0,0 @@ -namespace StandardScene.ExtendDevice.Door -{ - partial class DoorManager - { - /// - /// 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.doorControllerListView = new System.Windows.Forms.ListView(); - this.columnHeaderControllerIndex = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); - this.columnHeaderIp = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); - this.columnHeaderPort = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); - this.columnHeaderType = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); - this.groupBoxController = new System.Windows.Forms.GroupBox(); - this.btnSaveController = new System.Windows.Forms.Button(); - this.btnDeleteController = new System.Windows.Forms.Button(); - this.btnAddController = new System.Windows.Forms.Button(); - this.labelType = new System.Windows.Forms.Label(); - this.comboBoxType = new System.Windows.Forms.ComboBox(); - this.labelControllerIndex = new System.Windows.Forms.Label(); - this.textBoxControllerIndex = new System.Windows.Forms.TextBox(); - this.labelPort = new System.Windows.Forms.Label(); - this.textBoxPort = new System.Windows.Forms.TextBox(); - this.labelIp = new System.Windows.Forms.Label(); - this.textBoxIp = new System.Windows.Forms.TextBox(); - this.doorListView = new System.Windows.Forms.ListView(); - this.columnHeaderDoorIndex = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); - this.columnHeaderControlAddress = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); - this.columnHeaderOpenStatusAddress = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); - this.groupBoxDoor = new System.Windows.Forms.GroupBox(); - this.btnSaveDoor = new System.Windows.Forms.Button(); - this.btnDeleteDoor = new System.Windows.Forms.Button(); - this.btnAddDoor = new System.Windows.Forms.Button(); - this.labelOpenStatusAddress = new System.Windows.Forms.Label(); - this.textBoxOpenStatusAddress = new System.Windows.Forms.TextBox(); - this.labelControlAddress = new System.Windows.Forms.Label(); - this.textBoxControlAddress = new System.Windows.Forms.TextBox(); - this.labelDoorIndex = new System.Windows.Forms.Label(); - this.textBoxDoorIndex = new System.Windows.Forms.TextBox(); - this.labelTitle = new System.Windows.Forms.Label(); - this.groupBoxController.SuspendLayout(); - this.groupBoxDoor.SuspendLayout(); - this.SuspendLayout(); - // - // doorControllerListView - // - this.doorControllerListView.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) - | System.Windows.Forms.AnchorStyles.Left))); - this.doorControllerListView.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; - this.doorControllerListView.Columns.AddRange(new System.Windows.Forms.ColumnHeader[] { - this.columnHeaderControllerIndex, - this.columnHeaderIp, - this.columnHeaderPort, - this.columnHeaderType}); - this.doorControllerListView.Font = new System.Drawing.Font("微软雅黑", 10.5F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - this.doorControllerListView.FullRowSelect = true; - this.doorControllerListView.GridLines = true; - this.doorControllerListView.HeaderStyle = System.Windows.Forms.ColumnHeaderStyle.Nonclickable; - this.doorControllerListView.HideSelection = false; - this.doorControllerListView.Location = new System.Drawing.Point(15, 55); - this.doorControllerListView.MultiSelect = false; - this.doorControllerListView.Name = "doorControllerListView"; - this.doorControllerListView.OwnerDraw = true; - this.doorControllerListView.Size = new System.Drawing.Size(450, 290); - this.doorControllerListView.TabIndex = 0; - this.doorControllerListView.UseCompatibleStateImageBehavior = false; - this.doorControllerListView.View = System.Windows.Forms.View.Details; - this.doorControllerListView.SelectedIndexChanged += new System.EventHandler(this.doorControllerListView_SelectedIndexChanged); - // - // columnHeaderControllerIndex - // - this.columnHeaderControllerIndex.Text = "编码"; - this.columnHeaderControllerIndex.Width = 70; - // - // columnHeaderIp - // - this.columnHeaderIp.Text = "IP地址"; - this.columnHeaderIp.Width = 130; - // - // columnHeaderPort - // - this.columnHeaderPort.Text = "端口"; - this.columnHeaderPort.Width = 90; - // - // columnHeaderType - // - this.columnHeaderType.Text = "类型"; - this.columnHeaderType.Width = 140; - // - // groupBoxController - // - this.groupBoxController.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); - this.groupBoxController.Controls.Add(this.btnSaveController); - this.groupBoxController.Controls.Add(this.btnDeleteController); - this.groupBoxController.Controls.Add(this.btnAddController); - this.groupBoxController.Controls.Add(this.labelType); - this.groupBoxController.Controls.Add(this.comboBoxType); - this.groupBoxController.Controls.Add(this.labelControllerIndex); - this.groupBoxController.Controls.Add(this.textBoxControllerIndex); - this.groupBoxController.Controls.Add(this.labelPort); - this.groupBoxController.Controls.Add(this.textBoxPort); - this.groupBoxController.Controls.Add(this.labelIp); - this.groupBoxController.Controls.Add(this.textBoxIp); - this.groupBoxController.Font = new System.Drawing.Font("微软雅黑", 10.5F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - this.groupBoxController.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(68)))), ((int)(((byte)(68)))), ((int)(((byte)(68))))); - this.groupBoxController.Location = new System.Drawing.Point(15, 360); - this.groupBoxController.Name = "groupBoxController"; - this.groupBoxController.Padding = new System.Windows.Forms.Padding(12, 10, 12, 12); - this.groupBoxController.Size = new System.Drawing.Size(450, 250); - this.groupBoxController.TabIndex = 1; - this.groupBoxController.TabStop = false; - this.groupBoxController.Text = "门控制器信息"; - // - // btnSaveController - // - this.btnSaveController.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(0)))), ((int)(((byte)(122)))), ((int)(((byte)(204))))); - this.btnSaveController.FlatAppearance.BorderSize = 0; - this.btnSaveController.FlatAppearance.MouseDownBackColor = System.Drawing.Color.FromArgb(((int)(((byte)(0)))), ((int)(((byte)(92)))), ((int)(((byte)(153))))); - this.btnSaveController.FlatAppearance.MouseOverBackColor = System.Drawing.Color.FromArgb(((int)(((byte)(0)))), ((int)(((byte)(102)))), ((int)(((byte)(170))))); - this.btnSaveController.FlatStyle = System.Windows.Forms.FlatStyle.Flat; - this.btnSaveController.Font = new System.Drawing.Font("微软雅黑", 10.5F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - this.btnSaveController.ForeColor = System.Drawing.Color.White; - this.btnSaveController.Location = new System.Drawing.Point(330, 200); - this.btnSaveController.Name = "btnSaveController"; - this.btnSaveController.Size = new System.Drawing.Size(100, 38); - this.btnSaveController.TabIndex = 10; - this.btnSaveController.Text = "保存"; - this.btnSaveController.UseVisualStyleBackColor = false; - this.btnSaveController.Click += new System.EventHandler(this.btnSaveController_Click); - // - // btnDeleteController - // - this.btnDeleteController.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(220)))), ((int)(((byte)(53)))), ((int)(((byte)(69))))); - this.btnDeleteController.FlatAppearance.BorderSize = 0; - this.btnDeleteController.FlatAppearance.MouseDownBackColor = System.Drawing.Color.FromArgb(((int)(((byte)(165)))), ((int)(((byte)(40)))), ((int)(((byte)(52))))); - this.btnDeleteController.FlatAppearance.MouseOverBackColor = System.Drawing.Color.FromArgb(((int)(((byte)(187)))), ((int)(((byte)(45)))), ((int)(((byte)(59))))); - this.btnDeleteController.FlatStyle = System.Windows.Forms.FlatStyle.Flat; - this.btnDeleteController.Font = new System.Drawing.Font("微软雅黑", 10.5F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - this.btnDeleteController.ForeColor = System.Drawing.Color.White; - this.btnDeleteController.Location = new System.Drawing.Point(220, 200); - this.btnDeleteController.Name = "btnDeleteController"; - this.btnDeleteController.Size = new System.Drawing.Size(100, 38); - this.btnDeleteController.TabIndex = 9; - this.btnDeleteController.Text = "删除"; - this.btnDeleteController.UseVisualStyleBackColor = false; - this.btnDeleteController.Click += new System.EventHandler(this.btnDeleteController_Click); - // - // btnAddController - // - this.btnAddController.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(40)))), ((int)(((byte)(167)))), ((int)(((byte)(69))))); - this.btnAddController.FlatAppearance.BorderSize = 0; - this.btnAddController.FlatAppearance.MouseDownBackColor = System.Drawing.Color.FromArgb(((int)(((byte)(30)))), ((int)(((byte)(125)))), ((int)(((byte)(52))))); - this.btnAddController.FlatAppearance.MouseOverBackColor = System.Drawing.Color.FromArgb(((int)(((byte)(33)))), ((int)(((byte)(136)))), ((int)(((byte)(56))))); - this.btnAddController.FlatStyle = System.Windows.Forms.FlatStyle.Flat; - this.btnAddController.Font = new System.Drawing.Font("微软雅黑", 10.5F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - this.btnAddController.ForeColor = System.Drawing.Color.White; - this.btnAddController.Location = new System.Drawing.Point(110, 200); - this.btnAddController.Name = "btnAddController"; - this.btnAddController.Size = new System.Drawing.Size(100, 38); - this.btnAddController.TabIndex = 8; - this.btnAddController.Text = "添加"; - this.btnAddController.UseVisualStyleBackColor = false; - this.btnAddController.Click += new System.EventHandler(this.btnAddController_Click); - // - // labelType - // - this.labelType.AutoSize = true; - this.labelType.Font = new System.Drawing.Font("微软雅黑", 10.5F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - this.labelType.Location = new System.Drawing.Point(28, 168); - this.labelType.Name = "labelType"; - this.labelType.Size = new System.Drawing.Size(65, 24); - this.labelType.TabIndex = 7; - this.labelType.Text = "类型:"; - // - // comboBoxType - // - this.comboBoxType.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; - this.comboBoxType.Font = new System.Drawing.Font("微软雅黑", 10.5F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - this.comboBoxType.FormattingEnabled = true; - this.comboBoxType.Location = new System.Drawing.Point(110, 165); - this.comboBoxType.Name = "comboBoxType"; - this.comboBoxType.Size = new System.Drawing.Size(320, 32); - this.comboBoxType.TabIndex = 6; - // - // labelControllerIndex - // - this.labelControllerIndex.AutoSize = true; - this.labelControllerIndex.Font = new System.Drawing.Font("微软雅黑", 10.5F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - this.labelControllerIndex.Location = new System.Drawing.Point(28, 48); - this.labelControllerIndex.Name = "labelControllerIndex"; - this.labelControllerIndex.Size = new System.Drawing.Size(65, 24); - this.labelControllerIndex.TabIndex = 1; - this.labelControllerIndex.Text = "编码:"; - // - // textBoxControllerIndex - // - this.textBoxControllerIndex.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; - this.textBoxControllerIndex.Font = new System.Drawing.Font("微软雅黑", 10.5F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - this.textBoxControllerIndex.Location = new System.Drawing.Point(110, 45); - this.textBoxControllerIndex.Name = "textBoxControllerIndex"; - this.textBoxControllerIndex.Size = new System.Drawing.Size(320, 30); - this.textBoxControllerIndex.TabIndex = 0; - // - // labelIp - // - this.labelIp.AutoSize = true; - this.labelIp.Font = new System.Drawing.Font("微软雅黑", 10.5F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - this.labelIp.Location = new System.Drawing.Point(18, 88); - this.labelIp.Name = "labelIp"; - this.labelIp.Size = new System.Drawing.Size(85, 24); - this.labelIp.TabIndex = 3; - this.labelIp.Text = "IP地址:"; - // - // textBoxIp - // - this.textBoxIp.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; - this.textBoxIp.Font = new System.Drawing.Font("微软雅黑", 10.5F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - this.textBoxIp.Location = new System.Drawing.Point(110, 85); - this.textBoxIp.Name = "textBoxIp"; - this.textBoxIp.Size = new System.Drawing.Size(320, 30); - this.textBoxIp.TabIndex = 2; - this.textBoxIp.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(255)))), ((int)(((byte)(255)))), ((int)(((byte)(255))))); - // - // labelPort - // - this.labelPort.AutoSize = true; - this.labelPort.Font = new System.Drawing.Font("微软雅黑", 10.5F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - this.labelPort.Location = new System.Drawing.Point(28, 128); - this.labelPort.Name = "labelPort"; - this.labelPort.Size = new System.Drawing.Size(65, 24); - this.labelPort.TabIndex = 5; - this.labelPort.Text = "端口:"; - // - // textBoxPort - // - this.textBoxPort.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; - this.textBoxPort.Font = new System.Drawing.Font("微软雅黑", 10.5F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - this.textBoxPort.Location = new System.Drawing.Point(110, 125); - this.textBoxPort.Name = "textBoxPort"; - this.textBoxPort.Size = new System.Drawing.Size(320, 30); - this.textBoxPort.TabIndex = 4; - // - // doorListView - // - this.doorListView.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) - | System.Windows.Forms.AnchorStyles.Left))); - this.doorListView.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; - this.doorListView.Columns.AddRange(new System.Windows.Forms.ColumnHeader[] { - this.columnHeaderDoorIndex, - this.columnHeaderControlAddress, - this.columnHeaderOpenStatusAddress}); - this.doorListView.Font = new System.Drawing.Font("微软雅黑", 10.5F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - this.doorListView.FullRowSelect = true; - this.doorListView.GridLines = true; - this.doorListView.HeaderStyle = System.Windows.Forms.ColumnHeaderStyle.Nonclickable; - this.doorListView.HideSelection = false; - this.doorListView.Location = new System.Drawing.Point(483, 55); - this.doorListView.MultiSelect = false; - this.doorListView.Name = "doorListView"; - this.doorListView.OwnerDraw = true; - this.doorListView.Size = new System.Drawing.Size(500, 290); - this.doorListView.TabIndex = 2; - this.doorListView.UseCompatibleStateImageBehavior = false; - this.doorListView.View = System.Windows.Forms.View.Details; - this.doorListView.SelectedIndexChanged += new System.EventHandler(this.doorListView_SelectedIndexChanged); - // - // columnHeaderDoorIndex - // - this.columnHeaderDoorIndex.Text = "编码"; - this.columnHeaderDoorIndex.Width = 100; - // - // columnHeaderControlAddress - // - this.columnHeaderControlAddress.Text = "控制地址"; - this.columnHeaderControlAddress.Width = 180; - // - // columnHeaderOpenStatusAddress - // - this.columnHeaderOpenStatusAddress.Text = "开到位地址"; - this.columnHeaderOpenStatusAddress.Width = 180; - // - // groupBoxDoor - // - this.groupBoxDoor.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); - this.checkBoxNoControl = new System.Windows.Forms.CheckBox(); - this.groupBoxDoor.Controls.Add(this.checkBoxNoControl); - this.groupBoxDoor.Controls.Add(this.btnSaveDoor); - this.groupBoxDoor.Controls.Add(this.btnDeleteDoor); - this.groupBoxDoor.Controls.Add(this.btnAddDoor); - this.groupBoxDoor.Controls.Add(this.labelOpenStatusAddress); - this.groupBoxDoor.Controls.Add(this.textBoxOpenStatusAddress); - this.groupBoxDoor.Controls.Add(this.labelControlAddress); - this.groupBoxDoor.Controls.Add(this.textBoxControlAddress); - this.groupBoxDoor.Controls.Add(this.labelDoorIndex); - this.groupBoxDoor.Controls.Add(this.textBoxDoorIndex); - this.groupBoxDoor.Font = new System.Drawing.Font("微软雅黑", 10.5F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - this.groupBoxDoor.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(68)))), ((int)(((byte)(68)))), ((int)(((byte)(68))))); - this.groupBoxDoor.Location = new System.Drawing.Point(483, 360); - this.groupBoxDoor.Name = "groupBoxDoor"; - this.groupBoxDoor.Padding = new System.Windows.Forms.Padding(12, 10, 12, 12); - this.groupBoxDoor.Size = new System.Drawing.Size(500, 250); - this.groupBoxDoor.TabIndex = 3; - this.groupBoxDoor.TabStop = false; - this.groupBoxDoor.Text = "门信息"; - // - // btnSaveDoor - // - this.btnSaveDoor.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(0)))), ((int)(((byte)(122)))), ((int)(((byte)(204))))); - this.btnSaveDoor.FlatAppearance.BorderSize = 0; - this.btnSaveDoor.FlatAppearance.MouseDownBackColor = System.Drawing.Color.FromArgb(((int)(((byte)(0)))), ((int)(((byte)(92)))), ((int)(((byte)(153))))); - this.btnSaveDoor.FlatAppearance.MouseOverBackColor = System.Drawing.Color.FromArgb(((int)(((byte)(0)))), ((int)(((byte)(102)))), ((int)(((byte)(170))))); - this.btnSaveDoor.FlatStyle = System.Windows.Forms.FlatStyle.Flat; - this.btnSaveDoor.Font = new System.Drawing.Font("微软雅黑", 10.5F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - this.btnSaveDoor.ForeColor = System.Drawing.Color.White; - this.btnSaveDoor.Location = new System.Drawing.Point(380, 200); - this.btnSaveDoor.Name = "btnSaveDoor"; - this.btnSaveDoor.Size = new System.Drawing.Size(100, 38); - this.btnSaveDoor.TabIndex = 7; - this.btnSaveDoor.Text = "保存"; - this.btnSaveDoor.UseVisualStyleBackColor = false; - this.btnSaveDoor.Click += new System.EventHandler(this.btnSaveDoor_Click); - // - // btnDeleteDoor - // - this.btnDeleteDoor.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(220)))), ((int)(((byte)(53)))), ((int)(((byte)(69))))); - this.btnDeleteDoor.FlatAppearance.BorderSize = 0; - this.btnDeleteDoor.FlatAppearance.MouseDownBackColor = System.Drawing.Color.FromArgb(((int)(((byte)(165)))), ((int)(((byte)(40)))), ((int)(((byte)(52))))); - this.btnDeleteDoor.FlatAppearance.MouseOverBackColor = System.Drawing.Color.FromArgb(((int)(((byte)(187)))), ((int)(((byte)(45)))), ((int)(((byte)(59))))); - this.btnDeleteDoor.FlatStyle = System.Windows.Forms.FlatStyle.Flat; - this.btnDeleteDoor.Font = new System.Drawing.Font("微软雅黑", 10.5F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - this.btnDeleteDoor.ForeColor = System.Drawing.Color.White; - this.btnDeleteDoor.Location = new System.Drawing.Point(270, 200); - this.btnDeleteDoor.Name = "btnDeleteDoor"; - this.btnDeleteDoor.Size = new System.Drawing.Size(100, 38); - this.btnDeleteDoor.TabIndex = 6; - this.btnDeleteDoor.Text = "删除"; - this.btnDeleteDoor.UseVisualStyleBackColor = false; - this.btnDeleteDoor.Click += new System.EventHandler(this.btnDeleteDoor_Click); - // - // btnAddDoor - // - this.btnAddDoor.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(40)))), ((int)(((byte)(167)))), ((int)(((byte)(69))))); - this.btnAddDoor.FlatAppearance.BorderSize = 0; - this.btnAddDoor.FlatAppearance.MouseDownBackColor = System.Drawing.Color.FromArgb(((int)(((byte)(30)))), ((int)(((byte)(125)))), ((int)(((byte)(52))))); - this.btnAddDoor.FlatAppearance.MouseOverBackColor = System.Drawing.Color.FromArgb(((int)(((byte)(33)))), ((int)(((byte)(136)))), ((int)(((byte)(56))))); - this.btnAddDoor.FlatStyle = System.Windows.Forms.FlatStyle.Flat; - this.btnAddDoor.Font = new System.Drawing.Font("微软雅黑", 10.5F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - this.btnAddDoor.ForeColor = System.Drawing.Color.White; - this.btnAddDoor.Location = new System.Drawing.Point(160, 200); - this.btnAddDoor.Name = "btnAddDoor"; - this.btnAddDoor.Size = new System.Drawing.Size(100, 38); - this.btnAddDoor.TabIndex = 5; - this.btnAddDoor.Text = "添加"; - this.btnAddDoor.UseVisualStyleBackColor = false; - this.btnAddDoor.Click += new System.EventHandler(this.btnAddDoor_Click); - // - // labelOpenStatusAddress - // - this.labelOpenStatusAddress.AutoSize = true; - this.labelOpenStatusAddress.Font = new System.Drawing.Font("微软雅黑", 10.5F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - this.labelOpenStatusAddress.Location = new System.Drawing.Point(18, 128); - this.labelOpenStatusAddress.Name = "labelOpenStatusAddress"; - this.labelOpenStatusAddress.Size = new System.Drawing.Size(103, 24); - this.labelOpenStatusAddress.TabIndex = 4; - this.labelOpenStatusAddress.Text = "开到位地址:"; - // - // textBoxOpenStatusAddress - // - this.textBoxOpenStatusAddress.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; - this.textBoxOpenStatusAddress.Font = new System.Drawing.Font("微软雅黑", 10.5F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - this.textBoxOpenStatusAddress.Location = new System.Drawing.Point(150, 125); - this.textBoxOpenStatusAddress.Name = "textBoxOpenStatusAddress"; - this.textBoxOpenStatusAddress.Size = new System.Drawing.Size(330, 30); - this.textBoxOpenStatusAddress.TabIndex = 3; - // - // checkBoxNoControl - // - this.checkBoxNoControl.AutoSize = true; - this.checkBoxNoControl.Font = new System.Drawing.Font("微软雅黑", 10.5F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - this.checkBoxNoControl.Location = new System.Drawing.Point(150, 165); - this.checkBoxNoControl.Name = "checkBoxNoControl"; - this.checkBoxNoControl.Size = new System.Drawing.Size(162, 28); - this.checkBoxNoControl.TabIndex = 4; - this.checkBoxNoControl.Text = "禁止门控发送指令"; - this.checkBoxNoControl.UseVisualStyleBackColor = true; - // - // labelControlAddress - // - this.labelControlAddress.AutoSize = true; - this.labelControlAddress.Font = new System.Drawing.Font("微软雅黑", 10.5F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - this.labelControlAddress.Location = new System.Drawing.Point(18, 88); - this.labelControlAddress.Name = "labelControlAddress"; - this.labelControlAddress.Size = new System.Drawing.Size(103, 24); - this.labelControlAddress.TabIndex = 2; - this.labelControlAddress.Text = "控制地址:"; - // - // textBoxControlAddress - // - this.textBoxControlAddress.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; - this.textBoxControlAddress.Font = new System.Drawing.Font("微软雅黑", 10.5F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - this.textBoxControlAddress.Location = new System.Drawing.Point(150, 85); - this.textBoxControlAddress.Name = "textBoxControlAddress"; - this.textBoxControlAddress.Size = new System.Drawing.Size(330, 30); - this.textBoxControlAddress.TabIndex = 1; - // - // labelDoorIndex - // - this.labelDoorIndex.AutoSize = true; - this.labelDoorIndex.Font = new System.Drawing.Font("微软雅黑", 10.5F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - this.labelDoorIndex.Location = new System.Drawing.Point(28, 48); - this.labelDoorIndex.Name = "labelDoorIndex"; - this.labelDoorIndex.Size = new System.Drawing.Size(65, 24); - this.labelDoorIndex.TabIndex = 0; - this.labelDoorIndex.Text = "编码:"; - // - // textBoxDoorIndex - // - this.textBoxDoorIndex.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; - this.textBoxDoorIndex.Font = new System.Drawing.Font("微软雅黑", 10.5F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - this.textBoxDoorIndex.Location = new System.Drawing.Point(150, 45); - this.textBoxDoorIndex.Name = "textBoxDoorIndex"; - this.textBoxDoorIndex.Size = new System.Drawing.Size(330, 30); - this.textBoxDoorIndex.TabIndex = 0; - // - // labelTitle - // - this.labelTitle.AutoSize = true; - this.labelTitle.Font = new System.Drawing.Font("微软雅黑", 16F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - this.labelTitle.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(51)))), ((int)(((byte)(51)))), ((int)(((byte)(51))))); - this.labelTitle.Location = new System.Drawing.Point(15, 12); - this.labelTitle.Name = "labelTitle"; - this.labelTitle.Size = new System.Drawing.Size(150, 42); - this.labelTitle.TabIndex = 4; - this.labelTitle.Text = "门控制器管理"; - // - // DoorManager - // - this.AutoScaleDimensions = new System.Drawing.SizeF(9F, 18F); - this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - this.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(245)))), ((int)(((byte)(245)))), ((int)(((byte)(247))))); - this.ClientSize = new System.Drawing.Size(1000, 620); - this.Controls.Add(this.labelTitle); - this.Controls.Add(this.groupBoxDoor); - this.Controls.Add(this.doorListView); - this.Controls.Add(this.groupBoxController); - this.Controls.Add(this.doorControllerListView); - this.Font = new System.Drawing.Font("微软雅黑", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - this.MinimumSize = new System.Drawing.Size(1000, 620); - this.Name = "DoorManager"; - this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen; - this.Text = "门控制器管理"; - this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.DoorManager_FormClosing); - this.Load += new System.EventHandler(this.DoorManager_Load); - this.groupBoxController.ResumeLayout(false); - this.groupBoxController.PerformLayout(); - this.groupBoxDoor.ResumeLayout(false); - this.groupBoxDoor.PerformLayout(); - this.ResumeLayout(false); - this.PerformLayout(); - - } - - #endregion - - private System.Windows.Forms.ListView doorControllerListView; - private System.Windows.Forms.ColumnHeader columnHeaderControllerIndex; - private System.Windows.Forms.ColumnHeader columnHeaderIp; - private System.Windows.Forms.ColumnHeader columnHeaderPort; - private System.Windows.Forms.ColumnHeader columnHeaderType; - private System.Windows.Forms.GroupBox groupBoxController; - private System.Windows.Forms.TextBox textBoxIp; - private System.Windows.Forms.Label labelIp; - private System.Windows.Forms.Label labelPort; - private System.Windows.Forms.TextBox textBoxPort; - private System.Windows.Forms.Label labelControllerIndex; - private System.Windows.Forms.TextBox textBoxControllerIndex; - private System.Windows.Forms.Label labelType; - private System.Windows.Forms.ComboBox comboBoxType; - private System.Windows.Forms.Button btnAddController; - private System.Windows.Forms.Button btnDeleteController; - private System.Windows.Forms.Button btnSaveController; - private System.Windows.Forms.ListView doorListView; - private System.Windows.Forms.ColumnHeader columnHeaderDoorIndex; - private System.Windows.Forms.ColumnHeader columnHeaderControlAddress; - private System.Windows.Forms.ColumnHeader columnHeaderOpenStatusAddress; - private System.Windows.Forms.GroupBox groupBoxDoor; - private System.Windows.Forms.Label labelDoorIndex; - private System.Windows.Forms.TextBox textBoxDoorIndex; - private System.Windows.Forms.Label labelControlAddress; - private System.Windows.Forms.TextBox textBoxControlAddress; - private System.Windows.Forms.Label labelOpenStatusAddress; - private System.Windows.Forms.TextBox textBoxOpenStatusAddress; - private System.Windows.Forms.Button btnAddDoor; - private System.Windows.Forms.Button btnDeleteDoor; - private System.Windows.Forms.Button btnSaveDoor; - private System.Windows.Forms.Label labelTitle; - private System.Windows.Forms.CheckBox checkBoxNoControl; - } -} diff --git a/StandardScene.Core/ExtendDevice/Door/DoorManager.cs b/StandardScene.Core/ExtendDevice/Door/DoorManager.cs index bc939b8..24d0921 100644 --- a/StandardScene.Core/ExtendDevice/Door/DoorManager.cs +++ b/StandardScene.Core/ExtendDevice/Door/DoorManager.cs @@ -1,961 +1,412 @@ using System; using System.Collections.Generic; -using System.ComponentModel; -using System.Drawing; using System.IO; using System.Linq; using System.Net; using System.Reflection; using System.Text; using System.Text.RegularExpressions; -using System.Windows.Forms; +using System.Threading.Tasks; +using CycleGUI; using StandardScene.Utils; namespace StandardScene.ExtendDevice.Door { - public partial class DoorManager : Form + /// + /// 门控制器配置管理(CycleGUI 版,替代原 WinForms DoorManager 窗体)。 + /// + public class DoorManager { - private static DoorManager _instance = null; - private static readonly object _lock = new object(); - private const string DataFileName = "DoorConfig.json"; - private string _dataFilePath; + private static string DataFilePath => Path.Combine(AppDomain.CurrentDomain.BaseDirectory, DataFileName); + private static readonly object SaveLock = new object(); - private List _doorControllers = new List(); - private DoorControllerModel _currentController = null; - private DoorModel _currentDoor = null; + private static Panel _panel; + private static List _controllers = new List(); + private static int _selectedCtrlIdx = -1; + private static int _selectedDoorIdx = -1; + private static string _status = ""; - private int _controllerHoverIndex = -1; - private int _doorHoverIndex = -1; + private static string _ctrlIp = ""; + private static string _ctrlPort = "502"; + private static string _ctrlIndex = ""; + private static int _typeIdx; + private static string[] _typeNames = Array.Empty(); - /// - /// 获取单例实例 - /// - public static DoorManager Instance + private static string _doorIndex = ""; + private static string _doorCtrlAddr = ""; + private static string _doorOpenAddr = ""; + private static bool _doorNoControl; + + public static void OpenViewer() => Open(); + + public static void Open() { - get + if (_panel != null) { - if (_instance == null || _instance.IsDisposed) - { - lock (_lock) - { - if (_instance == null || _instance.IsDisposed) - { - _instance = new DoorManager(); - } - } - } - return _instance; + try { _panel.BringToFront(); return; } + catch { _panel = null; } } - } - - /// - /// 私有构造函数,确保单例模式 - /// - private DoorManager() - { - InitializeComponent(); - // 设置数据文件路径 - _dataFilePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, DataFileName); - } - - private void DoorManager_Load(object sender, EventArgs e) - { - // 设置ListView的视觉样式 - SetupListViewStyles(); - - // 初始化类型下拉框 - InitializeTypeComboBox(); + _typeNames = DiscoverTypes(); LoadData(); - RefreshControllerList(); + _selectedCtrlIdx = -1; + _selectedDoorIdx = -1; + ClearControllerFields(); + ClearDoorFields(); + + var panel = GUI.DeclarePanel() + .ShowTitle("门控制器管理") + .SetDefaultDocking(Panel.Docking.None) + .InitSize(1100, 720) + .InitPos(false, 0, 0, 0.5f, 0.5f, 0.5f, 0.5f); + _panel = panel; + panel.IfTerminalQuit(() => { if (_panel == panel) _panel = null; }); + + panel.Define(pb => + { + if (pb.Closing()) + { + SaveData(); + panel.Exit(); + _panel = null; + return; + } + + pb.SeparatorText("门控制器"); + pb.Table("door-ctrl-list", new[] { "控制器编码", "IP", "端口", "类型", "Ctrl操作" }, + _controllers.Count, (row, i) => + { + var c = _controllers[i]; + row.Label($"{c.Index}"); + row.Label(c.Ip); + row.Label($"{c.Port}"); + row.Label(c.Type); + if (row.ButtonGroup(new[] { "选控制器" }, new[] { "选择该控制器" }) == 0) + { + _selectedCtrlIdx = i; + _selectedDoorIdx = -1; + LoadControllerFields(c); + ClearDoorFields(); + } + }, height: 8, enableSearch: true); + + pb.SeparatorText("控制器编辑"); + var (ip, _) = pb.TextInput("1. IP", _ctrlIp, alwaysReturnString: true); + _ctrlIp = ip; + pb.SameLine(12); + var (port, _) = pb.TextInput("2. 端口", _ctrlPort, alwaysReturnString: true); + _ctrlPort = port; + var (idx, _) = pb.TextInput("3. 控制器编码", _ctrlIndex, alwaysReturnString: true); + _ctrlIndex = idx; + if (_typeNames.Length > 0) + pb.DropdownBox("4. 类型", _typeNames, ref _typeIdx); + + if (pb.Button("添加控制器", distinct: "door-add-ctrl")) AddController(); + pb.SameLine(8); + if (pb.Button("保存控制器", distinct: "door-save-ctrl")) SaveController(); + pb.SameLine(8); + if (pb.Button("删除控制器", distinct: "door-del-ctrl")) ConfirmDeleteController(); + + var doors = GetSelectedController()?.Doors ?? new List(); + pb.SeparatorText($"门列表(控制器 #{GetSelectedController()?.Index ?? 0},共 {doors.Count} 扇)"); + pb.Table("door-list", new[] { "门编码", "控制地址", "开到位地址", "无控制", "门操作" }, + doors.Count, (row, i) => + { + var d = doors[i]; + row.Label($"{d.Index}"); + row.Label($"{d.ControlAddress}"); + row.Label($"{d.OpenStatusAddress}"); + row.Label(d.NoControl ? "是" : "否"); + if (row.ButtonGroup(new[] { "选门" }, new[] { "选择该门" }) == 0) + { + _selectedDoorIdx = i; + LoadDoorFields(d); + } + }, height: 6, enableSearch: true); + + pb.SeparatorText("门编辑"); + var (dIdx, _) = pb.TextInput("5. 门编码", _doorIndex, alwaysReturnString: true); + _doorIndex = dIdx; + var (cAddr, _) = pb.TextInput("6. 控制地址", _doorCtrlAddr, alwaysReturnString: true); + _doorCtrlAddr = cAddr; + var (oAddr, _) = pb.TextInput("7. 开到位地址", _doorOpenAddr, alwaysReturnString: true); + _doorOpenAddr = oAddr; + pb.CheckBox("8. 无控制", ref _doorNoControl); + + if (pb.Button("添加门", distinct: "door-add")) AddDoor(); + pb.SameLine(8); + if (pb.Button("保存门", distinct: "door-save")) SaveDoor(); + pb.SameLine(8); + if (pb.Button("删除门", distinct: "door-del")) ConfirmDeleteDoor(); + + if (!string.IsNullOrEmpty(_status)) + { + pb.Separator(); + pb.Label(_status); + } + }); } - /// - /// 初始化类型下拉框,显示 DoorTypeAttribute.Name - /// - private void InitializeTypeComboBox() - { - comboBoxType.Items.Clear(); + private static DoorControllerModel GetSelectedController() => + _selectedCtrlIdx >= 0 && _selectedCtrlIdx < _controllers.Count ? _controllers[_selectedCtrlIdx] : null; + private static DoorModel GetSelectedDoor() + { + var ctrl = GetSelectedController(); + return ctrl != null && _selectedDoorIdx >= 0 && _selectedDoorIdx < ctrl.Doors.Count + ? ctrl.Doors[_selectedDoorIdx] : null; + } + + private static string[] DiscoverTypes() + { try { - // 获取当前命名空间下所有继承自BasicDoorController且带有DoorTypeAttribute特性的类 - // 在插件/宿主环境下 GetExecutingAssembly 可能不是 StandardScene.dll - // 跨程序集发现:门控制器具体类型可能位于卫星插件 dll(StandardScene.Devices.Door)。 - var controllerTypes = SimpleLite.Utils.UiTypeDiscovery.AllTypes() - .Where(t => t.IsClass - && !t.IsAbstract + var names = SimpleLite.Utils.UiTypeDiscovery.AllTypes() + .Where(t => t.IsClass && !t.IsAbstract && t.Namespace == typeof(BasicDoorController).Namespace && t.IsSubclassOf(typeof(BasicDoorController)) && t.IsDefined(typeof(DoorTypeAttribute), false)) - .ToList(); + .Select(t => t.GetCustomAttribute()?.Name) + .Where(n => !string.IsNullOrWhiteSpace(n)) + .OrderBy(n => n) + .ToArray(); + return names.Length > 0 ? names : new[] { "ModbusDoorController" }; + } + catch + { + return new[] { "ModbusDoorController" }; + } + } - var typeNames = new List(); - foreach (var type in controllerTypes) - { - var attr = type.GetCustomAttribute(); - if (attr != null && !string.IsNullOrWhiteSpace(attr.Name)) - { - typeNames.Add(attr.Name); - } - } - - // 按名称排序 - typeNames.Sort(); - - foreach (var typeName in typeNames) - { - comboBoxType.Items.Add(typeName); - } - - // 如果没有找到任何类型,添加默认选项 - if (comboBoxType.Items.Count == 0) - { - comboBoxType.Items.Add("ModbusDoorController"); - } + private static void LoadData() + { + try + { + if (!File.Exists(DataFilePath)) { _controllers = new List(); return; } + var json = File.ReadAllText(DataFilePath, Encoding.UTF8); + _controllers = string.IsNullOrWhiteSpace(json) + ? new List() + : json.JsonTo>() ?? new List(); } catch (Exception ex) { - MessageBox.Show($"初始化类型下拉框失败: {ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error); - comboBoxType.Items.Add("ModbusDoorController"); + _controllers = new List(); + _status = $"加载失败: {ex.Message}"; } } - /// - /// 设置ListView的视觉样式 - /// - private void SetupListViewStyles() + private static void SaveData() { - SetupListView(doorControllerListView, - ControllerListView_DrawItem, - ControllerListView_DrawSubItem, - ControllerListView_DrawColumnHeader, - ControllerListView_MouseMove, - ControllerListView_MouseLeave); - - SetupListView(doorListView, - DoorListView_DrawItem, - DoorListView_DrawSubItem, - DoorListView_DrawColumnHeader, - DoorListView_MouseMove, - DoorListView_MouseLeave); - } - - private void SetupListView(ListView listView, - DrawListViewItemEventHandler itemHandler, - DrawListViewSubItemEventHandler subItemHandler, - DrawListViewColumnHeaderEventHandler headerHandler, - MouseEventHandler mouseMoveHandler, - EventHandler mouseLeaveHandler) - { - listView.OwnerDraw = true; - listView.BackColor = Color.White; - listView.DrawItem += itemHandler; - listView.DrawSubItem += subItemHandler; - listView.DrawColumnHeader += headerHandler; - listView.MouseMove += mouseMoveHandler; - listView.MouseLeave += mouseLeaveHandler; - - // 启用双缓冲 - typeof(Control)?.GetProperty("DoubleBuffered", BindingFlags.Instance | BindingFlags.NonPublic)? - .SetValue(listView, true, null); - } - - private static readonly Color RowEvenColor = Color.FromArgb(250, 250, 252); - private static readonly Color RowOddColor = Color.White; - private static readonly Color RowHighlightColor = Color.FromArgb(230, 240, 255); - private static readonly Color TextRegularColor = Color.FromArgb(68, 68, 68); - private static readonly Color TextHighlightColor = Color.FromArgb(51, 51, 51); - - private void ControllerListView_MouseMove(object sender, MouseEventArgs e) - { - UpdateHoverIndex(doorControllerListView, e, true); - } - - private void ControllerListView_MouseLeave(object sender, EventArgs e) - { - ResetHoverIndex(doorControllerListView, true); - } - - private void DoorListView_MouseMove(object sender, MouseEventArgs e) - { - UpdateHoverIndex(doorListView, e, false); - } - - private void DoorListView_MouseLeave(object sender, EventArgs e) - { - ResetHoverIndex(doorListView, false); - } - - private void UpdateHoverIndex(ListView listView, MouseEventArgs e, bool isControllerList) - { - var hoveredItem = listView.GetItemAt(e.X, e.Y); - int newIndex = hoveredItem?.Index ?? -1; - - if (isControllerList) + var json = _controllers.ToJson(); + var path = DataFilePath; + Task.Run(() => { - if (_controllerHoverIndex != newIndex) + try { - _controllerHoverIndex = newIndex; - listView.Invalidate(); + lock (SaveLock) + File.WriteAllText(path, json, Encoding.UTF8); } - } - else - { - if (_doorHoverIndex != newIndex) + catch (Exception ex) { - _doorHoverIndex = newIndex; - listView.Invalidate(); + _status = $"保存失败: {ex.Message}"; + _panel?.Repaint(); } - } + }); } - private void ResetHoverIndex(ListView listView, bool isControllerList) + private static void LoadControllerFields(DoorControllerModel c) { - if (isControllerList) - { - if (_controllerHoverIndex != -1) - { - _controllerHoverIndex = -1; - listView.Invalidate(); - } - } - else - { - if (_doorHoverIndex != -1) - { - _doorHoverIndex = -1; - listView.Invalidate(); - } - } + _ctrlIp = c.Ip; + _ctrlPort = c.Port.ToString(); + _ctrlIndex = c.Index.ToString(); + _typeIdx = Math.Max(0, Array.IndexOf(_typeNames, c.Type)); + if (_typeIdx < 0) _typeIdx = 0; } - private void ControllerListView_DrawItem(object sender, DrawListViewItemEventArgs e) + private static void ClearControllerFields() { - var isHighlighted = e.Item.Selected - || e.ItemIndex == _controllerHoverIndex - || (e.State & ListViewItemStates.Focused) != 0; + _ctrlIp = ""; + _ctrlPort = "502"; + _ctrlIndex = ""; + _typeIdx = 0; + } - var backColor = isHighlighted - ? RowHighlightColor - : (e.ItemIndex % 2 == 0 ? RowEvenColor : RowOddColor); + private static void LoadDoorFields(DoorModel d) + { + _doorIndex = d.Index.ToString(); + _doorCtrlAddr = d.ControlAddress.ToString(); + _doorOpenAddr = d.OpenStatusAddress.ToString(); + _doorNoControl = d.NoControl; + } - using (var brush = new SolidBrush(backColor)) + private static void ClearDoorFields() + { + _doorIndex = ""; + _doorCtrlAddr = ""; + _doorOpenAddr = ""; + _doorNoControl = false; + } + + private static void AddController() + { + if (!TryParseControllerInput(out var index, out var ip, out var port, out var type, false, out var err)) { - e.Graphics.FillRectangle(brush, e.Bounds); + CycleUiHelper.Alert("错误", err); + return; } + if (_controllers.Any(c => c.Index == index)) { CycleUiHelper.Alert("错误", $"编码 {index} 已存在"); return; } + if (_controllers.Any(c => c.Ip == ip)) { CycleUiHelper.Alert("错误", $"IP {ip} 已存在"); return; } - var textColor = isHighlighted ? TextHighlightColor : TextRegularColor; - - TextRenderer.DrawText(e.Graphics, e.Item.Text, e.Item.Font, e.Bounds, - textColor, - TextFormatFlags.VerticalCenter | TextFormatFlags.Left | TextFormatFlags.EndEllipsis); - - e.DrawFocusRectangle(); + var c = new DoorControllerModel { Index = index, Ip = ip, Port = port, Type = type }; + _controllers.Add(c); + _selectedCtrlIdx = _controllers.Count - 1; + SaveData(); + _status = "已添加门控制器"; + _panel?.Repaint(); } - private void ControllerListView_DrawSubItem(object sender, DrawListViewSubItemEventArgs e) + private static void SaveController() { - var isHighlighted = e.Item.Selected - || e.ItemIndex == _controllerHoverIndex - || (e.ItemState & ListViewItemStates.Focused) != 0; - - var backColor = isHighlighted - ? RowHighlightColor - : (e.ItemIndex % 2 == 0 ? RowEvenColor : RowOddColor); - - using (var brush = new SolidBrush(backColor)) + var cur = GetSelectedController(); + if (cur == null) { CycleUiHelper.Alert("提示", "请先选择要保存的门控制器"); return; } + if (!TryParseControllerInput(out var index, out var ip, out var port, out var type, true, out var err)) { - e.Graphics.FillRectangle(brush, e.Bounds); + CycleUiHelper.Alert("错误", err); + return; } + if (_controllers.Any(c => c.Index == index && c != cur)) { CycleUiHelper.Alert("错误", $"编码 {index} 已存在"); return; } + if (_controllers.Any(c => c.Ip == ip && c != cur)) { CycleUiHelper.Alert("错误", $"IP {ip} 已存在"); return; } - var textColor = isHighlighted ? TextHighlightColor : TextRegularColor; - - TextRenderer.DrawText(e.Graphics, e.SubItem.Text, e.SubItem.Font, e.Bounds, - textColor, - TextFormatFlags.VerticalCenter | TextFormatFlags.Left | TextFormatFlags.EndEllipsis); + cur.Index = index; + cur.Ip = ip; + cur.Port = port; + cur.Type = type; + SaveData(); + _status = "门控制器已保存"; + CycleUiHelper.Alert("提示", "保存成功"); + _panel?.Repaint(); } - private void ControllerListView_DrawColumnHeader(object sender, DrawListViewColumnHeaderEventArgs e) + private static void ConfirmDeleteController() { - e.Graphics.FillRectangle(new SolidBrush(Color.FromArgb(245, 247, 250)), e.Bounds); - - e.Graphics.DrawLine(new Pen(Color.FromArgb(220, 220, 220)), - e.Bounds.Left, e.Bounds.Bottom - 1, e.Bounds.Right, e.Bounds.Bottom - 1); - - TextRenderer.DrawText(e.Graphics, e.Header.Text, - new Font("微软雅黑", 10.5F, FontStyle.Bold), - e.Bounds, Color.FromArgb(68, 68, 68), - TextFormatFlags.VerticalCenter | TextFormatFlags.Left | TextFormatFlags.HorizontalCenter); - } - - private void DoorListView_DrawItem(object sender, DrawListViewItemEventArgs e) - { - var isHighlighted = e.Item.Selected - || e.ItemIndex == _doorHoverIndex - || (e.State & ListViewItemStates.Focused) != 0; - - var backColor = isHighlighted - ? RowHighlightColor - : (e.ItemIndex % 2 == 0 ? RowEvenColor : RowOddColor); - - using (var brush = new SolidBrush(backColor)) + var cur = GetSelectedController(); + if (cur == null) { CycleUiHelper.Alert("提示", "请选择要删除的门控制器"); return; } + CycleUiHelper.ConfirmThen($"删除编码为 {cur.Index} 的门控制器?", () => { - e.Graphics.FillRectangle(brush, e.Bounds); - } - - var textColor = isHighlighted ? TextHighlightColor : TextRegularColor; - - TextRenderer.DrawText(e.Graphics, e.Item.Text, e.Item.Font, e.Bounds, - textColor, - TextFormatFlags.VerticalCenter | TextFormatFlags.Left | TextFormatFlags.EndEllipsis); - - e.DrawFocusRectangle(); - } - - private void DoorListView_DrawSubItem(object sender, DrawListViewSubItemEventArgs e) - { - var isHighlighted = e.Item.Selected - || e.ItemIndex == _doorHoverIndex - || (e.ItemState & ListViewItemStates.Focused) != 0; - - var backColor = isHighlighted - ? RowHighlightColor - : (e.ItemIndex % 2 == 0 ? RowEvenColor : RowOddColor); - - using (var brush = new SolidBrush(backColor)) - { - e.Graphics.FillRectangle(brush, e.Bounds); - } - - var textColor = isHighlighted ? TextHighlightColor : TextRegularColor; - - TextRenderer.DrawText(e.Graphics, e.SubItem.Text, e.SubItem.Font, e.Bounds, - textColor, - TextFormatFlags.VerticalCenter | TextFormatFlags.Left | TextFormatFlags.EndEllipsis); - } - - private void DoorListView_DrawColumnHeader(object sender, DrawListViewColumnHeaderEventArgs e) - { - e.Graphics.FillRectangle(new SolidBrush(Color.FromArgb(245, 247, 250)), e.Bounds); - - e.Graphics.DrawLine(new Pen(Color.FromArgb(220, 220, 220)), - e.Bounds.Left, e.Bounds.Bottom - 1, e.Bounds.Right, e.Bounds.Bottom - 1); - - TextRenderer.DrawText(e.Graphics, e.Header.Text, - new Font("微软雅黑", 10.5F, FontStyle.Bold), - e.Bounds, Color.FromArgb(68, 68, 68), - TextFormatFlags.VerticalCenter | TextFormatFlags.Left | TextFormatFlags.HorizontalCenter); - } - - /// - /// 刷新门控制器列表 - /// - private void RefreshControllerList() - { - doorControllerListView.Items.Clear(); - foreach (var controller in _doorControllers) - { - var item = new ListViewItem(controller.Index.ToString()); - item.SubItems.Add(controller.Ip); - item.SubItems.Add(controller.Port.ToString()); - item.SubItems.Add(controller.Type); - item.Tag = controller; - item.UseItemStyleForSubItems = false; - doorControllerListView.Items.Add(item); - } - } - - /// - /// 刷新门列表 - /// - private void RefreshDoorList() - { - doorListView.Items.Clear(); - if (_currentController != null) - { - foreach (var door in _currentController.Doors) - { - var item = new ListViewItem(door.Index.ToString()); - item.SubItems.Add(door.ControlAddress.ToString()); - item.SubItems.Add(door.OpenStatusAddress.ToString()); - item.Tag = door; - item.UseItemStyleForSubItems = false; - doorListView.Items.Add(item); - } - } - } - - /// - /// 门控制器列表选择改变 - /// - private void doorControllerListView_SelectedIndexChanged(object sender, EventArgs e) - { - if (doorControllerListView.SelectedItems.Count > 0) - { - _currentController = doorControllerListView.SelectedItems[0].Tag as DoorControllerModel; - if (_currentController != null) - { - // 填充门控制器编辑区域 - textBoxIp.Text = _currentController.Ip; - textBoxPort.Text = _currentController.Port.ToString(); - textBoxControllerIndex.Text = _currentController.Index.ToString(); - // 设置类型下拉框 - if (comboBoxType.Items.Contains(_currentController.Type)) - { - comboBoxType.SelectedItem = _currentController.Type; - } - else - { - comboBoxType.SelectedIndex = comboBoxType.Items.Count > 0 ? 0 : -1; - } - - // 刷新门列表 - RefreshDoorList(); - } - } - else - { - _currentController = null; + _controllers.Remove(cur); + _selectedCtrlIdx = -1; + _selectedDoorIdx = -1; ClearControllerFields(); - doorListView.Items.Clear(); - } - } - - /// - /// 门列表选择改变 - /// - private void doorListView_SelectedIndexChanged(object sender, EventArgs e) - { - if (doorListView.SelectedItems.Count > 0) - { - _currentDoor = doorListView.SelectedItems[0].Tag as DoorModel; - if (_currentDoor != null) - { - // 填充门编辑区域 - textBoxDoorIndex.Text = _currentDoor.Index.ToString(); - textBoxControlAddress.Text = _currentDoor.ControlAddress.ToString(); - textBoxOpenStatusAddress.Text = _currentDoor.OpenStatusAddress.ToString(); - checkBoxNoControl.Checked = _currentDoor.NoControl; - } - } - else - { - _currentDoor = null; ClearDoorFields(); - } - } - - /// - /// 添加门控制器 - /// - private void btnAddController_Click(object sender, EventArgs e) - { - try - { - string ip = textBoxIp.Text.Trim(); - string portText = textBoxPort.Text.Trim(); - string indexText = textBoxControllerIndex.Text.Trim(); - string type = comboBoxType.SelectedItem?.ToString() ?? string.Empty; - - int newIndex; - if (!string.IsNullOrWhiteSpace(indexText)) - { - if (!int.TryParse(indexText, out newIndex)) - { - MessageBox.Show("编码必须是数字", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error); - return; - } - } - else - { - newIndex = _doorControllers.Count > 0 ? _doorControllers.Max(c => c.Index) + 1 : 1; - } - - string newIp; - if (!string.IsNullOrWhiteSpace(ip)) - { - if (!IsValidIpAddress(ip)) - { - MessageBox.Show("无效的IP地址,例如:192.168.1.100", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error); - return; - } - newIp = ip; - } - else - { - newIp = "192.168.1.100"; - } - - int newPort; - if (!string.IsNullOrWhiteSpace(portText)) - { - if (!int.TryParse(portText, out newPort)) - { - MessageBox.Show("端口必须是数字", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error); - return; - } - } - else - { - newPort = 502; - } - - string newType; - if (!string.IsNullOrWhiteSpace(type)) - { - newType = type; - } - else - { - newType = comboBoxType.Items.Count > 0 ? comboBoxType.Items[0].ToString() : "ModbusDoorController"; - } - - // 检查编码是否重复 - if (_doorControllers.Any(c => c.Index == newIndex)) - { - MessageBox.Show($"编码 {newIndex} 已存在,请使用其他编码", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error); - return; - } - - // 检查IP地址是否重复 - if (_doorControllers.Any(c => c.Ip == newIp)) - { - MessageBox.Show($"IP地址 {newIp} 已存在,请使用其他IP地址", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error); - return; - } - - var newController = new DoorControllerModel - { - Index = newIndex, - Ip = newIp, - Port = newPort, - Type = newType - }; - - _doorControllers.Add(newController); - RefreshControllerList(); SaveData(); - - // 选中新添加的门控制器 - foreach (ListViewItem item in doorControllerListView.Items) - { - if (item.Tag == newController) - { - item.Selected = true; - item.EnsureVisible(); - break; - } - } - } - catch (Exception ex) - { - MessageBox.Show($"添加门控制器失败: {ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error); - } + _status = "已删除门控制器"; + _panel?.Repaint(); + }); } - /// - /// 删除门控制器 - /// - private void btnDeleteController_Click(object sender, EventArgs e) + private static void AddDoor() { - if (_currentController == null) + var ctrl = GetSelectedController(); + if (ctrl == null) { CycleUiHelper.Alert("提示", "请先选择门控制器"); return; } + if (!TryParseDoorInput(out var index, out var cAddr, out var oAddr, false, out var err)) { - MessageBox.Show("请选择要删除的门控制器", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information); + CycleUiHelper.Alert("错误", err); return; } + if (ctrl.Doors.Any(d => d.Index == index)) { CycleUiHelper.Alert("错误", $"门编码 {index} 已存在"); return; } - var result = MessageBox.Show($"删除编码为 {_currentController.Index} 的门控制器?", "确认删除", - MessageBoxButtons.YesNo, MessageBoxIcon.Question); - - if (result == DialogResult.Yes) + var door = new DoorModel { - _doorControllers.Remove(_currentController); - _currentController = null; - ClearControllerFields(); - RefreshControllerList(); - doorListView.Items.Clear(); - SaveData(); - } + Index = index, + ControlAddress = cAddr, + OpenStatusAddress = oAddr, + NoControl = _doorNoControl + }; + ctrl.Doors.Add(door); + _selectedDoorIdx = ctrl.Doors.Count - 1; + SaveData(); + _status = "已添加门"; + _panel?.Repaint(); } - /// - /// 保存门控制器 - /// - private void btnSaveController_Click(object sender, EventArgs e) + private static void SaveDoor() { - if (_currentController == null) + var ctrl = GetSelectedController(); + var door = GetSelectedDoor(); + if (ctrl == null || door == null) { CycleUiHelper.Alert("提示", "请先选择要保存的门"); return; } + if (!TryParseDoorInput(out var index, out var cAddr, out var oAddr, true, out var err)) { - MessageBox.Show("请选择要保存的门控制器", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information); + CycleUiHelper.Alert("错误", err); return; } + if (ctrl.Doors.Any(d => d.Index == index && d != door)) { CycleUiHelper.Alert("错误", $"门编码 {index} 已存在"); return; } - try - { - string newIp = textBoxIp.Text.Trim(); - - if (!IsValidIpAddress(newIp)) - { - MessageBox.Show("无效的IP地址,例如:192.168.1.100", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error); - return; - } - - if (!int.TryParse(textBoxPort.Text, out int port)) - { - MessageBox.Show("端口必须是数字", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error); - return; - } - _currentController.Port = port; - - if (!int.TryParse(textBoxControllerIndex.Text, out int index)) - { - MessageBox.Show("编码必须是数字", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error); - return; - } - - // 检查编码是否重复(排除当前项) - if (_doorControllers.Any(c => c.Index == index && c != _currentController)) - { - MessageBox.Show($"编码 {index} 已存在,请使用其他编码", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error); - return; - } - - // 检查IP地址是否重复(排除当前项) - if (_doorControllers.Any(c => c.Ip == newIp && c != _currentController)) - { - MessageBox.Show($"IP地址 {newIp} 已存在,请使用其他IP地址", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error); - return; - } - - _currentController.Index = index; - _currentController.Type = comboBoxType.SelectedItem?.ToString() ?? string.Empty; - _currentController.Ip = newIp; - - RefreshControllerList(); - SaveData(); - MessageBox.Show("保存成功", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information); - } - catch (Exception ex) - { - MessageBox.Show($"保存失败: {ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error); - } + door.Index = index; + door.ControlAddress = cAddr; + door.OpenStatusAddress = oAddr; + door.NoControl = _doorNoControl; + SaveData(); + _status = "门已保存"; + CycleUiHelper.Alert("提示", "保存成功"); + _panel?.Repaint(); } - /// - /// 添加门 - /// - private void btnAddDoor_Click(object sender, EventArgs e) + private static void ConfirmDeleteDoor() { - if (_currentController == null) + var ctrl = GetSelectedController(); + var door = GetSelectedDoor(); + if (ctrl == null || door == null) { CycleUiHelper.Alert("提示", "请选择要删除的门"); return; } + CycleUiHelper.ConfirmThen($"删除编码为 {door.Index} 的门?", () => { - MessageBox.Show("请先选择门控制器", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information); - return; - } - - try - { - string indexText = textBoxDoorIndex.Text.Trim(); - string controlAddressText = textBoxControlAddress.Text.Trim(); - string openStatusAddressText = textBoxOpenStatusAddress.Text.Trim(); - - int newIndex; - if (!string.IsNullOrWhiteSpace(indexText)) - { - if (!int.TryParse(indexText, out newIndex)) - { - MessageBox.Show("门编码必须是数字", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error); - return; - } - } - else - { - newIndex = _currentController.Doors.Count > 0 - ? _currentController.Doors.Max(d => d.Index) + 1 - : 1; - } - - // 检查门编码是否重复 - if (_currentController.Doors.Any(d => d.Index == newIndex)) - { - MessageBox.Show($"门编码 {newIndex} 已存在,请使用其他编码", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error); - return; - } - - ushort controlAddress = 0; - if (!string.IsNullOrWhiteSpace(controlAddressText)) - { - if (!ushort.TryParse(controlAddressText, out controlAddress)) - { - MessageBox.Show("控制地址必须是0-65535之间的数字", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error); - return; - } - } - - ushort openStatusAddress = 0; - if (!string.IsNullOrWhiteSpace(openStatusAddressText)) - { - if (!ushort.TryParse(openStatusAddressText, out openStatusAddress)) - { - MessageBox.Show("开到位地址必须是0-65535之间的数字", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error); - return; - } - } - - var newDoor = new DoorModel - { - Index = newIndex, - ControlAddress = controlAddress, - OpenStatusAddress = openStatusAddress, - NoControl = checkBoxNoControl.Checked - }; - - _currentController.Doors.Add(newDoor); - RefreshDoorList(); - SaveData(); - - // 选中新添加的门 - foreach (ListViewItem item in doorListView.Items) - { - if (item.Tag == newDoor) - { - item.Selected = true; - item.EnsureVisible(); - break; - } - } - } - catch (Exception ex) - { - MessageBox.Show($"添加门失败: {ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error); - } - } - - /// - /// 删除门 - /// - private void btnDeleteDoor_Click(object sender, EventArgs e) - { - if (_currentController == null) - { - MessageBox.Show("请先选择门控制器", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information); - return; - } - - if (_currentDoor == null) - { - MessageBox.Show("请选择要删除的门", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information); - return; - } - - var result = MessageBox.Show($"删除编码为 {_currentDoor.Index} 的门?", "确认删除", - MessageBoxButtons.YesNo, MessageBoxIcon.Question); - - if (result == DialogResult.Yes) - { - _currentController.Doors.Remove(_currentDoor); - _currentDoor = null; + ctrl.Doors.Remove(door); + _selectedDoorIdx = -1; ClearDoorFields(); - RefreshDoorList(); SaveData(); - } + _status = "已删除门"; + _panel?.Repaint(); + }); } - /// - /// 保存门 - /// - private void btnSaveDoor_Click(object sender, EventArgs e) + private static bool TryParseControllerInput(out int index, out string ip, out int port, out string type, bool requireSelection, out string err) { - if (_currentController == null) + index = 0; ip = ""; port = 502; type = ""; err = ""; + ip = string.IsNullOrWhiteSpace(_ctrlIp) ? "192.168.1.100" : _ctrlIp.Trim(); + if (!IsValidIp(ip)) { err = "无效的 IP 地址"; return false; } + if (!int.TryParse(_ctrlPort.Trim(), out port)) { err = "端口必须是数字"; return false; } + if (!int.TryParse(_ctrlIndex.Trim(), out index)) { - MessageBox.Show("请先选择门控制器", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information); - return; - } - - if (_currentDoor == null) - { - MessageBox.Show("请选择要保存的门", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information); - return; - } - - try - { - if (!int.TryParse(textBoxDoorIndex.Text, out int index)) - { - MessageBox.Show("门编码必须是数字", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error); - return; - } - - // 检查门编码是否重复(排除当前门) - if (_currentController.Doors.Any(d => d.Index == index && d != _currentDoor)) - { - MessageBox.Show($"门编码 {index} 已存在,请使用其他编码", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error); - return; - } - - if (!ushort.TryParse(textBoxControlAddress.Text, out ushort controlAddress)) - { - MessageBox.Show("控制地址必须是0-65535之间的数字", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error); - return; - } - - if (!ushort.TryParse(textBoxOpenStatusAddress.Text, out ushort openStatusAddress)) - { - MessageBox.Show("开到位地址必须是0-65535之间的数字", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error); - return; - } - - _currentDoor.Index = index; - _currentDoor.ControlAddress = controlAddress; - _currentDoor.OpenStatusAddress = openStatusAddress; - _currentDoor.NoControl = checkBoxNoControl.Checked; - - RefreshDoorList(); - SaveData(); - MessageBox.Show("保存成功", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information); - } - catch (Exception ex) - { - MessageBox.Show($"保存失败: {ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error); + index = _controllers.Count > 0 ? _controllers.Max(c => c.Index) + 1 : 1; + if (!string.IsNullOrWhiteSpace(_ctrlIndex.Trim())) { err = "编码必须是数字"; return false; } } + type = _typeNames.Length > 0 ? _typeNames[_typeIdx] : "ModbusDoorController"; + if (requireSelection && GetSelectedController() == null) { err = "请先选择门控制器"; return false; } + return true; } - /// - /// 清空门控制器字段 - /// - private void ClearControllerFields() + private static bool TryParseDoorInput(out int index, out ushort cAddr, out ushort oAddr, bool requireSelection, out string err) { - textBoxIp.Text = string.Empty; - textBoxPort.Text = string.Empty; - textBoxControllerIndex.Text = string.Empty; - comboBoxType.SelectedIndex = -1; + index = 0; cAddr = 0; oAddr = 0; err = ""; + var ctrl = GetSelectedController(); + if (requireSelection && (ctrl == null || GetSelectedDoor() == null)) { err = "请先选择门"; return false; } + if (!int.TryParse(_doorIndex.Trim(), out index)) + { + index = ctrl?.Doors.Count > 0 ? ctrl.Doors.Max(d => d.Index) + 1 : 1; + if (!string.IsNullOrWhiteSpace(_doorIndex.Trim())) { err = "门编码必须是数字"; return false; } + } + if (!ushort.TryParse(string.IsNullOrWhiteSpace(_doorCtrlAddr) ? "0" : _doorCtrlAddr.Trim(), out cAddr)) + { err = "控制地址必须是 0-65535"; return false; } + if (!ushort.TryParse(string.IsNullOrWhiteSpace(_doorOpenAddr) ? "0" : _doorOpenAddr.Trim(), out oAddr)) + { err = "开到位地址必须是 0-65535"; return false; } + return true; } - /// - /// 清空门字段 - /// - private void ClearDoorFields() + private static bool IsValidIp(string ip) { - textBoxDoorIndex.Text = string.Empty; - textBoxControlAddress.Text = string.Empty; - textBoxOpenStatusAddress.Text = string.Empty; - checkBoxNoControl.Checked = false; - } - - /// - /// 加载数据 - /// - private void LoadData() - { - try - { - if (File.Exists(_dataFilePath)) - { - var jsonContent = File.ReadAllText(_dataFilePath, Encoding.UTF8); - if (!string.IsNullOrWhiteSpace(jsonContent)) - { - _doorControllers = jsonContent.JsonTo>(); - if (_doorControllers == null) - { - _doorControllers = new List(); - } - } - else - { - _doorControllers = new List(); - } - } - else - { - _doorControllers = new List(); - } - } - catch (Exception ex) - { - MessageBox.Show($"加载数据失败: {ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error); - _doorControllers = new List(); - } - } - - /// - /// 保存数据 - /// - private void SaveData() - { - try - { - var jsonContent = _doorControllers.ToJson(); - File.WriteAllText(_dataFilePath, jsonContent, Encoding.UTF8); - } - catch (Exception ex) - { - MessageBox.Show($"保存数据失败: {ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error); - } - } - - /// - /// 验证IP地址格式 - /// - private bool IsValidIpAddress(string ipAddress) - { - if (string.IsNullOrWhiteSpace(ipAddress)) - { - return false; - } - - string pattern = @"^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$"; - if (Regex.IsMatch(ipAddress, pattern)) - { - IPAddress address; - return IPAddress.TryParse(ipAddress, out address) && address.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork; - } - - return false; - } - - /// - /// 窗体关闭事件 - /// - private void DoorManager_FormClosing(object sender, FormClosingEventArgs e) - { - if (e.CloseReason == CloseReason.UserClosing) - { - // 关闭前保存数据 - SaveData(); - e.Cancel = true; - this.Visible = false; - } - } - - /// - /// 打开管理界面(静态方法) - /// - public static void OpenViewer() - { - try - { - var manager = Instance; - - if (manager.Visible) - { - if (manager.WindowState == FormWindowState.Minimized) - { - manager.WindowState = FormWindowState.Normal; - } - manager.Activate(); - manager.BringToFront(); - } - else - { - manager.Show(); - manager.Activate(); - } - } - catch (Exception ex) - { - MessageBox.Show($"打开门控制器管理界面失败: {ex.Message}", "错误", - MessageBoxButtons.OK, MessageBoxIcon.Error); - } + if (string.IsNullOrWhiteSpace(ip)) return false; + var pattern = @"^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$"; + return Regex.IsMatch(ip, pattern) && IPAddress.TryParse(ip, out var addr) + && addr.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork; } } } diff --git a/StandardScene.Core/ExtendDevice/Door/DoorManager.resx b/StandardScene.Core/ExtendDevice/Door/DoorManager.resx deleted file mode 100644 index 4391a28..0000000 --- a/StandardScene.Core/ExtendDevice/Door/DoorManager.resx +++ /dev/null @@ -1,64 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - 17, 17 - - diff --git a/StandardScene.Core/ExtendDevice/Door/DoorMission.cs b/StandardScene.Core/ExtendDevice/Door/DoorMission.cs index 5a315ae..2ddc3ea 100644 --- a/StandardScene.Core/ExtendDevice/Door/DoorMission.cs +++ b/StandardScene.Core/ExtendDevice/Door/DoorMission.cs @@ -1013,29 +1013,11 @@ namespace StandardScene.ExtendDevice.Door { try { - var monitor = DoorMonitor.Instance; - - if (monitor.Visible) - { - if (monitor.WindowState == System.Windows.Forms.FormWindowState.Minimized) - { - monitor.WindowState = System.Windows.Forms.FormWindowState.Normal; - } - monitor.Activate(); - monitor.BringToFront(); - } - else - { - monitor.Show(); - monitor.Activate(); - } - - monitor.EnsureRefreshActive(); + DoorMonitor.Open(); } catch (Exception ex) { - System.Windows.Forms.MessageBox.Show($"打开门控监控界面失败: {ex.Message}", "错误", - System.Windows.Forms.MessageBoxButtons.OK, System.Windows.Forms.MessageBoxIcon.Error); + CycleUiHelper.Alert("错误", $"打开门控监控界面失败: {ex.Message}"); } } } diff --git a/StandardScene.Core/ExtendDevice/Door/DoorMonitor.Designer.cs b/StandardScene.Core/ExtendDevice/Door/DoorMonitor.Designer.cs deleted file mode 100644 index a705f2e..0000000 --- a/StandardScene.Core/ExtendDevice/Door/DoorMonitor.Designer.cs +++ /dev/null @@ -1,263 +0,0 @@ -namespace StandardScene.ExtendDevice.Door -{ - partial class DoorMonitor - { - /// - /// 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.doorListView = new System.Windows.Forms.ListView(); - this.columnHeaderControllerIndex = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); - this.columnHeaderDoorIndex = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); - this.columnHeaderState = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); - this.columnHeaderTarget = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); - this.columnHeaderSource = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); - this.columnHeaderManualRemain = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); - this.columnHeaderCarsInArea = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); - this.columnHeaderControlAddress = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); - this.columnHeaderOpenStatusAddress = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); - this.groupBoxControl = new System.Windows.Forms.GroupBox(); - this.btnClose = new System.Windows.Forms.Button(); - this.btnOpen = new System.Windows.Forms.Button(); - this.btnClearCars = new System.Windows.Forms.Button(); - this.labelDoorInfo = new System.Windows.Forms.Label(); - this.labelTitle = new System.Windows.Forms.Label(); - this.timerRefresh = new System.Windows.Forms.Timer(); - this.groupBoxControl.SuspendLayout(); - this.SuspendLayout(); - // - // doorListView - // - this.doorListView.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) - | System.Windows.Forms.AnchorStyles.Left) - | System.Windows.Forms.AnchorStyles.Right))); - this.doorListView.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; - this.doorListView.Columns.AddRange(new System.Windows.Forms.ColumnHeader[] { - this.columnHeaderControllerIndex, - this.columnHeaderDoorIndex, - this.columnHeaderState, - this.columnHeaderTarget, - this.columnHeaderSource, - this.columnHeaderManualRemain, - this.columnHeaderCarsInArea, - this.columnHeaderControlAddress, - this.columnHeaderOpenStatusAddress}); - this.doorListView.Font = new System.Drawing.Font("微软雅黑", 10.5F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - this.doorListView.FullRowSelect = true; - this.doorListView.GridLines = true; - this.doorListView.HeaderStyle = System.Windows.Forms.ColumnHeaderStyle.Nonclickable; - this.doorListView.HideSelection = false; - this.doorListView.Location = new System.Drawing.Point(15, 55); - this.doorListView.MultiSelect = false; - this.doorListView.Name = "doorListView"; - this.doorListView.OwnerDraw = true; - this.doorListView.Size = new System.Drawing.Size(800, 400); - this.doorListView.TabIndex = 0; - this.doorListView.UseCompatibleStateImageBehavior = false; - this.doorListView.View = System.Windows.Forms.View.Details; - this.doorListView.SelectedIndexChanged += new System.EventHandler(this.doorListView_SelectedIndexChanged); - // - // columnHeaderControllerIndex - // - this.columnHeaderControllerIndex.Text = "控制器编码"; - this.columnHeaderControllerIndex.Width = 120; - // - // columnHeaderDoorIndex - // - this.columnHeaderDoorIndex.Text = "门编码"; - this.columnHeaderDoorIndex.Width = 100; - // - // columnHeaderState - // - this.columnHeaderState.Text = "状态"; - this.columnHeaderState.Width = 100; - // - // columnHeaderTarget - // - this.columnHeaderTarget.Text = "控制目标"; - this.columnHeaderTarget.Width = 100; - // - // columnHeaderSource - // - this.columnHeaderSource.Text = "控制来源"; - this.columnHeaderSource.Width = 100; - // - // columnHeaderManualRemain - // - this.columnHeaderManualRemain.Text = "手动剩余(s)"; - this.columnHeaderManualRemain.Width = 110; - // - // columnHeaderCarsInArea - // - this.columnHeaderCarsInArea.Text = "车辆占用"; - this.columnHeaderCarsInArea.Width = 150; - // - // columnHeaderControlAddress - // - this.columnHeaderControlAddress.Text = "控制地址"; - this.columnHeaderControlAddress.Width = 120; - // - // columnHeaderOpenStatusAddress - // - this.columnHeaderOpenStatusAddress.Text = "开到位地址"; - this.columnHeaderOpenStatusAddress.Width = 120; - // - // groupBoxControl - // - this.groupBoxControl.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left) - | System.Windows.Forms.AnchorStyles.Right))); - this.groupBoxControl.Controls.Add(this.btnClose); - this.groupBoxControl.Controls.Add(this.btnOpen); - this.groupBoxControl.Controls.Add(this.btnClearCars); - this.groupBoxControl.Controls.Add(this.labelDoorInfo); - this.groupBoxControl.Font = new System.Drawing.Font("微软雅黑", 10.5F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - this.groupBoxControl.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(68)))), ((int)(((byte)(68)))), ((int)(((byte)(68))))); - this.groupBoxControl.Location = new System.Drawing.Point(15, 470); - this.groupBoxControl.Name = "groupBoxControl"; - this.groupBoxControl.Padding = new System.Windows.Forms.Padding(12, 10, 12, 12); - this.groupBoxControl.Size = new System.Drawing.Size(800, 120); - this.groupBoxControl.TabIndex = 1; - this.groupBoxControl.TabStop = false; - this.groupBoxControl.Text = "手动控制"; - // - // btnClose - // - this.btnClose.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(220)))), ((int)(((byte)(53)))), ((int)(((byte)(69))))); - this.btnClose.FlatAppearance.BorderSize = 0; - this.btnClose.FlatAppearance.MouseDownBackColor = System.Drawing.Color.FromArgb(((int)(((byte)(165)))), ((int)(((byte)(40)))), ((int)(((byte)(52))))); - this.btnClose.FlatAppearance.MouseOverBackColor = System.Drawing.Color.FromArgb(((int)(((byte)(187)))), ((int)(((byte)(45)))), ((int)(((byte)(59))))); - this.btnClose.FlatStyle = System.Windows.Forms.FlatStyle.Flat; - this.btnClose.Font = new System.Drawing.Font("微软雅黑", 12F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - this.btnClose.ForeColor = System.Drawing.Color.White; - this.btnClose.Location = new System.Drawing.Point(450, 50); - this.btnClose.Name = "btnClose"; - this.btnClose.Size = new System.Drawing.Size(120, 50); - this.btnClose.TabIndex = 2; - this.btnClose.Text = "关闭"; - this.btnClose.UseVisualStyleBackColor = false; - this.btnClose.Click += new System.EventHandler(this.btnClose_Click); - // - // btnOpen - // - this.btnOpen.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(40)))), ((int)(((byte)(167)))), ((int)(((byte)(69))))); - this.btnOpen.FlatAppearance.BorderSize = 0; - this.btnOpen.FlatAppearance.MouseDownBackColor = System.Drawing.Color.FromArgb(((int)(((byte)(30)))), ((int)(((byte)(125)))), ((int)(((byte)(52))))); - this.btnOpen.FlatAppearance.MouseOverBackColor = System.Drawing.Color.FromArgb(((int)(((byte)(33)))), ((int)(((byte)(136)))), ((int)(((byte)(56))))); - this.btnOpen.FlatStyle = System.Windows.Forms.FlatStyle.Flat; - this.btnOpen.Font = new System.Drawing.Font("微软雅黑", 12F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - this.btnOpen.ForeColor = System.Drawing.Color.White; - this.btnOpen.Location = new System.Drawing.Point(300, 50); - this.btnOpen.Name = "btnOpen"; - this.btnOpen.Size = new System.Drawing.Size(120, 50); - this.btnOpen.TabIndex = 1; - this.btnOpen.Text = "打开"; - this.btnOpen.UseVisualStyleBackColor = false; - this.btnOpen.Click += new System.EventHandler(this.btnOpen_Click); - // - // btnClearCars - // - this.btnClearCars.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(108)))), ((int)(((byte)(117)))), ((int)(((byte)(125))))); - this.btnClearCars.FlatAppearance.BorderSize = 0; - this.btnClearCars.FlatStyle = System.Windows.Forms.FlatStyle.Flat; - this.btnClearCars.Font = new System.Drawing.Font("微软雅黑", 10.5F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - this.btnClearCars.ForeColor = System.Drawing.Color.White; - this.btnClearCars.Location = new System.Drawing.Point(600, 50); - this.btnClearCars.Name = "btnClearCars"; - this.btnClearCars.Size = new System.Drawing.Size(140, 50); - this.btnClearCars.TabIndex = 3; - this.btnClearCars.Text = "清空占用"; - this.btnClearCars.UseVisualStyleBackColor = false; - this.btnClearCars.Click += new System.EventHandler(this.btnClearCars_Click); - // - // labelDoorInfo - // - this.labelDoorInfo.AutoSize = true; - this.labelDoorInfo.Font = new System.Drawing.Font("微软雅黑", 10.5F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - this.labelDoorInfo.Location = new System.Drawing.Point(20, 35); - this.labelDoorInfo.Name = "labelDoorInfo"; - this.labelDoorInfo.Size = new System.Drawing.Size(200, 24); - this.labelDoorInfo.TabIndex = 0; - this.labelDoorInfo.Text = "请选择要控制的门"; - // - // labelTitle - // - this.labelTitle.AutoSize = true; - this.labelTitle.Font = new System.Drawing.Font("微软雅黑", 16F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - this.labelTitle.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(51)))), ((int)(((byte)(51)))), ((int)(((byte)(51))))); - this.labelTitle.Location = new System.Drawing.Point(15, 12); - this.labelTitle.Name = "labelTitle"; - this.labelTitle.Size = new System.Drawing.Size(150, 42); - this.labelTitle.TabIndex = 2; - this.labelTitle.Text = "门控监控"; - // - // timerRefresh - // - this.timerRefresh.Interval = 1000; - this.timerRefresh.Tick += new System.EventHandler(this.timerRefresh_Tick); - // - // DoorMonitor - // - this.AutoScaleDimensions = new System.Drawing.SizeF(9F, 18F); - this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - this.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(245)))), ((int)(((byte)(245)))), ((int)(((byte)(247))))); - this.ClientSize = new System.Drawing.Size(830, 600); - this.Controls.Add(this.labelTitle); - this.Controls.Add(this.groupBoxControl); - this.Controls.Add(this.doorListView); - this.Font = new System.Drawing.Font("微软雅黑", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); - this.MinimumSize = new System.Drawing.Size(830, 600); - this.Name = "DoorMonitor"; - this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen; - this.Text = "门控监控"; - this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.DoorMonitor_FormClosing); - this.Load += new System.EventHandler(this.DoorMonitor_Load); - this.groupBoxControl.ResumeLayout(false); - this.groupBoxControl.PerformLayout(); - this.ResumeLayout(false); - this.PerformLayout(); - - } - - #endregion - - private System.Windows.Forms.ListView doorListView; - private System.Windows.Forms.ColumnHeader columnHeaderControllerIndex; - private System.Windows.Forms.ColumnHeader columnHeaderDoorIndex; - private System.Windows.Forms.ColumnHeader columnHeaderState; - private System.Windows.Forms.ColumnHeader columnHeaderTarget; - private System.Windows.Forms.ColumnHeader columnHeaderSource; - private System.Windows.Forms.ColumnHeader columnHeaderManualRemain; - private System.Windows.Forms.ColumnHeader columnHeaderCarsInArea; - private System.Windows.Forms.ColumnHeader columnHeaderControlAddress; - private System.Windows.Forms.ColumnHeader columnHeaderOpenStatusAddress; - private System.Windows.Forms.GroupBox groupBoxControl; - private System.Windows.Forms.Label labelDoorInfo; - private System.Windows.Forms.Button btnOpen; - private System.Windows.Forms.Button btnClose; - private System.Windows.Forms.Button btnClearCars; - private System.Windows.Forms.Label labelTitle; - private System.Windows.Forms.Timer timerRefresh; - } -} diff --git a/StandardScene.Core/ExtendDevice/Door/DoorMonitor.cs b/StandardScene.Core/ExtendDevice/Door/DoorMonitor.cs index 93f977b..b54184c 100644 --- a/StandardScene.Core/ExtendDevice/Door/DoorMonitor.cs +++ b/StandardScene.Core/ExtendDevice/Door/DoorMonitor.cs @@ -2,445 +2,241 @@ using System; using System.Collections.Generic; using System.Drawing; using System.Linq; -using System.Reflection; -using System.Windows.Forms; +using CycleGUI; using SimpleLite; +using StandardScene.Utils; namespace StandardScene.ExtendDevice.Door { - public partial class DoorMonitor : Form + /// + /// 门控监控界面(CycleGUI 版,替代原 WinForms DoorMonitor 窗体)。 + /// + /// 单实例:再次打开则把已有面板置前。 + /// pb.Table 展示门列表,单击行选中以进行手动控制。 + /// 约每 500ms 重绘刷新门状态快照(替代 WinForms 定时器)。 + /// + /// + public class DoorMonitor { - private static DoorMonitor _instance = null; - private static readonly object _lock = new object(); + private const string TableId = "door-monitor-list"; - private int _doorHoverIndex = -1; - private (int ControllerIndex, int DoorIndex)? _selectedDoor = null; + private static readonly Color SelectedRowColor = Color.FromArgb(230, 240, 255); - private static readonly Color RowEvenColor = Color.FromArgb(250, 250, 252); - private static readonly Color RowOddColor = Color.White; - private static readonly Color RowHighlightColor = Color.FromArgb(230, 240, 255); - private static readonly Color TextRegularColor = Color.FromArgb(68, 68, 68); - private static readonly Color TextHighlightColor = Color.FromArgb(51, 51, 51); - private static readonly Color StateOpenColor = Color.FromArgb(40, 167, 69); - private static readonly Color StateClosedColor = Color.FromArgb(220, 53, 69); + private static Panel _panel; + private static (int ControllerIndex, int DoorIndex)? _selectedDoor; - /// - /// 获取单例实例 - /// - public static DoorMonitor Instance + /// 打开(或置前)门控监控面板。 + public static void Open() { - get + if (_panel != null) { - if (_instance == null || _instance.IsDisposed) + try { - lock (_lock) - { - if (_instance == null || _instance.IsDisposed) - { - _instance = new DoorMonitor(); - } - } + _panel.BringToFront(); + return; } - return _instance; - } - } - - /// - /// 私有构造函数,确保单例模式 - /// - private DoorMonitor() - { - InitializeComponent(); - } - - /// - /// 确保刷新定时器处于激活状态,并立即刷新一次 - /// - public void EnsureRefreshActive() - { - if (IsDisposed) - { - return; - } - - if (!timerRefresh.Enabled) - { - timerRefresh.Start(); - } - - RefreshDoorList(); - } - - private void DoorMonitor_Load(object sender, EventArgs e) - { - SetupListViewStyles(); - // 禁用系统的悬停/热跟踪高亮,避免鼠标移动时短暂出现默认遮罩 - doorListView.HoverSelection = false; - doorListView.HotTracking = false; - EnsureRefreshActive(); - } - - /// - /// 设置ListView的视觉样式 - /// - private void SetupListViewStyles() - { - doorListView.OwnerDraw = true; - doorListView.BackColor = Color.White; - doorListView.DrawItem += DoorListView_DrawItem; - doorListView.DrawSubItem += DoorListView_DrawSubItem; - doorListView.DrawColumnHeader += DoorListView_DrawColumnHeader; - doorListView.MouseMove += DoorListView_MouseMove; - doorListView.MouseLeave += DoorListView_MouseLeave; - - // 启用双缓冲 - typeof(Control)?.GetProperty("DoubleBuffered", BindingFlags.Instance | BindingFlags.NonPublic)? - .SetValue(doorListView, true, null); - } - - private void DoorListView_MouseMove(object sender, MouseEventArgs e) - { - var hoveredItem = doorListView.GetItemAt(e.X, e.Y); - int newIndex = hoveredItem?.Index ?? -1; - - if (_doorHoverIndex != newIndex) - { - _doorHoverIndex = newIndex; - doorListView.Invalidate(); - } - } - - private void DoorListView_MouseLeave(object sender, EventArgs e) - { - if (_doorHoverIndex != -1) - { - _doorHoverIndex = -1; - doorListView.Invalidate(); - } - } - - private void DoorListView_DrawItem(object sender, DrawListViewItemEventArgs e) - { - var isHighlighted = e.Item.Selected - || e.ItemIndex == _doorHoverIndex - || (doorListView.Focused && (e.State & ListViewItemStates.Focused) != 0); - - var backColor = isHighlighted - ? RowHighlightColor - : (e.ItemIndex % 2 == 0 ? RowEvenColor : RowOddColor); - - using (var brush = new SolidBrush(backColor)) - { - e.Graphics.FillRectangle(brush, e.Bounds); - } - - var textColor = isHighlighted ? TextHighlightColor : TextRegularColor; - - TextRenderer.DrawText(e.Graphics, e.Item.Text, e.Item.Font, e.Bounds, - textColor, - TextFormatFlags.VerticalCenter | TextFormatFlags.Left | TextFormatFlags.EndEllipsis); - - e.DrawFocusRectangle(); - } - - private void DoorListView_DrawSubItem(object sender, DrawListViewSubItemEventArgs e) - { - var isHighlighted = e.Item.Selected - || e.ItemIndex == _doorHoverIndex - || (doorListView.Focused && (e.ItemState & ListViewItemStates.Focused) != 0); - - var backColor = isHighlighted - ? RowHighlightColor - : (e.ItemIndex % 2 == 0 ? RowEvenColor : RowOddColor); - - using (var brush = new SolidBrush(backColor)) - { - e.Graphics.FillRectangle(brush, e.Bounds); - } - - Color textColor = TextRegularColor; - - // 如果是状态列,根据状态设置颜色 - if (e.ColumnIndex == 2) // 状态列 - { - var stateText = e.SubItem.Text; - if (stateText == "打开") + catch { - textColor = StateOpenColor; - } - else if (stateText == "关闭") - { - textColor = StateClosedColor; - } - } - // 如果是目标控制列,按目标状态着色 - else if (e.ColumnIndex == 3) // 控制目标列 - { - var targetText = e.SubItem.Text; - if (targetText == "开") - { - textColor = StateOpenColor; - } - else - { - textColor = StateClosedColor; - } - } - // 其他列使用默认颜色 - else - { - textColor = isHighlighted ? TextHighlightColor : TextRegularColor; - } - - TextRenderer.DrawText(e.Graphics, e.SubItem.Text, e.SubItem.Font, e.Bounds, - textColor, - TextFormatFlags.VerticalCenter | TextFormatFlags.Left | TextFormatFlags.EndEllipsis); - } - - private void DoorListView_DrawColumnHeader(object sender, DrawListViewColumnHeaderEventArgs e) - { - e.Graphics.FillRectangle(new SolidBrush(Color.FromArgb(245, 247, 250)), e.Bounds); - - e.Graphics.DrawLine(new Pen(Color.FromArgb(220, 220, 220)), - e.Bounds.Left, e.Bounds.Bottom - 1, e.Bounds.Right, e.Bounds.Bottom - 1); - - TextRenderer.DrawText(e.Graphics, e.Header.Text, - new Font("微软雅黑", 10.5F, FontStyle.Bold), - e.Bounds, Color.FromArgb(68, 68, 68), - TextFormatFlags.VerticalCenter | TextFormatFlags.Left | TextFormatFlags.HorizontalCenter); - } - - /// - /// 刷新门列表 - /// - private void RefreshDoorList() - { - doorListView.Items.Clear(); - - // 保存当前选中的门 - (int ControllerIndex, int DoorIndex)? previousSelected = _selectedDoor; - _selectedDoor = null; - labelDoorInfo.Text = "请选择要控制的门"; - - // 获取所有门控制器 - var mission = SimpleProject.proj?.Missions?.OfType().FirstOrDefault(); - if (mission == null) - { - return; - } - - var doorSnapshot = mission.GetDoorMonitorSnapshot(); - if (doorSnapshot.Count == 0) - { - return; - } - - ListViewItem selectedItem = null; - - foreach (var door in doorSnapshot) - { - var stateText = door.State == DoorState.Open ? "打开" : door.State == DoorState.Closed ? "关闭" : "未知"; - var targetText = door.Target ? "开" : "关"; - var sourceText = door.Source == DoorMission.ControlSource.Manual ? "手动" : "自动"; - var remainText = door.Source == DoorMission.ControlSource.Manual && door.ManualRemainingSeconds.HasValue - ? Math.Ceiling(door.ManualRemainingSeconds.Value).ToString() - : "-"; - var carsText = door.CarsInArea.Count > 0 ? string.Join(", ", door.CarsInArea) : "无"; - - var item = new ListViewItem(door.ControllerIndex.ToString()); - item.SubItems.Add(door.DoorIndex.ToString()); - item.SubItems.Add(stateText); - item.SubItems.Add(targetText); - item.SubItems.Add(sourceText); - item.SubItems.Add(remainText); - item.SubItems.Add(carsText); - item.SubItems.Add(door.ControlAddress.ToString()); - item.SubItems.Add(door.OpenStatusAddress.ToString()); - item.Tag = (door.ControllerIndex, door.DoorIndex); - item.UseItemStyleForSubItems = false; - doorListView.Items.Add(item); - - // 如果之前选中的门存在,恢复选中状态 - if (previousSelected.HasValue && - previousSelected.Value.ControllerIndex == door.ControllerIndex && - previousSelected.Value.DoorIndex == door.DoorIndex) - { - selectedItem = item; + _panel = null; } } - // 恢复选中状态 - if (selectedItem != null) - { - selectedItem.Selected = true; - selectedItem.EnsureVisible(); - doorListView_SelectedIndexChanged(doorListView, EventArgs.Empty); - } - } + var panel = GUI.DeclarePanel() + .ShowTitle("门控监控") + .SetDefaultDocking(Panel.Docking.None) + .InitSize(1200, 620) + .InitPos(false, 0, 0, 0.5f, 0.5f, 0.5f, 0.5f); + _panel = panel; + panel.IfTerminalQuit(() => _panel = null); - /// - /// 门列表选择改变 - /// - private void doorListView_SelectedIndexChanged(object sender, EventArgs e) - { - if (doorListView.SelectedItems.Count > 0) + panel.Define(pb => { - var tag = doorListView.SelectedItems[0].Tag; - if (tag != null && tag is ValueTuple) - { - var doorInfo = (ValueTuple)tag; - _selectedDoor = doorInfo; - labelDoorInfo.Text = $"控制器编码: {doorInfo.Item1}, 门编码: {doorInfo.Item2}"; - - // 根据占用状态决定关闭按钮是否可用 - var mission = SimpleProject.proj?.Missions?.OfType().FirstOrDefault(); - var carsInArea = mission?.GetCarsInArea(doorInfo.Item1, doorInfo.Item2) ?? Array.Empty(); - btnClose.Enabled = carsInArea.Count == 0; - } - else + if (pb.Closing()) { + panel.Exit(); + _panel = null; _selectedDoor = null; - labelDoorInfo.Text = "请选择要控制的门"; - btnClose.Enabled = true; + return; } - } - else - { - _selectedDoor = null; - labelDoorInfo.Text = "请选择要控制的门"; - btnClose.Enabled = true; - } + + var mission = GetMission(); + var snapshot = mission?.GetDoorMonitorSnapshot() ?? Array.Empty(); + + pb.Label($"共 {snapshot.Count} 扇门"); + pb.Label(GetDoorInfoText(snapshot)); + + pb.Table(TableId, + new[] + { + "控制器编码", "门编码", "状态", "控制目标", "控制来源", + "手动剩余(s)", "车辆占用", "控制地址", "开到位地址" + }, + snapshot.Count, (row, i) => + { + var door = snapshot[i]; + if (IsRowSelected(door)) + row.SetColor(SelectedRowColor); + + LabelCell(row, door, door.ControllerIndex.ToString()); + LabelCell(row, door, door.DoorIndex.ToString()); + LabelCell(row, door, FormatState(door.State)); + LabelCell(row, door, door.Target ? "开" : "关"); + LabelCell(row, door, door.Source == DoorMission.ControlSource.Manual ? "手动" : "自动"); + LabelCell(row, door, FormatManualRemain(door)); + LabelCell(row, door, FormatCars(door)); + LabelCell(row, door, door.ControlAddress.ToString()); + LabelCell(row, door, door.OpenStatusAddress.ToString()); + }, height: 18, enableSearch: true); + + pb.Separator(); + pb.Label("手动控制"); + + var canClose = CanCloseSelected(mission); + if (pb.Button("打开", distinct: "door-monitor-open")) + OpenSelectedDoor(); + pb.SameLine(12); + if (pb.Button("关闭", distinct: "door-monitor-close", disabled: !canClose)) + CloseSelectedDoor(); + pb.SameLine(12); + if (pb.Button("清空占用", distinct: "door-monitor-clear-cars")) + ClearSelectedCars(); + + pb.Panel.Repaint(repaintTimeMs: 500); + }); } - /// - /// 打开门 - /// - private void btnOpen_Click(object sender, EventArgs e) + private static void LabelCell(PanelBuilder.Row row, DoorMission.DoorMonitorSnapshotItem door, string text) + { + if (row.Label(text)) + _selectedDoor = (door.ControllerIndex, door.DoorIndex); + } + + private static DoorMission GetMission() => + SimpleProject.proj?.Missions?.OfType().FirstOrDefault(); + + private static bool IsRowSelected(DoorMission.DoorMonitorSnapshotItem door) => + _selectedDoor.HasValue + && _selectedDoor.Value.ControllerIndex == door.ControllerIndex + && _selectedDoor.Value.DoorIndex == door.DoorIndex; + + private static string GetDoorInfoText(IReadOnlyList snapshot) + { + if (!_selectedDoor.HasValue) + return "请选择要控制的门"; + + var door = snapshot.FirstOrDefault(d => + d.ControllerIndex == _selectedDoor.Value.ControllerIndex + && d.DoorIndex == _selectedDoor.Value.DoorIndex); + if (door == null) + return $"控制器编码: {_selectedDoor.Value.ControllerIndex}, 门编码: {_selectedDoor.Value.DoorIndex}"; + + return $"控制器编码: {door.ControllerIndex}, 门编码: {door.DoorIndex}"; + } + + private static bool CanCloseSelected(DoorMission mission) + { + if (!_selectedDoor.HasValue || mission == null) + return true; + + var cars = mission.GetCarsInArea(_selectedDoor.Value.ControllerIndex, _selectedDoor.Value.DoorIndex); + return cars.Count == 0; + } + + private static string FormatState(DoorState state) => + state == DoorState.Open ? "打开" : state == DoorState.Closed ? "关闭" : "未知"; + + private static string FormatManualRemain(DoorMission.DoorMonitorSnapshotItem door) => + door.Source == DoorMission.ControlSource.Manual && door.ManualRemainingSeconds.HasValue + ? Math.Ceiling(door.ManualRemainingSeconds.Value).ToString() + : "-"; + + private static string FormatCars(DoorMission.DoorMonitorSnapshotItem door) => + door.CarsInArea.Count > 0 ? string.Join(", ", door.CarsInArea) : "无"; + + private static void OpenSelectedDoor() { if (!_selectedDoor.HasValue) { - MessageBox.Show("请先选择要控制的门", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information); + CycleUiHelper.Alert("提示", "请先选择要控制的门"); return; } try { - var mission = SimpleProject.proj?.Missions?.OfType().FirstOrDefault(); + var mission = GetMission(); if (mission == null) { - MessageBox.Show("未找到门控进程", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error); + CycleUiHelper.Alert("错误", "未找到门控进程"); return; } var (controllerIndex, doorIndex) = _selectedDoor.Value; - // 手动控制:默认保持10秒 mission.SetManualDoorControl(controllerIndex, doorIndex, true); - MessageBox.Show($"控制器 {controllerIndex} 门 {doorIndex} 已设置手动打开(10秒)", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information); + CycleUiHelper.Alert("提示", $"控制器 {controllerIndex} 门 {doorIndex} 已设置手动打开(10秒)"); } catch (Exception ex) { - MessageBox.Show($"设置门打开目标失败: {ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error); + CycleUiHelper.Alert("错误", $"设置门打开目标失败: {ex.Message}"); } } - /// - /// 关闭门 - /// - private void btnClose_Click(object sender, EventArgs e) + private static void CloseSelectedDoor() { if (!_selectedDoor.HasValue) { - MessageBox.Show("请先选择要控制的门", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information); + CycleUiHelper.Alert("提示", "请先选择要控制的门"); return; } try { - var mission = SimpleProject.proj?.Missions?.OfType().FirstOrDefault(); + var mission = GetMission(); if (mission == null) { - MessageBox.Show("未找到门控进程", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error); + CycleUiHelper.Alert("错误", "未找到门控进程"); return; } var (controllerIndex, doorIndex) = _selectedDoor.Value; - // 车辆占用时禁止手动关闭 var success = mission.SetManualDoorControl(controllerIndex, doorIndex, false); if (!success) { - MessageBox.Show("门存在车辆占用,禁止手动关闭。", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information); + CycleUiHelper.Alert("提示", "门存在车辆占用,禁止手动关闭。"); return; } - MessageBox.Show($"控制器 {controllerIndex} 门 {doorIndex} 已设置手动关闭(10秒)", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information); + + CycleUiHelper.Alert("提示", $"控制器 {controllerIndex} 门 {doorIndex} 已设置手动关闭(10秒)"); } catch (Exception ex) { - MessageBox.Show($"设置门关闭目标失败: {ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error); + CycleUiHelper.Alert("错误", $"设置门关闭目标失败: {ex.Message}"); } } - /// - /// 清空车辆占用 - /// - private void btnClearCars_Click(object sender, EventArgs e) + private static void ClearSelectedCars() { if (!_selectedDoor.HasValue) { - MessageBox.Show("请先选择要清空占用的门", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information); + CycleUiHelper.Alert("提示", "请先选择要清空占用的门"); return; } try { - var mission = SimpleProject.proj?.Missions?.OfType().FirstOrDefault(); + var mission = GetMission(); if (mission == null) { - MessageBox.Show("未找到门控进程", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error); + CycleUiHelper.Alert("错误", "未找到门控进程"); return; } var (controllerIndex, doorIndex) = _selectedDoor.Value; mission.ClearCarsInArea(controllerIndex, doorIndex); - MessageBox.Show($"控制器 {controllerIndex} 门 {doorIndex} 已清空占用", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information); - RefreshDoorList(); + CycleUiHelper.Alert("提示", $"控制器 {controllerIndex} 门 {doorIndex} 已清空占用"); } catch (Exception ex) { - MessageBox.Show($"清空占用失败: {ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error); - } - } - - /// - /// 定时刷新 - /// - private void timerRefresh_Tick(object sender, EventArgs e) - { - RefreshDoorList(); - } - - /// - /// 窗体关闭事件 - /// - private void DoorMonitor_FormClosing(object sender, FormClosingEventArgs e) - { - if (e.CloseReason == CloseReason.UserClosing) - { - timerRefresh.Stop(); - e.Cancel = true; - this.Visible = false; - } - } - - protected override void OnVisibleChanged(EventArgs e) - { - base.OnVisibleChanged(e); - if (Visible) - { - EnsureRefreshActive(); - } - else - { - timerRefresh.Stop(); + CycleUiHelper.Alert("错误", $"清空占用失败: {ex.Message}"); } } } diff --git a/StandardScene.Core/ExtendDevice/Door/DoorMonitor.resx b/StandardScene.Core/ExtendDevice/Door/DoorMonitor.resx deleted file mode 100644 index 4391a28..0000000 --- a/StandardScene.Core/ExtendDevice/Door/DoorMonitor.resx +++ /dev/null @@ -1,64 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - 17, 17 - - diff --git a/StandardScene.Core/InterLock/AbstractInterlockMission.cs b/StandardScene.Core/InterLock/AbstractInterlockMission.cs index 15484be..81379f9 100644 --- a/StandardScene.Core/InterLock/AbstractInterlockMission.cs +++ b/StandardScene.Core/InterLock/AbstractInterlockMission.cs @@ -12,7 +12,7 @@ using SimpleLite.CADTools; using SimpleLite.Props; using SimpleLite.UI; using SimpleLite; -using System.Windows.Forms; +using StandardScene.Utils; using Newtonsoft.Json; namespace StandardScene.InterLock @@ -297,7 +297,7 @@ namespace StandardScene.InterLock var vv = await Program.UI.Input("请输入true/false", title, "true"); if (vv == null || !bool.TryParse(vv, out var allow)) { - MessageBox.Show("输入错误"); + CycleUiHelper.Alert("错误", "输入错误"); return; } lock (sync) toManipulate[site.id] = allow; diff --git a/StandardScene.Core/InterLock/TrafficInterlockMission.cs b/StandardScene.Core/InterLock/TrafficInterlockMission.cs index 2ff03be..08bbba9 100644 --- a/StandardScene.Core/InterLock/TrafficInterlockMission.cs +++ b/StandardScene.Core/InterLock/TrafficInterlockMission.cs @@ -10,7 +10,6 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; -using System.Windows.Forms; namespace StandardScene.InterLock { @@ -60,7 +59,7 @@ namespace StandardScene.InterLock { try { - string ConfigPath = Path.Combine(Application.StartupPath, "Config/traffic.json"); + string ConfigPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Config", "traffic.json"); if (File.Exists(ConfigPath)) { @@ -137,6 +136,11 @@ namespace StandardScene.InterLock public class TrafficArea { + /// + /// 区域稳定标识,用于界面编辑/批量删除时避免行索引错位。 + /// + public string Id { get; set; } + /// /// 区域名称 /// diff --git a/StandardScene.Core/InterLock/TrafficInterlockViewer.Designer.cs b/StandardScene.Core/InterLock/TrafficInterlockViewer.Designer.cs deleted file mode 100644 index e4302aa..0000000 --- a/StandardScene.Core/InterLock/TrafficInterlockViewer.Designer.cs +++ /dev/null @@ -1,325 +0,0 @@ -using System; -using System.Drawing; -using System.Windows.Forms; - -namespace LoopViewerApp -{ - partial class TrafficInterlockViewer - { - private System.ComponentModel.IContainer components = null; - - private ListView lstTasks; - private GroupBox grpEdit; - - private ColumnHeader colAreaName; - private ColumnHeader colSites; - private ColumnHeader colControlRight; - private ColumnHeader colIsOccupied; - private ColumnHeader colIsEnabled; - - private Label lblAreaName; - private TextBox txtAreaName; - private Label lblStationIds; - private TextBox txtStationIds; - private Label lblControlRight; - private TextBox txtControlRight; - private Label lblIsOccupied; - private CheckBox chkIsOccupied; - private Label lblIsEnabled; - private CheckBox chkIsEnabled; - private Label lblEditingHint; - private Button btnSave; - private Button btnRefresh; - private Button btnNew; - private Button btnDelete; - - protected override void Dispose(bool disposing) - { - if (disposing && (components != null)) - { - components.Dispose(); - } - base.Dispose(disposing); - } - - private void InitializeComponent() - { - this.lstTasks = new System.Windows.Forms.ListView(); - this.colAreaName = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); - this.colSites = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); - this.colControlRight = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); - this.colIsOccupied = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); - this.colIsEnabled = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); - this.grpEdit = new System.Windows.Forms.GroupBox(); - this.lblEditingHint = new System.Windows.Forms.Label(); - this.lblAreaName = new System.Windows.Forms.Label(); - this.txtAreaName = new System.Windows.Forms.TextBox(); - this.lblStationIds = new System.Windows.Forms.Label(); - this.txtStationIds = new System.Windows.Forms.TextBox(); - this.lblControlRight = new System.Windows.Forms.Label(); - this.txtControlRight = new System.Windows.Forms.TextBox(); - this.lblIsOccupied = new System.Windows.Forms.Label(); - this.chkIsOccupied = new System.Windows.Forms.CheckBox(); - this.lblIsEnabled = new System.Windows.Forms.Label(); - this.chkIsEnabled = new System.Windows.Forms.CheckBox(); - this.btnSave = new System.Windows.Forms.Button(); - this.btnRefresh = new System.Windows.Forms.Button(); - this.btnNew = new System.Windows.Forms.Button(); - this.btnDelete = new System.Windows.Forms.Button(); - this.grpEdit.SuspendLayout(); - this.SuspendLayout(); - // - // lstTasks - // - this.lstTasks.BackColor = System.Drawing.Color.White; - this.lstTasks.Columns.AddRange(new System.Windows.Forms.ColumnHeader[] { - this.colAreaName, - this.colSites, - this.colControlRight, - this.colIsOccupied, - this.colIsEnabled}); - this.lstTasks.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(33)))), ((int)(((byte)(33)))), ((int)(((byte)(33))))); - this.lstTasks.FullRowSelect = true; - this.lstTasks.HideSelection = false; - this.lstTasks.Location = new System.Drawing.Point(12, 12); - this.lstTasks.Name = "lstTasks"; - this.lstTasks.OwnerDraw = true; - this.lstTasks.Size = new System.Drawing.Size(760, 320); - this.lstTasks.TabIndex = 0; - this.lstTasks.UseCompatibleStateImageBehavior = false; - this.lstTasks.View = System.Windows.Forms.View.Details; - this.lstTasks.DrawColumnHeader += new System.Windows.Forms.DrawListViewColumnHeaderEventHandler(this.lstTasks_DrawColumnHeader); - this.lstTasks.DrawItem += new System.Windows.Forms.DrawListViewItemEventHandler(this.lstTasks_DrawItem); - this.lstTasks.DrawSubItem += new System.Windows.Forms.DrawListViewSubItemEventHandler(this.lstTasks_DrawSubItem); - this.lstTasks.SelectedIndexChanged += new System.EventHandler(this.lstTasks_SelectedIndexChanged); - // - // colAreaName - // - this.colAreaName.Text = "区域名称"; - this.colAreaName.Width = 140; - // - // colSites - // - this.colSites.Text = "区域站点集合"; - this.colSites.TextAlign = System.Windows.Forms.HorizontalAlignment.Center; - this.colSites.Width = 280; - // - // colControlRight - // - this.colControlRight.Text = "控制权"; - this.colControlRight.TextAlign = System.Windows.Forms.HorizontalAlignment.Center; - this.colControlRight.Width = 120; - // - // colIsOccupied - // - this.colIsOccupied.Text = "是否被占用"; - this.colIsOccupied.TextAlign = System.Windows.Forms.HorizontalAlignment.Center; - this.colIsOccupied.Width = 100; - // - // colIsEnabled - // - this.colIsEnabled.Text = "是否启用"; - this.colIsEnabled.TextAlign = System.Windows.Forms.HorizontalAlignment.Center; - this.colIsEnabled.Width = 100; - // - // grpEdit - // - this.grpEdit.Controls.Add(this.lblEditingHint); - this.grpEdit.Controls.Add(this.lblAreaName); - this.grpEdit.Controls.Add(this.txtAreaName); - this.grpEdit.Controls.Add(this.lblStationIds); - this.grpEdit.Controls.Add(this.txtStationIds); - this.grpEdit.Controls.Add(this.lblControlRight); - this.grpEdit.Controls.Add(this.txtControlRight); - this.grpEdit.Controls.Add(this.lblIsOccupied); - this.grpEdit.Controls.Add(this.chkIsOccupied); - this.grpEdit.Controls.Add(this.lblIsEnabled); - this.grpEdit.Controls.Add(this.chkIsEnabled); - this.grpEdit.Controls.Add(this.btnSave); - this.grpEdit.Controls.Add(this.btnRefresh); - this.grpEdit.Controls.Add(this.btnNew); - this.grpEdit.Controls.Add(this.btnDelete); - this.grpEdit.Font = new System.Drawing.Font("微软雅黑", 10F, System.Drawing.FontStyle.Bold); - this.grpEdit.Location = new System.Drawing.Point(12, 345); - this.grpEdit.Name = "grpEdit"; - this.grpEdit.Size = new System.Drawing.Size(760, 165); - this.grpEdit.TabIndex = 1; - this.grpEdit.TabStop = false; - this.grpEdit.Text = "数据新增/编辑(点击表格行可在此查看并编辑该行数据)"; - // - // lblEditingHint - // - this.lblEditingHint.AutoSize = true; - this.lblEditingHint.Font = new System.Drawing.Font("微软雅黑", 10F, System.Drawing.FontStyle.Bold); - this.lblEditingHint.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(0)))), ((int)(((byte)(120)))), ((int)(((byte)(215))))); - this.lblEditingHint.Location = new System.Drawing.Point(12, 125); - this.lblEditingHint.Name = "lblEditingHint"; - this.lblEditingHint.Size = new System.Drawing.Size(65, 19); - this.lblEditingHint.TabIndex = 0; - this.lblEditingHint.Text = "新增区域"; - // - // lblAreaName - // - this.lblAreaName.AutoSize = true; - this.lblAreaName.Font = new System.Drawing.Font("微软雅黑", 10F); - this.lblAreaName.Location = new System.Drawing.Point(12, 28); - this.lblAreaName.Name = "lblAreaName"; - this.lblAreaName.Size = new System.Drawing.Size(79, 20); - this.lblAreaName.TabIndex = 1; - this.lblAreaName.Text = "区域名称:"; - // - // txtAreaName - // - this.txtAreaName.Font = new System.Drawing.Font("微软雅黑", 10F); - this.txtAreaName.Location = new System.Drawing.Point(100, 24); - this.txtAreaName.Name = "txtAreaName"; - this.txtAreaName.Size = new System.Drawing.Size(200, 25); - this.txtAreaName.TabIndex = 2; - // - // lblStationIds - // - this.lblStationIds.AutoSize = true; - this.lblStationIds.Font = new System.Drawing.Font("微软雅黑", 10F); - this.lblStationIds.Location = new System.Drawing.Point(320, 28); - this.lblStationIds.Name = "lblStationIds"; - this.lblStationIds.Size = new System.Drawing.Size(79, 20); - this.lblStationIds.TabIndex = 3; - this.lblStationIds.Text = "站点集合:"; - // - // txtStationIds - // - this.txtStationIds.Font = new System.Drawing.Font("微软雅黑", 10F); - this.txtStationIds.Location = new System.Drawing.Point(418, 24); - this.txtStationIds.Name = "txtStationIds"; - this.txtStationIds.Size = new System.Drawing.Size(320, 25); - this.txtStationIds.TabIndex = 4; - // - // lblControlRight - // - this.lblControlRight.AutoSize = true; - this.lblControlRight.Font = new System.Drawing.Font("微软雅黑", 10F); - this.lblControlRight.Location = new System.Drawing.Point(12, 58); - this.lblControlRight.Name = "lblControlRight"; - this.lblControlRight.Size = new System.Drawing.Size(65, 20); - this.lblControlRight.TabIndex = 5; - this.lblControlRight.Text = "控制权:"; - // - // txtControlRight - // - this.txtControlRight.Font = new System.Drawing.Font("微软雅黑", 10F); - this.txtControlRight.Location = new System.Drawing.Point(100, 54); - this.txtControlRight.Name = "txtControlRight"; - this.txtControlRight.Size = new System.Drawing.Size(200, 25); - this.txtControlRight.TabIndex = 6; - // - // lblIsOccupied - // - this.lblIsOccupied.AutoSize = true; - this.lblIsOccupied.Font = new System.Drawing.Font("微软雅黑", 10F); - this.lblIsOccupied.Location = new System.Drawing.Point(320, 58); - this.lblIsOccupied.Name = "lblIsOccupied"; - this.lblIsOccupied.Size = new System.Drawing.Size(93, 20); - this.lblIsOccupied.TabIndex = 7; - this.lblIsOccupied.Text = "是否被占用:"; - // - // chkIsOccupied - // - this.chkIsOccupied.AutoSize = true; - this.chkIsOccupied.Font = new System.Drawing.Font("Segoe UI", 9F); - this.chkIsOccupied.Location = new System.Drawing.Point(418, 59); - this.chkIsOccupied.Name = "chkIsOccupied"; - this.chkIsOccupied.Size = new System.Drawing.Size(39, 19); - this.chkIsOccupied.TabIndex = 8; - this.chkIsOccupied.Text = "是"; - // - // lblIsEnabled - // - this.lblIsEnabled.AutoSize = true; - this.lblIsEnabled.Font = new System.Drawing.Font("微软雅黑", 10F); - this.lblIsEnabled.Location = new System.Drawing.Point(500, 58); - this.lblIsEnabled.Name = "lblIsEnabled"; - this.lblIsEnabled.Size = new System.Drawing.Size(79, 20); - this.lblIsEnabled.TabIndex = 9; - this.lblIsEnabled.Text = "是否启用:"; - // - // chkIsEnabled - // - this.chkIsEnabled.AutoSize = true; - this.chkIsEnabled.Checked = true; - this.chkIsEnabled.CheckState = System.Windows.Forms.CheckState.Checked; - this.chkIsEnabled.Font = new System.Drawing.Font("Segoe UI", 9F); - this.chkIsEnabled.Location = new System.Drawing.Point(585, 59); - this.chkIsEnabled.Name = "chkIsEnabled"; - this.chkIsEnabled.Size = new System.Drawing.Size(39, 19); - this.chkIsEnabled.TabIndex = 10; - this.chkIsEnabled.Text = "是"; - // - // btnSave - // - this.btnSave.BackColor = System.Drawing.Color.LightBlue; - this.btnSave.FlatStyle = System.Windows.Forms.FlatStyle.Flat; - this.btnSave.Font = new System.Drawing.Font("微软雅黑", 11F, System.Drawing.FontStyle.Bold); - this.btnSave.Location = new System.Drawing.Point(260, 118); - this.btnSave.Name = "btnSave"; - this.btnSave.Size = new System.Drawing.Size(140, 40); - this.btnSave.TabIndex = 12; - this.btnSave.Text = "保存"; - this.btnSave.UseVisualStyleBackColor = false; - this.btnSave.Click += new System.EventHandler(this.btnSave_Click); - // - // btnRefresh - // - this.btnRefresh.BackColor = System.Drawing.SystemColors.Control; - this.btnRefresh.FlatStyle = System.Windows.Forms.FlatStyle.Flat; - this.btnRefresh.Font = new System.Drawing.Font("微软雅黑", 11F); - this.btnRefresh.Location = new System.Drawing.Point(410, 118); - this.btnRefresh.Name = "btnRefresh"; - this.btnRefresh.Size = new System.Drawing.Size(140, 40); - this.btnRefresh.TabIndex = 13; - this.btnRefresh.Text = "刷新"; - this.btnRefresh.UseVisualStyleBackColor = false; - this.btnRefresh.Click += new System.EventHandler(this.btnRefresh_Click); - // - // btnNew - // - this.btnNew.BackColor = System.Drawing.SystemColors.Control; - this.btnNew.FlatStyle = System.Windows.Forms.FlatStyle.Flat; - this.btnNew.Font = new System.Drawing.Font("微软雅黑", 11F); - this.btnNew.Location = new System.Drawing.Point(560, 118); - this.btnNew.Name = "btnNew"; - this.btnNew.Size = new System.Drawing.Size(140, 40); - this.btnNew.TabIndex = 14; - this.btnNew.Text = "新增"; - this.btnNew.UseVisualStyleBackColor = false; - this.btnNew.Click += new System.EventHandler(this.btnNew_Click); - // - // btnDelete - // - this.btnDelete.BackColor = System.Drawing.SystemColors.Control; - this.btnDelete.FlatStyle = System.Windows.Forms.FlatStyle.Flat; - this.btnDelete.Font = new System.Drawing.Font("微软雅黑", 11F); - this.btnDelete.Location = new System.Drawing.Point(110, 118); - this.btnDelete.Name = "btnDelete"; - this.btnDelete.Size = new System.Drawing.Size(140, 40); - this.btnDelete.TabIndex = 11; - this.btnDelete.Text = "删除"; - this.btnDelete.UseVisualStyleBackColor = false; - this.btnDelete.Click += new System.EventHandler(this.btnDelete_Click); - // - // TrafficInterlockViewer - // - this.ClientSize = new System.Drawing.Size(784, 521); - this.Controls.Add(this.lstTasks); - this.Controls.Add(this.grpEdit); - this.Font = new System.Drawing.Font("微软雅黑", 9F); - this.MinimumSize = new System.Drawing.Size(700, 450); - this.Name = "TrafficInterlockViewer"; - this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen; - this.Text = "交通联锁区域管理"; - this.grpEdit.ResumeLayout(false); - this.grpEdit.PerformLayout(); - this.ResumeLayout(false); - - } - } -} diff --git a/StandardScene.Core/InterLock/TrafficInterlockViewer.cs b/StandardScene.Core/InterLock/TrafficInterlockViewer.cs index c8fb195..d69fe6a 100644 --- a/StandardScene.Core/InterLock/TrafficInterlockViewer.cs +++ b/StandardScene.Core/InterLock/TrafficInterlockViewer.cs @@ -1,235 +1,336 @@ -using Newtonsoft.Json; -using StandardScene.InterLock; // 数据类型采用 TrafficInterlockMission 中的 TrafficArea using System; using System.Collections.Generic; -using System.ComponentModel; -using System.Drawing; using System.IO; using System.Linq; -using System.Windows.Forms; +using System.Threading.Tasks; +using CycleGUI; +using Newtonsoft.Json; +using StandardScene.InterLock; +using StandardScene.Utils; namespace LoopViewerApp { - public partial class TrafficInterlockViewer : Form + /// + /// 交通联锁区域管理界面(CycleGUI 版,替代原 WinForms TrafficInterlockViewer 窗体)。 + /// + /// 维护 的增 / 改 / 删,并写入 Config/traffic.json + /// 单实例:再次打开则把已有面板置前。 + /// 勾选多行后「删除选中」可批量删除;每行「编辑」按钮加载下方编辑区。 + /// 文件写入放后台线程,绝不阻塞渲染线程。 + /// + /// 沿用 LoopViewer / DeliveryViewer 同套模式(单实例面板、pb.TableCycleUiHelper.ConfirmThen)。 + /// 保留可实例化 + 以兼容既有调用 new TrafficInterlockViewer().Show()。 + /// + public class TrafficInterlockViewer { - /// -1 表示新增模式;>=0 表示正在编辑对应索引 - private int _editingIndex = -1; + private const string TableId = "traffic-area-list"; - /// 选中行变化时是否允许加载到编辑区(避免在保存/取消时重复刷新) - private bool _allowLoadFromSelection = true; + private static string JsonPath => + Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Config", "traffic.json"); - public TrafficInterlockViewer() + private static readonly object SaveLock = new object(); + + private static Panel _panel; + private static string _editingAreaId = ""; + private static readonly HashSet _selected = new HashSet(StringComparer.Ordinal); + + private static string _areaName = ""; + private static string _stationIds = ""; + private static string _controlRight = ""; + private static bool _isOccupied; + private static bool _isEnabled = true; + private static string _editingHint = "新增区域"; + private static string _editErr = ""; + private static volatile string _status = ""; + + /// 打开(或置前)区域管理面板。兼容原 new TrafficInterlockViewer().Show() 调用方式。 + public void Show() => Open(); + + /// 打开(或置前)区域管理面板。 + public static void Open() { - InitializeComponent(); - - if (LicenseManager.UsageMode == LicenseUsageMode.Designtime) - return; - - try + if (_panel != null) { - RenderListView(); - ClearPanelInputs(); - } - catch (Exception ex) - { - System.Diagnostics.Debug.WriteLine($"TrafficInterlockViewer init error: {ex}"); - } - } - - #region 表格绘制(只读展示) - - private void lstTasks_DrawColumnHeader(object sender, DrawListViewColumnHeaderEventArgs e) - { - try - { - // 与 LoopViewer 一致:深蓝表头 + 白色加粗字体 - using (var backBrush = new SolidBrush(Color.FromArgb(63, 81, 181))) - using (var textBrush = new SolidBrush(Color.White)) - using (var font = new Font("微软雅黑", 9, FontStyle.Bold)) + try { - e.Graphics.FillRectangle(backBrush, e.Bounds); - var sf = new StringFormat { LineAlignment = StringAlignment.Center, Alignment = StringAlignment.Near }; - var rect = e.Bounds; - rect.Inflate(-8, 0); - e.Graphics.DrawString(e.Header.Text, font, textBrush, rect, sf); - using (var pen = new Pen(Color.FromArgb(200, 200, 200))) - e.Graphics.DrawLine(pen, e.Bounds.Left, e.Bounds.Bottom - 1, e.Bounds.Right, e.Bounds.Bottom - 1); + _panel.BringToFront(); + return; + } + catch + { + _panel = null; } } - catch + + ClearPanelInputs(); + + var panel = GUI.DeclarePanel() + .ShowTitle("交通联锁区域管理") + .SetDefaultDocking(Panel.Docking.None) + .InitSize(800, 680) + .InitPos(false, 0, 0, 0.5f, 0.5f, 0.5f, 0.5f); + _panel = panel; + panel.IfTerminalQuit(() => { if (_panel == panel) _panel = null; }); + + panel.Define(pb => { - e.DrawBackground(); - e.DrawText(); - } + if (pb.Closing()) + { + panel.Exit(); + _panel = null; + return; + } + + List areas; + lock (TrafficInterlockMission.TrafficAreaList) + { + EnsureAreaIdsLocked(); + areas = TrafficInterlockMission.TrafficAreaList.ToList(); + } + _selected.RemoveWhere(id => areas.All(a => a.Id != id)); + if (!string.IsNullOrEmpty(_editingAreaId) && areas.All(a => a.Id != _editingAreaId)) + ClearPanelInputs(); + + if (pb.Button("删除选中", distinct: "traffic-del-selected")) + ConfirmDeleteSelected(); + pb.SameLine(12); + if (pb.Button("刷新", distinct: "traffic-refresh")) + { + _status = ""; + _editErr = ""; + } + pb.SameLine(12); + pb.Label($"共 {areas.Count} 个区域,已选 {_selected.Count} 个"); + + pb.Table(TableId, + new[] { "选择", "区域名称", "区域站点集合", "控制权", "是否被占用", "是否启用", "操作" }, + areas.Count, (row, i) => + { + var a = areas[i]; + var areaId = EnsureAreaId(a); + + var sel = _selected.Contains(areaId); + if (row.Checkbox(ref sel, "勾选以批量删除")) + { + if (sel) _selected.Add(areaId); + else _selected.Remove(areaId); + } + + row.Label(a.AreaName ?? ""); + row.Label(a.SiteList != null && a.SiteList.Count > 0 + ? string.Join(", ", a.SiteList) + : ""); + row.Label(a.ControllerName ?? ""); + row.Label(a.IsOccupy ? "是" : "否"); + row.Label(a.IsEnable ? "是" : "否"); + + if (row.ButtonGroup(new[] { "编辑" }, new[] { "编辑该区域" }) == 0) + { + _editingAreaId = areaId; + LoadAreaToFields(a); + } + }, height: 14, enableSearch: true); + + pb.Separator(); + pb.Label("数据新增/编辑(点击表格行「编辑」可在此查看并修改该行数据)"); + pb.Label(_editingHint); + + var (name, _) = pb.TextInput("1. 区域名称", _areaName, alwaysReturnString: true); + _areaName = name; + var (sites, _) = pb.TextInput("2. 站点集合 (逗号/分号/空格分隔)", _stationIds, alwaysReturnString: true); + _stationIds = sites; + var (ctrl, _) = pb.TextInput("3. 控制权", _controlRight, alwaysReturnString: true); + _controlRight = ctrl; + pb.CheckBox("4. 是否被占用", ref _isOccupied); + pb.SameLine(16); + pb.CheckBox("5. 是否启用", ref _isEnabled); + + if (!string.IsNullOrEmpty(_editErr)) + { + pb.Separator(); + pb.Label(_editErr); + } + + pb.Separator(); + if (pb.Button("保存", distinct: "traffic-save")) + SaveArea(); + pb.SameLine(12); + if (pb.Button("新增", distinct: "traffic-new")) + { + _selected.Clear(); + ClearPanelInputs(); + } + + if (!string.IsNullOrEmpty(_status)) + { + pb.Separator(); + pb.Label(_status); + } + + pb.Panel.Repaint(repaintTimeMs: 500); + }); } - private void lstTasks_DrawItem(object sender, DrawListViewItemEventArgs e) + private static void ConfirmDeleteSelected() { - // 由 DrawSubItem 统一绘制 + if (_selected.Count == 0) + { + _status = "请先在上方列表中选择要删除的区域。"; + _panel?.Repaint(); + return; + } + + string prompt = _selected.Count == 1 + ? "确定要删除选中的区域吗?" + : $"确定要删除所选 {_selected.Count} 个区域吗?"; + + CycleUiHelper.ConfirmThen(prompt, DeleteSelected); } - private void lstTasks_DrawSubItem(object sender, DrawListViewSubItemEventArgs e) + private static void DeleteSelected() + { + var selectedIds = _selected.ToHashSet(StringComparer.Ordinal); + var removed = 0; + lock (TrafficInterlockMission.TrafficAreaList) + { + removed = TrafficInterlockMission.TrafficAreaList.RemoveAll(a => selectedIds.Contains(a.Id)); + } + + _selected.Clear(); + SaveToConfig(); + ClearPanelInputs(); + _status = $"已删除 {removed} 个区域"; + _panel?.Repaint(); + } + + private static void SaveArea() { try { - bool selected = e.Item.Selected; - Rectangle bounds = e.Bounds; - // 与 LoopViewer 一致:选中行蓝色强调,交替行背景,深灰文字 - Color selectedBack = Color.FromArgb(0, 120, 215); - Color selectedFore = Color.White; - Color evenBack = Color.White; - Color oddBack = Color.FromArgb(250, 251, 253); - Color normalFore = Color.FromArgb(33, 33, 33); - - if (selected) + string areaName = (_areaName ?? "").Trim(); + if (string.IsNullOrEmpty(areaName)) { - using (var selBrush = new SolidBrush(selectedBack)) - e.Graphics.FillRectangle(selBrush, bounds); + _editErr = "请输入区域名称。"; + _panel?.Repaint(); + return; + } + + var stationIds = ParseStationIds(_stationIds ?? ""); + string controlRight = (_controlRight ?? "").Trim(); + bool isOccupied = _isOccupied; + bool isEnabled = _isEnabled; + + if (!string.IsNullOrEmpty(_editingAreaId)) + { + lock (TrafficInterlockMission.TrafficAreaList) + { + var existing = TrafficInterlockMission.TrafficAreaList + .FirstOrDefault(a => string.Equals(a.Id, _editingAreaId, StringComparison.Ordinal)); + if (existing == null) + { + _editErr = "正在编辑的区域已不存在,请刷新后重试。"; + _panel?.Repaint(); + return; + } + + existing.AreaName = areaName; + existing.SiteList = stationIds; + existing.ControllerName = controlRight; + existing.IsOccupy = isOccupied; + existing.IsEnable = isEnabled; + } + _status = $"已保存区域:{areaName}"; } else { - using (var back = new SolidBrush(e.ItemIndex % 2 == 0 ? evenBack : oddBack)) - e.Graphics.FillRectangle(back, bounds); - } - - string text = e.SubItem?.Text ?? string.Empty; - Color fore = selected ? selectedFore : normalFore; - var textRect = bounds; - textRect.Inflate(-6, 0); - using (var font = new Font("微软雅黑", 9)) - TextRenderer.DrawText(e.Graphics, text, font, textRect, fore, TextFormatFlags.Left | TextFormatFlags.VerticalCenter); - } - catch - { - e.DrawBackground(); - e.DrawText(); - } - } - - #endregion - - #region 列表渲染与保存 - - private void RenderListView() - { - try - { - if (lstTasks == null) return; - _allowLoadFromSelection = false; - lstTasks.BeginUpdate(); - lstTasks.Items.Clear(); - foreach (var a in TrafficInterlockMission.TrafficAreaList) - { - var stationStr = a.SiteList != null && a.SiteList.Count > 0 ? string.Join(", ", a.SiteList) : ""; - - var lvi = new ListViewItem(new[] + lock (TrafficInterlockMission.TrafficAreaList) { - a.AreaName ?? "", - stationStr, - a.ControllerName ?? "", - a.IsOccupy ? "是" : "否", - a.IsEnable ? "是" : "否" - }); - - lstTasks.Items.Add(lvi); + TrafficInterlockMission.TrafficAreaList.Add(new TrafficArea + { + Id = NewAreaId(), + AreaName = areaName, + SiteList = stationIds, + ControllerName = controlRight, + IsOccupy = isOccupied, + IsEnable = isEnabled + }); + } + _status = "已新增区域"; } - lstTasks.EndUpdate(); - _allowLoadFromSelection = true; + + SaveToConfig(); + ClearPanelInputs(); + _editErr = ""; + _panel?.Repaint(); } catch (Exception ex) { - _allowLoadFromSelection = true; - System.Diagnostics.Debug.WriteLine($"RenderListView failed: {ex}"); + _editErr = "操作失败:" + ex.Message; + _panel?.Repaint(); } } - private void SaveToConfig() - { - try - { - string configPath = Path.Combine(Application.StartupPath, "Config", "traffic.json"); - string dir = Path.GetDirectoryName(configPath); - if (!string.IsNullOrEmpty(dir) && !Directory.Exists(dir)) - Directory.CreateDirectory(dir); - File.WriteAllText(configPath, JsonConvert.SerializeObject(TrafficInterlockMission.TrafficAreaList, Formatting.Indented)); - } - catch (Exception ex) - { - MessageBox.Show("保存失败:" + ex.Message); - } - } - - #endregion - - #region 点击表格行 → 编辑区展示该行数据 - - private void lstTasks_SelectedIndexChanged(object sender, EventArgs e) - { - if (!_allowLoadFromSelection || lstTasks == null || lstTasks.SelectedIndices.Count == 0) return; - int idx = lstTasks.SelectedIndices[0]; - if (idx < 0 || idx >= TrafficInterlockMission.TrafficAreaList.Count) return; - _editingIndex = idx; - LoadAreaToPanel(TrafficInterlockMission.TrafficAreaList[idx]); - } - - #endregion - - #region 编辑区:加载 / 清空 - - private void LoadAreaToPanel(TrafficArea a) + private static void LoadAreaToFields(TrafficArea a) { if (a == null) return; - try - { - if (lblEditingHint != null) - lblEditingHint.Text = $"编辑:{a.AreaName}"; - if (txtAreaName != null) - txtAreaName.Text = a.AreaName ?? ""; - if (txtStationIds != null) - txtStationIds.Text = a.SiteList != null && a.SiteList.Count > 0 - ? string.Join(", ", a.SiteList) - : ""; - if (txtControlRight != null) - txtControlRight.Text = a.ControllerName ?? ""; - if (chkIsOccupied != null) - chkIsOccupied.Checked = a.IsOccupy; - if (chkIsEnabled != null) - chkIsEnabled.Checked = a.IsEnable; - } - catch (Exception ex) - { - System.Diagnostics.Debug.WriteLine($"LoadAreaToPanel error: {ex}"); - } + _editingHint = $"编辑:{a.AreaName}"; + _areaName = a.AreaName ?? ""; + _stationIds = a.SiteList != null && a.SiteList.Count > 0 + ? string.Join(", ", a.SiteList) + : ""; + _controlRight = a.ControllerName ?? ""; + _isOccupied = a.IsOccupy; + _isEnabled = a.IsEnable; + _editErr = ""; } - private void ClearPanelInputs() + private static void ClearPanelInputs() { + _editingAreaId = ""; + _editingHint = "新增区域"; + _areaName = ""; + _stationIds = ""; + _controlRight = ""; + _isOccupied = false; + _isEnabled = true; + _editErr = ""; + } + + private static void SaveToConfig() + { + string json; try { - _editingIndex = -1; - if (lblEditingHint != null) - lblEditingHint.Text = "新增区域"; - if (txtAreaName != null) - txtAreaName.Text = ""; - if (txtStationIds != null) - txtStationIds.Text = ""; - if (txtControlRight != null) - txtControlRight.Text = ""; - if (chkIsOccupied != null) - chkIsOccupied.Checked = false; - if (chkIsEnabled != null) - chkIsEnabled.Checked = true; + lock (TrafficInterlockMission.TrafficAreaList) + json = JsonConvert.SerializeObject(TrafficInterlockMission.TrafficAreaList, Formatting.Indented); } catch (Exception ex) { - System.Diagnostics.Debug.WriteLine($"ClearPanelInputs error: {ex}"); + _status = "保存失败:" + ex.Message; + _panel?.Repaint(); + return; } + + var path = JsonPath; + var dir = Path.GetDirectoryName(path); + Task.Run(() => + { + try + { + if (!string.IsNullOrEmpty(dir) && !Directory.Exists(dir)) + Directory.CreateDirectory(dir); + lock (SaveLock) + File.WriteAllText(path, json); + } + catch (Exception ex) + { + _status = "保存失败:" + ex.Message; + _panel?.Repaint(); + } + }); } - #endregion - - #region 解析站点集合字符串 "1,2,3" -> List - + /// 解析站点集合字符串,如 "1,2,3" → of int。 private static List ParseStationIds(string text) { var list = new List(); @@ -242,129 +343,19 @@ namespace LoopViewerApp return list; } - #endregion - - #region 按钮:保存 / 刷新 / 新增 / 删除 - - private void btnSave_Click(object sender, EventArgs e) + private static void EnsureAreaIdsLocked() { - try - { - string areaName = txtAreaName?.Text?.Trim() ?? ""; - if (string.IsNullOrEmpty(areaName)) - { - MessageBox.Show("请输入区域名称。"); - return; - } - - var stationIds = ParseStationIds(txtStationIds?.Text ?? ""); - string controlRight = txtControlRight?.Text?.Trim() ?? ""; - bool isOccupied = chkIsOccupied?.Checked ?? false; - bool isEnabled = chkIsEnabled?.Checked ?? true; - - if (_editingIndex >= 0 && _editingIndex < TrafficInterlockMission.TrafficAreaList.Count) - { - lock (TrafficInterlockMission.TrafficAreaList) - { - var existing = TrafficInterlockMission.TrafficAreaList[_editingIndex]; - existing.AreaName = areaName; - existing.SiteList = stationIds; - existing.ControllerName = controlRight; - existing.IsOccupy = isOccupied; - existing.IsEnable = isEnabled; - } - } - else - { - lock (TrafficInterlockMission.TrafficAreaList) - { - TrafficInterlockMission.TrafficAreaList.Add(new TrafficArea - { - AreaName = areaName, - SiteList = stationIds, - ControllerName = controlRight, - IsOccupy = isOccupied, - IsEnable = isEnabled - }); - } - } - - SaveToConfig(); - RenderListView(); - ClearPanelInputs(); - } - catch (Exception ex) - { - System.Diagnostics.Debug.WriteLine($"btnSave_Click error: {ex}"); - MessageBox.Show("操作失败:" + ex.Message); - } + foreach (var area in TrafficInterlockMission.TrafficAreaList) + EnsureAreaId(area); } - private void btnRefresh_Click(object sender, EventArgs e) + private static string EnsureAreaId(TrafficArea area) { - try - { - RenderListView(); - } - catch (Exception ex) - { - System.Diagnostics.Debug.WriteLine($"btnRefresh_Click error: {ex}"); - } + if (string.IsNullOrWhiteSpace(area.Id)) + area.Id = NewAreaId(); + return area.Id; } - private void btnNew_Click(object sender, EventArgs e) - { - if (lstTasks != null) - lstTasks.SelectedIndices.Clear(); - ClearPanelInputs(); - // 进入新增模式:填写下方编辑区后点击“保存”即可新增一条数据 - } - - private void btnDelete_Click(object sender, EventArgs e) - { - try - { - if (lstTasks == null || lstTasks.SelectedIndices.Count == 0) - { - MessageBox.Show("请先在上方列表中选择要删除的区域。"); - return; - } - - var dialogResult = MessageBox.Show( - "确定要删除选中的区域吗?", - "确认删除", - MessageBoxButtons.YesNo, - MessageBoxIcon.Warning); - - if (dialogResult != DialogResult.Yes) - return; - - var indices = lstTasks.SelectedIndices.Cast() - .OrderByDescending(i => i) - .ToList(); - - lock (TrafficInterlockMission.TrafficAreaList) - { - foreach (var idx in indices) - { - if (idx >= 0 && idx < TrafficInterlockMission.TrafficAreaList.Count) - { - TrafficInterlockMission.TrafficAreaList.RemoveAt(idx); - } - } - } - - SaveToConfig(); - RenderListView(); - ClearPanelInputs(); - } - catch (Exception ex) - { - System.Diagnostics.Debug.WriteLine($"btnDelete_Click error: {ex}"); - MessageBox.Show("删除失败:" + ex.Message); - } - } - - #endregion + private static string NewAreaId() => Guid.NewGuid().ToString("N"); } } diff --git a/StandardScene.Core/InterLock/TrafficInterlockViewer.resx b/StandardScene.Core/InterLock/TrafficInterlockViewer.resx deleted file mode 100644 index 1af7de1..0000000 --- a/StandardScene.Core/InterLock/TrafficInterlockViewer.resx +++ /dev/null @@ -1,120 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - \ No newline at end of file diff --git a/StandardScene.Core/Model/Map.cs b/StandardScene.Core/Model/Map.cs index ec9ff92..1e8626c 100644 --- a/StandardScene.Core/Model/Map.cs +++ b/StandardScene.Core/Model/Map.cs @@ -13,7 +13,6 @@ using System.IO; using System.Linq; using System.Net.Http; using System.Threading.Tasks; -using System.Windows.Forms; using Newtonsoft.Json; using SimpleLite.RCS; using SimpleLite.RCS.CarTypes; diff --git a/StandardScene.Core/Properties/InternalsVisibleTo.cs b/StandardScene.Core/Properties/InternalsVisibleTo.cs index 09b0f67..f356e92 100644 --- a/StandardScene.Core/Properties/InternalsVisibleTo.cs +++ b/StandardScene.Core/Properties/InternalsVisibleTo.cs @@ -6,3 +6,5 @@ using System.Runtime.CompilerServices; [assembly: InternalsVisibleTo("StandardScene.Devices")] [assembly: InternalsVisibleTo("StandardScene.Magnetic")] [assembly: InternalsVisibleTo("StandardScene.QrLidar")] +// SimpleLite 反射 API 需读取 internal 字段袋(BasicTrackFields / KivaSiteFields 等)的 public 字段定义。 +[assembly: InternalsVisibleTo("SimpleLite")] diff --git a/StandardScene.Core/StandardCADTool.cs b/StandardScene.Core/StandardCADTool.cs index 604b08c..32dcb17 100644 --- a/StandardScene.Core/StandardCADTool.cs +++ b/StandardScene.Core/StandardCADTool.cs @@ -1,294 +1,5 @@ -using Newtonsoft.Json; -using SimpleLite; -using SimpleLite.RCS; -using SimpleLite.RCS.CarTypes; -using SimpleLite.CADTools; -using SimpleLite.Props; -using SimpleLite.UI; -using SimpleCore; -using SimpleCore.Library; -using SimpleCore.PropType; -using StandardScene.Model; -using System; -using System.Collections.Generic; -using System.Diagnostics.Eventing.Reader; -using System.IO; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using System.Windows.Forms; - -namespace StandardScene +namespace StandardScene { - [CADToolDescriptor(name = "复制目标站点所有字段")] - public class CopySiteFieldsFromTarget : CADTool - { - public override async void Invoke() - { - var sel = SimpleMonitor.selected.OfType().ToList(); - var targetPoint = await Program.UI.getPoint(new UIOps.getPointOptions() { site = true }); - var targetSite = SimpleLib.GetSite(targetPoint.site); - - var ignoreKey = new string[] { "mustFree" }; - if (sel.Count <= 0) return; - foreach (var site in sel) - { - foreach (var field in targetSite.fields) - { - if (ignoreKey.Contains(field.Key)) continue; - site.fields[field.Key] = field.Value; - } - } - } - } - - [CADToolDescriptor(name = "区域流量管控-限制进入区域的车数量")] - public class RegionalTrafficControl : CADTool - { - // 重写调用方法:执行区域流量管控的车辆数量限制配置 - public override async void Invoke() - { - // 获取选中的所有站点UI对象 - var selectedSites = SimpleMonitor.selected.OfType().ToList(); - // 弹出输入框,提示用户按「区域编号,限制数量」格式输入,取消则直接返回 - var inputDialogResult = InputBox.ShowDialog("请输入区域编号和限制数量,格式:1,3"); - if (inputDialogResult != SimpleLite.DialogResult.OK) return; - - // 拆分输入的区域编号和限制数量 - var inputValues = InputBox.ResultValue.Split(','); - // 若无配置信息,直接返回 - if (inputValues.Length <= 1) return; - // 解析区域编号(浮点型保留原类型,兼容后续扩展) - var areaNumber = float.Parse(inputValues[0]); - // 解析区域车辆限制数量(浮点型保留原类型,兼容非整数配置) - var limitVehicleCount = float.Parse(inputValues[1]); - // 遍历所有选中站点,为其添加区域流量管控的字段配置 - foreach (var currentSite in selectedSites) - { - // 配置字段:area+区域编号 作为键,限制数量作为值 - if (!currentSite.fields.ContainsKey($"Region{areaNumber}")) - { - currentSite.fields.Add($"Region{areaNumber}", limitVehicleCount.ToString()); - - } - - } - } - } - - - [CADToolDescriptor(name = "复制目标站点所有字段(不覆盖)")] - public class CopySiteFieldsFromTargetNoOverwrite : CADTool - { - public override async void Invoke() - { - var sel = SimpleMonitor.selected.OfType().ToList(); - var targetPoint = await Program.UI.getPoint(new UIOps.getPointOptions() { site = true }); - var targetSite = SimpleLib.GetSite(targetPoint.site); - var ignore = new string[] { "mustFree" }; - if (sel.Count <= 0) return; - foreach (var site in sel) - { - foreach (var field in targetSite.fields) - { - if (!site.fields.ContainsKey(field.Key)) - { - site.fields[field.Key] = field.Value; - } - } - } - } - } - - [CADToolDescriptor(name = "复制目标站点颜色")] - public class BatchChangeSiteColor : CADTool - { - public override async void Invoke() - { - var sel = SimpleMonitor.selected.OfType().ToList(); - var targetPoint = await Program.UI.getPoint(new UIOps.getPointOptions() { site = true }); - var targetSite = (UISite)SimpleLib.GetSite(targetPoint.site); - if (sel.Count <= 0) return; - foreach (var site in sel) - { - site.color = targetSite.color; - } - } - } - - [CADToolDescriptor(name = "导入FASS地图")] - public class ImportFASSMap : CADTool - { - public override void Invoke() - { - //打开文件选择框 - using (var ofd = new System.Windows.Forms.OpenFileDialog()) - { - ofd.InitialDirectory = "C:\\"; - ofd.Filter = "FASS地图文件(*.json)|*.json"; - if (ofd.ShowDialog() == System.Windows.Forms.DialogResult.OK) - { - string filePath = ofd.FileName; - //解析文件内容 - string jsonString = File.ReadAllText(filePath); - MapStructure mapStructure = JsonConvert.DeserializeObject(jsonString); - Model.Configuration configuration = ConvertMapStructureToConfiguration(mapStructure); - // 输出 JSON 字符串到文本文件 - // 将 MapStructure 对象序列化为 JSON 字符串 - // 设置格式化选项 - string jsonSsring = JsonConvert.SerializeObject(configuration); - - // 获取桌面路径 - string desktopPath = - AppDomain.CurrentDomain - .BaseDirectory; // Environment.GetFolderPath(Environment.SpecialFolder.Desktop); - string outPath = Path.Combine(desktopPath, "output.json"); // 输出文件路径 - - // 输出 JSON 字符串到桌面上的文本文件 - File.WriteAllText(outPath, jsonSsring); - - - Console.WriteLine($"JSON 数据已成功写入到 {outPath}"); - //todo:分析文件内容,并导入站点、路径信息 - } - } - } - - - - public static Model.Configuration ConvertMapStructureToConfiguration(MapStructure mapStructure) - { - Model.Configuration config = new Model.Configuration(); - List index = new List(); - - // 处理 Sites - foreach (var node in mapStructure.Nodes) - { - SimpleSite site = new SimpleSite - { - id = int.Parse(node.Code.Text), // 将 node.Code.Text 赋值给 SimpleSite 的 Id - name = node.Name.Text, // 将 node.Name.Text 赋值给 SimpleSite 的 Name - x = node.Base.Point.X, // 将 node.Base.Point.X 赋值给 SimpleSite 的 X - y = node.Base.Point.Y, // 将 node.Base.Point.Y 赋值给 SimpleSite 的 Y - color = "defaultColor", // 默认颜色示例,您可以根据需要更改 - displaySetting = "defaultDisplay", // 默认显示设置示例,您可以根据需要更改 - fields = new Dictionary(), // Assuming fields is an empty object - mustFree = new List() // Assuming mustFree is an empty array - }; - - config.Sites[node.Code.Text] = site; // 将 SimpleSite 添加到 Sites 字典中 - AddInDescendingOrder(index, int.Parse(node.Code.Text)); - } - - // 处理 Tracks - foreach (var edge in mapStructure.Edges) - { - - int id = index[0] + 1; - AddInDescendingOrder(index, id); - StandardScene.Model.Track track = new StandardScene.Model.Track - { - id = id, // 将 edge.Index 赋值给 Track 的 Id - name = "NoName", // 将 edge.Name.Text 赋值给 Track 的 Name - siteA = int.Parse(mapStructure.Nodes.Find(n => n.Id == edge.Data.StartNodeId).Code - .Text), // 找到 StartNode 的索引 - siteB = int.Parse(mapStructure.Nodes.Find(n => n.Id == edge.Data.EndNodeId).Code - .Text), // 找到 EndNode 的索引 - _siteA = int.Parse(mapStructure.Nodes.Find(n => n.Id == edge.Data.StartNodeId).Code.Text), - _siteB = int.Parse(mapStructure.Nodes.Find(n => n.Id == edge.Data.EndNodeId).Code.Text), - fields = new Dictionary(), - typeInfo = "0", - layerName = "g", - displaySetting = "" - }; - - - config.Tracks[id.ToString()] = track; // 将 Track 添加到 Tracks 字典中 - - } - - return config; - } - - static void AddInDescendingOrder(List list, int number) - { - // 找到插入位置 - int i = 0; - while (i < list.Count && list[i] >= number) - { - i++; - } - - list.Insert(i, number); // 在找到的位置插入 - } - } - - [CADToolDescriptor(name = "批量间距生成站点")] - public class BulkIntervalSiteCreator : CADTool - { - public override async void Invoke() - { - - try - { - G.pushStatus("请选择参考点"); - var target = await Program.UI.getPoint(); - var templateSite = SimpleLib.GetAllSites().OrderBy(p => LessMath.dist(target.x, target.y, p.x, p.y)) - .FirstOrDefault(); - // 检查是否找到模板站点 - if (templateSite == null) - { - G.pushStatus("未找到参考站点"); - return; - } - - // 存储所有生成的站点(用于处理组间连接) - List generatedSites = new List(); - if (InputBox.ShowDialog("请输入需要生成的二维码数量(必须是偶数数量)以及站点间距和延伸角度,,格式为\"6,1000,0\"。") != - SimpleLite.DialogResult.OK) return; - generatedSites.Add((UISite)templateSite); - var values = InputBox.ResultValue.Split(',').Select(ss => float.Parse(ss)).ToArray(); - // 验证数量是否为偶数 - if (values[0] % 2 != 0) - { - G.pushStatus("数量必须是偶数"); - return; - } - - for (int i = 0; i < values[0] / 2; i = i + 2) - { - var curPos = Tuple.Create(templateSite.x, templateSite.y, values[2]); - var targetPos = LessMath.Transform2D(curPos, Tuple.Create(values[1] * (i + 1), 0f, 0f)); - var targetPos2 = LessMath.Transform2D(curPos, Tuple.Create(values[1] * (i + 2), 0f, 0f)); - - var siteA = new UISite() { id = Prop.GenerateID(), x = targetPos.Item1, y = targetPos.Item2 }; - ((UISite)siteA).color = ""; // - var siteB = new UISite() { id = Prop.GenerateID(), x = targetPos2.Item1, y = targetPos2.Item2 }; - SimpleLib.SetSite(siteA); - SimpleLib.SetSite(siteB); - generatedSites.Add(siteA); - generatedSites.Add(siteB); - - } - - for (int i = 0; i < generatedSites.Count - 1; i++) - { - Commons.AddOrUpdateSiteField(generatedSites[i], "tag", "0"); - // 每两个相邻站点都创建路径(包含组内和组间) - SimpleLib.SetTrack(new UITrack( - generatedSites[i].id, - generatedSites[i + 1].id - )); - } - } - catch (Exception e) - { - Console.WriteLine(e); - } - - - } - } - - // SyncQrMap(同步二维码地图到小车)已迁出至 StandardScene.QrLidar\Cad\SyncQrMap.cs(scene.qrlidar 平台)。 + // StandardScene 的 CAD 工具已迁移到 SimpleLite.CADTools.StandardSceneTools。 + // 保留此文件作为迁移记录,避免后续误以为遗漏了 StandardScene 侧工具。 } diff --git a/StandardScene.Core/StandardScene.Core.csproj b/StandardScene.Core/StandardScene.Core.csproj index e57e7e9..c880e34 100644 --- a/StandardScene.Core/StandardScene.Core.csproj +++ b/StandardScene.Core/StandardScene.Core.csproj @@ -3,7 +3,6 @@ net8.0-windows Library - true StandardScene StandardScene latest @@ -29,6 +28,12 @@ E:\Work\Core\Simple-FR\Simple\SimpleLite\bin\Debug\SimpleLite.dll + + + $(CGUILibDir)\CycleGUI.dll + false + E:\Work\Core\Simple-FR\Simple\SimpleCore\bin\Debug\netstandard2.0\SimpleCore.dll @@ -59,6 +64,7 @@ + diff --git a/StandardScene.Core/TCP/AsyncTcpClient.cs b/StandardScene.Core/TCP/AsyncTcpClient.cs index 6a8fd0e..1462abd 100644 --- a/StandardScene.Core/TCP/AsyncTcpClient.cs +++ b/StandardScene.Core/TCP/AsyncTcpClient.cs @@ -7,7 +7,6 @@ using System.Net.NetworkInformation; using System.Net.Sockets; using System.Text; using System.Threading; -using static System.Windows.Forms.VisualStyles.VisualStyleElement.ToolTip; namespace StandardScene.TCP { diff --git a/StandardScene.Core/Utils/CarRemoteHelper.cs b/StandardScene.Core/Utils/CarRemoteHelper.cs new file mode 100644 index 0000000..9470028 --- /dev/null +++ b/StandardScene.Core/Utils/CarRemoteHelper.cs @@ -0,0 +1,37 @@ +using System; +using System.Diagnostics; + +namespace StandardScene.Utils +{ + /// + /// 车辆远程访问辅助工具。 + /// + public static class CarRemoteHelper + { + /// + /// 使用系统默认浏览器打开车辆 Web 管理页面。 + /// + /// 车辆 IP 地址 + /// Web 服务端口,默认 8081 + public static void OpenVehicleWebPage(string ip, int port = 8081) + { + if (string.IsNullOrWhiteSpace(ip)) + { + return; + } + + try + { + Process.Start(new ProcessStartInfo + { + FileName = $"http://{ip}:{port}", + UseShellExecute = true + }); + } + catch (Exception ex) + { + Console.WriteLine($"打开车辆 Web 页面失败: {ex.Message}"); + } + } + } +} diff --git a/StandardScene.Core/Utils/CycleUiHelper.cs b/StandardScene.Core/Utils/CycleUiHelper.cs new file mode 100644 index 0000000..76578a4 --- /dev/null +++ b/StandardScene.Core/Utils/CycleUiHelper.cs @@ -0,0 +1,91 @@ +using CycleGUI; + +namespace StandardScene.Utils +{ + /// + /// CycleGUI 通用 UI 小工具:把多个界面都会用到的轻量对话框收敛到一处,避免各处各写一套(不造重复轮子)。 + /// 注意:同一 Panel.Define 内所有控件的 label 文本必须唯一(含 pb.Table 列头), + /// 否则 ImGui 会抛 Duplicated id。编辑区 label 建议加 ASCII 序号前缀(如 1. IP), + /// 且勿与表格列头同名。 + /// + public static class CycleUiHelper + { + /// + /// 非阻塞二次确认对话框:用户点「确认」后,在当前(渲染)线程同步执行 。 + /// 若 含文件 IO / 锁等耗时操作,调用方应自行用 Task.Run 包裹, + /// 避免阻塞渲染线程导致界面卡死。 + /// + public static void ConfirmThen(string message, System.Action onConfirm) + { + // 不用 Modal:CycleGUI 原生「模态弹窗 + 标题栏关闭X」存在 BeginPopupModal/EndPopup 配对 bug + //(X 关闭时 BeginPopupModal 返回 false 仍调用 EndPopup → ImGui 断言 "Calling End() too many times!" 崩溃)。 + // 改为置顶非模态:用 Begin/End 路径,X 关闭干净,效果等同点「取消」。 + var dlg = GUI.DeclarePanel() + .ShowTitle("确认") + .TopMost(true) + .InitSize(380, 150) + .InitPos(false, 0, 0, 0.5f, 0.5f, 0.5f, 0.5f); + dlg.Define(pb => + { + if (pb.Closing()) + { + dlg.Exit(); + return; + } + + pb.Label(message); + pb.Separator(); + if (pb.Button("确认", distinct: "cycleui-confirm-ok")) + { + dlg.Exit(); + onConfirm(); + } + pb.SameLine(8); + if (pb.Button("取消", distinct: "cycleui-confirm-cancel")) + dlg.Exit(); + }); + } + + /// 非阻塞文件选择对话框,替代 WinForms OpenFileDialog + public static void PickOpenFile(string label, string filter, System.Action onPicked) + { + var dlg = GUI.DeclarePanel() + .ShowTitle("选择文件") + .TopMost(true) + .InitSize(420, 120) + .InitPos(false, 0, 0, 0.5f, 0.5f, 0.5f, 0.5f); + dlg.Define(pb => + { + if (pb.Closing()) { dlg.Exit(); return; } + if (pb.OpenFile(label, filter, out var path)) + { + dlg.Exit(); + onPicked?.Invoke(path); + } + }); + } + + /// 非阻塞提示对话框。 + public static void Alert(string title, string message) + { + var dlg = GUI.DeclarePanel() + .ShowTitle(title) + .TopMost(true) + .InitSize(380, 150) + .InitPos(false, 0, 0, 0.5f, 0.5f, 0.5f, 0.5f); + dlg.Define(pb => + { + if (pb.Closing()) + { + dlg.Exit(); + return; + } + + pb.Label(message); + pb.Separator(); + if (pb.Button("确定", distinct: "cycleui-alert-ok")) + dlg.Exit(); + }); + } + } +} diff --git a/StandardScene.Devices/Charge/MuXingChargeStation.cs b/StandardScene.Devices/Charge/MuXingChargeStation.cs index 5c3628c..40ee6d8 100644 --- a/StandardScene.Devices/Charge/MuXingChargeStation.cs +++ b/StandardScene.Devices/Charge/MuXingChargeStation.cs @@ -15,7 +15,6 @@ using System.Text; using System.Threading; using System.Threading.Tasks; using System.Timers; -using System.Windows.Forms; namespace StandardScene.ChargeStationType diff --git a/StandardScene.Devices/Charge/PCBChargeStation.cs b/StandardScene.Devices/Charge/PCBChargeStation.cs index 525db2b..5a6ae50 100644 --- a/StandardScene.Devices/Charge/PCBChargeStation.cs +++ b/StandardScene.Devices/Charge/PCBChargeStation.cs @@ -13,7 +13,6 @@ using System.Net.Sockets; using System.Text; using System.Threading; using System.Threading.Tasks; -using System.Windows.Forms; using static SimpleCore.Traffic.TrafficControl; namespace StandardScene.ChargeStationType diff --git a/StandardScene.Devices/StandardScene.Devices.csproj b/StandardScene.Devices/StandardScene.Devices.csproj index f5f0b04..b81d123 100644 --- a/StandardScene.Devices/StandardScene.Devices.csproj +++ b/StandardScene.Devices/StandardScene.Devices.csproj @@ -3,7 +3,6 @@ net8.0-windows Library - true StandardScene StandardScene.Devices latest diff --git a/StandardScene.Magnetic/CarTypes/MagCar.cs b/StandardScene.Magnetic/CarTypes/MagCar.cs new file mode 100644 index 0000000..5018242 --- /dev/null +++ b/StandardScene.Magnetic/CarTypes/MagCar.cs @@ -0,0 +1,202 @@ +using LessokajiWeaverUtilities.Utilities; +using SimpleLite; +using SimpleLite.RCS; +using SimpleLite.RCS.CarTypes; +using SimpleLite.Props; +using SimpleLite.UI; +using SimpleCore; +using SimpleCore.Compiler; +using SimpleCore.Library; +using SimpleCore.PropType; +using StandardScene.Magnetic.Coders; +using StandardScene.Utils; +using System; +using System.Diagnostics; +using System.Drawing; +using System.Net.Sockets; +using System.Threading; +using System.Threading.Tasks; + +namespace StandardScene.CarTypes +{ + /// + /// 磁导航专用车型(scene.mag 平台)。 + /// 与激光/二维码车(Kiva、叉车等走 HTTP:8008)不同:磁导航车与下位机通过 UDP 协议 交互; + /// 路径由 生成 agv.MagGo / agv.NaiveMagGo 脚本 + /// (按 track.Magnet 触发)。本类只承载磁导航相关能力。 + /// ⚠ UDP 报文的端口 / 字节布局 / 命令码因下位机协议而异,下方以常量 + TODO 标注, + /// 待按实际磁导航 AGV 协议填充 与各控制命令的编码。 + /// + [ProgramTrackCoderSettings(priority = 19, program = typeof(MagneticTrackCoder))] + [CarType(Name = "磁导航车", editor = typeof(MagCar))] + [I18N.DocumentTranslation(Name = "Magnetic AGV", locale = "en")] + [EnvelopConfig(centerX = 0, centerY = 0, lengthX = 1000, lengthY = 600)] + public class MagCar : GhostCar + { + // ── 磁导航 UDP 通信参数(默认值待按实际下位机协议确认)────────────────── + /// 下位机 UDP 命令端口(RCS → AGV 下发)。 + [FieldMember] public int UdpCommandPort = 5000; + + /// 本机 UDP 状态监听端口(AGV → RCS 上报)。 + [FieldMember] public int UdpStatusPort = 5001; + + [FieldMember] public float CarLength = 1000; + [FieldMember] public float CarWidth = 600; + + private UdpClient _udp; + private CancellationTokenSource _cts; + private readonly object _sendLock = new object(); + + public static async Task Create() + { + var car = new MagCar + { + lstatus = "连接中", + address = "127.0.0.1", + name = "磁导航车", + haveCoordination = true, + speed = 1, + }; + car.StartUdp(); + return car; + } + + // ── UDP 通信:协作式停止 + 单帧异常隔离(参考 ChargeUdpService 范式)───────── + /// 启动磁导航 UDP 通信(幂等:会先停止已有连接)。 + public void StartUdp() + { + StopUdp(); + _cts = new CancellationTokenSource(); + var token = _cts.Token; + Task.Run(() => ReceiveLoop(token), token); + } + + /// 停止磁导航 UDP 通信并释放资源。 + public void StopUdp() + { + try { _cts?.Cancel(); } catch { /* 取消令牌释放兜底 */ } + try { _udp?.Close(); } catch { /* 关闭套接字兜底 */ } + _udp = null; + _cts = null; + } + + private async Task ReceiveLoop(CancellationToken token) + { + try + { + _udp = new UdpClient(UdpStatusPort); + } + catch (Exception ex) + { + // 多车共用监听端口时此处会绑定失败——若实际协议为单端口多车,应改为单例监听 + 按来源 IP 分发 + Diagnosis.Log($"磁导航车{name}({id}) UDP 监听端口 {UdpStatusPort} 绑定失败: {ex.Message}", "MagCar", true); + return; + } + + while (!token.IsCancellationRequested) + { + try + { + var result = await _udp.ReceiveAsync(); + // 多车共用监听端口时按来源 IP 过滤,仅处理本车报文 + if (!string.IsNullOrEmpty(address) && + !string.Equals(result.RemoteEndPoint.Address.ToString(), address, StringComparison.Ordinal)) + continue; + ParseStatus(result.Buffer); + } + catch (Exception e) + { + // 单帧异常(越界 / 半包 / 套接字关闭)不得中断本车 UDP 接收循环 + if (token.IsCancellationRequested) break; + Diagnosis.Log($"磁导航车{name}({id}) UDP 接收异常: {e.Message}", "MagCar", true); + } + } + } + + /// + /// 解析下位机 UDP 状态报文并写入 status.enums。 + /// TODO(待协议确认):下方字节偏移为占位,请按实际磁导航 AGV 协议替换。 + /// + private void ParseStatus(byte[] data) + { + if (data == null || data.Length < 4) return; // 长度校验,避免半包 / 越界 + // —— 按实际协议填充,例如:—— + // status.enums["ChassisMode"] = data[?].ToString(); + // status.enums["Soc"] = data[?].ToString(); + // status.enums["AlarmLevel"] = data[?].ToString(); + // status.enums["AlarmInfo"] = ...; + // 若磁导航上报绝对位姿,可在此回填 x / y / th(坐标系需与地图一致)。 + } + + /// 向下位机发送 UDP 命令。TODO:命令字节编码按实际协议实现。 + private void SendUdp(byte[] payload) + { + if (payload == null || payload.Length == 0) return; + try + { + lock (_sendLock) + { + using var sender = new UdpClient(); + sender.Send(payload, payload.Length, address, UdpCommandPort); + } + } + catch (Exception e) + { + Diagnosis.Log($"磁导航车{name}({id}) UDP 发送失败: {e.Message}", "MagCar", true); + } + } + + protected override void draw(Graphics eGraphics) + { + try + { + var w = CarLength; + var h = CarWidth; + var rect = new Rectangle((int)(-w / 2), (int)(-h / 2), (int)w, (int)h); + eGraphics.FillRectangle(Brushes.SteelBlue, rect); + eGraphics.DrawRectangle(Pens.White, rect); + using var orientPen = new Pen(Color.White, 3); + eGraphics.DrawLine(orientPen, 0, 0, (int)(w / 2), 0); // 朝向指示 + } + catch (Exception e) + { + Diagnosis.Post("绘制磁导航车异常" + ExceptionFormatter.FormatEx(e), "绘制小车异常", true); + } + } + + public override string SetDisplayInfo() + { + try + { + var str = $"{name}({id})\n"; + status.enums.TryGetValue("ChassisMode", out var manual); + var ms = manual == "0" ? "自动" : "手动"; + status.enums.TryGetValue("Soc", out var soc); + str = $"{str}{ms}|soc:{soc}"; + var alarmStr = Commons.GetCarStatus(this, "AlarmInfo"); + if (!string.IsNullOrEmpty(alarmStr)) + str = $"{str}|{alarmStr}"; + return str; + } + catch (Exception e) + { + Console.WriteLine(e); + return "bad car"; + } + } + + [MethodMember(Name = "进入小车管理界面", Description = "在浏览器中打开小车 Web 页面")] + [I18N.DocumentTranslation(Name = "Open vehicle management page", Description = "Open the car's management page in browser", locale = "en")] + public void OpenVehicleManagementPage() + { + CarRemoteHelper.OpenVehicleWebPage(address); + } + + [MethodMember(Name = "显示车辆监控", Description = "打开车辆状态监控窗口")] + public void ShowVehicleMonitor() => CarRemoteHelper.OpenVehicleWebPage(address); + + [MethodMember(Name = "重连通信", Description = "重启磁导航 UDP 通信")] + [I18N.DocumentTranslation(Name = "Reconnect", Description = "Restart magnetic UDP link", locale = "en")] + public void ReconnectUdp() => StartUdp(); + } +} diff --git a/StandardScene.Magnetic/MagneticSceneProfile.cs b/StandardScene.Magnetic/MagneticSceneProfile.cs index b642c9d..932f6d3 100644 --- a/StandardScene.Magnetic/MagneticSceneProfile.cs +++ b/StandardScene.Magnetic/MagneticSceneProfile.cs @@ -6,7 +6,9 @@ using StandardScene.CarTypes; namespace StandardScene.Magnetic { /// - /// scene.mag 平台画像:磁导航场景插件(磁条循迹为主,兼容二维码地标段)。 + /// scene.mag 平台画像:磁导航场景插件。 + /// 车型为磁导航专用 (与下位机通过 UDP 协议交互);路径由 + /// MagneticTrackCoder 生成 agv.MagGo / agv.NaiveMagGo 脚本(按 track.Magnet 触发)。 /// 宿主(SimpleLite)加载本 dll 后反射实例化并 OnActivate / 注册。 /// public sealed class MagneticSceneProfile : NavigationProfileBase @@ -19,13 +21,12 @@ namespace StandardScene.Magnetic public override IReadOnlyList CarTypes => new[] { - typeof(Kiva), - typeof(MultiWheelLifterCar), + typeof(MagCar), }; public override void OnActivate(ISceneContext context) { - context.Log($"{DisplayName} 已激活(车型:Kiva / 多舵轮顶升车)"); + context.Log($"{DisplayName} 已激活(车型:磁导航车 MagCar)"); } } } diff --git a/StandardScene.Magnetic/StandardScene.Magnetic.csproj b/StandardScene.Magnetic/StandardScene.Magnetic.csproj index c4726ee..fc82e5a 100644 --- a/StandardScene.Magnetic/StandardScene.Magnetic.csproj +++ b/StandardScene.Magnetic/StandardScene.Magnetic.csproj @@ -3,7 +3,6 @@ net8.0-windows Library - true StandardScene.Magnetic StandardScene.Magnetic latest diff --git a/StandardScene.Magnetic/StandardScene.Magnetic.scene.json b/StandardScene.Magnetic/StandardScene.Magnetic.scene.json index 5161cd9..31e6b7d 100644 --- a/StandardScene.Magnetic/StandardScene.Magnetic.scene.json +++ b/StandardScene.Magnetic/StandardScene.Magnetic.scene.json @@ -6,7 +6,7 @@ "coreVersion": ">=1.0.0", "requiresCore": "StandardScene.dll", "provides": { - "carTypes": [ "Kiva", "MultiWheelLifterCar" ], + "carTypes": [ "MagCar" ], "missionTypes": [] } } diff --git a/StandardScene.Protocol.VDA5050/Properties/InternalsVisibleTo.cs b/StandardScene.Protocol.VDA5050/Properties/InternalsVisibleTo.cs new file mode 100644 index 0000000..67ece0a --- /dev/null +++ b/StandardScene.Protocol.VDA5050/Properties/InternalsVisibleTo.cs @@ -0,0 +1,4 @@ +using System.Runtime.CompilerServices; + +// SimpleLite 反射 API 需读取 VDA5050 插件内 internal 字段袋。 +[assembly: InternalsVisibleTo("SimpleLite")] diff --git a/StandardScene.Protocol.VDA5050/StandardScene.Protocol.VDA5050.csproj b/StandardScene.Protocol.VDA5050/StandardScene.Protocol.VDA5050.csproj index d73788d..722bc68 100644 --- a/StandardScene.Protocol.VDA5050/StandardScene.Protocol.VDA5050.csproj +++ b/StandardScene.Protocol.VDA5050/StandardScene.Protocol.VDA5050.csproj @@ -3,7 +3,6 @@ net8.0-windows Library - true StandardScene StandardScene.Protocol.VDA5050 latest @@ -29,6 +28,14 @@ + + + + $(CGUILibDir)\CycleGUI.dll + false + + + diff --git a/StandardScene.Protocol.VDA5050/VDACar/TextViewer.Designer.cs b/StandardScene.Protocol.VDA5050/VDACar/TextViewer.Designer.cs deleted file mode 100644 index db7443d..0000000 --- a/StandardScene.Protocol.VDA5050/VDACar/TextViewer.Designer.cs +++ /dev/null @@ -1,58 +0,0 @@ -namespace StandardScene.CarTypes -{ - partial class TextViewer - { - /// - /// 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.richTextBox1 = new System.Windows.Forms.RichTextBox(); - this.SuspendLayout(); - // - // richTextBox1 - // - this.richTextBox1.Location = new System.Drawing.Point(12, 12); - this.richTextBox1.Name = "richTextBox1"; - this.richTextBox1.Size = new System.Drawing.Size(776, 1032); - this.richTextBox1.TabIndex = 0; - this.richTextBox1.Text = ""; - // - // TextViewer - // - this.AutoScaleDimensions = new System.Drawing.SizeF(9F, 18F); - this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - this.ClientSize = new System.Drawing.Size(800, 1056); - this.Controls.Add(this.richTextBox1); - this.Name = "TextViewer"; - this.Text = "TextViewer"; - this.ResumeLayout(false); - - } - - #endregion - - private System.Windows.Forms.RichTextBox richTextBox1; - } -} \ No newline at end of file diff --git a/StandardScene.Protocol.VDA5050/VDACar/TextViewer.cs b/StandardScene.Protocol.VDA5050/VDACar/TextViewer.cs index 0a6c00d..0836616 100644 --- a/StandardScene.Protocol.VDA5050/VDACar/TextViewer.cs +++ b/StandardScene.Protocol.VDA5050/VDACar/TextViewer.cs @@ -1,28 +1,62 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Data; -using System.Drawing; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using System.Windows.Forms; +using CycleGUI; namespace StandardScene.CarTypes { - public partial class TextViewer : Form + /// + /// 长文本只读查看面板(CycleGUI 版,替代原 WinForms TextViewer 窗体)。 + /// 保留可实例化 + 以兼容 new TextViewer().Show() 调用。 + /// + public class TextViewer { - public TextViewer() + private Panel _panel; + private volatile string _text = ""; + + /// 打开(或置前)文本查看面板。 + public void Show() => Open(); + + /// 打开(或置前)文本查看面板。 + public void Open() { - InitializeComponent(); + if (_panel != null) + { + try + { + _panel.BringToFront(); + return; + } + catch + { + _panel = null; + } + } + + var panel = GUI.DeclarePanel() + .ShowTitle("TextViewer") + .SetDefaultDocking(Panel.Docking.None) + .InitSize(800, 600) + .InitPos(false, 0, 0, 0.5f, 0.5f, 0.5f, 0.5f); + _panel = panel; + panel.IfTerminalQuit(() => { if (_panel == panel) _panel = null; }); + + panel.Define(pb => + { + if (pb.Closing()) + { + panel.Exit(); + if (_panel == panel) _panel = null; + return; + } + + pb.SelectableText(null, _text ?? "", copyButton: true); + pb.Panel.Repaint(repaintTimeMs: 200); + }); } + /// 更新显示文本并触发面板重绘(可从非渲染线程调用)。 public void UpdateText(string str) { - richTextBox1.Invoke((Action)delegate - { - richTextBox1.Text = str; - }); + _text = str ?? ""; + _panel?.Repaint(); } } } diff --git a/StandardScene.Protocol.VDA5050/VDACar/TextViewer.resx b/StandardScene.Protocol.VDA5050/VDACar/TextViewer.resx deleted file mode 100644 index 1af7de1..0000000 --- a/StandardScene.Protocol.VDA5050/VDACar/TextViewer.resx +++ /dev/null @@ -1,120 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - \ No newline at end of file diff --git a/StandardScene.Protocol.VDA5050/VDACar/VDA5050Car.cs b/StandardScene.Protocol.VDA5050/VDACar/VDA5050Car.cs index bf28d9c..f19e5c7 100644 --- a/StandardScene.Protocol.VDA5050/VDACar/VDA5050Car.cs +++ b/StandardScene.Protocol.VDA5050/VDACar/VDA5050Car.cs @@ -29,10 +29,7 @@ using System.Net.Http; using System.Security.Policy; using System.Threading; using System.Threading.Tasks; -using System.Windows.Forms; using static SimpleLite.RCS.CarTypes.ClumsyCar; -using static SimpleLite.RCS.CarTypes.DummyCar; -using static System.Windows.Forms.VisualStyles.VisualStyleElement.TaskbarClock; using Site = SimpleCore.PropType.Site; namespace StandardScene.CarTypes diff --git a/StandardScene.Protocol.VDA5050/VDACar/VDA5050Interface.cs b/StandardScene.Protocol.VDA5050/VDACar/VDA5050Interface.cs index 20bb83e..62a5165 100644 --- a/StandardScene.Protocol.VDA5050/VDACar/VDA5050Interface.cs +++ b/StandardScene.Protocol.VDA5050/VDACar/VDA5050Interface.cs @@ -16,7 +16,6 @@ using System.Net.Http; using Nancy.Routing; using Newtonsoft.Json; using SimpleLite; -using System.Windows.Forms; namespace StandardScene.CarTypes { diff --git a/StandardScene.QrLidar/Cad/SyncQrMap.cs b/StandardScene.QrLidar/Cad/SyncQrMap.cs index 2e378ba..b46899f 100644 --- a/StandardScene.QrLidar/Cad/SyncQrMap.cs +++ b/StandardScene.QrLidar/Cad/SyncQrMap.cs @@ -1,6 +1,6 @@ using System; using System.Linq; -using System.Windows.Forms; +using StandardScene.Utils; using Newtonsoft.Json; using SimpleCore; using SimpleLite.CADTools; @@ -30,7 +30,7 @@ namespace StandardScene.QrLidar.Cad var tagTh = tagSite.fields.TryGetValue("th", out var field) ? float.Parse(field) : 0f; if (ApiController.QrMap.ContainsKey(tag)) { - MessageBox.Show(($@"存在重复码值:{tag} 站点id为:{tagSite.id}")); + CycleUiHelper.Alert("错误", $@"存在重复码值:{tag} 站点id为:{tagSite.id}"); return; } diff --git a/StandardScene.QrLidar/CarTypes/ArmCar.cs b/StandardScene.QrLidar/CarTypes/ArmCar.cs index 8d3c0dd..42a292c 100644 --- a/StandardScene.QrLidar/CarTypes/ArmCar.cs +++ b/StandardScene.QrLidar/CarTypes/ArmCar.cs @@ -17,6 +17,7 @@ using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; +using StandardScene.Utils; namespace StandardScene.CarTypes { @@ -239,21 +240,11 @@ namespace StandardScene.CarTypes } } - [MethodMember(Name = "进入小车远程", Description = "进入小车远程桌面")] - [I18N.DocumentTranslation(Name = "Open remote desktop", Description = "Open the car's remote desktop", locale = "en")] - public void Mstsc() + [MethodMember(Name = "进入小车管理界面", Description = "在浏览器中打开小车 Web 页面")] + [I18N.DocumentTranslation(Name = "Open vehicle management page", Description = "Open the car's management page in browser", locale = "en")] + public void OpenVehicleManagementPage() { - var ip = this.address; - // 启动mstsc并传递IP地址 - Process.Start( - new ProcessStartInfo - { - FileName = "mstsc", - Arguments = $"/v:{ip}", - UseShellExecute = false, - CreateNoWindow = true - } - ); + CarRemoteHelper.OpenVehicleWebPage(address); } diff --git a/StandardScene.QrLidar/CarTypes/DualLiftingCar.cs b/StandardScene.QrLidar/CarTypes/DualLiftingCar.cs index 8bdaa1e..458f6ab 100644 --- a/StandardScene.QrLidar/CarTypes/DualLiftingCar.cs +++ b/StandardScene.QrLidar/CarTypes/DualLiftingCar.cs @@ -10,7 +10,7 @@ using System.Diagnostics; using System.Linq; using System.Threading; using System.Threading.Tasks; -using System.Windows.Forms; +using StandardScene.Utils; namespace StandardScene.CarTypes { @@ -58,21 +58,11 @@ namespace StandardScene.CarTypes } } - [MethodMember(Name = "进入小车远程", Description = "进入小车远程桌面")] - [I18N.DocumentTranslation(Name = "Open remote desktop", Description = "Open the car's remote desktop", locale = "en")] - public void Mstsc() + [MethodMember(Name = "进入小车管理界面", Description = "在浏览器中打开小车 Web 页面")] + [I18N.DocumentTranslation(Name = "Open vehicle management page", Description = "Open the car's management page in browser", locale = "en")] + public void OpenVehicleManagementPage() { - var ip = this.address; - // 启动mstsc并传递IP地址 - Process.Start( - new ProcessStartInfo - { - FileName = "mstsc", - Arguments = $"/v:{ip}", - UseShellExecute = false, - CreateNoWindow = true - } - ); + CarRemoteHelper.OpenVehicleWebPage(address); } private bool _running; diff --git a/StandardScene.QrLidar/CarTypes/Forklift.cs b/StandardScene.QrLidar/CarTypes/Forklift.cs index 36511d1..d890327 100644 --- a/StandardScene.QrLidar/CarTypes/Forklift.cs +++ b/StandardScene.QrLidar/CarTypes/Forklift.cs @@ -20,6 +20,7 @@ using System.Numerics; using System.Threading.Tasks; using StandardScene.Model; using StandardScene.Coders; +using StandardScene.Utils; namespace StandardScene.CarTypes { @@ -182,21 +183,11 @@ namespace StandardScene.CarTypes eGraphics.DrawEllipse(Pens.White, -160, -160, 320, 320); eGraphics.DrawLine(_orientPen, 0, 0, 480, 0); } - [MethodMember(Name = "进入小车远程", Description = "进入小车远程桌面")] - [I18N.DocumentTranslation(Name = "Open remote desktop", Description = "Open the car's remote desktop", locale = "en")] - public void Mstsc() + [MethodMember(Name = "进入小车管理界面", Description = "在浏览器中打开小车 Web 页面")] + [I18N.DocumentTranslation(Name = "Open vehicle management page", Description = "Open the car's management page in browser", locale = "en")] + public void OpenVehicleManagementPage() { - var ip = this.address; - // 启动mstsc并传递IP地址 - Process.Start( - new ProcessStartInfo - { - FileName = "mstsc", - Arguments = $"/v:{ip}", - UseShellExecute = false, - CreateNoWindow = true - } - ); + CarRemoteHelper.OpenVehicleWebPage(address); } public override string SetDisplayInfo() { @@ -253,10 +244,7 @@ namespace StandardScene.CarTypes } [MethodMember(Name = "显示车辆监控", Description = "打开车辆状态监控窗口")] - public void ShowVehicleMonitor() - { - VehicleMonitor.ShowMonitor(); - } + public void ShowVehicleMonitor() => CarRemoteHelper.OpenVehicleWebPage(address); public void newReset(int resetId = 0) { //if (!status.programs.task.IsCompleted && diff --git a/StandardScene.Magnetic/CarTypes/Kiva.cs b/StandardScene.QrLidar/CarTypes/Kiva.cs similarity index 97% rename from StandardScene.Magnetic/CarTypes/Kiva.cs rename to StandardScene.QrLidar/CarTypes/Kiva.cs index 84d82ab..20046e9 100644 --- a/StandardScene.Magnetic/CarTypes/Kiva.cs +++ b/StandardScene.QrLidar/CarTypes/Kiva.cs @@ -22,10 +22,9 @@ using System.Numerics; using System.Reflection; using System.Threading; using System.Threading.Tasks; -using System.Windows.Forms; +using StandardScene.Utils; using StandardScene.Model; using StandardScene.Coders; -using StandardScene.Magnetic.Coders; using Track = SimpleCore.PropType.Track; namespace StandardScene.CarTypes @@ -141,7 +140,6 @@ namespace StandardScene.CarTypes [ProgramTrackCoderSettings(priority = 5, program = typeof(KivaCarTrackCoder))] - [ProgramTrackCoderSettings(priority = 19, program = typeof(MagneticTrackCoder))] [ProgramTrackCoderSettings(priority = 17, program = typeof(LidarAreaSwitchCoder))] [ProgramTrackCoderSettings(priority = 20, program = typeof(AvoidanceDistanceCoder))] [ProgramTrackCoderSettings(priority = 20, program = typeof(IoAreaSwitchCoder))] @@ -343,27 +341,14 @@ namespace StandardScene.CarTypes } } - [MethodMember(Name = "进入小车远程", Description = "进入小车远程桌面")] - public void Mstsc() + [MethodMember(Name = "进入小车管理界面", Description = "在浏览器中打开小车 Web 页面")] + public void OpenVehicleManagementPage() { - var ip = this.address; - // 启动mstsc并传递IP地址 - Process.Start( - new ProcessStartInfo - { - FileName = "mstsc", - Arguments = $"/v:{ip}", - UseShellExecute = false, - CreateNoWindow = true - } - ); + CarRemoteHelper.OpenVehicleWebPage(address); } [MethodMember(Name = "显示车辆监控", Description = "打开车辆状态监控窗口")] - public void ShowVehicleMonitor() - { - VehicleMonitor.ShowMonitor(); - } + public void ShowVehicleMonitor() => CarRemoteHelper.OpenVehicleWebPage(address); [MethodMember(Name = "模拟电量", Description = "/")] public void SimarSoc() @@ -419,9 +404,8 @@ namespace StandardScene.CarTypes } catch (Exception ex) { - MessageBox.Show( - $@"未能立即强制结束小车{name}({id}),原因:{ExceptionFormatter.FormatEx(ex)}" - ); + CycleUiHelper.Alert("错误", + $@"未能立即强制结束小车{name}({id}),原因:{ExceptionFormatter.FormatEx(ex)}"); } }) { diff --git a/StandardScene.QrLidar/CarTypes/MultiWheelForkLifter.cs b/StandardScene.QrLidar/CarTypes/MultiWheelForkLifter.cs index 29a9de4..8b9513a 100644 --- a/StandardScene.QrLidar/CarTypes/MultiWheelForkLifter.cs +++ b/StandardScene.QrLidar/CarTypes/MultiWheelForkLifter.cs @@ -10,7 +10,6 @@ using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; -using static System.Windows.Forms.VisualStyles.VisualStyleElement; namespace StandardScene.CarTypes { diff --git a/StandardScene.Magnetic/CarTypes/MultiWheelLifterCar.cs b/StandardScene.QrLidar/CarTypes/MultiWheelLifterCar.cs similarity index 95% rename from StandardScene.Magnetic/CarTypes/MultiWheelLifterCar.cs rename to StandardScene.QrLidar/CarTypes/MultiWheelLifterCar.cs index 013ebf3..073ef7c 100644 --- a/StandardScene.Magnetic/CarTypes/MultiWheelLifterCar.cs +++ b/StandardScene.QrLidar/CarTypes/MultiWheelLifterCar.cs @@ -14,7 +14,7 @@ using SimpleCore.Library; using SimpleCore.PropType; using StandardScene.Model; using StandardScene.Coders; -using StandardScene.Magnetic.Coders; +using StandardScene.Utils; using System; using System.Collections.Generic; using System.Diagnostics; @@ -26,7 +26,6 @@ using System.Runtime; using System.Text; using System.Threading; using System.Threading.Tasks; -using System.Windows.Forms; using Track = SimpleCore.PropType.Track; namespace StandardScene.CarTypes @@ -100,7 +99,6 @@ namespace StandardScene.CarTypes // 纠偏阈值 coder 已抽离为 StandardScene.Coders.TrackingErrThreshCoder - [ProgramTrackCoderSettings(priority = 19, program = typeof(MagneticTrackCoder))] [ProgramTrackCoderSettings(priority = 27, program = typeof(LidarAreaSwitchCoder))] [ProgramTrackCoderSettings(priority = 20, program = typeof(AvoidanceDistanceCoder))] [ProgramTrackCoderSettings(priority = 20, program = typeof(IoAreaSwitchCoder))] @@ -126,21 +124,11 @@ namespace StandardScene.CarTypes return car; } - [MethodMember(Name = "进入小车远程", Description = "进入小车远程桌面")] - [I18N.DocumentTranslation(Name = "Open remote desktop",Description = "Open the car's remote desktop", locale = "en")] - public void Mstsc() + [MethodMember(Name = "进入小车管理界面", Description = "在浏览器中打开小车 Web 页面")] + [I18N.DocumentTranslation(Name = "Open vehicle management page", Description = "Open the car's management page in browser", locale = "en")] + public void OpenVehicleManagementPage() { - var ip = this.address; - // 启动mstsc并传递IP地址 - Process.Start( - new ProcessStartInfo - { - FileName = "mstsc", - Arguments = $"/v:{ip}", - UseShellExecute = false, - CreateNoWindow = true - } - ); + CarRemoteHelper.OpenVehicleWebPage(address); } public void newTrafficReset(int resetId = 0) diff --git a/StandardScene.QrLidar/Properties/InternalsVisibleTo.cs b/StandardScene.QrLidar/Properties/InternalsVisibleTo.cs new file mode 100644 index 0000000..bc2898a --- /dev/null +++ b/StandardScene.QrLidar/Properties/InternalsVisibleTo.cs @@ -0,0 +1,4 @@ +using System.Runtime.CompilerServices; + +// SimpleLite 反射 API 需读取 QrLidar 插件内 internal 字段袋(ForkliftSiteFields 等)。 +[assembly: InternalsVisibleTo("SimpleLite")] diff --git a/StandardScene.QrLidar/QrLidarSceneProfile.cs b/StandardScene.QrLidar/QrLidarSceneProfile.cs index 4ae47eb..f9b4c3d 100644 --- a/StandardScene.QrLidar/QrLidarSceneProfile.cs +++ b/StandardScene.QrLidar/QrLidarSceneProfile.cs @@ -9,6 +9,7 @@ namespace StandardScene.QrLidar /// scene.qrlidar 平台画像:激光 + 二维码融合导航场景插件。 /// 激光(SLAM 坐标导航)由内核 GhostCar 的 BasicGo 兜底提供;二维码 QrGo 按轨道两端 /// tag 字段逐段触发——同一台车同一条路线可全激光、全二维码或混合(融合 / 单独使用均可)。 + /// Kiva / 多舵轮顶升车原属本平台(激光+二维码 + 货架取放),会话39 由 scene.mag 迁入。 /// public sealed class QrLidarSceneProfile : NavigationProfileBase { @@ -27,11 +28,13 @@ namespace StandardScene.QrLidar typeof(DualLiftingCar), typeof(MultiVehicleCar), typeof(ArmCar), + typeof(Kiva), + typeof(MultiWheelLifterCar), }; public override void OnActivate(ISceneContext context) { - context.Log($"{DisplayName} 已激活(车型:叉车 / 多舵轮叉车 / 锂电双举升 / 多车联动 / ArmCar)"); + context.Log($"{DisplayName} 已激活(车型:叉车 / 多舵轮叉车 / 锂电双举升 / 多车联动 / ArmCar / Kiva / 多舵轮顶升车)"); } } } diff --git a/StandardScene.QrLidar/StandardScene.QrLidar.csproj b/StandardScene.QrLidar/StandardScene.QrLidar.csproj index 004032e..c442334 100644 --- a/StandardScene.QrLidar/StandardScene.QrLidar.csproj +++ b/StandardScene.QrLidar/StandardScene.QrLidar.csproj @@ -3,7 +3,6 @@ net8.0-windows Library - true StandardScene.QrLidar StandardScene.QrLidar latest diff --git a/StandardScene.QrLidar/StandardScene.QrLidar.scene.json b/StandardScene.QrLidar/StandardScene.QrLidar.scene.json index 05cc634..9141434 100644 --- a/StandardScene.QrLidar/StandardScene.QrLidar.scene.json +++ b/StandardScene.QrLidar/StandardScene.QrLidar.scene.json @@ -7,7 +7,7 @@ "coreVersion": ">=1.0.0", "requiresCore": "StandardScene.dll", "provides": { - "carTypes": [ "Forklift", "MultiWheelForkLifter", "DualLiftingCar", "MultiVehicleCar", "ArmCar" ], + "carTypes": [ "Forklift", "MultiWheelForkLifter", "DualLiftingCar", "MultiVehicleCar", "ArmCar", "Kiva", "MultiWheelLifterCar" ], "missionTypes": [] } } diff --git a/StandardScene代码审查报告-会话8复核增强版.md b/StandardScene代码审查报告-会话8复核增强版.md deleted file mode 100644 index 00cd5bc..0000000 --- a/StandardScene代码审查报告-会话8复核增强版.md +++ /dev/null @@ -1,166 +0,0 @@ -# StandardScene 代码审查报告 · 会话8 复核增强版 - -> 审查对象:`E:\Work\Core\Simple-FR\StandardSence`(StandardScene.Core / StandardScene.Devices / StandardScene.Protocol.VDA5050) -> 审查方式:全量反模式扫描(ripgrep)+ 高风险/代表性文件逐行精读核实 + 对既有《StandardScene代码审查报告.md》的逐项复核 -> 目标框架:`net8.0-windows`(最终目标 `net8.0`,去 WinForms) -> 报告日期:2026-06-09(会话8) -> 说明:本报告是对同日既有报告的**复核增强版**。复核发现既有报告中的**多个 P0 已被修复**,本报告据实更新现状、补充精确行号、并记录若干**新发现问题**。 - ---- - -## 〇、覆盖度与方法说明 - -- **全量扫描**:对解决方案内全部 `.cs` 做反模式扫描(`Thread.Abort` / `async void` / 空 `catch{}` / 硬编码 IP / 局部 `new HttpClient` / `throw ex;` / `while(true)` / `Thread.Sleep` / `Console.*` / 反射 `GetMethod`)。 -- **精读核实**(逐行读取、行号精确):`ChargeUdpService`、`StandardChargeMission`(停止)、`AbstractLoopMission`(停止)、`WebApi`(反射端点+白名单+开头)、`Commons`、`PCBChargeStation`、`MuXingChargeStation`、`VDA5050Car`、`AsyncTcpClient`、`CommunicationMessageService`、`AtomicFileUpdateHelper`、`SnowflakeIdGenerator`、`WebAPIHelper`、`JsonParser`、`JsonTool`、`ModbusDoorController`、`Kiva`(代表车型)。 -- **未逐行覆盖**:部分 Model/Designer/Viewer 与少数车型仅做扫描级核对(已在清单标注),不影响主结论。 - ---- - -## 一、对既有报告的复核结论(重点) - -| 既有报告条目 | 复核现状 | 证据(精确行号) | -|---|---|---| -| **P0-1 `Thread.Abort` 被空 catch 吞** | ✅ **已修复** | `Charge/StandardChargeMission.cs:683-685` 改为 `myThread?.Join(2000)`;`Chained/AbstractLoopMission.cs:1313-1314 / 1328-1329` 改为 `flag=false + Join(2000)`;全解决方案 `Thread.Abort` 仅余 1 处注释 | -| **P0-2 WebApi 无鉴权反射任意方法(RCE)** | ⚠️ **部分修复(降为 P1)** | `WebApi.cs:145-151` 新增白名单 `IsReflectionInvokable`(黑名单 `NoReflectionApi` 优先 + 必须标 `MethodMember`/`ReflectionApiWithParameter`);端点 `330-331 / 387-388` 已拦截。**但仍 GET 执行(295/358)、仍无网络层鉴权** | -| **P0-3 实时报文按固定下标取值、缺长度校验** | ✅ **主要路径已修复** | `Charge/ChargeUdpService.cs:49-50` 加 `message.Length>28` 校验且 `56` 不吞断线程;`Devices/Charge/PCBChargeStation.cs:30-31` 加 `message.Length>1` 校验 | -| 范本:`CommunicationMessageService` 安全解析 | ✅ 确认范本 | `Charge/CommunicationMessageService.cs:198 / 276` 先校验 `parts.Length` 再 `byte.TryParse(InvariantCulture)` | -| 范本:`AsyncTcpClient` | ✅ 确认范本 | `TCP/AsyncTcpClient.cs` `_reconnectGate` 锁 + `_closing/_isConnecting/_isReconnecting` + Timer 重连 + IDisposable | -| 范本:`ModbusDoorController` | ✅ 确认范本 | `Devices/Door/ModbusDoorController.cs:40` `CancellationTokenSource` + `_syncLock` + 去抖 `_lastSentControl` + 可配间隔 | - -**结论**:既有报告标注的 3 个 P0 中,P0-1、P0-3 已实质修复,P0-2 已被方法白名单有效缓解。**当前已无 P0 级阻断**。技术债主要集中在 P1/P2(旧模块的 async void、硬编码、Console 日志、上god文件、半成品死代码)。 - ---- - -## 二、仍然存在的问题 - -### P1 高危 - -#### P1-1 WebApi 反射端点无网络层鉴权 + 用 GET 执行副作用操作 -- 位置:`StandardScene.Core/WebApi.cs:295`(`/car_reflection/execute/{id}/{method}`)、`358`(`/mission_reflection/execute/{id}/{method}`);开头 `38` `ApiController : NancyModule` 无任何 `Before`/鉴权管线 -- 现象:虽有方法白名单(145-151),但任何能访问该 HTTP 端口者均可对白名单内方法发起调用,其中包含 `关闭进程`、`立即强制结束` 等危险操作(如 `Kiva.ForceStop`、`StandardChargeMission.Stop`);且为 GET 语义,易被浏览器预取/日志/CSRF 触发。 -- 影响:现场误触发可导致小车强停、充电进程关闭等安全相关后果。 -- 修复:① 增加统一鉴权(API Token / 来源 IP 白名单,Nancy `Before` 管线集中校验);② 执行类端点改 `POST`;③ 对“危险方法”增加二次确认/单独权限位。 - -#### P1-2 VDA5050 硬编码现场设备 IP(换现场/多车必失效) -- 位置:`StandardScene.Protocol.VDA5050/VDACar/VDA5050Car.cs:156`、`909`、`913`;`VDACar/VDA5050Interface.cs:152`、`156`(均为 `http://192.168.2.1:8008/...`) -- 现象:把对外取值/置值的设备 IP 写死为 `192.168.2.1`,且 `VDA5050Car.cs:154` 注释里本有 `this.address` 的正确写法却被弃用。 -- 影响:多 AGV 或换现场时全部失效;所有车都打到同一 IP。 -- 修复:统一取 `this.address`/配置项;端口与路径走配置;删除写死分支。 - -#### P1-3 `Commons.AddOrUpdateTag` 名不符实(只 Add,已存在会抛异常) -- 位置:`StandardScene.Core/Commons.cs:96-107`(正确的“存在则更新”逻辑被注释,仅保留 `item.Add(tag, value)` 于 `106`) -- 现象:方法语义是 AddOrUpdate,实际只 Add;当 tag 已存在时按 `TagSet.Add` 行为可能抛异常或产生重复项。 -- 影响:调用方(如 `AbstractLoopMission.MarkTaskStartSitesAsTerminal → AddOrUpdateSiteField`)在重复标记场景下可能异常或脏数据。 -- 修复:恢复“存在则 `item[tag]=value`,否则 `Add`”语义。 - -#### P1-4 局部 `new HttpClient()`(socket/端口耗尽风险) -- 位置:`WebApi.cs:977 / 1256 / 1322`、`Model/Map.cs:194`(均为方法内 `new HttpClient()` 后即用即弃) -- 现象:高频路径每次新建 HttpClient,底层 socket 进入 TIME_WAIT 累积,长期运行端口耗尽。 -- 影响:运行一段时间后 HTTP 调用大面积超时/失败。 -- 修复:复用静态单例 / `IHttpClientFactory`;项目已有正确范例可参照(`Chained/DeliveryViewer.cs:24`、`Chained/TransportDeliveryCallbacks.cs:26` 的 `static readonly HttpClient`,以及 `Utils/WebAPIHelper` 的连接池设计)。 - -#### P1-5 `async void` 泛滥(异常逃逸、无法等待、无法取消) -- 代表位置:`VDA5050Car.cs:150`(`GetVDA5050StateFromC`,且 `165-167` 空 catch 吞异常)、`Charge/ChargeUdpService.cs:28`(`ListenerProcess`,被 `new Thread(...)` 包裹更失语义)、`Devices/Charge/MuXingChargeStation.cs:424`(`SendMessage`,内部无 await)、`Chained/TransportMission.cs:374/425/470/507`、`Chained/TransportDeliveryCallbacks.cs:79/109/136/145/154/161`、`Chained/AbstractChainedDeliveryMission.cs:672/1020/1276`、`InterLock/AbstractInterlockMission.cs:289/326`、车型 `Kiva.cs:495/619`、`Forklift.cs:419`、`DummyCar.cs:461/576/626/671` 等 -- 现象:`async void` 抛出的异常直达 SynchronizationContext,常导致进程级未观测异常;其中多处 catch 为空(异常被吞)。 -- 影响:偶发崩溃/状态错乱且难排障。 -- 修复:业务异步方法一律返回 `Task` 并由上层 `await`/`ContinueWith` 处理异常;确需 fire-and-forget 的入口(事件/框架回调)内部必须 `try/catch + Diagnosis.Log`;`MuXing.SendMessage` 这种无 await 的应直接改 `void`。 - -### P2 结构 / 可维护性 - -#### P2-1 `WebApi.cs` 上帝文件(约 2800+ 行单 NancyModule) -- 位置:`StandardScene.Core/WebApi.cs`(`ApiController : NancyModule` 单类承载全部路由) -- 修复:按功能域拆分为多个 NancyModule(车辆/任务/地图/充电/交管/系统),公共逻辑(鉴权、统一响应封装、参数绑定、反射执行)下沉到基类/中间件。 - -#### P2-2 `GetMethods` 只扫描当前程序集,与运行期类型发现口径不一致 -- 位置:`WebApi.cs:47` `Assembly.GetExecutingAssembly().GetTypes()` -- 现象:拆分后卫星 dll(Devices/VDA5050)中的车型/Mission 方法不会出现在 `get_type_methods` 列表里;而运行期类型发现走的是 `UiTypeDiscovery.AllTypes()`(全域)。 -- 影响:前端“可用动作”列表缺失卫星类型的方法(execute 端点按实例反射仍可用,但 UI 发现不全)。 -- 修复:`GetMethods` 改用 `UiTypeDiscovery.AllTypes()` 统一口径。 - -#### P2-3 `AtomicFileUpdateHelper` 并非真正“原子”写 -- 位置:`StandardScene.Core/CommonTools/AtomicFileUpdateHelper.cs:54`(`File.WriteAllText` 直接覆盖) -- 现象:仅用 `ConcurrentDictionary` 保证**进程内同路径串行**(线程安全 OK),但写入是直接覆盖,**进程崩溃/断电时文件可能损坏(半截内容)**;类名“Atomic”有误导。另:`PathLocks` 只增不减(长期运行轻微累积,`14`)。 -- 修复:改“写临时文件 → Flush → `File.Replace`/`File.Move` 覆盖”实现真正原子落盘;或在文档/命名上明确其仅保证串行而非崩溃原子性。 - -#### P2-4 `JsonParser` 死代码与文件损坏隐患 -- 位置:`StandardScene.Core/Utils/JsonParser.cs:31-59`(`JsonChangeValue` 的 `foreach` 循环体整段被注释,`Task.Run` 跑空循环,等同 NOP);`22` `WriteJsonFile` 用 `File.AppendAllText` -- 现象:`JsonChangeValue` 是“改值”语义却什么都不做;`WriteJsonFile` 对同一 `TaskId` 重复调用会把多个 JSON 追加进同一文件,得到非法 JSON。 -- 修复:删除/重写 `JsonChangeValue`;`WriteJsonFile` 改为覆盖写(配合 P2-3 的原子写)。 - -#### P2-5 `WebAPIHelper` 退化为空壳 -- 位置:`StandardScene.Core/Utils/WebAPIHelper.cs:52-142`(Get/Post 等全部方法被注释);`getClient` 用 `ContainsKey + 索引器`(`29-33`)非原子 -- 现象:连接池设计正确(`23` `ConcurrentDictionary`),但对外没有任何可用请求方法 → 各处只能各自 `new HttpClient`(正是 P1-4 的根因之一);`getClient` 并发下可能创建多个 client。 -- 修复:恢复/重写 `GetAsync/PostAsync` 并全项目改用之;`getClient` 改 `GetOrAdd`。 - -#### P2-6 UDP 发送的 `SendAsync` 未 await + `using` 竞态 -- 位置:`Devices/Charge/PCBChargeStation.cs:71-73`(`udpClient.SendAsync(...)` 未 await,紧接 `Thread.Sleep(100)` 后 `using` 块结束 Dispose) -- 现象:异步发送可能在 `UdpClient` 被 Dispose 后才真正发出,存在 `ObjectDisposedException`/丢包风险(靠 `Sleep(100)` 掩盖)。 -- 修复:改同步 `Send` 或 `await SendAsync` 后再退出 `using`。 - -#### P2-7 `Console.*` 作为生产日志 -- 代表:`VDA5050Car.cs`(约 30 处)、`MasterMQTTCommunication.cs`(约 22 处)、`DummyCar.cs`(约 29 处)、`PCBChargeStation.cs:97`、`MuXingChargeStation.cs:438`、`Kiva.cs:567`、`Commons.cs`(约6)、`WebApi.cs`(约8) -- 修复:统一改 `Diagnosis.Log/Post`(项目既有日志门面),保留级别与可检索性。 - -#### P2-8 `throw ex;` 丢失原始堆栈 -- 位置:`VDA5050Car.cs:240`、`CarTypes/DummyCar.cs:451` -- 修复:改 `throw;`(重抛)或 `throw new XxxException(msg, ex)`(包装保留 inner)。 - -#### P2-9 反射调用非公开方法 / 按配置名反射 -- 位置:`CarTypes/VehicleMonitor.cs:1671`(`GetMethod(name, Public|NonPublic)` 可达私有方法);`ExtendDevice/ButtonBox/ButtonMission.cs:594`(按 `buttonConfig.TriggerMethod` 反射) -- 修复:限制到公开+白名单;对配置驱动的反射做方法存在性与白名单校验。 - -#### P2-10 UI 与业务耦合(服务端/无人值守会阻塞) -- 位置:`Commons.cs:42`(死锁回调里 `MessageBox.Show`)、`Kiva.cs:648` 等车型在后台线程 `MessageBox.Show` -- 修复:业务层只产生事件/日志,是否弹窗交由表现层决定(迁 migu 平台时一并解决)。 - -### P3 整洁 / 卫生 - -- **空 `catch{}`/静默吞异常**(建议至少 `Diagnosis.Log`):`Kiva.cs:606-610 / 637-639`、`VDA5050Car.cs:165-167 / 997-1000`、`Devices/Charge/FLChargeStation.cs:173 / 243`、`Chained/AbstractLoopMission.cs:1318/1333/1396/1576/1587/1601`、`Chained/LoopViewer.cs:260`、`Charge/CommunicationMessageService.cs:183-186 / 261-264`、`Commons.cs:44`。(注:`AsyncTcpClient` 中 `try{Close();}catch{}` 属清理性吞异常,可接受。) -- **本地回环/端口硬编码**(建议配置化,风险低于 P1-2):车型 `address="127.0.0.1"` 多处;`Model/Map.cs:195/207`(端口 4321);`Chained/LoopMission.cs:74`(`SiemensClient ...,"127.0.0.1",103`);`Chained/TransportDeliveryCallbacks.cs:27`(`_callbackUrl ...20101`)。 -- **`float.Parse`/`int.Parse` 未指定 Culture / 未 TryParse**:`Devices/Charge/PCBChargeStation.cs:61-62`、`Kiva.cs:557/601-603` 等。 -- **`SnowflakeIdGenerator`**:实现良好(`51` 锁、`54-58` 时钟回拨等待);仅提示 `DefaultEpochMs=2026-01-01`(`10`)部署到系统时间早于该值的机器会在构造期抛异常(`33-36`)。 - ---- - -## 三、本次新发现(既有报告未记录) - -| 级别 | 问题 | 位置 | -|---|---|---| -| P2(逻辑bug) | `Kiva.LoopTest` 复制粘贴错误:构造了 `plan4` 却 `await plan2.Compile("go").Queue()`;且 `LoopTestRunning` 标志设了但循环体从不检查(`LoopTestStop` 实际无效,“循环测试”并不循环) | `CarTypes/Kiva.cs:517-520`、`492/497/528` | -| P2 | `MuXingChargeStation.SendMessage` 标 `async void` 但内部全是同步 `stream.Write/Flush`,无 await;且只判 `client!=null` 未判 `stream` | `Devices/Charge/MuXingChargeStation.cs:424-440` | -| P2 | `JsonParser.JsonChangeValue` 空循环死代码;`WriteJsonFile` 用 `AppendAllText` 会损坏 JSON | `Utils/JsonParser.cs:31-59 / 22` | -| P2 | `AtomicFileUpdateHelper` 非真原子写 | `CommonTools/AtomicFileUpdateHelper.cs:54` | -| P2 | `WebAPIHelper` 请求方法全注释成空壳 | `Utils/WebAPIHelper.cs:52-142` | -| P2 | `GetMethods` 仅扫当前程序集,与全域类型发现口径不一致 | `WebApi.cs:47` | - ---- - -## 四、子系统评分(复核更新) - -| 子系统 | 旧评 | 复核新评 | 变化说明 | -|---|---|---|---| -| 设备驱动 `StandardScene.Devices` | ★★★★ | ★★★★ | `ModbusDoorController` 范本;`MuXing/PCB` 有 async void/UDP 竞态待修 | -| TCP 基础设施 `AsyncTcpClient` | ★★★★ | ★★★★ | 维持 | -| `SnowflakeIdGenerator` / `AtomicFileUpdateHelper` | —(未单列) | ★★★★ / ★★★ | Snowflake 好;AtomicFile 名不符实 | -| Coders(重构后) | ★★★★ | ★★★★ | 维持 | -| 任务族 Missions | ★★ | ★★★ | Thread.Abort 已改协作式停止(关键回升);async void/while+Sleep 仍在 | -| 充电 Charge | ★★ | ★★★ | 报文越界已加校验、停止已协作式;UDP 发送竞态/async void 待修 | -| VDA5050 协议 | ★★ | ★★ | 硬编码 IP / async void / throw ex / Console 仍集中,债务最重 | -| WebApi | ★ | ★★ | 反射白名单已加(关键回升);仍上帝文件 + 无鉴权 + GET 执行 | -| Commons / 公共层 | ★★ | ★★ | `AddOrUpdateTag` 名不符实、`WebAPIHelper` 空壳、`JsonParser` 死代码 | - ---- - -## 五、优先整改清单(建议顺序) - -1. **P1-1 WebApi 鉴权 + 危险操作语义化**(安全相关,AGV 现场风险最高)。 -2. **P1-2 VDA5050 硬编码 `192.168.2.1` 配置化**(多车/换现场必踩)。 -3. **P1-3 `Commons.AddOrUpdateTag` 修复语义**(影响面广、易引异常)。 -4. **P1-4 局部 `new HttpClient` 收敛为单例/工厂**(长稳性)。 -5. **P1-5 `async void` 收敛为 `Task` + 异常处理**(先 VDA5050 / 充电 / Transport 回调三处重点)。 -6. **P2-3/2-4/2-5 修死代码与伪原子**(`AtomicFileUpdateHelper`、`JsonParser`、`WebAPIHelper`)。 -7. **P2-1/2-2 WebApi 拆分 + 类型发现口径统一**。 -8. **P2-7 Console → Diagnosis 日志统一**(可脚本化批量替换,先 VDA5050)。 -9. **P3 空 catch 补日志 / 本地端口配置化 / Parse 加 Culture**(清扫)。 - -> 说明:本轮为只读审查,未改动任何源码。`Thread.Abort`、报文越界、反射白名单等旧 P0 经核实已修复,故当前不再列 P0。 diff --git a/StandardScene代码审查报告.md b/StandardScene代码审查报告.md deleted file mode 100644 index d2c4664..0000000 --- a/StandardScene代码审查报告.md +++ /dev/null @@ -1,267 +0,0 @@ -# StandardScene 代码审查报告 - -> 审查对象:`E:\Work\Core\Simple-FR\StandardSence`(StandardScene.Core / StandardScene.Devices / StandardScene.Protocol.VDA5050) -> 审查方式:全量反模式扫描(ripgrep)+ 关键文件精读 + 编译告警分类(dotnet build --no-incremental) -> 目标框架:`net8.0-windows`(最终目标 `net8.0`) -> 报告日期:2026-06-09 - ---- - -## 一、审查范围与方法 - -- **代码规模**:113 个 `.cs` 文件;最大文件 `WebApi.cs`(2661 行)、`AbstractLoopMission.cs`(1858 行)。 -- **扫描维度**:并发/线程、异常处理、报文解析边界、资源管理、配置硬编码、日志、UI/业务耦合、安全、编译告警。 -- **证据标注**: - - `精读确认`:已逐行读取、行号精确。 - - `扫描命中`:ripgrep 命中文件级,行号待整改时逐一核对。 - ---- - -## 二、总体评价与子系统评分 - -| 子系统 | 评分 | 说明 | -|---|---|---| -| 设备驱动(StandardScene.Devices·新) | ★★★★☆ | `ModbusDoorController` 工程化优秀,可作团队样板 | -| TCP 基础设施(AsyncTcpClient) | ★★★★☆ | 锁/陈旧回调防护/重连完善,少量瑕疵 | -| Coders(重构后) | ★★★★☆ | 本轮已去重,结构清晰 | -| CarTypes 车型族 | ★★☆☆☆ | 巨类、async void、空 catch、硬编码集中 | -| 任务族 Missions | ★★☆☆☆ | Thread.Abort、while(true)+Sleep、状态机分散 | -| 充电 Charge | ★★☆☆☆ | 实时报文路径越界风险、UDP 线程模型粗放 | -| VDA5050 协议 | ★★☆☆☆ | 硬编码 IP、async void、throw ex、Console 日志 | -| WebApi(老 Nancy) | ★☆☆☆☆ | 2661 行上帝文件、无鉴权反射调用、模板代码爆炸 | -| Commons / 公共层 | ★★☆☆☆ | 上帝工具类、重复实现、隐性 bug | - -**核心判断**:技术债**集中在旧模块**(CarTypes / Missions / Charge / VDA5050 / WebApi / Commons);**新写模块**(Devices、AsyncTcpClient)质量明显更高,说明团队具备写好代码的能力,债务主要是历史遗留。整改应"以新模块为范本、按子系统收敛旧债"。 - ---- - -## 三、问题严重级别汇总 - -| 级别 | 含义 | 主要条目 | -|---|---|---| -| **P0 阻断** | .NET8 下会崩溃/失效,或存在安全风险 | Thread.Abort 被吞、WebApi 无鉴权反射、实时报文越界 | -| **P1 高危** | 生产环境易触发故障/难排障 | 硬编码 IP、async void+throw、空 catch、HttpClient 滥用 | -| **P2 结构** | 可维护性/扩展性差 | 上帝类、重复代码、Console 日志、UI/业务耦合 | -| **P3 整洁** | 编译告警与代码卫生 | 45 项告警(重复 using、未用字段、隐藏成员等) | - ---- - -## 四、P0 阻断级问题 - -### P0-1 `Thread.Abort()` 在 .NET8 必抛异常且被静默吞掉(线程停不掉) - -`Thread.Abort()` 在 .NET8 抛 `PlatformNotSupportedException`(告警 SYSLIB0006,共 14 条/双配置)。多处 `Abort()` 外层是空 `catch{}`,导致**异常被吞、线程实际未停止**——任务"停止"后后台线程仍在跑,造成重复下发、资源泄漏、状态错乱。 - -精读确认命中点: - -| 文件 | 行 | -|---|---| -| `StandardScene.Core/Chained/AbstractLoopMission.cs` | 1314 `_strategyThread?.Abort()`、1329 `_logicThread?.Abort()` | -| `StandardScene.Core/Charge/StandardChargeMission.cs` | 681 `myThread?.Abort()`、684 `ChargeThread?.Abort()` | -| `StandardScene.Core/Scheduler/SecuritySignalMission.cs` | 176 `myThread?.Abort()` | -| `StandardScene.Core/Scheduler/NodeIsEnableMission.cs` | 121 `myThread?.Abort()` | -| `StandardScene.Core/Scheduler/HeartBeatMission.cs` | 68 `_myThread?.Abort()` | -| `StandardScene.Core/Chained/AbstractChainedDeliveryMission.cs` | 1606 `myThread.Abort()` | - -典型代码(`AbstractLoopMission.cs:1309`,`精读确认`): - -```1309:1334:E:\Work\Core\Simple-FR\StandardSence\StandardScene.Core\Chained\AbstractLoopMission.cs - public virtual void StopLoop() - { - try - { - _strategyRunning = false; - _strategyThread?.Abort(); - _strategyThread = null; - ... - } - catch { } - } -``` - -**修复方向**:改为**协作式停止**——已有 `_strategyRunning/_logicRunning` 布尔标志,循环体应周期检查该标志退出;线程创建用 `IsBackground=true`,停止时 `flag=false` 后 `Join(timeout)`。删除所有 `Abort()`。对阻塞型循环用 `CancellationToken` + 可中断等待(`Task.Delay(token)` / `ManualResetEventSlim.Wait(token)`)替代 `Thread.Sleep`。参考 `ModbusDoorController` 的 `CancellationTokenSource` 范式。 - ---- - -### P0-2 WebApi 反射接口:无鉴权远程调用任意方法(RCE 级风险) - -`StandardScene.Core/WebApi.cs`(`精读确认`,272–314 / 332–368)暴露: - -```272:296:E:\Work\Core\Simple-FR\StandardSence\StandardScene.Core\WebApi.cs - Get("/car_reflection/execute/{id}/{method}", parameters => - { - ... - methodInfo = car.GetType().GetMethod((string)parameters.method); - ... - return ExecuteMethod(methodInfo, car, queryParams); - }); -``` - -`mission_reflection/execute/{id}/{method}` 同款。问题:**通过 HTTP 即可按名反射调用 Car/Mission 上任意 public 方法**,无身份校验、无方法白名单、无危险方法拦截。结合 `GET` 语义,配置不当会暴露在内网甚至外网,构成命令执行级攻击面。 - -**修复方向**: -1. 最小化:加访问令牌/来源 IP 限制(中间件统一校验)。 -2. 方法白名单:仅允许标注了 `[MethodMember]`(或新增 `[WebInvokable]`)的方法被反射调用;拒绝其余。 -3. 语义化:执行类操作改 `POST`;统一错误响应封装(见 P2-2)。 - ---- - -### P0-3 实时报文解析按固定下标取值、缺长度校验(IndexOutOfRange 崩溃) - -对比发现两条解析路径**风险等级不同**: - -- ✅ **正确范本**:`StandardScene.Core/Charge/CommunicationMessageService.cs`(`精读确认`)在取下标前先校验 `parts.Length < 30 / < 10`(198、276 行),再访问 `bytes[28]` 等,越界已被防住。 -- ❌ **风险路径**:实时 UDP/回调路径按固定下标直接取值,未见等价长度校验(`扫描命中`): - - `StandardScene.Core/ChargeUdpService.cs`(`message[28]` 等) - - `StandardScene.Devices/Charge/PCBChargeStation.cs`(`OnUdpMessage` 中 `message[1]`) - - `StandardScene.Core/ChargeStationType/MuXingChargeStation.cs` - -设备掉线/半包/异常帧时,直接 `IndexOutOfRangeException`,且若发生在 UDP 接收线程会拖垮整条接收链路。 - -**修复方向**:所有报文解析入口统一"先校验长度(与帧头/类型匹配)再取值",越界返回 null 并 `Diagnosis.Log` 记录原始帧;抽出 `ChargeFrameParser` 复用 `CommunicationMessageService` 的安全解析。 - ---- - -## 五、P1 高危问题 - -### P1-1 配置硬编码(IP/URL/路径),多 AGV/换环境必失效(`扫描命中` 14 文件) - -最典型(`精读确认`)`StandardScene.Protocol.VDA5050/VDACar/VDA5050Car.cs:150`: - -```150:168:E:\Work\Core\Simple-FR\StandardSence\StandardScene.Protocol.VDA5050\VDACar\VDA5050Car.cs - public async void GetVDA5050StateFromC() - { - try - { - //string jsonResponse1 = await hc.GetStringAsync($"http://{this.address}:8008/getStat"); - string jsonResponse1 = await hc.GetStringAsync($"http://192.168.2.1:8008/getStat"); - ... - } - catch (Exception ex) - { - } - } -``` - -把按车 `this.address` 的写法**注释掉、写死 `192.168.2.1`**——多 AGV 场景必然全部打到同一地址;外加空 catch 吞错,故障"静默"。其余命中:`Kiva.cs`/`Forklift.cs`(`192.168.2.1:8008`)、`Map.cs`(`127.0.0.1:4321`)、`LoopMission.cs`(西门子 PLC IP)、`TransportDeliveryCallbacks.cs`(回调 URL)、`StandardCADTool.cs`(Windows 盘符路径)等。 - -**修复方向**:抽 `scene.json`/配置中心统一注入;禁止业务代码内联端点;`StandardCADTool` 路径改相对/可配置。 - -### P1-2 `async void` + 在其中 `throw`(CA2200 失栈)→ 进程级崩溃风险 - -- `Kiva.cs:619` `public new async void ForceStop()`(`精读确认`):`async void`,catch 内 `MessageBox.Show`(UI 耦合),最外层 `Console.WriteLine(e); throw;`——`async void` 抛出的异常无法被调用方捕获,直接打到 `SynchronizationContext`/线程池,可致进程崩溃。`new` 还隐藏基类 `ForceStop`(CS0114)。 -- `VDA5050Car.cs:240` `throw ex;`(`精读确认`)破坏原始堆栈(CA2200,全仓 4 条)。`DummyCar.cs` 同款。 -- `async void` 在 CarTypes/Charge/VDA5050/CAD 等多文件广泛存在(`扫描命中`)。 - -**修复方向**:事件处理器之外一律 `async Task`;确需 `async void` 的入口用 `try/catch` 兜底并 `Diagnosis.Log`,不得外抛;`throw ex;` → `throw;`。 - -### P1-3 空 `catch{}` 静默吞异常(`扫描命中` 10 文件) - -`Kiva.cs`、`VDA5050Car.cs`、`FLChargeStation.cs`、`AbstractLoopMission.cs`、`Commons.cs`、`AsyncTcpClient.cs`、`LoopViewer.cs` 等。`Kiva.cs:606`、`VDA5050Car.cs:165` 为典型业务路径空 catch。 - -> 注:`AsyncTcpClient` / `ModbusDoorController` 中环绕 `Close()/Dispose()` 的空 catch 属可接受的清理兜底,应保留但加注释;业务路径空 catch 必须改为记录日志。 - -### P1-4 同步阻塞调用 `.Result/.Wait()/GetAwaiter().GetResult()`(`扫描命中` 7 文件) - -`ButtonMission.cs`、`DoorMission.cs`、`VDA5050Car.cs` 等。`Kiva.cs:635 status.programs.task.Wait()`(`精读确认`)在 UI/线程上下文易死锁。**修复**:异步链路打通,避免 sync-over-async。 - -### P1-5 `new HttpClient()` 反复实例化(Socket 耗尽)(`扫描命中`) - -`Forklift.cs`/`Kiva.cs`/`VDA5050Car.cs`/`WebApi.cs` 等频繁 `new HttpClient`。**修复**:单例或 `IHttpClientFactory`/`SocketsHttpHandler`(设 `PooledConnectionLifetime`)。 - ---- - -## 六、P2 结构 / 质量问题 - -### P2-1 `Commons.cs` 上帝工具类(`精读确认`) -- `AddOrUpdateCarField/SiteField/MissionField` "ContainsKey→Remove+Add / else Add" 模板**重复 4 份**,应为 `dict[key]=value`。 -- `AddOrUpdateTag` 名为 update 实为 add(更新逻辑被注释),重复键会抛错,命名误导。 -- `CarValue`:存在 `electricCurrent` 时**忽略入参 key** 直接返回,疑似 bug。 -- `GoSite` catch 内 `Console.WriteLine + Thread.Sleep(3000)`,注释写"重新执行"但**并未真正重试**。 -- 调度 `NearestTask`(约 398–538)巨函数、深嵌套、大段 `#region ObsoleteCode` 注释代码、魔法优先级 `Priority=50`。 - -### P2-2 `WebApi.cs` 2661 行上帝文件(`精读确认`) -错误响应 `new { Success=false, Code=500, Data="null", Message=... }` 模板**复制几十处**;路由全堆一个文件。**修复**:抽 `ApiResult.Fail/Ok` 帮助器;按资源拆分路由模块;老接口归入 `WebApi.Core(deprecated)` 并规划迁移。 - -### P2-3 日志体系不统一(`扫描命中` 22 文件 `Console.WriteLine`) -与 `Diagnosis.Log` 混用。`ModbusDoorController` 已全程 `Diagnosis.Log`,应作为统一范式推广;禁用 `Console.WriteLine` 于业务代码。 - -### P2-4 UI 与业务耦合(`扫描命中`) -`MessageBox.Show` 出现在 `Commons.cs`(TrafficControl.OnDeadLock)、`Kiva.cs:648`、`DoorManager.cs`、`ButtonBoxManager.cs` 等业务/管理类中。**修复**:业务层只发事件/日志,弹窗交由 UI 层(后续 migu 平台)。 - -### P2-5 `while(true)+Thread.Sleep` 忙等/阻塞(`扫描命中` 13 文件) -`Forklift.cs`、`StandardChargeMission.cs`、`ChargeUdpService.cs`、`VDA5050Car.cs` 等。**修复**:改 `CancellationToken`+可中断等待或定时器;与 P0-1 协作式停止一并处理。 - -### P2-6 AsyncTcpClient 细节(`精读确认`) -- `Send` 失败抛 `InvalidProgramException`(异常类型不当,应 `InvalidOperationException`/自定义)。 -- `HandleDatagramWritten` 调 `EndWrite` 无 try,写失败异常落到线程池无人观测。 -- `uint on = 1;` 未使用字段(CS0414)。 - ---- - -## 七、编译告警分类(全量,双配置合计 90 实例 ≈ 45/配置) - -| 告警码 | 数量 | 含义 | 处置 | -|---|---|---|---| -| CS0108 | 16 | 隐藏继承成员未加 `new` | 显式 `new`/`override` 或改名 | -| CS4014 | 14 | 调用未 `await`(即发即弃) | 显式 `await` 或 `_ =` 并说明 | -| CS0414 | 14 | 私有字段赋值但从未使用 | 删除 | -| SYSLIB0006 | 14 | `Thread.Abort` 已弃用 | 见 **P0-1** | -| CS0105 | 6 | 重复 using | 删除 | -| CS0162 | 6 | 不可达代码 | 清理 | -| CS0168 | 6 | 变量声明未用 | 删除 | -| CA2200 | 4 | `throw ex` 失栈 | 改 `throw;` | -| CS8321 | 2 | 局部函数未用 | 删除(如 VDA5050Car `monitor()`) | -| CS0169 | 2 | 字段从未使用 | 删除 | -| CS0114 | 2 | 隐藏继承成员(如 ForceStop) | `override`/`new` | -| CS0219 | 2 | 变量赋值未用 | 删除 | -| CS0649 | 2 | 字段从未赋值 | 初始化或删除 | - ---- - -## 八、分子系统评述(摘要,详版见架构方案) - -- **CarTypes**:`BasicCarFields/SiteFields/TrackFields/PlanFields` 字段袋设计合理(`BasicFields.cs`,`CarLength/CarWidth` 默认 `-1` 作"未配置"哨兵,已被避障 Coder 正确利用);但具体车型(Kiva/Forklift/VDA5050Car)是巨类,混杂通信、状态、UI、调度,async void/空 catch/硬编码集中。 -- **Coders**:本轮已完成磁导航统一与避障去重(`AvoidanceParamCoder` 4 参 + `AvoidanceParamLWCoder` 2 参),结构清晰,建议继续把车型内联 Coder 收敛到 `CommonTrackCoders`。 -- **Missions**:Chained/InterLock/Scheduler 线程模型粗放(裸 `new Thread`+`Abort`+`while(true)`),状态机散落字符串 `status.status`。建议统一 `MissionRunnerBase`(CancellationToken + 状态枚举)。 -- **Charge**:解析存在"安全范本"与"风险路径"并存(见 P0-3);UDP 服务与 Mission 线程耦合。 -- **Devices(新)**:`ModbusDoorController` 优秀;唯一瑕疵是用析构函数兜底 `Disconnect`(在 GC 线程取锁+`Wait`,有风险),应实现 `IDisposable` 显式释放。 -- **VDA5050**:MQTT/HTTP 异步用法不规范(async void、throw ex、硬编码 IP、Console 日志),状态处理 `ProcessCacheAndSendOrderMessage` 内大量 Console。 -- **WebApi**:见 P0-2 / P2-2,最高优先级重构对象。 - ---- - -## 九、正面样板(建议作为团队基线) - -1. `StandardScene.Devices/Door/ModbusDoorController.cs`:`CancellationTokenSource` 协作式停止、`lock` 线程安全、变更检测(`_lastSentControl`)、重连节流、统一 `Diagnosis.Log`、资源清理。 -2. `StandardScene.Core/TCP/AsyncTcpClient.cs`:锁保护、陈旧回调防护、自动重连。 -3. `StandardScene.Core/Charge/CommunicationMessageService.cs`:报文解析前置长度校验(安全解析范本)。 - ---- - -## 十、整改路线图 - -**第 1 批(P0,本次执行)** -1. Thread.Abort → 协作式停止(6 文件 8 处)。 -2. WebApi 反射 execute 加来源校验 + `[MethodMember]` 白名单。 -3. 充电实时报文路径补长度校验(复用安全解析)。 - -**第 2 批(P1)** -4. 硬编码端点配置化(VDA5050/Kiva/Forklift/Map/Loop/CAD)。 -5. `async void`→`Task`、`throw ex`→`throw`、业务空 catch 加日志。 -6. HttpClient 单例化;sync-over-async 拆解。 - -**第 3 批(P2/结构)** -7. `ApiResult` 帮助器 + WebApi 拆分;`Commons` 拆服务、修 `CarValue`/`GoSite`/`AddOrUpdate`。 -8. 统一 `Diagnosis.Log`;UI 解耦;`MissionRunnerBase` 统一线程/状态机。 - -**第 4 批(P3 卫生)** -9. 清 45 项告警(重复 using、未用字段、不可达代码、隐藏成员)。 - ---- - -## 附录:扫描方法 - -- 反模式:`rg` 扫描 `Thread.Abort` / `catch\s*\{\s*\}` / `throw ex;` / `async void` / `\.Result|\.Wait\(\)` / 硬编码 IP / `Console.WriteLine` / `while\s*\(\s*true\s*\)` / `new HttpClient` / `MessageBox.Show` / 魔法下标。 -- 告警:`dotnet build --no-incremental` → `_buildwarnings.txt` → 按告警码聚合计数。 -- 精读:AsyncTcpClient、Commons、CommunicationMessageService、VDA5050Car、WebApi、Kiva、AbstractLoopMission、ModbusDoorController、BasicFields 等。 diff --git a/StandardScene会话14交接摘要.md b/StandardScene会话14交接摘要.md deleted file mode 100644 index 323c853..0000000 --- a/StandardScene会话14交接摘要.md +++ /dev/null @@ -1,241 +0,0 @@ -# StandardScene 插件化拆分 — 会话14 交接摘要 - -> **用途**:供其他 AI 会话快速读取上下文,继续推进 StandardScene 大改或 SimpleLite 内核改造。 -> **工作目录**:`E:\Work\Core\Simple-FR\StandardSence` -> **主交付物**:`StandardScene拆分计划.md`(**v2**,已落盘) -> **上游设计**:`E:\Work\Core\Simple-FR\配置向导与导航场景插件化设计.md`(第 5 节 StandardScene 拆分、第 11.3 节 Phase C) -> **新宿主 API 文档**:`E:\Work\Core\Simple-FR\Simple\SimpleLite\Docs\MIGU-API.md` -> **整理时间**:2026-06-09 - ---- - -## 1. 本会话做了什么 - -| 阶段 | 内容 | 状态 | -|---|---|---| -| 启动 | 用户要求用 wuxianChat 建立「会话14」 | 曾尝试 MCP,一度因执行后端不可用失败 | -| 精读代码 | 对 `StandardSence` 约 110 个 `.cs` 做逐模块走查(车型、Mission、设备、WebApi、VDA5050 等) | ✅ 完成 | -| 产出 v1 计划 | 写入 `StandardScene拆分计划.md`:目录规划、功能归属矩阵、抽离清单、技术难点、分阶段路线 | ✅ 完成 | -| 用户评审反馈 | 宿主改 SimpleLite、net8.0、设备独立 dll+热插拔、删除 FactoryTest/松灵、WebApi 暂留后期废弃 | ✅ 已纳入 | -| 阅读 SimpleLite | 确认 `SimpleLite.csproj` = net8.0 / CycleGUI / EmbedIO;阅读 MIGU-API 做 WebApi 迁移索引 | ✅ 完成 | -| 产出 v2 计划 | 全文更新 `StandardScene拆分计划.md` | ✅ 完成 | -| 代码改动 | **无**(本会话仅分析与文档,未改业务代码) | — | - ---- - -## 2. 用户原始诉求(按时间) - -### 2.1 第一轮 - -> 整个仓库需要大改。参考 `配置向导与导航场景插件化设计.md` 中关于 StandardScene 的「导航场景插件化」部分;精细化读当前代码,抽离标准功能,做好仓库目录规划与功能规划,**先把 plan 做出来**到 StandardScene 本地 md 文件。 - -### 2.2 第二轮(评审反馈,已写入 v2) - -1. **设备驱动**需要独立 dll,并且**支持热卸载和加载**。 -2. StandardScene 依赖 **`SimpleLite.dll`**,老 **`SimpleComposer` 废弃**(**另一个 AI 对话窗口**正在改相关引用)。 -3. **目标框架统一 `net8.0`**(WinForms 场景需 `net8.0-windows` 的说明在评审中被修正:SimpleLite 本身是纯 `net8.0` + CycleGUI,故 StandardScene 应**去 WinForms**)。 -4. **`FactoryTest`、松灵残留**不纳入 Core,**直接删除**。 -5. **`WebApi.cs`** 是针对老平台的 Nancy 接口:整理后**暂时保留在 Core**,后期废弃,改用 SimpleLite 接口能力(MIGU-API.md);**此版本先保留**。 - -### 2.3 第三轮 - -> 把以上会话整理成 md 文件,分享给另一个会话读取。 -> → 即本文档。 - ---- - -## 3. 核心结论(另一会话必须知道的) - -### 3.1 架构修正:导航 ≠ 车型 - -上游设计隐含「按车型/导航整包切 dll」,**与代码事实不符**: - -- 导航由 **轨道/站点 fields + TrackCoder** 决定,与车型**正交**。 -- 例:`Kiva` 同一类上同时挂 **磁导航 coder**、**二维码 coder**、**激光避障 coder**(非 SLAM 定位)。 -- **正确拆法**:`StandardScene.Core` 保留车型本体 + 任务/充电/互锁等;导航能力抽到 `Magnetic` / `QrCode` / `Laser` 等 dll,以**可插拔 coder** 形式挂载。 - -### 3.2 激光:避障 vs 导航定位 - -| 能力 | 代表 | 归属 | -|---|---|---| -| 激光避障 | `LidarArea`、`SwitchLidarArea`、各车型通用 | **Core** | -| 激光 SLAM 定位/地图 | `LidarMap`、`getLidarMap`、拉 `127.0.0.1:4321` | **Laser dll** | - -### 3.3 三个正交维度 - -1. **导航**:磁 / 二维码 / 激光(SLAM) -2. **车型**:Kiva、叉车、多舵轮顶升、机械臂、仿真车等 -3. **设备驱动**:充电桩(FL/MuXing/PCB)、门(Modbus)、按钮盒(Leeg/Azowie) - -设备驱动必须 **独立 `Devices.*` dll + 热插拔**(SimpleLite `/plugins` + collectible ALC)。 - -### 3.4 VDA5050 - -- `CarTypes/VDACar/` 是完整 **MQTT 协议栈**,建议独立 `StandardScene.Protocol.VDA5050` dll。 -- 耦合点:`ArmCar` 引用 `VDA5050SiteField`,拆分时需解耦。 - -### 3.5 宿主与框架(v2 已定) - -| 项 | 旧 | 新 | -|---|---|---| -| 宿主 | `SimpleComposer.exe` (.NET 4.8) | **`SimpleLite`** (net8.0) | -| 契约 | `RefSimpleCore.dll` + Composer 程序集 | **`SimpleCore` + SimpleLite** | -| UI | WinForms(大量窗体) | **CycleGUI / 平台 Web**(去 WinForms) | -| 对外 API | `WebApi.cs` (Nancy 1.4.5) | 暂留 Core;后期 **EmbedIO**(MIGU-API) | - -**SimpleLite 事实**(来自 `SimpleLite.csproj`):`TargetFramework=net8.0`,`OutputType=Exe`,UI 用 CycleGUI,WebApi 用 EmbedIO,引用 `SimpleCore`。 - -### 3.6 老 WebApi 处置 - -- `WebApi.cs` ~123KB,40+ 端点,**与导航弱相关**(导航相关主要是 `QrMap`、`getLidarMap`)。 -- **本版**:整理为 `WebApi.Core.cs` 暂留 Core,标 `[Obsolete]` / deprecated。 -- **后期**:按 `StandardScene拆分计划.md` §4.7 映射到 SimpleLite `/api/sl/projection/*`。 -- **缺口**:二维码地图下发、激光 SLAM 取图在 MIGU-API **暂无直接对应**,迁移前需在 SimpleLite 或场景插件侧补接口。 - -### 3.7 必须删除的文件(不进 Core/Customer) - -| 文件 | 原因 | -|---|---| -| `FactoryTest.cs` | 产测专用 | -| 根目录 `SongLingDeliveryViewer.Designer.cs` | 松灵客户残留,无主文件、未编入 csproj | -| 根目录 `MultiWheelLifterCar.cs` | 与 `CarTypes/MultiWheelLifterCar.cs` 重名死文件,未编入 csproj | -| `CarTypes/UselessCar.cs` | `[CarType]` 已注释,测试残留 | - ---- - -## 4. 目标 dll 结构(摘要) - -``` -StandardScene.Core/ net8.0 基座(alwaysLoad) -StandardScene.Magnetic/ 磁导航 -StandardScene.QrCode/ 二维码导航 + SyncQrMap -StandardScene.Laser/ SLAM / LidarMap -StandardScene.Protocol.VDA5050/ VDA5050 MQTT 栈 -StandardScene.Devices.Charge/ 充电桩驱动(热插拔) -StandardScene.Devices.Door/ 门控驱动(热插拔) -StandardScene.Devices.ButtonBox/ 按钮盒驱动(热插拔) -``` - -各可激活导航/设备 dll 配 `scene.json`,对接 SimpleLite `POST /api/sl/projection/scenes/apply` 与 `active-scenes.json`。 - -**详细文件级归属矩阵**见 `StandardScene拆分计划.md` §4,勿在本摘要重复展开。 - ---- - -## 5. 可抽离的重复代码(C0 优先) - -| 重复项 | 现状位置 | 目标 | -|---|---|---| -| 磁循迹器 | `Kiva.AllCarMagTrackCoder` + `MultiWheelLifterCar.MagTrackCoder` | 合并为 `Magnetic.MagneticTrackCoder` | -| `newReset` / `newTrafficReset` | Kiva、Forklift、MultiWheelLifterCar 各一份 | 上提 `StandardCarBase` | -| `SetDisplayInfo`、远程急停/复位 HTTP | 多车型雷同 | Core 基类默认实现 | -| `GetCarStatus` | Commons 与 MultiWheelForkLifter 各一份 | 统一 Commons | -| 避障/IO/纠偏 coder 模板 | 各车型粘贴 | Core 通用 coder 模板集 | - ---- - -## 6. 技术难点(需与内核会话协同) - -### 6.1 TrackCoder 运行期注册(最关键) - -- **现状**:`[TemplateTrackCoderSettings]` / `[ProgramTrackCoderSettings]` **编译期硬绑定**在车型类上。 -- **目标**:导航 dll 加载时为车型**注册** coder,卸载时移除。 -- **方案**:优先在 **`SimpleCore` 增加 coder 注册表**;导航 dll 在 `INavigationProfile.OnActivate` 注册。 -- **兜底**:Core 暂保留全量 coder,导航 dll 只承载 API/地图/清单(保证不回归)。 - -### 6.2 net4.8 → net8.0 + 去 WinForms - -- 引用从 `SimpleComposer.exe` 切到 `SimpleLite` / `SimpleCore`。 -- 命名空间 `SimpleComposer.RCS` → `SimpleLite.RCS` 等(**另一会话进行中**)。 -- 所有 WinForms 窗体迁 **CycleGUI** 或平台 Web;`MessageBox` 改 CycleGUI 弹窗。 -- 涉及窗体:`DeliveryViewer`、`LoopViewer`、`TrafficInterlockViewer`、`VehicleMonitor`、各 `Charge/*Form`、`DoorMonitor`、`ButtonBoxManager`、`VDACar/TextViewer` 等。 - -### 6.3 热插拔清理 - -设备/导航 dll 卸载前须 `OnDeactivate`:清理 coder 注册、MQTT/TCP 连接、全局回调;配合 collectible ALC,确保无存活 `Car`/`Mission` 实例。 - ---- - -## 7. 分阶段实施(当前均未启动代码) - -| 阶段 | 内容 | 状态 | -|---|---|---| -| **C0** | 删除残留 + 去重沉淀 + 命名空间收敛 + 与 SimpleCore 内核对齐 | ⬜ 待启动 | -| **C1** | 新建 `StandardScene.Core`(net8.0),换宿主,去 WinForms,整理 WebApi.Core | ⬜ | -| **C2** | Magnetic / QrCode / Laser 三导航 dll | ⬜ | -| **C3** | Devices.Charge / Door / ButtonBox(热插拔) | ⬜ | -| **C4** | Protocol.VDA5050 | ⬜ | -| **C5** | 联调 + WebApi 迁 SimpleLite + 文档收尾 | ⬜ | - ---- - -## 8. 待确认项(评审未拍板) - -1. **SimpleCore 是否提供 TrackCoder 运行期注册 API**?(决定 C2 用注册表还是兜底方案) -2. **UI 迁移节奏**:一次性迁 CycleGUI,还是按 dll 分批;哪些仅保留平台 Web? -3. **VDA5050** 是否确认独立 dll?(计划推荐独立) -4. **二维码地图下发 / 激光 SLAM 取图** 在 SimpleLite 侧由谁补接口、何时补? -5. 是否存在**车型仅支持单一导航**的硬约束? - ---- - -## 9. 跨会话分工(重要) - -| 会话/方向 | 负责内容 | 与本计划关系 | -|---|---|---| -| **本会话(会话14)** | StandardScene 代码精读 + 拆分计划 v1/v2 | 主文档已落盘 | -| **另一 AI 会话(用户提及)** | SimpleLite / SimpleCore 引用改造、`SimpleComposer` → `SimpleLite` 命名空间与程序集引用 | StandardScene C1 依赖其进度 | -| **平台 / 配置向导** | `WizardController`、`data/config-deployment.json` | 通过 `/scenes/apply` 驱动插件加载 | - -**协作接口建议**: - -- StandardScene 侧等待:SimpleCore 的 **coder 注册表**、**插件生命周期**(OnActivate/OnDeactivate)、**Collectible ALC** 约定。 -- 内核侧等待:StandardScene 的 **scene.json 清单**、**NavKind 枚举**、各 dll 的 **provides** 字段(见拆分计划 §9)。 - ---- - -## 10. 关键文件索引 - -| 路径 | 说明 | -|---|---| -| `E:\Work\Core\Simple-FR\StandardSence\StandardScene拆分计划.md` | **主计划(v2)**,含归属矩阵、§4.7 WebApi 迁移表、分阶段、风险 | -| `E:\Work\Core\Simple-FR\StandardSence\StandardScene会话14交接摘要.md` | **本文档**,会话级交接 | -| `E:\Work\Core\Simple-FR\配置向导与导航场景插件化设计.md` | 上游框架设计 | -| `E:\Work\Core\Simple-FR\Simple\SimpleLite\Docs\MIGU-API.md` | SimpleLite 新 API(WebApi 迁移目标) | -| `E:\Work\Core\Simple-FR\Simple\SimpleLite\SimpleLite.csproj` | 宿主:net8.0 / CycleGUI / EmbedIO | -| `E:\Work\Core\Simple-FR\StandardSence\StandardScene.csproj` | 现状:net4.8 单体插件 | -| `E:\Work\Core\Simple-FR\StandardSence\Commons.cs` | 插件契约、`CustomOperationsBeforeLoading`、`NoReflectionApi` | -| `E:\Work\Core\Simple-FR\StandardSence\WebApi.cs` | 老 Nancy API(待 deprecated) | -| `E:\Work\Core\Simple-FR\StandardSence\CarTypes\Kiva.cs` | 导航与车型耦合的典型样本 | -| `E:\Work\Core\Simple-FR\StandardSence\CarTypes\BasicFields.cs` | `TagValue` 等轨道/站点字段定义 | - ---- - -## 11. 给下一会话的推荐起手式 - -若继续 **StandardScene 实施**: - -1. 先读 `StandardScene拆分计划.md` §0(TL;DR)和 §4(归属矩阵)。 -2. 从 **C0** 开始:删除 §3.7 所列文件;合并磁循迹器;提取 `StandardCarBase`;`AMRScene1` 命名空间收敛。 -3. 与 **SimpleLite 改造会话**对齐:引用是否已可编译、coder 注册表 API 是否就绪。 -4. **不要**在未确认前大规模改 WebApi 或 WinForms——C1 才系统性处理。 - -若继续 **SimpleLite / SimpleCore 改造**: - -1. 优先落实 **TrackCoder 运行期注册**、**插件 OnActivate/OnDeactivate**、**/plugins 热卸载** 与 StandardScene 计划 §7 对齐。 -2. 评估 MIGU-API 是否需补 **QrMap**、**getLidarMap** 等价端点。 -3. 确认 `SimpleComposer.RCS` → `SimpleLite.RCS` 迁移范围,避免 StandardScene 引用断裂。 - ---- - -## 12. 变更记录 - -| 版本 | 日期 | 说明 | -|---|---|---| -| v1 | 2026-06-09 | 初版拆分计划(代码精读结论) | -| v2 | 2026-06-09 | 纳入 SimpleLite/net8.0/去 WinForms、设备热插拔、删除项、WebApi 暂留+迁移索引 | -| 交接摘要 | 2026-06-09 | 本会话整理,供跨会话阅读 | - ---- - -*本文档为会话级摘要;实施细节、完整类级归属与 WebApi 端点映射以 `StandardScene拆分计划.md` 为准。* diff --git a/StandardScene架构重构方案.md b/StandardScene架构重构方案.md deleted file mode 100644 index 2bc974d..0000000 --- a/StandardScene架构重构方案.md +++ /dev/null @@ -1,267 +0,0 @@ -# StandardScene 架构重构方案 - -> 配套文档:《StandardScene代码审查报告.md》(质量/缺陷)、《StandardScene拆分计划.md》(拆分进度) -> 本文聚焦**结构与架构合理性**:现状全景 → 逐模块深度分析 → 目标分层 → 程序集边界 → net8.0 去 Windows → 分阶段迁移路径。 -> 日期:2026-06-09 - ---- - -## 一、现状架构全景 - -### 1.1 程序集与依赖 - -| 程序集 | AssemblyName | TFM | 角色 | 依赖 | -|---|---|---|---|---| -| StandardScene.Core | `StandardScene` | net8.0-windows | 基座:抽象+业务+任务+调度+WebApi+UI | SimpleLite/SimpleCore/CommonUsage/MDCSToolBox/Topaz + NuGet(MQTTnet/Nancy/Jint/EasyModbus/IoTClient/OpenXml/Newtonsoft) | -| StandardScene.Devices | `StandardScene.Devices` | net8.0-windows | 具体设备驱动(门/充电桩/按钮盒) | →Core + SimpleLite/SimpleCore/Topaz + leegKeys-sdk | -| StandardScene.Protocol.VDA5050 | `StandardScene.Protocol.VDA5050` | net8.0-windows | VDA5050 协议车型 | →Core + SimpleLite/SimpleCore + NuGet(MQTTnet/Nancy/Jint/Newtonsoft) | - -插件清单 `scene.json`(Devices / VDA5050 各一份): - -```json -{ "id":"devices", "assembly":"StandardScene.Devices.dll", "requiresCore":"StandardScene.dll", - "provides": { "doorControllers":[...], "chargeStations":[...], "buttonBoxes":[...] } } -``` - -发现机制:`SimpleLite.Utils.UiTypeDiscovery.AllTypes()` 跨程序集扫描 + 类型特性(`[DoorType]`/`[ChargeType]`/`[ButtonBox...]`/`[CarType]`)。 - -### 1.2 当前依赖方向(问题版) - -```mermaid -graph TD - Devices --> Core - VDA5050 --> Core - Core -->|NuGet| MQTTnet - Core -->|NuGet| Nancy - Core -->|NuGet| EasyModbus - Core -->|NuGet| IoTClient - Core -->|NuGet| OpenXml - Core --> WinForms[WinForms 12+ 窗体] - Core --> SimpleLite - subgraph 卫星 - Devices - VDA5050 - end -``` - -**核心结构问题**:Core 是"万能基座"——既是抽象基座,又塞满了具体协议依赖(MQTT 属 VDA5050、Modbus/IoTClient 属设备、Nancy 属 WebApi、OpenXml 属报表),还内置 12+ WinForms 窗体。卫星只能依赖这个臃肿 Core,无法独立演进。 - -### 1.3 体量分布(非 Designer,前列) - -WebApi 2686 / AbstractLoopMission 1858 / VehicleMonitor 1502(UI) / AbstractChainedDeliveryMission 1407(已 Compile Remove) / ChainedDeliveryMission 1365 / ChargeStationManagementForm 1250(UI) / AbstractChargeLogicMission 1143 / VDA5050Car 938 / DoorMission 932 / ButtonBoxManager 913 / DoorManager 848 / Kiva 827 / ButtonMission 743 / StandardChargeMission 671 / Commons 653 … - -> 12+ 个 600~2700 行巨类,是可维护性的主要矛盾。 - ---- - -## 二、结构性问题诊断(按影响排序) - -| # | 问题 | 证据 | 影响 | -|---|---|---|---| -| S1 | **分层污染**:Core 背负协议/设备/报表专有依赖 | Core.csproj 引 MQTTnet/EasyModbus/IoTClient/Nancy/OpenXml | 卫星无法瘦身;Core 编译/部署重;职责不清 | -| S2 | **WinForms 全模块渗透**,阻塞纯 net8.0 | 12+ `*.Designer.cs`(Charge 4 个 Form、VehicleMonitor、各 Manager/Viewer);三 csproj 均 `UseWindowsForms=true` + `net8.0-windows` | 无法 `net8.0` 跨平台/瘦运行;与"UI 迁 migu"目标冲突 | -| S3 | **God-class 泛滥** | WebApi 2686 / AbstractLoopMission 1858 / ChainedDeliveryMission 1365 / AbstractChargeLogicMission 1143 … | 改动风险高、测试困难、并发态难推理 | -| S4 | **充电子系统未独立**,却已自成体系(20 文件) | `Charge/` 任务+站点+配置+数据服务+通信+4 表单 | 应为独立卫星,却深埋 Core | -| S5 | **抽象与实现同居 Core** | 设备基类/特性/Mission/Manager 在 Core,仅具体驱动在 Devices | 卫星仍强依赖 Core 内部;热插拔受限 | -| S6 | **干净抽象反向耦合到巨类** | `Loop/ILoopRules.cs` 顶部 `using static AbstractLoopMission;`(依赖其嵌套 `LoopPoint`) | 好接口被巨类绑架,无法独立复用 | -| S7 | **全局可变静态** | `DeliveryCallbackRegistry`(static 字典)、`Commons` 静态工具、各 `static HttpClient` | 隐式耦合、测试隔离难、生命周期不可控 | -| S8 | **内核 Coder 注册表限定本程序集反射** | `ProgramCoderSet`/`SegmentPlan.Coder` 特性驱动、按内核程序集反射 | 导航类卫星(磁/二维码/激光)无法热插拔(C2 阻塞) | -| S9 | **构建可移植性差** | csproj 多处绝对 `HintPath`(E:\Work…、D:\MDCS…) | 换机/CI 无法直接构建 | -| S10 | **死文件/弃用并存** | `AbstractChainedDeliveryMission.cs`(1407) 被 `Compile Remove` 仍在树 | 认知噪音、误改风险 | -| S11 | **命名空间与程序集名不一致** | 三程序集 `RootNamespace=StandardScene`;类型散落 `StandardScene.*` 子命名空间 | 物理边界与逻辑边界错位,难判断"谁属于谁" | - ---- - -## 三、逐模块深度分析 - -> 每个模块:**职责 / 结构 / 依赖与耦合 / 主要问题 / 目标处置**。 - -### M1 CarTypes(车型族,11 文件) -- **职责**:定义各 AGV 车型(Kiva 827、Forklift 398、MultiWheel*、MultiVehicle、DualLifting、ArmCar、DummyCar 666)+ 字段袋 `BasicFields`(50) + 车辆监控 UI `VehicleMonitor`(1502, WinForms)。 -- **结构**:字段袋 `BasicCarFields/SiteFields/TrackFields/PlanFields` 设计合理(`-1` 哨兵语义被 Coder 正确利用);车型类承载通信(HttpClient)、状态机、调度、UI、强制控制等多职责。 -- **耦合**:车型直引 `Commons`、HttpClient、`MessageBox`、内核类型;`VehicleMonitor` 把 UI 与车辆模型绑定。 -- **问题**:巨类(Kiva 827)、`async void`+`throw`(Kiva.ForceStop 619/659)、空 catch、硬编码 IP(192.168.2.1:8008)。 -- **目标处置**:车型保留在领域层;剥离"通信/HTTP/UI"为协作者(`ICarTransport`/`ICarStatusView`);`VehicleMonitor` 进 UI 程序集。 - -### M2 Coders(轨迹编码器,1 文件 + 车型内联) -- **职责**:`CommonTrackCoders` 通用 `ITrackCoder`(磁导航统一 `MagneticTrackCoder`、避障 `AvoidanceParamCoder`4参/`AvoidanceParamLWCoder`2参)。 -- **结构**:本轮已去重、结构清晰;`CommonTemplateTrackCoder` 提供 `SiteFieldsType` 扩展点。 -- **耦合**:受内核 `ProgramTrackCoderSettings`/`TemplateTrackCoderSettings` 特性约束(S8)。 -- **目标处置**:继续把车型内联 Coder 收敛至此;待内核放开注册表后,导航类 Coder 可下沉到导航卫星。 - -### M3 Chained(链式/循环任务族,12+2 文件) -- **职责**:`AbstractLoopMission`(1858)、`ChainedDeliveryMission`(1365)、`TransportMission`(549)、`LoopMission`、回调注册表/附着器、`Loop/` 规则接口、若干 Viewer(UI)。 -- **结构**:**两面性**——`Loop/ILoopRules`(IEnter/IExit/IJoin/IBranch/ITaskStrategy) 与 `DeliveryCallbackRegistry` 是规范的策略/注册表模式(亮点);但 `AbstractLoopMission` 是 1858 行巨类,且接口 `using static AbstractLoopMission`(S6)反向耦合其嵌套类型。 -- **耦合**:裸 `new Thread`+`while`+`Thread.Sleep`(P0-1 已改协作式停止);UI Viewer 混入。 -- **问题**:巨类、状态用字符串 `status.status`、`AbstractChainedDeliveryMission`(1407) 死文件(S10)。 -- **目标处置**:把 `LoopPoint/LoopTask` 等领域模型从巨类**上提**到 Model/Abstractions,让 `Loop` 接口独立;巨类按"调度循环/任务编排/显示"拆分;删除/归档死文件。 - -### M4 InterLock(互锁,4 文件) -- **职责**:`AbstractInterlockMission`(367)、`TrafficInterlockMission`、Viewer(UI)。交通互锁逻辑。 -- **耦合**:与调度/交通控制耦合;含 Viewer。 -- **目标处置**:归入"交通/调度"领域子模块;UI 外提。 - -### M5 Scheduler(调度后台任务,4 文件) -- **职责**:`HeartBeatMission`、`NodeIsEnableMission`、`SecuritySignalMission`、`RegionalTrafficControlMission`(377)。周期性后台任务(心跳/站点禁用/安全信号上传/区域交通)。 -- **耦合**:直发 HTTP(硬编码端点)、`Console.WriteLine`、原 `while(true)`+`Thread.Abort`(P0-1 已修为 `while(started)`/协作式)。 -- **问题**:每个任务各写一套线程循环(重复),无统一基类。 -- **目标处置**:抽 `MissionRunnerBase`(CancellationToken + 状态枚举 + 统一日志),所有周期任务复用。 - -### M6 Charge(充电子系统,20 文件 + ChargeStationType)★需独立 -- **职责**:任务(`StandardChargeMission`671/`AbstractChargeLogicMission`1143)、站点(`AbstractChargeStation`/`ChargeStation`)、配置(`ChargeStrategyConfig`/`AlarmConfig`/`ChargingSetting`)、数据服务(3 个 *DataService)、通信(`ChargeUdpService`/`CommunicationMessage(Service)`)、Helper、**4 个 WinForms 表单**。 -- **结构**:自成完整子系统(任务+设备+配置+持久化+通信+UI),但全埋 Core。`CommunicationMessageService` 是**安全解析范本**(先校验长度)。 -- **耦合**:与具体充电桩驱动(Devices/Charge)双向(Core 持任务/抽象,Devices 持 PCB/FL/MuXing 驱动);实时 UDP 路径越界(P0-3 已修)。 -- **目标处置**:升级为**独立卫星 `StandardScene.Charge`**(含 Mission/抽象/配置/通信),具体桩驱动留 `Devices` 或并入;4 表单进 UI 程序集;通过 Abstractions 与 Core 解耦。 - -### M7 ExtendDevice + Devices(设备:门/按钮盒,Core 侧 13 + Devices 侧 6) -- **职责**:Core 侧 = 基类(`BasicDoorController`/`BasicButtonBox`)+特性(`DoorTypeAttribute` 等)+管理器(`DoorManager`848/`ButtonBoxManager`913, 含 UI)+任务(`DoorMission`932/`ButtonMission`743)+模型/Monitor(UI);Devices 侧 = 具体驱动(`ModbusDoorController`/`Azowie`/`Leeg`/3 充电桩)。 -- **结构**:**新驱动质量优秀**(`ModbusDoorController`:CTS 协作停止/锁/变更检测/重连节流/统一日志);`DoorTypeAttribute` 特性发现规范。 -- **耦合**:抽象+业务在 Core、驱动在 Devices(S5);Manager 含 WinForms。 -- **问题**:`ModbusDoorController` 用析构函数兜底 `Disconnect`(GC 线程取锁+Wait,风险)→应实现 `IDisposable`;Manager 巨类含 UI。 -- **目标处置**:把设备**抽象+特性**下沉到 `StandardScene.Abstractions`;Manager 拆"设备生命周期服务 + UI";驱动统一 `IDisposable`。 - -### M8 Protocol.VDA5050(协议卫星,9 文件) -- **职责**:`VDA5050Car`(938)、`MasterMQTTCommunication`、`VDA5050Interface/Segment/Helper/Commons`、`VDA5050WebApi`、`TextViewer`(UI)。 -- **结构**:已是独立卫星(好);自带 WebApi 与 MQTT 栈。 -- **问题**:`async void`+`throw ex`(240)、硬编码 `192.168.2.1:8008` 且注释掉按车地址(150)、空 catch、`Console.WriteLine` 满布、`monitor()` 死局部函数。 -- **目标处置**:作为协议卫星范本;端点配置化(按车 `address`)、异步规范化、日志统一、`VDA5050WebApi` 与 Core WebApi 走统一 `ApiResult`/路由约定。 - -### M9 WebApi(老 Nancy 接口,2686 行)★最高优先重构 -- **职责**:车辆/任务/地图/配置等 HTTP 接口(Nancy 2.0)。 -- **结构**:单文件上帝路由;错误响应 `new{Success=false,Code=500,...}` 复制几十处;反射 execute 端点(P0-2 已加白名单)。 -- **目标处置**:抽 `ApiResult.Ok/Fail` + 按资源拆模块(CarApi/MissionApi/MapApi…);统一鉴权中间件(来源/令牌,配置驱动);老接口归 `WebApi.Core(deprecated)` 规划迁移;最终独立 `StandardScene.WebApi` 程序集(隔离 Nancy 依赖)。 - -### M10 Model(领域/配置模型,12 文件) -- **职责**:`Map/SimpleMap/MapStructure`、`SimpleConfig`、`TaskModel/LoopTask/MissionState`、`VehicleStatus`、各 `*Setting`。 -- **结构**:领域模型与配置混居;`Map.cs` 含硬编码 `127.0.0.1:4321`。 -- **目标处置**:拆"纯领域模型(→Abstractions)"与"配置(→Configuration)";端点配置化。 - -### M11 基础设施(TCP 5 / Utils 4 / CommonTools 2) -- **职责**:`AsyncTcpClient`(432, 质量较好)+事件args;`JsonParser/JsonTool/ModbusClass/WebAPIHelper`;`AtomicFileUpdateHelper/SnowflakeIdGenerator`。 -- **问题**:`AsyncTcpClient.Send` 抛 `InvalidProgramException`(类型不当)、`EndWrite` 无异常处理、`uint on` 未用;`ModbusClass` 与设备 Modbus 重复关注点。 -- **目标处置**:归入 `StandardScene.Infrastructure`(net8.0 纯净,无 Windows);TCP/IO/序列化/ID 通用化。 - -### M12 Core 根 God-files(Commons 653 / Heuristic / LadderLogic / StandardCADTool / WebApi) -- **职责**:`Commons` 万能工具+调度(`NearestTask`)、`Heuristic` 启发式、`LadderLogic` 梯形逻辑、`StandardCADTool` CAD(硬编码盘符路径、`async void`)。 -- **问题**:`Commons.AddOrUpdateXxxField` 重复 4 份、`CarValue` 忽略 key、`GoSite` 假重试(详见审查报告 P2-1);根目录堆放无归属大文件。 -- **目标处置**:`Commons` 按职责拆(字段服务/调度服务/控制台辅助);`StandardCADTool` 路径配置化、归 CAD 子模块。 - ---- - -## 四、目标架构 - -### 4.1 分层与程序集边界(目标) - -```mermaid -graph TD - subgraph L0[抽象层 net8.0 纯净] - Abstractions[StandardScene.Abstractions
接口/特性/字段袋/领域模型/ITrackCoder/设备契约] - Infra[StandardScene.Infrastructure
TCP/序列化/ID/IO/ILogger] - Config[StandardScene.Configuration
配置模型+读写] - end - subgraph L1[领域层 net8.0] - Core2[StandardScene.Core
车型/任务族/调度/交通/Coders] - end - subgraph L2[卫星 net8.0] - Charge2[StandardScene.Charge] - Devices2[StandardScene.Devices] - VDA[StandardScene.Protocol.VDA5050] - Nav[StandardScene.Nav.*(磁/二维码/激光,待内核放开)] - end - subgraph L3[宿主/接入 net8.0-windows] - Web[StandardScene.WebApi(Nancy 隔离)] - UI[StandardScene.UI.WinForms(临时·弃用,待 migu)] - end - Core2 --> Abstractions - Core2 --> Infra - Core2 --> Config - Charge2 --> Abstractions - Devices2 --> Abstractions - VDA --> Abstractions - Nav --> Abstractions - Charge2 -. 受限 .-> Core2 - Web --> Core2 - UI --> Core2 - Devices2 -. NuGet .-> Modbus - VDA -. NuGet .-> MQTT - Web -. NuGet .-> Nancy -``` - -**关键规则**: -1. **依赖只向下**:卫星/宿主 → Abstractions(+受限 Core);**Core 不得依赖任何具体协议/设备 NuGet**(MQTT/Modbus/IoTClient/OpenXml/Nancy 全部下放到对应卫星/宿主)。 -2. **抽象先行**:接口、注册特性、字段袋、纯领域模型、`ITrackCoder`/设备契约统一进 `Abstractions`(net8.0,无 Windows),卫星只认 Abstractions。 -3. **UI 与协议 = 边缘**:WinForms 全部收口到 `UI.WinForms`(标 deprecated,仅过渡,迁 migu 后删);Nancy 收口到 `WebApi`。如此 Abstractions/Infrastructure/Core/卫星可去 `-windows`,回到纯 `net8.0`。 -4. **发现统一**:设备/车型/协议统一"`[XxxType]` 特性 + `UiTypeDiscovery.AllTypes()`",`scene.json` 声明 `provides`。 - -### 4.2 边界接口(最小集) -- `ILogger`(取代 `Console.WriteLine`/直连 Diagnosis):业务只依赖抽象。 -- `IDeviceDriver`/`IDoorController`/`IChargeStation`/`IButtonBox`(下沉 Abstractions),`IDisposable` 释放。 -- `MissionRunnerBase`(CancellationToken + 状态枚举 + 统一异常/日志):统一所有后台任务线程模型。 -- `ApiResult`(统一 HTTP 响应),HTTP 鉴权中间件。 -- `IEndpointProvider`/配置注入:消灭硬编码 IP/URL/路径。 -- `ICarTransport`(车辆通信抽象):把 HttpClient 从车型类剥离,单例化。 - ---- - -## 五、net8.0 去 Windows 依赖路径(解 S2) - -1. **隔离 UI**:所有 `*Form/*Viewer/*Monitor + *.Designer.cs`(12+)迁 `StandardScene.UI.WinForms`(唯一 `net8.0-windows`+`UseWindowsForms`)。 -2. **去 MessageBox**:业务层 `MessageBox.Show`(Commons/Kiva/各 Manager)改为事件/`ILogger`,弹窗交 UI 层。 -3. **核对 Windows-only API**:去掉 CA1416 抑制后逐项消解(P/Invoke 控制台显隐等收口到宿主)。 -4. **切 TFM**:Abstractions/Infrastructure/Core/卫星改 `net8.0`;仅 UI 与(如需)宿主保留 `-windows`。 - ---- - -## 六、分阶段迁移路径(低风险·每阶段 build-green·可回归) - -> 延续既有"路线乙":**先结构移动(无逻辑变更)→ 再去重/解耦**,每步可独立验证。 - -- **A 抽象层奠基**:建 `StandardScene.Abstractions`,**纯移动**接口/特性/字段袋/纯模型(`ILoopRules`、`*TypeAttribute`、`BasicFields`、`LoopPoint/LoopTask` 等)。解 S6/S11。 -- **B 基础设施收口**:建 `Infrastructure`(TCP/Utils/CommonTools)+`ILogger`;修 `AsyncTcpClient` 异常类型/EndWrite。解 S1(部分)。 -- **C UI 隔离**:建 `UI.WinForms`,移走全部窗体;业务去 `MessageBox`。解 S2,打通去 `-windows`。 -- **D 协议/设备依赖下放**:MQTT→VDA5050、Modbus/IoTClient→Devices、Nancy→WebApi、OpenXml→报表所在卫星;Core.csproj 清空专有 NuGet。解 S1。 -- **E 充电独立**:抽 `StandardScene.Charge` 卫星(Mission/抽象/配置/通信),表单已在 UI 层。解 S4。 -- **F 任务线程统一**:落地 `MissionRunnerBase`,迁移 Scheduler/Chained/Charge 后台循环(在 P0-1 协作式停止基础上)。解 S3(并发面)。 -- **G WebApi 重构**:`ApiResult`+按资源拆分+鉴权中间件;老接口归 deprecated。解 S3/S9(WebApi)。 -- **H 巨类拆分**:AbstractLoopMission/ChainedDeliveryMission/AbstractChargeLogicMission/Kiva 按职责拆分。解 S3。 -- **I 切 net8.0 + 清死文件/绝对路径**:TFM 收敛;删 `AbstractChainedDeliveryMission` 等死文件;`HintPath` 改相对/包变量。解 S2/S9/S10。 -- **J 导航卫星(依赖内核)**:待内核放开 Coder 注册表(S8),抽磁/二维码/激光导航卫星。 - -每阶段出口标准:`dotnet build` 0 错误、警告不增、关键路径冒烟可过。 - ---- - -## 七、模块处置矩阵(速查) - -| 模块 | 现位置 | 目标位置 | 关键动作 | -|---|---|---|---| -| 接口/特性/字段袋/纯模型 | Core 各处 | **Abstractions** | 纯移动 | -| TCP/Utils/CommonTools | Core | **Infrastructure** | 移动+`ILogger`+修 TCP | -| 全部窗体/Viewer/Monitor | 各模块 | **UI.WinForms(deprecated)** | 移动+去 MessageBox | -| 充电(任务/抽象/配置/通信) | Core/Charge | **StandardScene.Charge** | 卫星化 | -| 充电桩具体驱动 | Devices | Devices 或并入 Charge | 统一 IDisposable | -| 门/按钮盒 抽象+特性 | Core | Abstractions | 下沉 | -| 门/按钮盒 驱动 | Devices | Devices | IDisposable | -| MQTT/Modbus/IoTClient/Nancy/OpenXml | Core NuGet | 各卫星/WebApi | 依赖下放 | -| WebApi | Core 单文件 | **StandardScene.WebApi** | 拆分+鉴权+ApiResult | -| 车型 | Core/CarTypes | Core(领域) | 剥离通信/UI 协作者 | -| Coders | Core/Coders | Core(→导航卫星 J 阶段) | 继续收敛 | -| Commons/Heuristic/LadderLogic/CAD | Core 根 | 按职责归子模块 | 拆分+配置化 | - ---- - -## 八、风险与约束 - -1. **内核耦合(SimpleLite/SimpleCore)**:Coder 注册表按内核程序集反射(S8)→导航卫星热插拔需内核改造;`UiTypeDiscovery.AllTypes()` 已支持跨程序集发现(设备/车型可行)。 -2. **无 Git 基线**:大量文件未纳入版本控制 → **强烈建议先建 Git 基线**再执行 A~J,保证可回滚。 -3. **行为等价**:车型/充电/VDA5050 含设备协议时序,移动需保持时序与字段语义(沿用"先移动后去重")。 -4. **绝对 HintPath/本地 dll**:迁移期保持 HintPath 可用,I 阶段统一相对化,避免中途断链。 -5. **migu 平台 UI**:UI.WinForms 仅过渡;接口层(Abstractions/WebApi)应面向 migu 提供稳定契约,UI 迁移后整包删除。 - ---- - -## 九、近期可立即执行(已具备条件,低风险) -1. 删除/归档死文件 `AbstractChainedDeliveryMission.cs`(已 Compile Remove)。 -2. 新建 `StandardScene.Abstractions`,先迁 `BasicFields`、`*TypeAttribute`、`Loop/ILoopRules`(解 S6/S11,且不改逻辑)。 -3. `MissionRunnerBase` 抽取(承接 P0-1 协作式停止成果,统一 Scheduler 四任务)。 -4. `ApiResult` 帮助器(先在 WebApi 内消重,零行为变化)。