refactor: 插件 UI 从 WinForms 迁移到 CycleGUI,并修复代码质量问题

将 StandardScene 各插件的配置/监控窗体从 WinForms 迁移到 CycleGUI(删除 .Designer.cs/.resx,重写为 PanelBuilder 立即模式 UI,新增 CycleUiHelper 统一对话框)。

同时修复代码审核中的问题:
- 后台文件写入加锁 + try/catch(ButtonBoxManager / DoorManager,对齐 LoopViewer.SaveTasks 模式)
- CoderFieldsMetadata.cs 启用 #nullable enable,消除 CS8632 警告
- DummyCar 移除已废弃的 rightClickAction()/SetPosition()
- CarRemoteHelper.OpenVehicleWebPage 的 Process.Start 加 try/catch
- 重命名名不副实的 Mstsc()(现为打开网页)
- 统一弃元命名为 _
- TrafficInterlockViewer 改用稳定 Id(GUID)做选择/编辑,替代行索引
- csproj 改用 $(CGUILibDir) 解析 CycleGUI,绝对路径收敛到 Directory.Build.props

构建:dotnet build StandardScene.sln → 0 错误,30 警告(均为历史遗留)。
注:static 单例状态重构(审核第 8 项)暂未处理,留待单独任务。
This commit is contained in:
zhaowei.huang
2026-06-26 15:00:53 +08:00
parent c8e540d272
commit a0dc1e6cd0
91 changed files with 3946 additions and 15419 deletions
+5
View File
@@ -0,0 +1,5 @@
<Project>
<PropertyGroup>
<CGUILibDir Condition="'$(CGUILibDir)' == ''">D:\MDCS\Dependencies\Commons</CGUILibDir>
</PropertyGroup>
</Project>
+40
View File
@@ -0,0 +1,40 @@
<Project>
<!--
StandardScene 插件工程编译后,自动将产物复制到 build\plugins\SimpleComposer / SimpleLite 宿主加载目录)。
适用:StandardScene.Core + Magnetic / QrLidar / Devices / Protocol.VDA5050 等卫星插件。
-->
<PropertyGroup>
<StandardSceneRepoRoot>$(MSBuildThisFileDirectory)</StandardSceneRepoRoot>
<StandardScenePluginsDir>$(StandardSceneRepoRoot)build\plugins</StandardScenePluginsDir>
<IsStandardScenePluginCopy>false</IsStandardScenePluginCopy>
<IsStandardScenePluginCopy Condition="'$(MSBuildProjectName)' == 'StandardScene.Core'">true</IsStandardScenePluginCopy>
<IsStandardScenePluginCopy Condition="'$(MSBuildProjectName)' == 'StandardScene.Magnetic'">true</IsStandardScenePluginCopy>
<IsStandardScenePluginCopy Condition="'$(MSBuildProjectName)' == 'StandardScene.QrLidar'">true</IsStandardScenePluginCopy>
<IsStandardScenePluginCopy Condition="'$(MSBuildProjectName)' == 'StandardScene.Devices'">true</IsStandardScenePluginCopy>
<IsStandardScenePluginCopy Condition="'$(MSBuildProjectName)' == 'StandardScene.Protocol.VDA5050'">true</IsStandardScenePluginCopy>
</PropertyGroup>
<Target Name="CopyStandardScenePluginOutputs"
AfterTargets="Build"
Condition="'$(IsStandardScenePluginCopy)' == 'true' and '$(TargetPath)' != ''">
<MakeDir Directories="$(StandardScenePluginsDir)" />
<ItemGroup>
<StandardScenePluginCopyFile Include="$(TargetPath)" />
<StandardScenePluginCopyFile Include="$(TargetDir)$(TargetName).pdb"
Condition="Exists('$(TargetDir)$(TargetName).pdb')" />
<StandardScenePluginCopyFile Include="$(TargetDir)$(TargetName).scene.json"
Condition="Exists('$(TargetDir)$(TargetName).scene.json')" />
<!-- Devices 插件运行时依赖 -->
<StandardScenePluginCopyFile Include="$(TargetDir)leegKeys-sdk.dll"
Condition="Exists('$(TargetDir)leegKeys-sdk.dll')" />
</ItemGroup>
<Copy SourceFiles="@(StandardScenePluginCopyFile)"
DestinationFolder="$(StandardScenePluginsDir)"
SkipUnchangedFiles="true" />
<Message Importance="high"
Text="[StandardScene] 已复制 $(MSBuildProjectName) 插件产物到 $(StandardScenePluginsDir)" />
</Target>
</Project>
+525
View File
@@ -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.dllscene.mag
├── StandardScene.QrLidar → 输出 StandardScene.QrLidar.dllscene.qrlidar
├── StandardScene.Devices → 输出 StandardScene.Devices.dllscene.device
└── StandardScene.Protocol.VDA5050 → 输出 StandardScene.Protocol.VDA5050.dllscene.vda5050
```
### 2.1 依赖关系(星型拓扑)
```mermaid
graph TD
Host["SimpleLite.exe"]
SC["SimpleCore.dll"]
SL["SimpleLite.dll"]
Core["StandardScene.dll<br/>基座"]
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` | 否 | VDA5050MQTT)协议栈与标准车型 |
---
## 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 / SegmentPlanSimpleCore
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<Type> 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 NuGetCore 最重)
`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-classWebApi、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
→ TransportMissionChainedDeliveryMission.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 专题手册 |
---
## 附录 Ascene.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 启动即可生效** 的全局初始化入口之一。
@@ -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
<Compile Include="Scheduler\HelloMission.cs" />
```
当前工程已是 SDK 风格 `.csproj``net8.0-windows`),目录下的 `.cs` 文件会被自动包含,无需再手工添加 `<Compile Include>`。新增任务/车型/驱动后,记得补上对应特性(`[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` 中启用对应场景
### 区域流控不生效
+6 -4
View File
@@ -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`,体验区域流控
View File
+9 -13
View File
@@ -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
<Compile Include="Scheduler\HelloMission.cs" />
```
当前工程已是 SDK 风格 `.csproj``net8.0-windows`),目录下 `.cs` 文件会被自动包含,无需手工添加 `<Compile Include>`。卫星插件需在 `active-scenes.json` 中启用对应场景才会被宿主加载。
## 4. 新增功能时先看谁
+18 -16
View File
@@ -480,8 +480,8 @@
<div class="stats">
<div class="stat">
<div class="value">.NET 4.8</div>
<div class="label">工程目标框架</div>
<div class="value">.NET 8</div>
<div class="label">工程目标框架 (net8.0-windows)</div>
</div>
<div class="stat">
<div class="value">Plugin DLL</div>
@@ -527,7 +527,7 @@
<section class="panel" id="overview">
<h3>01. 项目总览</h3>
<p>
`StandardScene` 是 AGV/AMR 场景插件库,不是独立 EXE。它依赖宿主 `SimpleComposer.exe` 运行,能力覆盖搬运调度、环线任务、
`StandardScene` 是 AGV/AMR 场景插件库,不是独立 EXE。它依赖宿主 `SimpleLite.exe`CycleGUI 应用)运行,能力覆盖搬运调度、环线任务、
交通互锁、区域流量控制、充电协同、门控联动,以及 HTTP / MQTT / Modbus 等外围接口。
</p>
<div class="cards">
@@ -546,7 +546,7 @@
</div>
<div class="pill-row">
<span class="pill">输出类型:Library</span>
<span class="pill">宿主:SimpleComposer.exe</span>
<span class="pill">宿主:SimpleLite.exe</span>
<span class="pill">外部接口:Nancy / HTTP</span>
<span class="pill">典型协议:MQTT / Modbus</span>
</div>
@@ -566,7 +566,7 @@
<tr>
<td>宿主层</td>
<td>启动程序、装载插件、展示配置与 Mission</td>
<td><code>build/SimpleComposer.exe</code></td>
<td><code>SimpleLite.exe</code></td>
</tr>
<tr>
<td>场景逻辑层</td>
@@ -650,21 +650,23 @@
<h3>04. 构建与运行</h3>
<h4>本机依赖路径</h4>
<pre><code>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</code></pre>
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</code></pre>
<h4>构建步骤</h4>
<ol>
<li>先构建宿主依赖:<code>dotnet build Simple\SimpleLite\SimpleLite.csproj</code>(一并构建 SimpleCore</li>
<li>打开 <code>StandardScene.sln</code></li>
<li>编译 <code>Debug|Any CPU</code><code>Release|Any CPU</code></li>
<li>确认构建事件已将插件复制到 <code>build/plugins</code></li>
<li>运行 <code>build/SimpleComposer.exe</code></li>
<li>编译 <code>Debug|x64</code><code>Release|x64</code></li>
<li>确认 <code>Directory.Build.targets</code> 已将 5 个插件 DLL 复制到 <code>build/plugins</code></li>
<li> <code>build/plugins</code> 部署到宿主 <code>plugins/</code>,运行 <code>SimpleLite.exe</code></li>
</ol>
<div class="warning">
当前项目是 SDK 风格工程。新增 `.cs` 文件后,必须确认它已经被加入 `.csproj`;否则文件存在但不会参与编译
当前项目是 SDK 风格工程`net8.0-windows`),目录下 `.cs` 文件会被自动包含,无需手工加入 `.csproj`。注意:StandardScene 通过 `HintPath` 引用宿主产物,构建前必须先编译 Simple 解决方案,否则会报 `SimpleCore` 版本不匹配
</div>
</section>
@@ -677,7 +679,7 @@ D:\MDCS\Executables\Simple\SimpleComposer.exe</code></pre>
</p>
<pre><code>using System.Threading;
using Newtonsoft.Json;
using SimpleComposer.RCS;
using SimpleLite.RCS;
using SimpleCore;
namespace StandardScene.Scheduler
@@ -721,9 +723,9 @@ namespace StandardScene.Scheduler
}
}</code></pre>
<ol>
<li>新建 <code>Scheduler/HelloMission.cs</code></li>
<li>确认它已被加入工程</li>
<li>编译并打开 <code>build/SimpleComposer.exe</code></li>
<li>新建 <code>Scheduler/HelloMission.cs</code>SDK 工程自动包含)</li>
<li>补上 <code>[MissionType]</code> 特性与静态 <code>Create()</code></li>
<li>编译后部署到宿主 <code>plugins/</code>,运行 <code>SimpleLite.exe</code></li>
<li>启动后观察 <code>status.status</code> 是否按秒递增</li>
</ol>
+36 -16
View File
@@ -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` | VDA5050MQTT)标准协议栈与标准车型 |
卫星插件依赖基座(星型,单向,无循环引用);各卫星附带 `<dll>.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` 选择性加载插件
## 开发阅读顺序
@@ -0,0 +1,102 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
namespace StandardScene.CarTypes
{
/// <summary>
/// Coder 字段袋元数据导出。
/// <para>在 StandardScene 程序集内执行反射,可正确读取 internal Fields 类(如 BasicTrackFields)。</para>
/// </summary>
public static class CoderFieldsMetadata
{
/// <summary>
/// 描述单个 Fields 类型的字段清单(含继承链上的 public 字段)。
/// </summary>
/// <param name="fieldsType">Fields 字段袋类型</param>
/// <returns>供 SimpleLite API 序列化的匿名结构;fieldsType 为 null 时返回 null</returns>
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()
};
}
/// <summary>
/// 自基类到派生类收集 public 实例字段。
/// </summary>
static List<FieldInfo> CollectPublicFieldInfos(Type type)
{
var ordered = new List<FieldInfo>();
var seen = new HashSet<string>(StringComparer.Ordinal);
var chain = new List<Type>();
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()
};
}
}
}
+5 -30
View File
@@ -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(
@@ -119,14 +118,6 @@ namespace AMRScene1
};
}
public override void rightClickAction(float mouseX, float mouseY)
{
x = mouseX;
y = mouseY;
}
public class AGV: AGVInterface
{
public DummyCar car;
@@ -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()
{
-46
View File
@@ -1,46 +0,0 @@
namespace StandardScene
{
partial class VehicleMonitor
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
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
}
}
File diff suppressed because it is too large Load Diff
@@ -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();
+165 -161
View File
@@ -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
/// <summary>
/// 搬运任务管理界面(CycleGUI 版,替代原 WinForms <c>DeliveryViewer</c> 窗体)。
/// <list type="bullet">
/// <item>单实例:再次打开则把已有面板置前。</item>
/// <item>约每 1s 节流刷新任务快照(在渲染线程内节流,避免并发),面板 500ms 准实时重绘。</item>
/// <item>每行提供「取消 / 重发 / 换车重发」按钮(带二次确认),超时任务整行高亮。</item>
/// </list>
/// 保留可实例化 + <see cref="Show"/> 以兼容既有调用 <c>new DeliveryViewer().Show()</c>。
/// </summary>
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();
/// <summary>选中行的背景色</summary>
private static readonly Color SelectedRowBackColor = Color.FromArgb(220, 230, 250);
/// <summary>缓存选中行索引,避免在 RetrieveVirtualItem 中访问 SelectedIndices 引发递归</summary>
private readonly HashSet<int> _selectedIndicesCache = new HashSet<int>();
/// <summary>超时任务整行底色(深色主题下的暗红,醒目但不刺眼)。</summary>
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<Delivery> _snapshot = new List<Delivery>();
private static volatile bool _refreshing;
private static DateTime _lastFlush = DateTime.MinValue;
private static readonly TimeSpan FlushInterval = TimeSpan.FromSeconds(1);
private static volatile string _status = "";
/// <summary>打开(或置前)任务管理面板。兼容原 <c>new DeliveryViewer().Show()</c> 调用方式。</summary>
public void Show() => Open();
/// <summary>打开(或置前)任务管理面板。</summary>
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)
/// <summary>
/// 渲染线程调用:到达刷新间隔且无在途刷新时,<b>在后台线程</b>重新拉取任务快照(超时任务置顶)。
/// 业务侧的锁与文件 IO 一律放到后台,渲染线程只读 <see cref="_snapshot"/> 引用,避免界面卡死。
/// </summary>
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<Delivery>();
foreach (var cdm in SimpleProject.proj.Missions.OfType<ChainedDeliveryMission>())
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<string[]> _listDeliveries = new List<string[]>();
private static string SafeSiteName(int siteId)
{
try { return SimpleLib.GetSite(siteId)?.name ?? ""; }
catch { return ""; }
}
/// <summary>将任务标记为已取消(Canceled)。</summary>
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<ChainedDeliveryMission>())
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<TransportMission>().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<TransportMission>().FirstOrDefault();
if (cdm == null) return;
var d = cdm.GetDeliveries(true, true, true, true)
.OfType<TransportDelivery>()
.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<TransportMission>().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());
}
}
}
-206
View File
@@ -1,206 +0,0 @@
using System.Windows.Forms;
namespace StandardScene.Chained
{
partial class DeliveryViewer
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
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
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
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;
}
}
@@ -1,123 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<metadata name="timer1.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>17, 17</value>
</metadata>
</root>
-570
View File
@@ -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);
}
}
}
+277 -533
View File
@@ -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
/// <summary>
/// 环线/循环任务配置管理界面(CycleGUI 版,替代原 WinForms <c>LoopViewer</c> 窗体)。
/// <list type="bullet">
/// <item>维护 <c>tasklist.json</c><see cref="List{T}"/> of <see cref="LoopTask"/>)的增 / 改 / 删;与 <c>AbstractLoopMission</c> 读取同一文件。</item>
/// <item>单实例:再次打开则把已有面板置前。</item>
/// <item>勾选多行后「删除选中」可批量删除(保留原 ListView 多选删除能力);每行「编辑」按钮打开编辑对话框。</item>
/// <item>文件写入放后台线程,绝不阻塞渲染线程(避免界面卡死)。</item>
/// </list>
/// 沿用 <c>DeliveryViewer</c> 的同套模式(单实例面板、<c>pb.Table</c>、<c>CycleUiHelper.ConfirmThen</c>),不另造轮子。
/// 保留可实例化 + <see cref="Show"/> 以兼容既有调用 <c>new LoopViewer().Show()</c>。
/// </summary>
public class LoopViewer
{
private readonly string jsonPath =
Path.Combine(Application.StartupPath, "tasklist.json");
private const string TableId = "loop-task-list";
private List<LoopTask> tasks = new List<LoopTask>();
// 与 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<LoopTask> _tasks = new List<LoopTask>(); // 仅渲染线程读写
private static readonly HashSet<int> _selected = new HashSet<int>(); // 仅渲染线程读写,存被勾选任务的 Id
private static volatile string _status = "";
/// <summary>打开(或置前)任务管理面板。兼容原 <c>new LoopViewer().Show()</c> 调用方式。</summary>
public void Show() => Open();
/// <summary>打开(或置前)任务管理面板。</summary>
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);
});
}
/// <summary>对选中项发起二次确认后删除(保留原多选删除的提示文案)。</summary>
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();
}
/// <summary>
/// 删除 ListView 中选中的任务(支持多选)
/// 打开「新增 / 编辑」对话框(置顶非模态、限单实例)。<paramref name="existing"/> 为 null 表示新增,否则编辑该任务(保留其 Id)。
/// 每次打开都是全新面板:<c>defaultText</c> 能正确初始化,规避立即模式下文本框缓冲难以重置的问题。
/// </summary>
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<int>().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<TaskKind>(KindNames[kindIdx], out var kind);
Enum.TryParse<TaskStartType>(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;
}
});
}
/// <summary>下一个可用任务 Id(当前最大 Id + 1,空表则为 1)。</summary>
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<List<LoopTask>>(text) ?? new List<LoopTask>();
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"OnDeleteSelectedTasks error: {ex}");
MessageBox.Show("删除失败:" + ex.Message);
_tasks = new List<LoopTask>();
_status = "加载 tasklist.json 失败,详见日志";
Diagnosis.Post($"LoopViewer 加载 tasklist.json 异常: {ExceptionFormatter.FormatEx(ex)}");
}
}
/// <summary>
/// 将 LoopViewer 的运行时样式调整为与 ChargeStationManagementForm 接近的视觉风格:
/// - 全局字体设为微软雅黑
/// - 表头暖色替换为蓝色沉稳风格(和充电界面一致)
/// - 按钮字号、背景色与充电界面保持一致(保存/删除/取消)
/// - 列表视图设置为整行选择、无边框、交替背景等
/// 注意:不修改 Designer 文件,仅在运行时统一控件表现,避免破坏设计器生成代码。
/// </summary>
private void ApplyChargeStyle()
/// <summary>序列化在渲染线程完成(极快),文件写入放后台线程,避免阻塞渲染线程。</summary>
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<List<LoopTask>>(text) ?? new List<LoopTask>();
}
catch (Exception ex)
{
tasks = new List<LoopTask>();
System.Diagnostics.Debug.WriteLine($"Load tasks failed: {ex}");
value = Clamp(value, min, max);
return true;
}
value = min;
return false;
}
#endregion
#region ID
/// <summary>
/// 获取下一个可用的任务ID(当前最大ID + 1)
/// </summary>
/// <returns>新的任务ID</returns>
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<TaskKind>(cmbTaskKind?.SelectedItem?.ToString() ?? "Loop", out var kind);
Enum.TryParse<TaskStartType>(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
}
}
-120
View File
@@ -1,120 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>
@@ -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
@@ -1,596 +0,0 @@
namespace StandardScene.Charge
{
partial class AlarmConfigManagementForm
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
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;
}
}
@@ -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
{
/// <summary>
/// 报警配置管理窗体
/// 报警配置管理界面(CycleGUI 版,替代原 WinForms 窗体)。
/// </summary>
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<AlarmConfig> _allAlarms = new List<AlarmConfig>();
private static int _levelFilterIdx;
private static string _status = "";
/// <summary>打开(或置前)报警配置管理面板。兼容原 <c>new AlarmConfigManagementForm().Show()</c> 调用方式。</summary>
public void Show() => Open();
/// <summary>打开(或置前)报警配置管理面板。</summary>
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);
}
}
/// <summary>
/// 窗体加载事件
/// </summary>
private void AlarmConfigManagementForm_Load(object sender, EventArgs e)
{
InitializeForm();
}
/// <summary>
/// 初始化窗体
/// </summary>
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);
}
}
/// <summary>
/// 加载报警配置列表
/// </summary>
private void LoadAlarmConfigs()
{
try
{
if (dgvAlarmConfigs == null)
{
return; // 控件还未初始化,直接返回
}
var alarmConfigs = dataService.GetAllAlarmConfigs();
if (alarmConfigs == null)
{
alarmConfigs = new System.Collections.Generic.List<AlarmConfig>();
}
// 根据级别筛选
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);
}
}
/// <summary>
/// 更新统计信息
/// </summary>
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<AlarmConfig>();
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);
});
}
/// <summary>
/// 更新标题显示筛选信息
/// </summary>
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<AlarmConfig>();
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"更新标题失败: {ex.Message}");
this.Text = "报警配置管理";
_allAlarms = new List<AlarmConfig>();
CycleUiHelper.Alert("错误", $"加载数据失败: {ex.Message}");
}
}
/// <summary>
/// 获取级别文本
/// </summary>
private string GetLevelText(AlarmLevel level)
private static List<AlarmConfig> GetFilteredAlarms()
{
IEnumerable<AlarmConfig> 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
}
}
/// <summary>
/// 清空编辑字段
/// </summary>
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}");
}
}
/// <summary>
/// 从字段创建报警配置
/// </summary>
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;
}
/// <summary>
/// 加载报警配置到编辑区
/// </summary>
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();
}
}
}
@@ -1,120 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>
@@ -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
/// </summary>
public static class ChargeStationHelper
{
private static ChargeStationManagementForm _managementForm;
/// <summary>打开充电桩管理面板(单实例)。</summary>
public static void OpenManagementWindow() => ChargeStationManagementForm.Open();
/// <summary>
/// 打开充电桩管理窗口(单例模式)
/// </summary>
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();
}
}
/// <summary>
/// 打开充电桩管理窗口(对话框模式)
/// </summary>
public static DialogResult OpenManagementDialog()
{
using (var form = new ChargeStationManagementForm())
{
return form.ShowDialog();
}
}
/// <summary>打开充电桩管理面板(兼容旧 API)。</summary>
public static void OpenManagementDialog() => ChargeStationManagementForm.Open();
/// <summary>
/// 获取指定站点的充电桩
@@ -297,65 +273,44 @@ namespace StandardScene.Charge
return success;
}
/// <summary>
/// 显示充电桩选择对话框
/// </summary>
/// <param name="filterByStatus">按状态过滤(null表示显示全部)</param>
/// <returns>选中的充电桩,取消则返回null</returns>
public static ChargeStation ShowStationSelectionDialog(ChargeStationStatus? filterByStatus = null)
/// <summary>显示充电桩选择面板(非阻塞;通过 <paramref name="onSelected"/> 回调返回结果)。</summary>
public static void ShowStationSelectionDialog(ChargeStationStatus? filterByStatus, System.Action<ChargeStation> 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;
}
});
}
/// <summary>
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -1,120 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>
@@ -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;
}
}
@@ -1,215 +1,259 @@
using System;
using System.Drawing;
using System.Windows.Forms;
using CycleGUI;
using StandardScene.Utils;
namespace StandardScene.Charge
{
/// <summary>
/// 充电策略配置窗体
/// 充电策略配置界面(CycleGUI 版,替代原 WinForms 窗体)。
/// </summary>
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;
/// <summary>打开(或置前)充电策略配置面板。兼容原 <c>new ChargeStrategyConfigForm().Show()</c> 调用方式。</summary>
public void Show() => Open();
/// <summary>打开(或置前)充电策略配置面板。</summary>
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();
}
/// <summary>
/// 加载配置到界面
/// </summary>
private void LoadConfig(bool isDef = false)
{
try
if (_panel != null)
{
if (!isDef)
try
{
config = configService.LoadConfig();
_panel.BringToFront();
return;
}
catch
{
_panel = null;
}
}
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 =>
{
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);
// 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;
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);
// 时间相关参数
numIdleChargeSeconds.Value = (decimal)config.IdleChargeSeconds;
numIdleSeconds.Value = (decimal)config.IdleSeconds;
numMustChargeSeconds.Value = (decimal)config.MustChargeSeconds;
numTopUpMinutes.Value = (decimal)config.TopUpMinutes;
pb.SeparatorText("任务参数");
pb.SliderInt("10. 允许空闲车充电的最小任务数", ref _minAllowFreeCarToChargeTaskCnt, min: 0, max: 100);
// 任务相关参数
numMinAllowFreeCarToChargeTaskCnt.Value = config.MinAllowFreeCarToChargeTaskCnt;
pb.SeparatorText("开关参数");
pb.CheckBox("11. 允许中断充电任务", ref _allowInterruptTask);
pb.CheckBox("12. 优先使用低电量车辆充电", ref _useLowerSocForCharge);
pb.CheckBox("13. 启用充电错误检测", ref _enableErrorChargeDetection);
pb.CheckBox("14. 使用充电站点筛选", ref _useChargeSiteFilter);
// 开关参数
chkAllowInterruptTask.Checked = config.AllowInterruptTask;
chkUseLowerSocForCharge.Checked = config.UseLowerSocForCharge;
chkEnableErrorChargeDetection.Checked = config.EnableErrorChargeDetection;
chkUseChargeSiteFilter.Checked = config.UseChargeSiteFilter;
pb.Separator();
if (!string.IsNullOrEmpty(_status))
pb.Label(_status);
lblStatus.Text = "配置加载成功";
lblStatus.ForeColor = Color.Green;
}
catch (Exception ex)
{
MessageBox.Show($"加载配置失败: {ex.Message}", "错误",
MessageBoxButtons.OK, MessageBoxIcon.Error);
lblStatus.Text = "配置加载失败";
lblStatus.ForeColor = Color.Red;
}
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;
}
});
}
/// <summary>
/// 从界面保存配置
/// </summary>
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;
}
}
/// <summary>
/// 恢复默认配置
/// </summary>
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;
}
/// <summary>
/// 验证配置参数
/// </summary>
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;
}
/// <summary>验证 SOC 阈值之间的逻辑关系(保存前)。</summary>
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();
});
}
}
}
@@ -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;
}
}
@@ -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
{
/// <summary>
/// 通讯监控窗体
/// 通讯监控面板(CycleGUI 版,替代原 WinForms 窗体)。
/// <list type="bullet">
/// <item>单实例:再次打开则把已有面板置前。</item>
/// <item>订阅 <see cref="CommunicationMessageService.MessageAdded"/>,批量刷新 UI500ms 节流),最多显示 100 行。</item>
/// <item>支持 IP 筛选、暂停/继续、清空(二次确认)、选中报文解析详情。</item>
/// </list>
/// 保留可实例化 + <see cref="Show"/> 以兼容既有调用。
/// </summary>
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<CommunicationMessage> pendingMessages = new Queue<CommunicationMessage>();
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<CommunicationMessage> _displayMessages = new List<CommunicationMessage>();
private static readonly Queue<CommunicationMessage> PendingMessages = new Queue<CommunicationMessage>();
private static readonly object PendingLock = new object();
private static DateTime _lastStatsRefresh = DateTime.MinValue;
private static bool _pendingStatsRefresh;
/// <summary>打开(或置前)通讯监控面板。兼容原 <c>new CommunicationMonitorForm().Show()</c> 调用方式。</summary>
public void Show() => Open();
/// <summary>打开(或置前)通讯监控面板。</summary>
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();
}
/// <summary>定时批量刷新 UI,避免每条报文都抢占渲染线程。</summary>
private static void FlushPendingBatch()
{
if (_paused)
return;
List<CommunicationMessage> batch = null;
lock (PendingLock)
{
if (PendingMessages.Count == 0)
return;
int count = Math.Min(UiBatchSize, PendingMessages.Count);
batch = new List<CommunicationMessage>(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();
}
/// <summary>
/// 刷新IP筛选下拉框
/// </summary>
private void RefreshIpFilter()
private static void RefreshIpFilter()
{
try
{
if (cmbIpFilter == null || messageService == null)
return;
var selectedIp = SelectedIpFilter();
var options = new List<string> { "全部" };
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
}
}
/// <summary>
/// 加载报文列表
/// </summary>
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();
}
}
}
/// <summary>
/// 定时批量刷新UI,避免每条报文都抢占UI线程
/// </summary>
private void UiFlushTimer_Tick(object sender, EventArgs e)
{
if (!isFormLoaded || isFormMessageStop)
if (string.IsNullOrWhiteSpace(ipAddress))
return;
List<CommunicationMessage> batch = null;
lock (pendingMessagesLock)
{
if (pendingMessages.Count == 0)
return;
int count = Math.Min(UiBatchSize, pendingMessages.Count);
batch = new List<CommunicationMessage>(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();
}
/// <summary>
/// 统计信息低频刷新(500ms
/// </summary>
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;
}
/// <summary>统计信息低频刷新(500ms)。</summary>
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);
}
}
/// <summary>
/// 向表格新增一条报文行(支持头部插入)
/// </summary>
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);
}
}
/// <summary>
/// 更新统计信息
/// </summary>
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 = "统计信息加载失败";
}
}
/// <summary>
/// 新报文添加事件处理(线程安全)
/// </summary>
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) + "…";
}
/// <summary>
/// 解析报文数据
/// </summary>
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}";
}
}
/// <summary>
/// 格式化十六进制字符串
/// </summary>
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}";
}
}
}
}
@@ -1,123 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<metadata name="type.UserAddedColumn" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>True</value>
</metadata>
</root>
@@ -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 = "已启动";
+2 -2
View File
@@ -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 { }
};
@@ -417,8 +417,8 @@ ChargeStationManagementExample.InitializeTestData();
## 📊 系统要求
### 软件要求
- .NET Framework 4.5 或更高版本
- Windows Forms
- .NET 8net8.0-windows),宿主 `SimpleLite.exe`
- Windows Forms(充电模块界面尚未迁移到 CycleGUI,过渡期仍依赖)
- Newtonsoft.JsonNuGet
### 硬件要求
@@ -1,608 +0,0 @@
namespace StandardScene.ExtendDevice.ButtonBox
{
partial class ButtonBoxManager
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
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;
}
}
File diff suppressed because it is too large Load Diff
@@ -1,120 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>
@@ -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}"); }
}
}
}
@@ -1,521 +0,0 @@
namespace StandardScene.ExtendDevice.Door
{
partial class DoorManager
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
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;
}
}
File diff suppressed because it is too large Load Diff
@@ -1,64 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<metadata name="timerRefresh.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>17, 17</value>
</metadata>
</root>
@@ -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}");
}
}
}
@@ -1,263 +0,0 @@
namespace StandardScene.ExtendDevice.Door
{
partial class DoorMonitor
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
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;
}
}
@@ -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
/// <summary>
/// 门控监控界面(CycleGUI 版,替代原 WinForms <c>DoorMonitor</c> 窗体)。
/// <list type="bullet">
/// <item>单实例:再次打开则把已有面板置前。</item>
/// <item><c>pb.Table</c> 展示门列表,单击行选中以进行手动控制。</item>
/// <item>约每 500ms 重绘刷新门状态快照(替代 WinForms 定时器)。</item>
/// </list>
/// </summary>
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;
/// <summary>
/// 获取单例实例
/// </summary>
public static DoorMonitor Instance
/// <summary>打开(或置前)门控监控面板。</summary>
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;
}
}
/// <summary>
/// 私有构造函数,确保单例模式
/// </summary>
private DoorMonitor()
{
InitializeComponent();
}
/// <summary>
/// 确保刷新定时器处于激活状态,并立即刷新一次
/// </summary>
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();
}
/// <summary>
/// 设置ListView的视觉样式
/// </summary>
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);
}
/// <summary>
/// 刷新门列表
/// </summary>
private void RefreshDoorList()
{
doorListView.Items.Clear();
// 保存当前选中的门
(int ControllerIndex, int DoorIndex)? previousSelected = _selectedDoor;
_selectedDoor = null;
labelDoorInfo.Text = "请选择要控制的门";
// 获取所有门控制器
var mission = SimpleProject.proj?.Missions?.OfType<DoorMission>().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);
/// <summary>
/// 门列表选择改变
/// </summary>
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<int, int>)
{
var doorInfo = (ValueTuple<int, int>)tag;
_selectedDoor = doorInfo;
labelDoorInfo.Text = $"控制器编码: {doorInfo.Item1}, 门编码: {doorInfo.Item2}";
// 根据占用状态决定关闭按钮是否可用
var mission = SimpleProject.proj?.Missions?.OfType<DoorMission>().FirstOrDefault();
var carsInArea = mission?.GetCarsInArea(doorInfo.Item1, doorInfo.Item2) ?? Array.Empty<int>();
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<DoorMission.DoorMonitorSnapshotItem>();
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);
});
}
/// <summary>
/// 打开门
/// </summary>
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<DoorMission>().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<DoorMission.DoorMonitorSnapshotItem> 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<DoorMission>().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}");
}
}
/// <summary>
/// 关闭门
/// </summary>
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<DoorMission>().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}");
}
}
/// <summary>
/// 清空车辆占用
/// </summary>
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<DoorMission>().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);
}
}
/// <summary>
/// 定时刷新
/// </summary>
private void timerRefresh_Tick(object sender, EventArgs e)
{
RefreshDoorList();
}
/// <summary>
/// 窗体关闭事件
/// </summary>
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}");
}
}
}
@@ -1,64 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<metadata name="timerRefresh.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>17, 17</value>
</metadata>
</root>
@@ -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;
@@ -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
{
/// <summary>
/// 区域稳定标识,用于界面编辑/批量删除时避免行索引错位。
/// </summary>
public string Id { get; set; }
/// <summary>
/// 区域名称
/// </summary>
@@ -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);
}
}
}
@@ -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
/// <summary>
/// 交通联锁区域管理界面(CycleGUI 版,替代原 WinForms <c>TrafficInterlockViewer</c> 窗体)。
/// <list type="bullet">
/// <item>维护 <see cref="TrafficInterlockMission.TrafficAreaList"/> 的增 / 改 / 删,并写入 <c>Config/traffic.json</c>。</item>
/// <item>单实例:再次打开则把已有面板置前。</item>
/// <item>勾选多行后「删除选中」可批量删除;每行「编辑」按钮加载下方编辑区。</item>
/// <item>文件写入放后台线程,绝不阻塞渲染线程。</item>
/// </list>
/// 沿用 <c>LoopViewer</c> / <c>DeliveryViewer</c> 同套模式(单实例面板、<c>pb.Table</c>、<c>CycleUiHelper.ConfirmThen</c>)。
/// 保留可实例化 + <see cref="Show"/> 以兼容既有调用 <c>new TrafficInterlockViewer().Show()</c>。
/// </summary>
public class TrafficInterlockViewer
{
/// <summary>-1 表示新增模式;>=0 表示正在编辑对应索引</summary>
private int _editingIndex = -1;
private const string TableId = "traffic-area-list";
/// <summary>选中行变化时是否允许加载到编辑区(避免在保存/取消时重复刷新)</summary>
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<string> _selected = new HashSet<string>(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 = "";
/// <summary>打开(或置前)区域管理面板。兼容原 <c>new TrafficInterlockViewer().Show()</c> 调用方式。</summary>
public void Show() => Open();
/// <summary>打开(或置前)区域管理面板。</summary>
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<TrafficArea> 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<int>
/// <summary>解析站点集合字符串,如 "1,2,3" → <see cref="List{T}"/> of int。</summary>
private static List<int> ParseStationIds(string text)
{
var list = new List<int>();
@@ -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<int>()
.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");
}
}
@@ -1,120 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>
-1
View File
@@ -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;
@@ -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")]
+3 -292
View File
@@ -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<UISite>().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<UISite>().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<UISite>().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<UISite>().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<MapStructure>(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<int> index = new List<int>();
// 处理 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<string, string>(), // Assuming fields is an empty object
mustFree = new List<object>() // 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<string, string>(),
typeInfo = "0",
layerName = "g",
displaySetting = ""
};
config.Tracks[id.ToString()] = track; // 将 Track 添加到 Tracks 字典中
}
return config;
}
static void AddInDescendingOrder(List<int> 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<UISite> generatedSites = new List<UISite>();
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.csscene.qrlidar 平台)。
// StandardScene 的 CAD 工具已迁移到 SimpleLite.CADTools.StandardSceneTools。
// 保留此文件作为迁移记录,避免后续误以为遗漏了 StandardScene 侧工具。
}
+7 -1
View File
@@ -3,7 +3,6 @@
<PropertyGroup>
<TargetFramework>net8.0-windows</TargetFramework>
<OutputType>Library</OutputType>
<UseWindowsForms>true</UseWindowsForms>
<RootNamespace>StandardScene</RootNamespace>
<AssemblyName>StandardScene</AssemblyName>
<LangVersion>latest</LangVersion>
@@ -29,6 +28,12 @@
<Reference Include="SimpleLite">
<HintPath>E:\Work\Core\Simple-FR\Simple\SimpleLite\bin\Debug\SimpleLite.dll</HintPath>
</Reference>
<!-- CycleGUI:插件 UI 已从 WinForms 迁移到 CycleGUIDeliveryViewer 等)。
Private=false:宿主 SimpleLite 已在默认 ALC 加载 CycleGUI,插件仅编译期引用、不随产物分发,避免重复 DLL。 -->
<Reference Include="CycleGUI">
<HintPath>$(CGUILibDir)\CycleGUI.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="SimpleCore">
<HintPath>E:\Work\Core\Simple-FR\Simple\SimpleCore\bin\Debug\netstandard2.0\SimpleCore.dll</HintPath>
</Reference>
@@ -59,6 +64,7 @@
<PackageReference Include="MQTTnet.Extensions.ManagedClient" Version="4.3.6.1152" />
<PackageReference Include="Nancy" Version="2.0.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
<PackageReference Include="System.Drawing.Common" Version="9.0.0" />
</ItemGroup>
</Project>
-1
View File
@@ -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
{
@@ -0,0 +1,37 @@
using System;
using System.Diagnostics;
namespace StandardScene.Utils
{
/// <summary>
/// 车辆远程访问辅助工具。
/// </summary>
public static class CarRemoteHelper
{
/// <summary>
/// 使用系统默认浏览器打开车辆 Web 管理页面。
/// </summary>
/// <param name="ip">车辆 IP 地址</param>
/// <param name="port">Web 服务端口,默认 8081</param>
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}");
}
}
}
}
+91
View File
@@ -0,0 +1,91 @@
using CycleGUI;
namespace StandardScene.Utils
{
/// <summary>
/// CycleGUI 通用 UI 小工具:把多个界面都会用到的轻量对话框收敛到一处,避免各处各写一套(不造重复轮子)。
/// <para>注意:同一 <c>Panel.Define</c> 内所有控件的 label 文本必须唯一(含 <c>pb.Table</c> 列头),
/// 否则 ImGui 会抛 <c>Duplicated id</c>。编辑区 label 建议加 ASCII 序号前缀(如 <c>1. IP</c>),
/// 且勿与表格列头同名。</para>
/// </summary>
public static class CycleUiHelper
{
/// <summary>
/// 非阻塞二次确认对话框:用户点「确认」后,在<b>当前(渲染)线程</b>同步执行 <paramref name="onConfirm"/>。
/// 若 <paramref name="onConfirm"/> 含文件 IO / 锁等耗时操作,调用方应自行用 <c>Task.Run</c> 包裹,
/// 避免阻塞渲染线程导致界面卡死。
/// </summary>
public static void ConfirmThen(string message, System.Action onConfirm)
{
// 不用 ModalCycleGUI 原生「模态弹窗 + 标题栏关闭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();
});
}
/// <summary>非阻塞文件选择对话框,替代 WinForms <c>OpenFileDialog</c>。</summary>
public static void PickOpenFile(string label, string filter, System.Action<string> 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);
}
});
}
/// <summary>非阻塞提示对话框。</summary>
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();
});
}
}
}
@@ -15,7 +15,6 @@ using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Timers;
using System.Windows.Forms;
namespace StandardScene.ChargeStationType
@@ -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
@@ -3,7 +3,6 @@
<PropertyGroup>
<TargetFramework>net8.0-windows</TargetFramework>
<OutputType>Library</OutputType>
<UseWindowsForms>true</UseWindowsForms>
<RootNamespace>StandardScene</RootNamespace>
<AssemblyName>StandardScene.Devices</AssemblyName>
<LangVersion>latest</LangVersion>
+202
View File
@@ -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
{
/// <summary>
/// 磁导航专用车型(scene.mag 平台)。
/// <para>与激光/二维码车(Kiva、叉车等走 HTTP:8008)不同:磁导航车与下位机通过 <b>UDP 协议</b> 交互;
/// 路径由 <see cref="MagneticTrackCoder"/> 生成 <c>agv.MagGo</c> / <c>agv.NaiveMagGo</c> 脚本
/// (按 <c>track.Magnet</c> 触发)。本类只承载磁导航相关能力。</para>
/// <para>⚠ UDP 报文的端口 / 字节布局 / 命令码因下位机协议而异,下方以常量 + TODO 标注,
/// 待按实际磁导航 AGV 协议填充 <see cref="ParseStatus"/> 与各控制命令的编码。</para>
/// </summary>
[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 通信参数(默认值待按实际下位机协议确认)──────────────────
/// <summary>下位机 UDP 命令端口(RCS → AGV 下发)。</summary>
[FieldMember] public int UdpCommandPort = 5000;
/// <summary>本机 UDP 状态监听端口(AGV → RCS 上报)。</summary>
[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<MagCar> Create()
{
var car = new MagCar
{
lstatus = "连接中",
address = "127.0.0.1",
name = "磁导航车",
haveCoordination = true,
speed = 1,
};
car.StartUdp();
return car;
}
// ── UDP 通信:协作式停止 + 单帧异常隔离(参考 ChargeUdpService 范式)─────────
/// <summary>启动磁导航 UDP 通信(幂等:会先停止已有连接)。</summary>
public void StartUdp()
{
StopUdp();
_cts = new CancellationTokenSource();
var token = _cts.Token;
Task.Run(() => ReceiveLoop(token), token);
}
/// <summary>停止磁导航 UDP 通信并释放资源。</summary>
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);
}
}
}
/// <summary>
/// 解析下位机 UDP 状态报文并写入 <c>status.enums</c>。
/// TODO(待协议确认):下方字节偏移为占位,请按实际磁导航 AGV 协议替换。
/// </summary>
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(坐标系需与地图一致)。
}
/// <summary>向下位机发送 UDP 命令。TODO:命令字节编码按实际协议实现。</summary>
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();
}
}
@@ -6,7 +6,9 @@ using StandardScene.CarTypes;
namespace StandardScene.Magnetic
{
/// <summary>
/// scene.mag 平台画像:磁导航场景插件(磁条循迹为主,兼容二维码地标段)
/// scene.mag 平台画像:磁导航场景插件。
/// <para>车型为磁导航专用 <see cref="MagCar"/>(与下位机通过 UDP 协议交互);路径由
/// MagneticTrackCoder 生成 agv.MagGo / agv.NaiveMagGo 脚本(按 track.Magnet 触发)。</para>
/// 宿主(SimpleLite)加载本 dll 后反射实例化并 OnActivate / 注册。
/// </summary>
public sealed class MagneticSceneProfile : NavigationProfileBase
@@ -19,13 +21,12 @@ namespace StandardScene.Magnetic
public override IReadOnlyList<Type> CarTypes => new[]
{
typeof(Kiva),
typeof(MultiWheelLifterCar),
typeof(MagCar),
};
public override void OnActivate(ISceneContext context)
{
context.Log($"{DisplayName} 已激活(车型:Kiva / 多舵轮顶升车");
context.Log($"{DisplayName} 已激活(车型:磁导航车 MagCar");
}
}
}
@@ -3,7 +3,6 @@
<PropertyGroup>
<TargetFramework>net8.0-windows</TargetFramework>
<OutputType>Library</OutputType>
<UseWindowsForms>true</UseWindowsForms>
<RootNamespace>StandardScene.Magnetic</RootNamespace>
<AssemblyName>StandardScene.Magnetic</AssemblyName>
<LangVersion>latest</LangVersion>
@@ -6,7 +6,7 @@
"coreVersion": ">=1.0.0",
"requiresCore": "StandardScene.dll",
"provides": {
"carTypes": [ "Kiva", "MultiWheelLifterCar" ],
"carTypes": [ "MagCar" ],
"missionTypes": []
}
}
@@ -0,0 +1,4 @@
using System.Runtime.CompilerServices;
// SimpleLite 反射 API 需读取 VDA5050 插件内 internal 字段袋。
[assembly: InternalsVisibleTo("SimpleLite")]
@@ -3,7 +3,6 @@
<PropertyGroup>
<TargetFramework>net8.0-windows</TargetFramework>
<OutputType>Library</OutputType>
<UseWindowsForms>true</UseWindowsForms>
<RootNamespace>StandardScene</RootNamespace>
<AssemblyName>StandardScene.Protocol.VDA5050</AssemblyName>
<LangVersion>latest</LangVersion>
@@ -29,6 +28,14 @@
<ProjectReference Include="..\StandardScene.Core\StandardScene.Core.csproj" />
</ItemGroup>
<!-- CycleGUITextViewer 等 UI 已从 WinForms 迁移到 CycleGUI(与 Core 一致,Private=false 由宿主加载) -->
<ItemGroup>
<Reference Include="CycleGUI">
<HintPath>$(CGUILibDir)\CycleGUI.dll</HintPath>
<Private>false</Private>
</Reference>
</ItemGroup>
<!-- 程序集引用:与 Core 保持一致的本地契约/工具 dll -->
<ItemGroup>
<Reference Include="SimpleLite">
@@ -1,58 +0,0 @@
namespace StandardScene.CarTypes
{
partial class TextViewer
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
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;
}
}
@@ -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
/// <summary>
/// 长文本只读查看面板(CycleGUI 版,替代原 WinForms <c>TextViewer</c> 窗体)。
/// 保留可实例化 + <see cref="Show"/> 以兼容 <c>new TextViewer().Show()</c> 与 <see cref="UpdateText"/> 调用。
/// </summary>
public class TextViewer
{
public TextViewer()
private Panel _panel;
private volatile string _text = "";
/// <summary>打开(或置前)文本查看面板。</summary>
public void Show() => Open();
/// <summary>打开(或置前)文本查看面板。</summary>
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);
});
}
/// <summary>更新显示文本并触发面板重绘(可从非渲染线程调用)。</summary>
public void UpdateText(string str)
{
richTextBox1.Invoke((Action)delegate
{
richTextBox1.Text = str;
});
_text = str ?? "";
_panel?.Repaint();
}
}
}
@@ -1,120 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>
@@ -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
@@ -16,7 +16,6 @@ using System.Net.Http;
using Nancy.Routing;
using Newtonsoft.Json;
using SimpleLite;
using System.Windows.Forms;
namespace StandardScene.CarTypes
{
+2 -2
View File
@@ -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;
}
+5 -14
View File
@@ -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);
}
@@ -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;
+6 -18
View File
@@ -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 &&
@@ -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)}");
}
})
{
@@ -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
{
@@ -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)
@@ -0,0 +1,4 @@
using System.Runtime.CompilerServices;
// SimpleLite 反射 API 需读取 QrLidar 插件内 internal 字段袋(ForkliftSiteFields 等)。
[assembly: InternalsVisibleTo("SimpleLite")]
+4 -1
View File
@@ -9,6 +9,7 @@ namespace StandardScene.QrLidar
/// scene.qrlidar 平台画像:激光 + 二维码融合导航场景插件。
/// <para>激光(SLAM 坐标导航)由内核 GhostCar 的 BasicGo 兜底提供;二维码 QrGo 按轨道两端
/// tag 字段逐段触发——同一台车同一条路线可全激光、全二维码或混合(融合 / 单独使用均可)。</para>
/// <para>Kiva / 多舵轮顶升车原属本平台(激光+二维码 + 货架取放),会话39 由 scene.mag 迁入。</para>
/// </summary>
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 / 多舵轮顶升车");
}
}
}
@@ -3,7 +3,6 @@
<PropertyGroup>
<TargetFramework>net8.0-windows</TargetFramework>
<OutputType>Library</OutputType>
<UseWindowsForms>true</UseWindowsForms>
<RootNamespace>StandardScene.QrLidar</RootNamespace>
<AssemblyName>StandardScene.QrLidar</AssemblyName>
<LangVersion>latest</LangVersion>
@@ -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": []
}
}
@@ -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()`
- 现象:拆分后卫星 dllDevices/VDA5050)中的车型/Mission 方法不会出现在 `get_type_methods` 列表里;而运行期类型发现走的是 `UiTypeDiscovery.AllTypes()`(全域)。
- 影响:前端“可用动作”列表缺失卫星类型的方法(execute 端点按实例反射仍可用,但 UI 发现不全)。
- 修复:`GetMethods` 改用 `UiTypeDiscovery.AllTypes()` 统一口径。
#### P2-3 `AtomicFileUpdateHelper` 并非真正“原子”写
- 位置:`StandardScene.Core/CommonTools/AtomicFileUpdateHelper.cs:54``File.WriteAllText` 直接覆盖)
- 现象:仅用 `ConcurrentDictionary<path,lock>` 保证**进程内同路径串行**(线程安全 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<host:port, HttpClient>`),但对外没有任何可用请求方法 → 各处只能各自 `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。
-267
View File
@@ -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``精读确认`272314 / 332368)暴露:
```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 等。
-241
View File
@@ -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 用 CycleGUIWebApi 用 EmbedIO,引用 `SimpleCore`
### 3.6 老 WebApi 处置
- `WebApi.cs` ~123KB40+ 端点,**与导航弱相关**(导航相关主要是 `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 新 APIWebApi 迁移目标) |
| `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` 为准。*
-267
View File
@@ -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、硬编码 IP192.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、驱动在 DevicesS5);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-filesCommons 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<br/>接口/特性/字段袋/领域模型/ITrackCoder/设备契约]
Infra[StandardScene.Infrastructure<br/>TCP/序列化/ID/IO/ILogger]
Config[StandardScene.Configuration<br/>配置模型+读写]
end
subgraph L1[领域层 net8.0]
Core2[StandardScene.Core<br/>车型/任务族/调度/交通/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.WebApiNancy 隔离)]
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<T>`(统一 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/S9WebApi)。
- **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 内消重,零行为变化)。