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:
@@ -0,0 +1,5 @@
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<CGUILibDir Condition="'$(CGUILibDir)' == ''">D:\MDCS\Dependencies\Commons</CGUILibDir>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
@@ -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>
|
||||
@@ -0,0 +1,525 @@
|
||||
# StandardScene 代码架构文档
|
||||
|
||||
> **目标读者**:接手本仓库的工程师、AI Agent、架构审查人员
|
||||
> **最后更新**:2026-06-25
|
||||
> **配套文档**:`DEVELOPMENT_GUIDE.md`(开发实操)、`QUICK_REFERENCE.md`(速查)、`StandardScene架构重构方案.md`(演进方向)
|
||||
|
||||
---
|
||||
|
||||
## 1. 项目定位
|
||||
|
||||
StandardScene 是一套 **AGV/AMR 场内调度与联动控制** 的场景插件库,不是独立可执行程序。
|
||||
|
||||
| 维度 | 说明 |
|
||||
| --- | --- |
|
||||
| 工程类型 | 类库插件(1 基座 + 4 卫星 = 5 个 DLL) |
|
||||
| 宿主 | `SimpleLite.exe`(CycleGUI 桌面应用) |
|
||||
| 内核 | `SimpleCore.dll`(Mission、交通、路径编译、导航契约) |
|
||||
| 目标框架 | `net8.0-windows`(x64) |
|
||||
| 语言 | C# |
|
||||
| UI 演进 | WinForms → CycleGUI(`DeliveryViewer` 等已迁移) |
|
||||
|
||||
典型业务能力:搬运任务、环线任务、区域交通管制、充电编排、门禁/按钮盒联动、HTTP / MQTT / Modbus 对外接口。
|
||||
|
||||
---
|
||||
|
||||
## 2. 解决方案结构
|
||||
|
||||
```
|
||||
StandardScene.sln
|
||||
├── StandardScene.Core → 输出 StandardScene.dll(基座,始终加载)
|
||||
├── StandardScene.Magnetic → 输出 StandardScene.Magnetic.dll(scene.mag)
|
||||
├── StandardScene.QrLidar → 输出 StandardScene.QrLidar.dll(scene.qrlidar)
|
||||
├── StandardScene.Devices → 输出 StandardScene.Devices.dll(scene.device)
|
||||
└── StandardScene.Protocol.VDA5050 → 输出 StandardScene.Protocol.VDA5050.dll(scene.vda5050)
|
||||
```
|
||||
|
||||
### 2.1 依赖关系(星型拓扑)
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
Host["SimpleLite.exe"]
|
||||
SC["SimpleCore.dll"]
|
||||
SL["SimpleLite.dll"]
|
||||
|
||||
Core["StandardScene.dll<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` | 否 | VDA5050(MQTT)协议栈与标准车型 |
|
||||
|
||||
---
|
||||
|
||||
## 3. 运行时架构
|
||||
|
||||
### 3.1 启动与加载流程
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Host as SimpleLite.exe
|
||||
participant PM as PluginManager
|
||||
participant Core as StandardScene.dll
|
||||
participant Sat as 卫星 DLL
|
||||
|
||||
Host->>PM: 扫描 plugins\ 目录
|
||||
PM->>Core: 加载 requiresCore 指向的基座(不可回收)
|
||||
PM->>PM: 读取 active-scenes.json / CLI --scenes
|
||||
PM->>Sat: 按场景 id 加载选中的卫星 DLL
|
||||
Sat->>Core: 引用已加载的基座类型
|
||||
PM->>PM: UiTypeDiscovery 反射 [MissionType]/[CarType]/[DoorType] 等
|
||||
alt 导航插件
|
||||
PM->>Sat: 实例化 NavigationProfileBase → OnActivate
|
||||
end
|
||||
Host->>Core: CustomOperationsBeforeLoading.Set()(死锁回调等)
|
||||
```
|
||||
|
||||
**场景选择优先级**(高 → 低):
|
||||
1. CLI 参数 `--scenes`
|
||||
2. `active-scenes.json`
|
||||
3. `simple.json` 中的 `scenes` 字段
|
||||
4. 加载全部已发现的卫星
|
||||
|
||||
> **注意**:若 `active-scenes.json` 仅包含 `scene.mag`,则 `scene.device` / `scene.vda5050` 不会加载,门控/充电桩/VDA5050 车型将不可用。生产部署需显式包含所需场景 id。
|
||||
|
||||
### 3.2 构建与部署
|
||||
|
||||
`Directory.Build.targets` 在编译后自动将产物复制到 `build\plugins\`:
|
||||
|
||||
- 各插件 DLL + PDB
|
||||
- 各卫星的 `*.scene.json`
|
||||
- Devices 插件附带的 `leegKeys-sdk.dll`
|
||||
|
||||
部署步骤:
|
||||
1. 先构建宿主:`dotnet build Simple\SimpleLite\SimpleLite.csproj`
|
||||
2. 构建本方案:`dotnet build StandardScene.sln`
|
||||
3. 将 `build\plugins\` 内容复制到 `SimpleLite.exe` 工作目录的 `plugins\`
|
||||
4. 配置 `active-scenes.json` 后启动 `SimpleLite.exe`
|
||||
|
||||
### 3.3 业务运行时链路
|
||||
|
||||
```
|
||||
外部系统 (HTTP/MQTT/Modbus)
|
||||
↓
|
||||
WebApi / 设备驱动 / 车型通信
|
||||
↓
|
||||
Mission(后台进程:搬运/环线/充电/门控/心跳…)
|
||||
↓
|
||||
SimpleLib / TrafficControl / SegmentPlan(SimpleCore)
|
||||
↓
|
||||
GhostCar / Car 实例(车辆状态机 + 下位机通信)
|
||||
↓
|
||||
地图站点 / 轨道 / fields & tags 配置
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. 核心概念
|
||||
|
||||
### 4.1 Mission(场景进程)
|
||||
|
||||
Mission 是本系统的 **可启动后台业务单元**,继承自 `SimpleCore.Mission`,由宿主 UI 或 HTTP 反射 API 启停。
|
||||
|
||||
**生命周期模式**(以 `HeartBeatMission` 为范本):
|
||||
|
||||
| 阶段 | 典型实现 |
|
||||
| --- | --- |
|
||||
| 注册 | `[MissionType(Name="...", editor=typeof(...))]` |
|
||||
| 工厂 | `public static Mission Create()` |
|
||||
| 启动 | `[MethodMember(Name="启动进程")] public override void Execute()` |
|
||||
| 后台 | `Thread` / `Task.Run` / `async` + `CancellationTokenSource` |
|
||||
| 状态 | 更新 `status.status` 或自定义 `MissionStatus` 子类 |
|
||||
| 停止 | `[MethodMember(Name="关闭进程")] public void Stop()` |
|
||||
|
||||
**Mission 继承树**:
|
||||
|
||||
```
|
||||
Mission (SimpleCore)
|
||||
├── HeartBeatMission, RegionalTrafficControlMission, SecuritySignalMission
|
||||
├── NodeIsEnableMission
|
||||
├── DoorMission, ButtonMission
|
||||
├── AbstractInterlockMission
|
||||
│ ├── TrafficInterlockMission
|
||||
│ └── AbstractChargeLogicMission → StandardChargeMission
|
||||
├── ChainedDeliveryMission → TransportMission(搬运调度)
|
||||
└── AbstractLoopMission → LoopMission(环线任务)
|
||||
```
|
||||
|
||||
### 4.2 CarType(车型)
|
||||
|
||||
车型代表一种 AGV 下位机协议与行为模型,继承自 `SimpleLite.RCS.CarTypes.GhostCar` 或 `Car`。
|
||||
|
||||
**注册三要素**:
|
||||
|
||||
1. **类型特性**:`[CarType(Name="显示名", editor=typeof(XxxCar))]`
|
||||
2. **轨迹编码器**:`[ProgramTrackCoderSettings(priority=N, program=typeof(XxxCoder))]`
|
||||
3. **场景清单**:`scene.json` 的 `provides.carTypes` +(导航插件)`NavigationProfileBase.CarTypes`
|
||||
|
||||
**Coder(轨迹编码器)** 实现 `SimpleCore.Compiler.ITrackCoder`,在路径规划时将站点/轨道上的 `fields` 编译为 Topaz 脚本片段(如 `agv.MagGo`、`agv.BasicGo`)。
|
||||
|
||||
### 4.3 设备驱动(门 / 充电桩 / 按钮盒)
|
||||
|
||||
设备采用 **抽象在 Core、实现在 Devices** 的模式:
|
||||
|
||||
| 设备 | Core 抽象 | Devices 实现 | 发现机制 |
|
||||
| --- | --- | --- | --- |
|
||||
| 门控 | `BasicDoorController` | `ModbusDoorController` | `[DoorType("...")]` + `UiTypeDiscovery` |
|
||||
| 充电桩 | `AbstractChargeStation` | `FL/PCB/MuXingChargeStation` | 类型全名 + `UiTypeDiscovery` |
|
||||
| 按钮盒 | `BasicButtonBox` | `Leeg/AzowieButtonBox` | 类名 + `UiTypeDiscovery` |
|
||||
|
||||
### 4.4 Fields & Tags(轻量配置)
|
||||
|
||||
除 JSON 配置文件外,系统大量使用地图上的 **fields**(键值对)和 **tags** 驱动行为,例如:
|
||||
- 站点 `Region1=1` → 区域流控
|
||||
- 轨道 `Magnet=1` → 磁导航段
|
||||
- 车辆 `address` → 下位机 IP
|
||||
|
||||
通用读写入口:`Commons.cs` 中的 `GetXxxField` / `AddOrUpdateXxxField` 系列方法。
|
||||
|
||||
### 4.5 NavigationProfile(导航场景画像)
|
||||
|
||||
仅 **Magnetic** 与 **QrLidar** 两个导航卫星实现 `SimpleCore.Navigation.NavigationProfileBase`:
|
||||
|
||||
```csharp
|
||||
public sealed class MagneticSceneProfile : NavigationProfileBase
|
||||
{
|
||||
public override NavKind Kind => NavKind.Magnetic;
|
||||
public override string SceneId => "scene.mag";
|
||||
public override IReadOnlyList<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 NuGet(Core 最重)
|
||||
|
||||
`Nancy`, `MQTTnet`, `EasyModbusTCP`, `IoTClient`, `Jint`, `DocumentFormat.OpenXml`, `Newtonsoft.Json`
|
||||
|
||||
Protocol.VDA5050 额外使用 `MQTTnet`;Magnetic / QrLidar 仅 `Newtonsoft.Json`。
|
||||
|
||||
---
|
||||
|
||||
## 9. 关键设计决策与现状问题
|
||||
|
||||
### 9.1 当前设计的合理之处
|
||||
|
||||
- **星型插件拓扑**:基座承载全部 Mission 与设备抽象,卫星按场景热插拔
|
||||
- **`scene.json` 清单**:声明式描述插件能力,宿主无需硬编码
|
||||
- **`NavigationProfileBase`**:导航平台与车型注册的标准契约
|
||||
- **`Chained/Loop/` 规则接口**:环线策略可插拔(`IEnterRule` 等)
|
||||
- **`InternalsVisibleTo` 拆分策略**:在不破坏封装的前提下外移车型代码
|
||||
|
||||
### 9.2 已知架构债务(详见 `StandardScene架构重构方案.md`)
|
||||
|
||||
| 编号 | 问题 | 影响 |
|
||||
| --- | --- | --- |
|
||||
| S1 | Core 背负 MQTT/Modbus/Nancy/OpenXml 等协议依赖 | 基座臃肿,卫星无法独立瘦身 |
|
||||
| S2 | WinForms 与 CycleGUI 并存 | 阻塞纯 `net8.0` 跨平台 |
|
||||
| S3 | 多个 600~2700 行 God-class(WebApi、AbstractLoopMission 等) | 改动风险高、难测试 |
|
||||
| S8 | Coder 注册表限定内核程序集反射 | 导航 Coder 热插拔受限 |
|
||||
| S9 | csproj 绝对路径 HintPath | CI / 换机构建需手动适配 |
|
||||
|
||||
### 9.3 演进方向(摘要)
|
||||
|
||||
1. 抽离 `StandardScene.Abstractions`(纯契约 + 模型)
|
||||
2. 充电子系统独立为卫星 `StandardScene.Charge`
|
||||
3. WebApi 按资源拆模块并迁出 Core
|
||||
4. UI 全部迁至 CycleGUI 或独立 UI 程序集
|
||||
5. 目标 TFM 从 `net8.0-windows` 过渡到 `net8.0`(基础设施层)
|
||||
|
||||
---
|
||||
|
||||
## 10. AI / 新工程师接手清单
|
||||
|
||||
### 10.1 第一天:建立全局认知
|
||||
|
||||
1. 阅读 `README.md` → 本文档 → `DEVELOPMENT_GUIDE.md`
|
||||
2. 打开 `DocumentHub.html` 浏览模块导航
|
||||
3. 本地构建:先 SimpleLite,再 StandardScene,部署到 `plugins\`
|
||||
4. 启动宿主,确认 `active-scenes.json` 包含所需场景
|
||||
|
||||
### 10.2 第二天:跟踪一条完整链路
|
||||
|
||||
**搬运任务链路**(推荐):
|
||||
```
|
||||
WebApi /car/createTask
|
||||
→ TransportMission(ChainedDeliveryMission.LoopAsync)
|
||||
→ Commons.NearestTask / 选车逻辑
|
||||
→ GhostCar 下发路径(SegmentPlan + Coder 脚本)
|
||||
→ TrafficControl 互锁
|
||||
→ 下位机 HTTP/MQTT/UDP 通信
|
||||
```
|
||||
|
||||
**充电链路**:
|
||||
```
|
||||
StandardChargeMission
|
||||
→ AbstractChargeLogicMission(电量策略)
|
||||
→ AbstractInterlockMission(站点互锁)
|
||||
→ AbstractChargeStation 实例(Devices 插件驱动)
|
||||
```
|
||||
|
||||
### 10.3 按任务类型定位文件
|
||||
|
||||
| 我要做… | 先看 |
|
||||
| --- | --- |
|
||||
| 最小 Mission 样板 | `Scheduler/HeartBeatMission.cs` |
|
||||
| 搬运调度 | `Chained/ChainedDeliveryMission.cs`, `Chained/TransportMission.cs` |
|
||||
| 环线任务 | `Chained/AbstractLoopMission.cs`, `Chained/LoopMission.cs` |
|
||||
| 区域流控 | `Scheduler/RegionalTrafficControlMission.cs` |
|
||||
| 充电 | `Charge/StandardChargeMission.cs`, `Charge/AbstractChargeLogicMission.cs` |
|
||||
| 门控 | `ExtendDevice/Door/DoorMission.cs`, `Devices/Door/ModbusDoorController.cs` |
|
||||
| HTTP API | `WebApi.cs` |
|
||||
| 新车型 | 对应卫星 `CarTypes/` + `Coders/` + `*SceneProfile.cs` |
|
||||
| 字段/选车/路径辅助 | `Commons.cs` |
|
||||
| 地图与任务模型 | `Model/` |
|
||||
|
||||
### 10.4 修改前的安全检查
|
||||
|
||||
- [ ] 确认目标变更属于 Core 还是卫星(避免在 Core 放平台专有逻辑)
|
||||
- [ ] 若改车型,同步 `scene.json` + `NavigationProfile.CarTypes`
|
||||
- [ ] 若改设备驱动,确认 `scene.device` 在 `active-scenes.json` 中
|
||||
- [ ] 若改 Coder,注意 `priority` 与字段袋哨兵值(`-1` 语义)
|
||||
- [ ] 编译后检查 `build\plugins\` 产物是否完整
|
||||
|
||||
---
|
||||
|
||||
## 11. 文档索引
|
||||
|
||||
| 文档 | 用途 |
|
||||
| --- | --- |
|
||||
| **本文档 `ARCHITECTURE.md`** | 整体架构、模块地图、扩展点、接手清单 |
|
||||
| `DEVELOPMENT_GUIDE.md` | 开发环境、案例教程、调试建议 |
|
||||
| `QUICK_REFERENCE.md` | 高频入口与配置速查 |
|
||||
| `StandardScene架构重构方案.md` | 架构演进与分层目标 |
|
||||
| `StandardScene拆分计划.md` | 程序集拆分进度 |
|
||||
| `StandardScene代码审查报告.md` | 质量缺陷与修复记录 |
|
||||
| `StandardScene.Core/Docs/` | 充电、门控、Coder 专题手册 |
|
||||
|
||||
---
|
||||
|
||||
## 附录 A:scene.json 完整示例
|
||||
|
||||
**导航插件(scene.mag)**:
|
||||
```json
|
||||
{
|
||||
"id": "scene.mag",
|
||||
"displayName": "磁导航平台",
|
||||
"navKind": "magnetic",
|
||||
"assembly": "StandardScene.Magnetic.dll",
|
||||
"coreVersion": ">=1.0.0",
|
||||
"requiresCore": "StandardScene.dll",
|
||||
"provides": {
|
||||
"carTypes": ["MagCar"],
|
||||
"missionTypes": []
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**设备插件(scene.device)**:
|
||||
```json
|
||||
{
|
||||
"id": "scene.device",
|
||||
"displayName": "设备驱动(门 / 充电桩 / 按钮盒)",
|
||||
"assembly": "StandardScene.Devices.dll",
|
||||
"coreVersion": ">=1.0.0",
|
||||
"requiresCore": "StandardScene.dll",
|
||||
"provides": {
|
||||
"doorControllers": ["ModbusDoorController"],
|
||||
"chargeStations": ["FLChargeStation", "PCBChargeStation", "MuXingChargeStation"],
|
||||
"buttonBoxes": ["LeegButtonBox", "AzowieButtonBox"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 附录 B:插件引导钩子
|
||||
|
||||
基座在宿主加载早期通过反射调用:
|
||||
|
||||
```csharp
|
||||
// StandardScene.Core/Commons.cs
|
||||
public class CustomOperationsBeforeLoading
|
||||
{
|
||||
public static void Set()
|
||||
{
|
||||
TrafficControl.OnDeadLock = (loopingCar) => { /* 死锁告警 */ };
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
这是少数几个 **无 Mission 启动即可生效** 的全局初始化入口之一。
|
||||
@@ -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,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`,体验区域流控
|
||||
@@ -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
@@ -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>
|
||||
|
||||
|
||||
@@ -1,22 +1,38 @@
|
||||
# StandardScene
|
||||
|
||||
`StandardScene` 是一个基于 `.NET Framework 4.8` 的场景插件库,运行时由 `SimpleComposer.exe` 作为宿主加载。项目面向 AGV/AMR 场内调度与联动控制,覆盖搬运任务、环线任务、区域交通管制、充电管理、门禁联动,以及 HTTP / MQTT / Modbus 等对外通信能力。
|
||||
`StandardScene` 是一个基于 `.NET 8`(`net8.0-windows`)的场景插件库,运行时由 `SimpleLite.exe` 作为宿主加载。项目面向 AGV/AMR 场内调度与联动控制,覆盖搬运任务、环线任务、区域交通管制、充电管理、门禁联动,以及 HTTP / MQTT / Modbus 等对外通信能力。
|
||||
|
||||
> 历史说明:早期版本基于 `.NET Framework 4.8` 并由 `SimpleComposer.exe` 加载;现已迁移到 `net8.0-windows` + `SimpleLite.exe` 宿主,并拆分为「基座 + 4 个卫星」共 5 个插件 DLL。界面正逐步从 WinForms 迁移到 CycleGUI(与宿主同栈),任务列表 `DeliveryViewer` 已完成迁移。
|
||||
|
||||
## 文档入口
|
||||
|
||||
- `DocumentHub.html`:单文件文档门户,双击即可在浏览器中打开
|
||||
- `DEVELOPMENT_GUIDE.md`:面向整个仓库的开发指南
|
||||
- `QUICK_REFERENCE.md`:高频开发速查表
|
||||
- `INDEX.md`:阅读顺序与导航说明
|
||||
- `Doc/ARCHITECTURE.md`:整体代码架构文档(模块地图、插件机制、扩展点、接手清单)
|
||||
- `Doc/DEVELOPMENT_GUIDE.md`:面向整个仓库的开发指南
|
||||
- `Doc/QUICK_REFERENCE.md`:高频开发速查表
|
||||
- `Doc/INDEX.md`:阅读顺序与导航说明
|
||||
|
||||
## 项目定位
|
||||
|
||||
- 工程类型:类库插件,不是独立 EXE
|
||||
- 宿主程序:`SimpleComposer.exe`
|
||||
- 目标框架:`.NET Framework 4.8`
|
||||
- 工程类型:类库插件(基座 + 4 个卫星,共 5 个 DLL),不是独立 EXE
|
||||
- 宿主程序:`SimpleLite.exe`(CycleGUI 应用)
|
||||
- 目标框架:`net8.0-windows`
|
||||
- 主要语言:`C#`
|
||||
- 界面技术:逐步从 WinForms 迁移到 CycleGUI(宿主同栈);`DeliveryViewer`(任务列表)已迁移
|
||||
- 典型场景:仓储搬运、区域流控、充电协同、门控联动、车辆协议接入
|
||||
|
||||
## 插件构成
|
||||
|
||||
| 插件 DLL | 场景 id | 职责 |
|
||||
| --- | --- | --- |
|
||||
| `StandardScene.dll`(Core,基座) | —(alwaysLoad) | 任务调度、交通互锁、充电编排、门禁/按钮盒、HTTP API、数据模型、抽象契约 |
|
||||
| `StandardScene.Magnetic.dll` | `scene.mag` | 磁导航平台车型与磁循迹 Coder |
|
||||
| `StandardScene.QrLidar.dll` | `scene.qrlidar` | 激光 SLAM + 二维码导航,多种车型 |
|
||||
| `StandardScene.Devices.dll` | `scene.device` | 具体硬件驱动(Modbus 门控、充电桩、按钮盒) |
|
||||
| `StandardScene.Protocol.VDA5050.dll` | `scene.vda5050` | VDA5050(MQTT)标准协议栈与标准车型 |
|
||||
|
||||
卫星插件依赖基座(星型,单向,无循环引用);各卫星附带 `<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()
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
{
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
@@ -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
@@ -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);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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"/>,批量刷新 UI(500ms 节流),最多显示 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 = "已启动";
|
||||
|
||||
@@ -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 8(net8.0-windows),宿主 `SimpleLite.exe`
|
||||
- Windows Forms(充电模块界面尚未迁移到 CycleGUI,过渡期仍依赖)
|
||||
- Newtonsoft.Json(NuGet)
|
||||
|
||||
### 硬件要求
|
||||
|
||||
@@ -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>
|
||||
@@ -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")]
|
||||
|
||||
@@ -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.cs(scene.qrlidar 平台)。
|
||||
// StandardScene 的 CAD 工具已迁移到 SimpleLite.CADTools.StandardSceneTools。
|
||||
// 保留此文件作为迁移记录,避免后续误以为遗漏了 StandardScene 侧工具。
|
||||
}
|
||||
|
||||
@@ -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 迁移到 CycleGUI(DeliveryViewer 等)。
|
||||
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>
|
||||
|
||||
@@ -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}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
{
|
||||
// 不用 Modal:CycleGUI 原生「模态弹窗 + 标题栏关闭X」存在 BeginPopupModal/EndPopup 配对 bug
|
||||
//(X 关闭时 BeginPopupModal 返回 false 仍调用 EndPopup → ImGui 断言 "Calling End() too many times!" 崩溃)。
|
||||
// 改为置顶非模态:用 Begin/End 路径,X 关闭干净,效果等同点「取消」。
|
||||
var dlg = GUI.DeclarePanel()
|
||||
.ShowTitle("确认")
|
||||
.TopMost(true)
|
||||
.InitSize(380, 150)
|
||||
.InitPos(false, 0, 0, 0.5f, 0.5f, 0.5f, 0.5f);
|
||||
dlg.Define(pb =>
|
||||
{
|
||||
if (pb.Closing())
|
||||
{
|
||||
dlg.Exit();
|
||||
return;
|
||||
}
|
||||
|
||||
pb.Label(message);
|
||||
pb.Separator();
|
||||
if (pb.Button("确认", distinct: "cycleui-confirm-ok"))
|
||||
{
|
||||
dlg.Exit();
|
||||
onConfirm();
|
||||
}
|
||||
pb.SameLine(8);
|
||||
if (pb.Button("取消", distinct: "cycleui-confirm-cancel"))
|
||||
dlg.Exit();
|
||||
});
|
||||
}
|
||||
|
||||
/// <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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
<!-- CycleGUI:TextViewer 等 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
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
+5
-17
@@ -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")]
|
||||
@@ -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()`
|
||||
- 现象:拆分后卫星 dll(Devices/VDA5050)中的车型/Mission 方法不会出现在 `get_type_methods` 列表里;而运行期类型发现走的是 `UiTypeDiscovery.AllTypes()`(全域)。
|
||||
- 影响:前端“可用动作”列表缺失卫星类型的方法(execute 端点按实例反射仍可用,但 UI 发现不全)。
|
||||
- 修复:`GetMethods` 改用 `UiTypeDiscovery.AllTypes()` 统一口径。
|
||||
|
||||
#### P2-3 `AtomicFileUpdateHelper` 并非真正“原子”写
|
||||
- 位置:`StandardScene.Core/CommonTools/AtomicFileUpdateHelper.cs:54`(`File.WriteAllText` 直接覆盖)
|
||||
- 现象:仅用 `ConcurrentDictionary<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。
|
||||
@@ -1,267 +0,0 @@
|
||||
# StandardScene 代码审查报告
|
||||
|
||||
> 审查对象:`E:\Work\Core\Simple-FR\StandardSence`(StandardScene.Core / StandardScene.Devices / StandardScene.Protocol.VDA5050)
|
||||
> 审查方式:全量反模式扫描(ripgrep)+ 关键文件精读 + 编译告警分类(dotnet build --no-incremental)
|
||||
> 目标框架:`net8.0-windows`(最终目标 `net8.0`)
|
||||
> 报告日期:2026-06-09
|
||||
|
||||
---
|
||||
|
||||
## 一、审查范围与方法
|
||||
|
||||
- **代码规模**:113 个 `.cs` 文件;最大文件 `WebApi.cs`(2661 行)、`AbstractLoopMission.cs`(1858 行)。
|
||||
- **扫描维度**:并发/线程、异常处理、报文解析边界、资源管理、配置硬编码、日志、UI/业务耦合、安全、编译告警。
|
||||
- **证据标注**:
|
||||
- `精读确认`:已逐行读取、行号精确。
|
||||
- `扫描命中`:ripgrep 命中文件级,行号待整改时逐一核对。
|
||||
|
||||
---
|
||||
|
||||
## 二、总体评价与子系统评分
|
||||
|
||||
| 子系统 | 评分 | 说明 |
|
||||
|---|---|---|
|
||||
| 设备驱动(StandardScene.Devices·新) | ★★★★☆ | `ModbusDoorController` 工程化优秀,可作团队样板 |
|
||||
| TCP 基础设施(AsyncTcpClient) | ★★★★☆ | 锁/陈旧回调防护/重连完善,少量瑕疵 |
|
||||
| Coders(重构后) | ★★★★☆ | 本轮已去重,结构清晰 |
|
||||
| CarTypes 车型族 | ★★☆☆☆ | 巨类、async void、空 catch、硬编码集中 |
|
||||
| 任务族 Missions | ★★☆☆☆ | Thread.Abort、while(true)+Sleep、状态机分散 |
|
||||
| 充电 Charge | ★★☆☆☆ | 实时报文路径越界风险、UDP 线程模型粗放 |
|
||||
| VDA5050 协议 | ★★☆☆☆ | 硬编码 IP、async void、throw ex、Console 日志 |
|
||||
| WebApi(老 Nancy) | ★☆☆☆☆ | 2661 行上帝文件、无鉴权反射调用、模板代码爆炸 |
|
||||
| Commons / 公共层 | ★★☆☆☆ | 上帝工具类、重复实现、隐性 bug |
|
||||
|
||||
**核心判断**:技术债**集中在旧模块**(CarTypes / Missions / Charge / VDA5050 / WebApi / Commons);**新写模块**(Devices、AsyncTcpClient)质量明显更高,说明团队具备写好代码的能力,债务主要是历史遗留。整改应"以新模块为范本、按子系统收敛旧债"。
|
||||
|
||||
---
|
||||
|
||||
## 三、问题严重级别汇总
|
||||
|
||||
| 级别 | 含义 | 主要条目 |
|
||||
|---|---|---|
|
||||
| **P0 阻断** | .NET8 下会崩溃/失效,或存在安全风险 | Thread.Abort 被吞、WebApi 无鉴权反射、实时报文越界 |
|
||||
| **P1 高危** | 生产环境易触发故障/难排障 | 硬编码 IP、async void+throw、空 catch、HttpClient 滥用 |
|
||||
| **P2 结构** | 可维护性/扩展性差 | 上帝类、重复代码、Console 日志、UI/业务耦合 |
|
||||
| **P3 整洁** | 编译告警与代码卫生 | 45 项告警(重复 using、未用字段、隐藏成员等) |
|
||||
|
||||
---
|
||||
|
||||
## 四、P0 阻断级问题
|
||||
|
||||
### P0-1 `Thread.Abort()` 在 .NET8 必抛异常且被静默吞掉(线程停不掉)
|
||||
|
||||
`Thread.Abort()` 在 .NET8 抛 `PlatformNotSupportedException`(告警 SYSLIB0006,共 14 条/双配置)。多处 `Abort()` 外层是空 `catch{}`,导致**异常被吞、线程实际未停止**——任务"停止"后后台线程仍在跑,造成重复下发、资源泄漏、状态错乱。
|
||||
|
||||
精读确认命中点:
|
||||
|
||||
| 文件 | 行 |
|
||||
|---|---|
|
||||
| `StandardScene.Core/Chained/AbstractLoopMission.cs` | 1314 `_strategyThread?.Abort()`、1329 `_logicThread?.Abort()` |
|
||||
| `StandardScene.Core/Charge/StandardChargeMission.cs` | 681 `myThread?.Abort()`、684 `ChargeThread?.Abort()` |
|
||||
| `StandardScene.Core/Scheduler/SecuritySignalMission.cs` | 176 `myThread?.Abort()` |
|
||||
| `StandardScene.Core/Scheduler/NodeIsEnableMission.cs` | 121 `myThread?.Abort()` |
|
||||
| `StandardScene.Core/Scheduler/HeartBeatMission.cs` | 68 `_myThread?.Abort()` |
|
||||
| `StandardScene.Core/Chained/AbstractChainedDeliveryMission.cs` | 1606 `myThread.Abort()` |
|
||||
|
||||
典型代码(`AbstractLoopMission.cs:1309`,`精读确认`):
|
||||
|
||||
```1309:1334:E:\Work\Core\Simple-FR\StandardSence\StandardScene.Core\Chained\AbstractLoopMission.cs
|
||||
public virtual void StopLoop()
|
||||
{
|
||||
try
|
||||
{
|
||||
_strategyRunning = false;
|
||||
_strategyThread?.Abort();
|
||||
_strategyThread = null;
|
||||
...
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
```
|
||||
|
||||
**修复方向**:改为**协作式停止**——已有 `_strategyRunning/_logicRunning` 布尔标志,循环体应周期检查该标志退出;线程创建用 `IsBackground=true`,停止时 `flag=false` 后 `Join(timeout)`。删除所有 `Abort()`。对阻塞型循环用 `CancellationToken` + 可中断等待(`Task.Delay(token)` / `ManualResetEventSlim.Wait(token)`)替代 `Thread.Sleep`。参考 `ModbusDoorController` 的 `CancellationTokenSource` 范式。
|
||||
|
||||
---
|
||||
|
||||
### P0-2 WebApi 反射接口:无鉴权远程调用任意方法(RCE 级风险)
|
||||
|
||||
`StandardScene.Core/WebApi.cs`(`精读确认`,272–314 / 332–368)暴露:
|
||||
|
||||
```272:296:E:\Work\Core\Simple-FR\StandardSence\StandardScene.Core\WebApi.cs
|
||||
Get("/car_reflection/execute/{id}/{method}", parameters =>
|
||||
{
|
||||
...
|
||||
methodInfo = car.GetType().GetMethod((string)parameters.method);
|
||||
...
|
||||
return ExecuteMethod(methodInfo, car, queryParams);
|
||||
});
|
||||
```
|
||||
|
||||
`mission_reflection/execute/{id}/{method}` 同款。问题:**通过 HTTP 即可按名反射调用 Car/Mission 上任意 public 方法**,无身份校验、无方法白名单、无危险方法拦截。结合 `GET` 语义,配置不当会暴露在内网甚至外网,构成命令执行级攻击面。
|
||||
|
||||
**修复方向**:
|
||||
1. 最小化:加访问令牌/来源 IP 限制(中间件统一校验)。
|
||||
2. 方法白名单:仅允许标注了 `[MethodMember]`(或新增 `[WebInvokable]`)的方法被反射调用;拒绝其余。
|
||||
3. 语义化:执行类操作改 `POST`;统一错误响应封装(见 P2-2)。
|
||||
|
||||
---
|
||||
|
||||
### P0-3 实时报文解析按固定下标取值、缺长度校验(IndexOutOfRange 崩溃)
|
||||
|
||||
对比发现两条解析路径**风险等级不同**:
|
||||
|
||||
- ✅ **正确范本**:`StandardScene.Core/Charge/CommunicationMessageService.cs`(`精读确认`)在取下标前先校验 `parts.Length < 30 / < 10`(198、276 行),再访问 `bytes[28]` 等,越界已被防住。
|
||||
- ❌ **风险路径**:实时 UDP/回调路径按固定下标直接取值,未见等价长度校验(`扫描命中`):
|
||||
- `StandardScene.Core/ChargeUdpService.cs`(`message[28]` 等)
|
||||
- `StandardScene.Devices/Charge/PCBChargeStation.cs`(`OnUdpMessage` 中 `message[1]`)
|
||||
- `StandardScene.Core/ChargeStationType/MuXingChargeStation.cs`
|
||||
|
||||
设备掉线/半包/异常帧时,直接 `IndexOutOfRangeException`,且若发生在 UDP 接收线程会拖垮整条接收链路。
|
||||
|
||||
**修复方向**:所有报文解析入口统一"先校验长度(与帧头/类型匹配)再取值",越界返回 null 并 `Diagnosis.Log` 记录原始帧;抽出 `ChargeFrameParser` 复用 `CommunicationMessageService` 的安全解析。
|
||||
|
||||
---
|
||||
|
||||
## 五、P1 高危问题
|
||||
|
||||
### P1-1 配置硬编码(IP/URL/路径),多 AGV/换环境必失效(`扫描命中` 14 文件)
|
||||
|
||||
最典型(`精读确认`)`StandardScene.Protocol.VDA5050/VDACar/VDA5050Car.cs:150`:
|
||||
|
||||
```150:168:E:\Work\Core\Simple-FR\StandardSence\StandardScene.Protocol.VDA5050\VDACar\VDA5050Car.cs
|
||||
public async void GetVDA5050StateFromC()
|
||||
{
|
||||
try
|
||||
{
|
||||
//string jsonResponse1 = await hc.GetStringAsync($"http://{this.address}:8008/getStat");
|
||||
string jsonResponse1 = await hc.GetStringAsync($"http://192.168.2.1:8008/getStat");
|
||||
...
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
把按车 `this.address` 的写法**注释掉、写死 `192.168.2.1`**——多 AGV 场景必然全部打到同一地址;外加空 catch 吞错,故障"静默"。其余命中:`Kiva.cs`/`Forklift.cs`(`192.168.2.1:8008`)、`Map.cs`(`127.0.0.1:4321`)、`LoopMission.cs`(西门子 PLC IP)、`TransportDeliveryCallbacks.cs`(回调 URL)、`StandardCADTool.cs`(Windows 盘符路径)等。
|
||||
|
||||
**修复方向**:抽 `scene.json`/配置中心统一注入;禁止业务代码内联端点;`StandardCADTool` 路径改相对/可配置。
|
||||
|
||||
### P1-2 `async void` + 在其中 `throw`(CA2200 失栈)→ 进程级崩溃风险
|
||||
|
||||
- `Kiva.cs:619` `public new async void ForceStop()`(`精读确认`):`async void`,catch 内 `MessageBox.Show`(UI 耦合),最外层 `Console.WriteLine(e); throw;`——`async void` 抛出的异常无法被调用方捕获,直接打到 `SynchronizationContext`/线程池,可致进程崩溃。`new` 还隐藏基类 `ForceStop`(CS0114)。
|
||||
- `VDA5050Car.cs:240` `throw ex;`(`精读确认`)破坏原始堆栈(CA2200,全仓 4 条)。`DummyCar.cs` 同款。
|
||||
- `async void` 在 CarTypes/Charge/VDA5050/CAD 等多文件广泛存在(`扫描命中`)。
|
||||
|
||||
**修复方向**:事件处理器之外一律 `async Task`;确需 `async void` 的入口用 `try/catch` 兜底并 `Diagnosis.Log`,不得外抛;`throw ex;` → `throw;`。
|
||||
|
||||
### P1-3 空 `catch{}` 静默吞异常(`扫描命中` 10 文件)
|
||||
|
||||
`Kiva.cs`、`VDA5050Car.cs`、`FLChargeStation.cs`、`AbstractLoopMission.cs`、`Commons.cs`、`AsyncTcpClient.cs`、`LoopViewer.cs` 等。`Kiva.cs:606`、`VDA5050Car.cs:165` 为典型业务路径空 catch。
|
||||
|
||||
> 注:`AsyncTcpClient` / `ModbusDoorController` 中环绕 `Close()/Dispose()` 的空 catch 属可接受的清理兜底,应保留但加注释;业务路径空 catch 必须改为记录日志。
|
||||
|
||||
### P1-4 同步阻塞调用 `.Result/.Wait()/GetAwaiter().GetResult()`(`扫描命中` 7 文件)
|
||||
|
||||
`ButtonMission.cs`、`DoorMission.cs`、`VDA5050Car.cs` 等。`Kiva.cs:635 status.programs.task.Wait()`(`精读确认`)在 UI/线程上下文易死锁。**修复**:异步链路打通,避免 sync-over-async。
|
||||
|
||||
### P1-5 `new HttpClient()` 反复实例化(Socket 耗尽)(`扫描命中`)
|
||||
|
||||
`Forklift.cs`/`Kiva.cs`/`VDA5050Car.cs`/`WebApi.cs` 等频繁 `new HttpClient`。**修复**:单例或 `IHttpClientFactory`/`SocketsHttpHandler`(设 `PooledConnectionLifetime`)。
|
||||
|
||||
---
|
||||
|
||||
## 六、P2 结构 / 质量问题
|
||||
|
||||
### P2-1 `Commons.cs` 上帝工具类(`精读确认`)
|
||||
- `AddOrUpdateCarField/SiteField/MissionField` "ContainsKey→Remove+Add / else Add" 模板**重复 4 份**,应为 `dict[key]=value`。
|
||||
- `AddOrUpdateTag` 名为 update 实为 add(更新逻辑被注释),重复键会抛错,命名误导。
|
||||
- `CarValue`:存在 `electricCurrent` 时**忽略入参 key** 直接返回,疑似 bug。
|
||||
- `GoSite` catch 内 `Console.WriteLine + Thread.Sleep(3000)`,注释写"重新执行"但**并未真正重试**。
|
||||
- 调度 `NearestTask`(约 398–538)巨函数、深嵌套、大段 `#region ObsoleteCode` 注释代码、魔法优先级 `Priority=50`。
|
||||
|
||||
### P2-2 `WebApi.cs` 2661 行上帝文件(`精读确认`)
|
||||
错误响应 `new { Success=false, Code=500, Data="null", Message=... }` 模板**复制几十处**;路由全堆一个文件。**修复**:抽 `ApiResult.Fail/Ok` 帮助器;按资源拆分路由模块;老接口归入 `WebApi.Core(deprecated)` 并规划迁移。
|
||||
|
||||
### P2-3 日志体系不统一(`扫描命中` 22 文件 `Console.WriteLine`)
|
||||
与 `Diagnosis.Log` 混用。`ModbusDoorController` 已全程 `Diagnosis.Log`,应作为统一范式推广;禁用 `Console.WriteLine` 于业务代码。
|
||||
|
||||
### P2-4 UI 与业务耦合(`扫描命中`)
|
||||
`MessageBox.Show` 出现在 `Commons.cs`(TrafficControl.OnDeadLock)、`Kiva.cs:648`、`DoorManager.cs`、`ButtonBoxManager.cs` 等业务/管理类中。**修复**:业务层只发事件/日志,弹窗交由 UI 层(后续 migu 平台)。
|
||||
|
||||
### P2-5 `while(true)+Thread.Sleep` 忙等/阻塞(`扫描命中` 13 文件)
|
||||
`Forklift.cs`、`StandardChargeMission.cs`、`ChargeUdpService.cs`、`VDA5050Car.cs` 等。**修复**:改 `CancellationToken`+可中断等待或定时器;与 P0-1 协作式停止一并处理。
|
||||
|
||||
### P2-6 AsyncTcpClient 细节(`精读确认`)
|
||||
- `Send` 失败抛 `InvalidProgramException`(异常类型不当,应 `InvalidOperationException`/自定义)。
|
||||
- `HandleDatagramWritten` 调 `EndWrite` 无 try,写失败异常落到线程池无人观测。
|
||||
- `uint on = 1;` 未使用字段(CS0414)。
|
||||
|
||||
---
|
||||
|
||||
## 七、编译告警分类(全量,双配置合计 90 实例 ≈ 45/配置)
|
||||
|
||||
| 告警码 | 数量 | 含义 | 处置 |
|
||||
|---|---|---|---|
|
||||
| CS0108 | 16 | 隐藏继承成员未加 `new` | 显式 `new`/`override` 或改名 |
|
||||
| CS4014 | 14 | 调用未 `await`(即发即弃) | 显式 `await` 或 `_ =` 并说明 |
|
||||
| CS0414 | 14 | 私有字段赋值但从未使用 | 删除 |
|
||||
| SYSLIB0006 | 14 | `Thread.Abort` 已弃用 | 见 **P0-1** |
|
||||
| CS0105 | 6 | 重复 using | 删除 |
|
||||
| CS0162 | 6 | 不可达代码 | 清理 |
|
||||
| CS0168 | 6 | 变量声明未用 | 删除 |
|
||||
| CA2200 | 4 | `throw ex` 失栈 | 改 `throw;` |
|
||||
| CS8321 | 2 | 局部函数未用 | 删除(如 VDA5050Car `monitor()`) |
|
||||
| CS0169 | 2 | 字段从未使用 | 删除 |
|
||||
| CS0114 | 2 | 隐藏继承成员(如 ForceStop) | `override`/`new` |
|
||||
| CS0219 | 2 | 变量赋值未用 | 删除 |
|
||||
| CS0649 | 2 | 字段从未赋值 | 初始化或删除 |
|
||||
|
||||
---
|
||||
|
||||
## 八、分子系统评述(摘要,详版见架构方案)
|
||||
|
||||
- **CarTypes**:`BasicCarFields/SiteFields/TrackFields/PlanFields` 字段袋设计合理(`BasicFields.cs`,`CarLength/CarWidth` 默认 `-1` 作"未配置"哨兵,已被避障 Coder 正确利用);但具体车型(Kiva/Forklift/VDA5050Car)是巨类,混杂通信、状态、UI、调度,async void/空 catch/硬编码集中。
|
||||
- **Coders**:本轮已完成磁导航统一与避障去重(`AvoidanceParamCoder` 4 参 + `AvoidanceParamLWCoder` 2 参),结构清晰,建议继续把车型内联 Coder 收敛到 `CommonTrackCoders`。
|
||||
- **Missions**:Chained/InterLock/Scheduler 线程模型粗放(裸 `new Thread`+`Abort`+`while(true)`),状态机散落字符串 `status.status`。建议统一 `MissionRunnerBase`(CancellationToken + 状态枚举)。
|
||||
- **Charge**:解析存在"安全范本"与"风险路径"并存(见 P0-3);UDP 服务与 Mission 线程耦合。
|
||||
- **Devices(新)**:`ModbusDoorController` 优秀;唯一瑕疵是用析构函数兜底 `Disconnect`(在 GC 线程取锁+`Wait`,有风险),应实现 `IDisposable` 显式释放。
|
||||
- **VDA5050**:MQTT/HTTP 异步用法不规范(async void、throw ex、硬编码 IP、Console 日志),状态处理 `ProcessCacheAndSendOrderMessage` 内大量 Console。
|
||||
- **WebApi**:见 P0-2 / P2-2,最高优先级重构对象。
|
||||
|
||||
---
|
||||
|
||||
## 九、正面样板(建议作为团队基线)
|
||||
|
||||
1. `StandardScene.Devices/Door/ModbusDoorController.cs`:`CancellationTokenSource` 协作式停止、`lock` 线程安全、变更检测(`_lastSentControl`)、重连节流、统一 `Diagnosis.Log`、资源清理。
|
||||
2. `StandardScene.Core/TCP/AsyncTcpClient.cs`:锁保护、陈旧回调防护、自动重连。
|
||||
3. `StandardScene.Core/Charge/CommunicationMessageService.cs`:报文解析前置长度校验(安全解析范本)。
|
||||
|
||||
---
|
||||
|
||||
## 十、整改路线图
|
||||
|
||||
**第 1 批(P0,本次执行)**
|
||||
1. Thread.Abort → 协作式停止(6 文件 8 处)。
|
||||
2. WebApi 反射 execute 加来源校验 + `[MethodMember]` 白名单。
|
||||
3. 充电实时报文路径补长度校验(复用安全解析)。
|
||||
|
||||
**第 2 批(P1)**
|
||||
4. 硬编码端点配置化(VDA5050/Kiva/Forklift/Map/Loop/CAD)。
|
||||
5. `async void`→`Task`、`throw ex`→`throw`、业务空 catch 加日志。
|
||||
6. HttpClient 单例化;sync-over-async 拆解。
|
||||
|
||||
**第 3 批(P2/结构)**
|
||||
7. `ApiResult` 帮助器 + WebApi 拆分;`Commons` 拆服务、修 `CarValue`/`GoSite`/`AddOrUpdate`。
|
||||
8. 统一 `Diagnosis.Log`;UI 解耦;`MissionRunnerBase` 统一线程/状态机。
|
||||
|
||||
**第 4 批(P3 卫生)**
|
||||
9. 清 45 项告警(重复 using、未用字段、不可达代码、隐藏成员)。
|
||||
|
||||
---
|
||||
|
||||
## 附录:扫描方法
|
||||
|
||||
- 反模式:`rg` 扫描 `Thread.Abort` / `catch\s*\{\s*\}` / `throw ex;` / `async void` / `\.Result|\.Wait\(\)` / 硬编码 IP / `Console.WriteLine` / `while\s*\(\s*true\s*\)` / `new HttpClient` / `MessageBox.Show` / 魔法下标。
|
||||
- 告警:`dotnet build --no-incremental` → `_buildwarnings.txt` → 按告警码聚合计数。
|
||||
- 精读:AsyncTcpClient、Commons、CommunicationMessageService、VDA5050Car、WebApi、Kiva、AbstractLoopMission、ModbusDoorController、BasicFields 等。
|
||||
@@ -1,241 +0,0 @@
|
||||
# StandardScene 插件化拆分 — 会话14 交接摘要
|
||||
|
||||
> **用途**:供其他 AI 会话快速读取上下文,继续推进 StandardScene 大改或 SimpleLite 内核改造。
|
||||
> **工作目录**:`E:\Work\Core\Simple-FR\StandardSence`
|
||||
> **主交付物**:`StandardScene拆分计划.md`(**v2**,已落盘)
|
||||
> **上游设计**:`E:\Work\Core\Simple-FR\配置向导与导航场景插件化设计.md`(第 5 节 StandardScene 拆分、第 11.3 节 Phase C)
|
||||
> **新宿主 API 文档**:`E:\Work\Core\Simple-FR\Simple\SimpleLite\Docs\MIGU-API.md`
|
||||
> **整理时间**:2026-06-09
|
||||
|
||||
---
|
||||
|
||||
## 1. 本会话做了什么
|
||||
|
||||
| 阶段 | 内容 | 状态 |
|
||||
|---|---|---|
|
||||
| 启动 | 用户要求用 wuxianChat 建立「会话14」 | 曾尝试 MCP,一度因执行后端不可用失败 |
|
||||
| 精读代码 | 对 `StandardSence` 约 110 个 `.cs` 做逐模块走查(车型、Mission、设备、WebApi、VDA5050 等) | ✅ 完成 |
|
||||
| 产出 v1 计划 | 写入 `StandardScene拆分计划.md`:目录规划、功能归属矩阵、抽离清单、技术难点、分阶段路线 | ✅ 完成 |
|
||||
| 用户评审反馈 | 宿主改 SimpleLite、net8.0、设备独立 dll+热插拔、删除 FactoryTest/松灵、WebApi 暂留后期废弃 | ✅ 已纳入 |
|
||||
| 阅读 SimpleLite | 确认 `SimpleLite.csproj` = net8.0 / CycleGUI / EmbedIO;阅读 MIGU-API 做 WebApi 迁移索引 | ✅ 完成 |
|
||||
| 产出 v2 计划 | 全文更新 `StandardScene拆分计划.md` | ✅ 完成 |
|
||||
| 代码改动 | **无**(本会话仅分析与文档,未改业务代码) | — |
|
||||
|
||||
---
|
||||
|
||||
## 2. 用户原始诉求(按时间)
|
||||
|
||||
### 2.1 第一轮
|
||||
|
||||
> 整个仓库需要大改。参考 `配置向导与导航场景插件化设计.md` 中关于 StandardScene 的「导航场景插件化」部分;精细化读当前代码,抽离标准功能,做好仓库目录规划与功能规划,**先把 plan 做出来**到 StandardScene 本地 md 文件。
|
||||
|
||||
### 2.2 第二轮(评审反馈,已写入 v2)
|
||||
|
||||
1. **设备驱动**需要独立 dll,并且**支持热卸载和加载**。
|
||||
2. StandardScene 依赖 **`SimpleLite.dll`**,老 **`SimpleComposer` 废弃**(**另一个 AI 对话窗口**正在改相关引用)。
|
||||
3. **目标框架统一 `net8.0`**(WinForms 场景需 `net8.0-windows` 的说明在评审中被修正:SimpleLite 本身是纯 `net8.0` + CycleGUI,故 StandardScene 应**去 WinForms**)。
|
||||
4. **`FactoryTest`、松灵残留**不纳入 Core,**直接删除**。
|
||||
5. **`WebApi.cs`** 是针对老平台的 Nancy 接口:整理后**暂时保留在 Core**,后期废弃,改用 SimpleLite 接口能力(MIGU-API.md);**此版本先保留**。
|
||||
|
||||
### 2.3 第三轮
|
||||
|
||||
> 把以上会话整理成 md 文件,分享给另一个会话读取。
|
||||
> → 即本文档。
|
||||
|
||||
---
|
||||
|
||||
## 3. 核心结论(另一会话必须知道的)
|
||||
|
||||
### 3.1 架构修正:导航 ≠ 车型
|
||||
|
||||
上游设计隐含「按车型/导航整包切 dll」,**与代码事实不符**:
|
||||
|
||||
- 导航由 **轨道/站点 fields + TrackCoder** 决定,与车型**正交**。
|
||||
- 例:`Kiva` 同一类上同时挂 **磁导航 coder**、**二维码 coder**、**激光避障 coder**(非 SLAM 定位)。
|
||||
- **正确拆法**:`StandardScene.Core` 保留车型本体 + 任务/充电/互锁等;导航能力抽到 `Magnetic` / `QrCode` / `Laser` 等 dll,以**可插拔 coder** 形式挂载。
|
||||
|
||||
### 3.2 激光:避障 vs 导航定位
|
||||
|
||||
| 能力 | 代表 | 归属 |
|
||||
|---|---|---|
|
||||
| 激光避障 | `LidarArea`、`SwitchLidarArea`、各车型通用 | **Core** |
|
||||
| 激光 SLAM 定位/地图 | `LidarMap`、`getLidarMap`、拉 `127.0.0.1:4321` | **Laser dll** |
|
||||
|
||||
### 3.3 三个正交维度
|
||||
|
||||
1. **导航**:磁 / 二维码 / 激光(SLAM)
|
||||
2. **车型**:Kiva、叉车、多舵轮顶升、机械臂、仿真车等
|
||||
3. **设备驱动**:充电桩(FL/MuXing/PCB)、门(Modbus)、按钮盒(Leeg/Azowie)
|
||||
|
||||
设备驱动必须 **独立 `Devices.*` dll + 热插拔**(SimpleLite `/plugins` + collectible ALC)。
|
||||
|
||||
### 3.4 VDA5050
|
||||
|
||||
- `CarTypes/VDACar/` 是完整 **MQTT 协议栈**,建议独立 `StandardScene.Protocol.VDA5050` dll。
|
||||
- 耦合点:`ArmCar` 引用 `VDA5050SiteField`,拆分时需解耦。
|
||||
|
||||
### 3.5 宿主与框架(v2 已定)
|
||||
|
||||
| 项 | 旧 | 新 |
|
||||
|---|---|---|
|
||||
| 宿主 | `SimpleComposer.exe` (.NET 4.8) | **`SimpleLite`** (net8.0) |
|
||||
| 契约 | `RefSimpleCore.dll` + Composer 程序集 | **`SimpleCore` + SimpleLite** |
|
||||
| UI | WinForms(大量窗体) | **CycleGUI / 平台 Web**(去 WinForms) |
|
||||
| 对外 API | `WebApi.cs` (Nancy 1.4.5) | 暂留 Core;后期 **EmbedIO**(MIGU-API) |
|
||||
|
||||
**SimpleLite 事实**(来自 `SimpleLite.csproj`):`TargetFramework=net8.0`,`OutputType=Exe`,UI 用 CycleGUI,WebApi 用 EmbedIO,引用 `SimpleCore`。
|
||||
|
||||
### 3.6 老 WebApi 处置
|
||||
|
||||
- `WebApi.cs` ~123KB,40+ 端点,**与导航弱相关**(导航相关主要是 `QrMap`、`getLidarMap`)。
|
||||
- **本版**:整理为 `WebApi.Core.cs` 暂留 Core,标 `[Obsolete]` / deprecated。
|
||||
- **后期**:按 `StandardScene拆分计划.md` §4.7 映射到 SimpleLite `/api/sl/projection/*`。
|
||||
- **缺口**:二维码地图下发、激光 SLAM 取图在 MIGU-API **暂无直接对应**,迁移前需在 SimpleLite 或场景插件侧补接口。
|
||||
|
||||
### 3.7 必须删除的文件(不进 Core/Customer)
|
||||
|
||||
| 文件 | 原因 |
|
||||
|---|---|
|
||||
| `FactoryTest.cs` | 产测专用 |
|
||||
| 根目录 `SongLingDeliveryViewer.Designer.cs` | 松灵客户残留,无主文件、未编入 csproj |
|
||||
| 根目录 `MultiWheelLifterCar.cs` | 与 `CarTypes/MultiWheelLifterCar.cs` 重名死文件,未编入 csproj |
|
||||
| `CarTypes/UselessCar.cs` | `[CarType]` 已注释,测试残留 |
|
||||
|
||||
---
|
||||
|
||||
## 4. 目标 dll 结构(摘要)
|
||||
|
||||
```
|
||||
StandardScene.Core/ net8.0 基座(alwaysLoad)
|
||||
StandardScene.Magnetic/ 磁导航
|
||||
StandardScene.QrCode/ 二维码导航 + SyncQrMap
|
||||
StandardScene.Laser/ SLAM / LidarMap
|
||||
StandardScene.Protocol.VDA5050/ VDA5050 MQTT 栈
|
||||
StandardScene.Devices.Charge/ 充电桩驱动(热插拔)
|
||||
StandardScene.Devices.Door/ 门控驱动(热插拔)
|
||||
StandardScene.Devices.ButtonBox/ 按钮盒驱动(热插拔)
|
||||
```
|
||||
|
||||
各可激活导航/设备 dll 配 `scene.json`,对接 SimpleLite `POST /api/sl/projection/scenes/apply` 与 `active-scenes.json`。
|
||||
|
||||
**详细文件级归属矩阵**见 `StandardScene拆分计划.md` §4,勿在本摘要重复展开。
|
||||
|
||||
---
|
||||
|
||||
## 5. 可抽离的重复代码(C0 优先)
|
||||
|
||||
| 重复项 | 现状位置 | 目标 |
|
||||
|---|---|---|
|
||||
| 磁循迹器 | `Kiva.AllCarMagTrackCoder` + `MultiWheelLifterCar.MagTrackCoder` | 合并为 `Magnetic.MagneticTrackCoder` |
|
||||
| `newReset` / `newTrafficReset` | Kiva、Forklift、MultiWheelLifterCar 各一份 | 上提 `StandardCarBase` |
|
||||
| `SetDisplayInfo`、远程急停/复位 HTTP | 多车型雷同 | Core 基类默认实现 |
|
||||
| `GetCarStatus` | Commons 与 MultiWheelForkLifter 各一份 | 统一 Commons |
|
||||
| 避障/IO/纠偏 coder 模板 | 各车型粘贴 | Core 通用 coder 模板集 |
|
||||
|
||||
---
|
||||
|
||||
## 6. 技术难点(需与内核会话协同)
|
||||
|
||||
### 6.1 TrackCoder 运行期注册(最关键)
|
||||
|
||||
- **现状**:`[TemplateTrackCoderSettings]` / `[ProgramTrackCoderSettings]` **编译期硬绑定**在车型类上。
|
||||
- **目标**:导航 dll 加载时为车型**注册** coder,卸载时移除。
|
||||
- **方案**:优先在 **`SimpleCore` 增加 coder 注册表**;导航 dll 在 `INavigationProfile.OnActivate` 注册。
|
||||
- **兜底**:Core 暂保留全量 coder,导航 dll 只承载 API/地图/清单(保证不回归)。
|
||||
|
||||
### 6.2 net4.8 → net8.0 + 去 WinForms
|
||||
|
||||
- 引用从 `SimpleComposer.exe` 切到 `SimpleLite` / `SimpleCore`。
|
||||
- 命名空间 `SimpleComposer.RCS` → `SimpleLite.RCS` 等(**另一会话进行中**)。
|
||||
- 所有 WinForms 窗体迁 **CycleGUI** 或平台 Web;`MessageBox` 改 CycleGUI 弹窗。
|
||||
- 涉及窗体:`DeliveryViewer`、`LoopViewer`、`TrafficInterlockViewer`、`VehicleMonitor`、各 `Charge/*Form`、`DoorMonitor`、`ButtonBoxManager`、`VDACar/TextViewer` 等。
|
||||
|
||||
### 6.3 热插拔清理
|
||||
|
||||
设备/导航 dll 卸载前须 `OnDeactivate`:清理 coder 注册、MQTT/TCP 连接、全局回调;配合 collectible ALC,确保无存活 `Car`/`Mission` 实例。
|
||||
|
||||
---
|
||||
|
||||
## 7. 分阶段实施(当前均未启动代码)
|
||||
|
||||
| 阶段 | 内容 | 状态 |
|
||||
|---|---|---|
|
||||
| **C0** | 删除残留 + 去重沉淀 + 命名空间收敛 + 与 SimpleCore 内核对齐 | ⬜ 待启动 |
|
||||
| **C1** | 新建 `StandardScene.Core`(net8.0),换宿主,去 WinForms,整理 WebApi.Core | ⬜ |
|
||||
| **C2** | Magnetic / QrCode / Laser 三导航 dll | ⬜ |
|
||||
| **C3** | Devices.Charge / Door / ButtonBox(热插拔) | ⬜ |
|
||||
| **C4** | Protocol.VDA5050 | ⬜ |
|
||||
| **C5** | 联调 + WebApi 迁 SimpleLite + 文档收尾 | ⬜ |
|
||||
|
||||
---
|
||||
|
||||
## 8. 待确认项(评审未拍板)
|
||||
|
||||
1. **SimpleCore 是否提供 TrackCoder 运行期注册 API**?(决定 C2 用注册表还是兜底方案)
|
||||
2. **UI 迁移节奏**:一次性迁 CycleGUI,还是按 dll 分批;哪些仅保留平台 Web?
|
||||
3. **VDA5050** 是否确认独立 dll?(计划推荐独立)
|
||||
4. **二维码地图下发 / 激光 SLAM 取图** 在 SimpleLite 侧由谁补接口、何时补?
|
||||
5. 是否存在**车型仅支持单一导航**的硬约束?
|
||||
|
||||
---
|
||||
|
||||
## 9. 跨会话分工(重要)
|
||||
|
||||
| 会话/方向 | 负责内容 | 与本计划关系 |
|
||||
|---|---|---|
|
||||
| **本会话(会话14)** | StandardScene 代码精读 + 拆分计划 v1/v2 | 主文档已落盘 |
|
||||
| **另一 AI 会话(用户提及)** | SimpleLite / SimpleCore 引用改造、`SimpleComposer` → `SimpleLite` 命名空间与程序集引用 | StandardScene C1 依赖其进度 |
|
||||
| **平台 / 配置向导** | `WizardController`、`data/config-deployment.json` | 通过 `/scenes/apply` 驱动插件加载 |
|
||||
|
||||
**协作接口建议**:
|
||||
|
||||
- StandardScene 侧等待:SimpleCore 的 **coder 注册表**、**插件生命周期**(OnActivate/OnDeactivate)、**Collectible ALC** 约定。
|
||||
- 内核侧等待:StandardScene 的 **scene.json 清单**、**NavKind 枚举**、各 dll 的 **provides** 字段(见拆分计划 §9)。
|
||||
|
||||
---
|
||||
|
||||
## 10. 关键文件索引
|
||||
|
||||
| 路径 | 说明 |
|
||||
|---|---|
|
||||
| `E:\Work\Core\Simple-FR\StandardSence\StandardScene拆分计划.md` | **主计划(v2)**,含归属矩阵、§4.7 WebApi 迁移表、分阶段、风险 |
|
||||
| `E:\Work\Core\Simple-FR\StandardSence\StandardScene会话14交接摘要.md` | **本文档**,会话级交接 |
|
||||
| `E:\Work\Core\Simple-FR\配置向导与导航场景插件化设计.md` | 上游框架设计 |
|
||||
| `E:\Work\Core\Simple-FR\Simple\SimpleLite\Docs\MIGU-API.md` | SimpleLite 新 API(WebApi 迁移目标) |
|
||||
| `E:\Work\Core\Simple-FR\Simple\SimpleLite\SimpleLite.csproj` | 宿主:net8.0 / CycleGUI / EmbedIO |
|
||||
| `E:\Work\Core\Simple-FR\StandardSence\StandardScene.csproj` | 现状:net4.8 单体插件 |
|
||||
| `E:\Work\Core\Simple-FR\StandardSence\Commons.cs` | 插件契约、`CustomOperationsBeforeLoading`、`NoReflectionApi` |
|
||||
| `E:\Work\Core\Simple-FR\StandardSence\WebApi.cs` | 老 Nancy API(待 deprecated) |
|
||||
| `E:\Work\Core\Simple-FR\StandardSence\CarTypes\Kiva.cs` | 导航与车型耦合的典型样本 |
|
||||
| `E:\Work\Core\Simple-FR\StandardSence\CarTypes\BasicFields.cs` | `TagValue` 等轨道/站点字段定义 |
|
||||
|
||||
---
|
||||
|
||||
## 11. 给下一会话的推荐起手式
|
||||
|
||||
若继续 **StandardScene 实施**:
|
||||
|
||||
1. 先读 `StandardScene拆分计划.md` §0(TL;DR)和 §4(归属矩阵)。
|
||||
2. 从 **C0** 开始:删除 §3.7 所列文件;合并磁循迹器;提取 `StandardCarBase`;`AMRScene1` 命名空间收敛。
|
||||
3. 与 **SimpleLite 改造会话**对齐:引用是否已可编译、coder 注册表 API 是否就绪。
|
||||
4. **不要**在未确认前大规模改 WebApi 或 WinForms——C1 才系统性处理。
|
||||
|
||||
若继续 **SimpleLite / SimpleCore 改造**:
|
||||
|
||||
1. 优先落实 **TrackCoder 运行期注册**、**插件 OnActivate/OnDeactivate**、**/plugins 热卸载** 与 StandardScene 计划 §7 对齐。
|
||||
2. 评估 MIGU-API 是否需补 **QrMap**、**getLidarMap** 等价端点。
|
||||
3. 确认 `SimpleComposer.RCS` → `SimpleLite.RCS` 迁移范围,避免 StandardScene 引用断裂。
|
||||
|
||||
---
|
||||
|
||||
## 12. 变更记录
|
||||
|
||||
| 版本 | 日期 | 说明 |
|
||||
|---|---|---|
|
||||
| v1 | 2026-06-09 | 初版拆分计划(代码精读结论) |
|
||||
| v2 | 2026-06-09 | 纳入 SimpleLite/net8.0/去 WinForms、设备热插拔、删除项、WebApi 暂留+迁移索引 |
|
||||
| 交接摘要 | 2026-06-09 | 本会话整理,供跨会话阅读 |
|
||||
|
||||
---
|
||||
|
||||
*本文档为会话级摘要;实施细节、完整类级归属与 WebApi 端点映射以 `StandardScene拆分计划.md` 为准。*
|
||||
@@ -1,267 +0,0 @@
|
||||
# StandardScene 架构重构方案
|
||||
|
||||
> 配套文档:《StandardScene代码审查报告.md》(质量/缺陷)、《StandardScene拆分计划.md》(拆分进度)
|
||||
> 本文聚焦**结构与架构合理性**:现状全景 → 逐模块深度分析 → 目标分层 → 程序集边界 → net8.0 去 Windows → 分阶段迁移路径。
|
||||
> 日期:2026-06-09
|
||||
|
||||
---
|
||||
|
||||
## 一、现状架构全景
|
||||
|
||||
### 1.1 程序集与依赖
|
||||
|
||||
| 程序集 | AssemblyName | TFM | 角色 | 依赖 |
|
||||
|---|---|---|---|---|
|
||||
| StandardScene.Core | `StandardScene` | net8.0-windows | 基座:抽象+业务+任务+调度+WebApi+UI | SimpleLite/SimpleCore/CommonUsage/MDCSToolBox/Topaz + NuGet(MQTTnet/Nancy/Jint/EasyModbus/IoTClient/OpenXml/Newtonsoft) |
|
||||
| StandardScene.Devices | `StandardScene.Devices` | net8.0-windows | 具体设备驱动(门/充电桩/按钮盒) | →Core + SimpleLite/SimpleCore/Topaz + leegKeys-sdk |
|
||||
| StandardScene.Protocol.VDA5050 | `StandardScene.Protocol.VDA5050` | net8.0-windows | VDA5050 协议车型 | →Core + SimpleLite/SimpleCore + NuGet(MQTTnet/Nancy/Jint/Newtonsoft) |
|
||||
|
||||
插件清单 `scene.json`(Devices / VDA5050 各一份):
|
||||
|
||||
```json
|
||||
{ "id":"devices", "assembly":"StandardScene.Devices.dll", "requiresCore":"StandardScene.dll",
|
||||
"provides": { "doorControllers":[...], "chargeStations":[...], "buttonBoxes":[...] } }
|
||||
```
|
||||
|
||||
发现机制:`SimpleLite.Utils.UiTypeDiscovery.AllTypes()` 跨程序集扫描 + 类型特性(`[DoorType]`/`[ChargeType]`/`[ButtonBox...]`/`[CarType]`)。
|
||||
|
||||
### 1.2 当前依赖方向(问题版)
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
Devices --> Core
|
||||
VDA5050 --> Core
|
||||
Core -->|NuGet| MQTTnet
|
||||
Core -->|NuGet| Nancy
|
||||
Core -->|NuGet| EasyModbus
|
||||
Core -->|NuGet| IoTClient
|
||||
Core -->|NuGet| OpenXml
|
||||
Core --> WinForms[WinForms 12+ 窗体]
|
||||
Core --> SimpleLite
|
||||
subgraph 卫星
|
||||
Devices
|
||||
VDA5050
|
||||
end
|
||||
```
|
||||
|
||||
**核心结构问题**:Core 是"万能基座"——既是抽象基座,又塞满了具体协议依赖(MQTT 属 VDA5050、Modbus/IoTClient 属设备、Nancy 属 WebApi、OpenXml 属报表),还内置 12+ WinForms 窗体。卫星只能依赖这个臃肿 Core,无法独立演进。
|
||||
|
||||
### 1.3 体量分布(非 Designer,前列)
|
||||
|
||||
WebApi 2686 / AbstractLoopMission 1858 / VehicleMonitor 1502(UI) / AbstractChainedDeliveryMission 1407(已 Compile Remove) / ChainedDeliveryMission 1365 / ChargeStationManagementForm 1250(UI) / AbstractChargeLogicMission 1143 / VDA5050Car 938 / DoorMission 932 / ButtonBoxManager 913 / DoorManager 848 / Kiva 827 / ButtonMission 743 / StandardChargeMission 671 / Commons 653 …
|
||||
|
||||
> 12+ 个 600~2700 行巨类,是可维护性的主要矛盾。
|
||||
|
||||
---
|
||||
|
||||
## 二、结构性问题诊断(按影响排序)
|
||||
|
||||
| # | 问题 | 证据 | 影响 |
|
||||
|---|---|---|---|
|
||||
| S1 | **分层污染**:Core 背负协议/设备/报表专有依赖 | Core.csproj 引 MQTTnet/EasyModbus/IoTClient/Nancy/OpenXml | 卫星无法瘦身;Core 编译/部署重;职责不清 |
|
||||
| S2 | **WinForms 全模块渗透**,阻塞纯 net8.0 | 12+ `*.Designer.cs`(Charge 4 个 Form、VehicleMonitor、各 Manager/Viewer);三 csproj 均 `UseWindowsForms=true` + `net8.0-windows` | 无法 `net8.0` 跨平台/瘦运行;与"UI 迁 migu"目标冲突 |
|
||||
| S3 | **God-class 泛滥** | WebApi 2686 / AbstractLoopMission 1858 / ChainedDeliveryMission 1365 / AbstractChargeLogicMission 1143 … | 改动风险高、测试困难、并发态难推理 |
|
||||
| S4 | **充电子系统未独立**,却已自成体系(20 文件) | `Charge/` 任务+站点+配置+数据服务+通信+4 表单 | 应为独立卫星,却深埋 Core |
|
||||
| S5 | **抽象与实现同居 Core** | 设备基类/特性/Mission/Manager 在 Core,仅具体驱动在 Devices | 卫星仍强依赖 Core 内部;热插拔受限 |
|
||||
| S6 | **干净抽象反向耦合到巨类** | `Loop/ILoopRules.cs` 顶部 `using static AbstractLoopMission;`(依赖其嵌套 `LoopPoint`) | 好接口被巨类绑架,无法独立复用 |
|
||||
| S7 | **全局可变静态** | `DeliveryCallbackRegistry`(static 字典)、`Commons` 静态工具、各 `static HttpClient` | 隐式耦合、测试隔离难、生命周期不可控 |
|
||||
| S8 | **内核 Coder 注册表限定本程序集反射** | `ProgramCoderSet`/`SegmentPlan.Coder` 特性驱动、按内核程序集反射 | 导航类卫星(磁/二维码/激光)无法热插拔(C2 阻塞) |
|
||||
| S9 | **构建可移植性差** | csproj 多处绝对 `HintPath`(E:\Work…、D:\MDCS…) | 换机/CI 无法直接构建 |
|
||||
| S10 | **死文件/弃用并存** | `AbstractChainedDeliveryMission.cs`(1407) 被 `Compile Remove` 仍在树 | 认知噪音、误改风险 |
|
||||
| S11 | **命名空间与程序集名不一致** | 三程序集 `RootNamespace=StandardScene`;类型散落 `StandardScene.*` 子命名空间 | 物理边界与逻辑边界错位,难判断"谁属于谁" |
|
||||
|
||||
---
|
||||
|
||||
## 三、逐模块深度分析
|
||||
|
||||
> 每个模块:**职责 / 结构 / 依赖与耦合 / 主要问题 / 目标处置**。
|
||||
|
||||
### M1 CarTypes(车型族,11 文件)
|
||||
- **职责**:定义各 AGV 车型(Kiva 827、Forklift 398、MultiWheel*、MultiVehicle、DualLifting、ArmCar、DummyCar 666)+ 字段袋 `BasicFields`(50) + 车辆监控 UI `VehicleMonitor`(1502, WinForms)。
|
||||
- **结构**:字段袋 `BasicCarFields/SiteFields/TrackFields/PlanFields` 设计合理(`-1` 哨兵语义被 Coder 正确利用);车型类承载通信(HttpClient)、状态机、调度、UI、强制控制等多职责。
|
||||
- **耦合**:车型直引 `Commons`、HttpClient、`MessageBox`、内核类型;`VehicleMonitor` 把 UI 与车辆模型绑定。
|
||||
- **问题**:巨类(Kiva 827)、`async void`+`throw`(Kiva.ForceStop 619/659)、空 catch、硬编码 IP(192.168.2.1:8008)。
|
||||
- **目标处置**:车型保留在领域层;剥离"通信/HTTP/UI"为协作者(`ICarTransport`/`ICarStatusView`);`VehicleMonitor` 进 UI 程序集。
|
||||
|
||||
### M2 Coders(轨迹编码器,1 文件 + 车型内联)
|
||||
- **职责**:`CommonTrackCoders` 通用 `ITrackCoder`(磁导航统一 `MagneticTrackCoder`、避障 `AvoidanceParamCoder`4参/`AvoidanceParamLWCoder`2参)。
|
||||
- **结构**:本轮已去重、结构清晰;`CommonTemplateTrackCoder` 提供 `SiteFieldsType` 扩展点。
|
||||
- **耦合**:受内核 `ProgramTrackCoderSettings`/`TemplateTrackCoderSettings` 特性约束(S8)。
|
||||
- **目标处置**:继续把车型内联 Coder 收敛至此;待内核放开注册表后,导航类 Coder 可下沉到导航卫星。
|
||||
|
||||
### M3 Chained(链式/循环任务族,12+2 文件)
|
||||
- **职责**:`AbstractLoopMission`(1858)、`ChainedDeliveryMission`(1365)、`TransportMission`(549)、`LoopMission`、回调注册表/附着器、`Loop/` 规则接口、若干 Viewer(UI)。
|
||||
- **结构**:**两面性**——`Loop/ILoopRules`(IEnter/IExit/IJoin/IBranch/ITaskStrategy) 与 `DeliveryCallbackRegistry` 是规范的策略/注册表模式(亮点);但 `AbstractLoopMission` 是 1858 行巨类,且接口 `using static AbstractLoopMission`(S6)反向耦合其嵌套类型。
|
||||
- **耦合**:裸 `new Thread`+`while`+`Thread.Sleep`(P0-1 已改协作式停止);UI Viewer 混入。
|
||||
- **问题**:巨类、状态用字符串 `status.status`、`AbstractChainedDeliveryMission`(1407) 死文件(S10)。
|
||||
- **目标处置**:把 `LoopPoint/LoopTask` 等领域模型从巨类**上提**到 Model/Abstractions,让 `Loop` 接口独立;巨类按"调度循环/任务编排/显示"拆分;删除/归档死文件。
|
||||
|
||||
### M4 InterLock(互锁,4 文件)
|
||||
- **职责**:`AbstractInterlockMission`(367)、`TrafficInterlockMission`、Viewer(UI)。交通互锁逻辑。
|
||||
- **耦合**:与调度/交通控制耦合;含 Viewer。
|
||||
- **目标处置**:归入"交通/调度"领域子模块;UI 外提。
|
||||
|
||||
### M5 Scheduler(调度后台任务,4 文件)
|
||||
- **职责**:`HeartBeatMission`、`NodeIsEnableMission`、`SecuritySignalMission`、`RegionalTrafficControlMission`(377)。周期性后台任务(心跳/站点禁用/安全信号上传/区域交通)。
|
||||
- **耦合**:直发 HTTP(硬编码端点)、`Console.WriteLine`、原 `while(true)`+`Thread.Abort`(P0-1 已修为 `while(started)`/协作式)。
|
||||
- **问题**:每个任务各写一套线程循环(重复),无统一基类。
|
||||
- **目标处置**:抽 `MissionRunnerBase`(CancellationToken + 状态枚举 + 统一日志),所有周期任务复用。
|
||||
|
||||
### M6 Charge(充电子系统,20 文件 + ChargeStationType)★需独立
|
||||
- **职责**:任务(`StandardChargeMission`671/`AbstractChargeLogicMission`1143)、站点(`AbstractChargeStation`/`ChargeStation`)、配置(`ChargeStrategyConfig`/`AlarmConfig`/`ChargingSetting`)、数据服务(3 个 *DataService)、通信(`ChargeUdpService`/`CommunicationMessage(Service)`)、Helper、**4 个 WinForms 表单**。
|
||||
- **结构**:自成完整子系统(任务+设备+配置+持久化+通信+UI),但全埋 Core。`CommunicationMessageService` 是**安全解析范本**(先校验长度)。
|
||||
- **耦合**:与具体充电桩驱动(Devices/Charge)双向(Core 持任务/抽象,Devices 持 PCB/FL/MuXing 驱动);实时 UDP 路径越界(P0-3 已修)。
|
||||
- **目标处置**:升级为**独立卫星 `StandardScene.Charge`**(含 Mission/抽象/配置/通信),具体桩驱动留 `Devices` 或并入;4 表单进 UI 程序集;通过 Abstractions 与 Core 解耦。
|
||||
|
||||
### M7 ExtendDevice + Devices(设备:门/按钮盒,Core 侧 13 + Devices 侧 6)
|
||||
- **职责**:Core 侧 = 基类(`BasicDoorController`/`BasicButtonBox`)+特性(`DoorTypeAttribute` 等)+管理器(`DoorManager`848/`ButtonBoxManager`913, 含 UI)+任务(`DoorMission`932/`ButtonMission`743)+模型/Monitor(UI);Devices 侧 = 具体驱动(`ModbusDoorController`/`Azowie`/`Leeg`/3 充电桩)。
|
||||
- **结构**:**新驱动质量优秀**(`ModbusDoorController`:CTS 协作停止/锁/变更检测/重连节流/统一日志);`DoorTypeAttribute` 特性发现规范。
|
||||
- **耦合**:抽象+业务在 Core、驱动在 Devices(S5);Manager 含 WinForms。
|
||||
- **问题**:`ModbusDoorController` 用析构函数兜底 `Disconnect`(GC 线程取锁+Wait,风险)→应实现 `IDisposable`;Manager 巨类含 UI。
|
||||
- **目标处置**:把设备**抽象+特性**下沉到 `StandardScene.Abstractions`;Manager 拆"设备生命周期服务 + UI";驱动统一 `IDisposable`。
|
||||
|
||||
### M8 Protocol.VDA5050(协议卫星,9 文件)
|
||||
- **职责**:`VDA5050Car`(938)、`MasterMQTTCommunication`、`VDA5050Interface/Segment/Helper/Commons`、`VDA5050WebApi`、`TextViewer`(UI)。
|
||||
- **结构**:已是独立卫星(好);自带 WebApi 与 MQTT 栈。
|
||||
- **问题**:`async void`+`throw ex`(240)、硬编码 `192.168.2.1:8008` 且注释掉按车地址(150)、空 catch、`Console.WriteLine` 满布、`monitor()` 死局部函数。
|
||||
- **目标处置**:作为协议卫星范本;端点配置化(按车 `address`)、异步规范化、日志统一、`VDA5050WebApi` 与 Core WebApi 走统一 `ApiResult`/路由约定。
|
||||
|
||||
### M9 WebApi(老 Nancy 接口,2686 行)★最高优先重构
|
||||
- **职责**:车辆/任务/地图/配置等 HTTP 接口(Nancy 2.0)。
|
||||
- **结构**:单文件上帝路由;错误响应 `new{Success=false,Code=500,...}` 复制几十处;反射 execute 端点(P0-2 已加白名单)。
|
||||
- **目标处置**:抽 `ApiResult.Ok/Fail` + 按资源拆模块(CarApi/MissionApi/MapApi…);统一鉴权中间件(来源/令牌,配置驱动);老接口归 `WebApi.Core(deprecated)` 规划迁移;最终独立 `StandardScene.WebApi` 程序集(隔离 Nancy 依赖)。
|
||||
|
||||
### M10 Model(领域/配置模型,12 文件)
|
||||
- **职责**:`Map/SimpleMap/MapStructure`、`SimpleConfig`、`TaskModel/LoopTask/MissionState`、`VehicleStatus`、各 `*Setting`。
|
||||
- **结构**:领域模型与配置混居;`Map.cs` 含硬编码 `127.0.0.1:4321`。
|
||||
- **目标处置**:拆"纯领域模型(→Abstractions)"与"配置(→Configuration)";端点配置化。
|
||||
|
||||
### M11 基础设施(TCP 5 / Utils 4 / CommonTools 2)
|
||||
- **职责**:`AsyncTcpClient`(432, 质量较好)+事件args;`JsonParser/JsonTool/ModbusClass/WebAPIHelper`;`AtomicFileUpdateHelper/SnowflakeIdGenerator`。
|
||||
- **问题**:`AsyncTcpClient.Send` 抛 `InvalidProgramException`(类型不当)、`EndWrite` 无异常处理、`uint on` 未用;`ModbusClass` 与设备 Modbus 重复关注点。
|
||||
- **目标处置**:归入 `StandardScene.Infrastructure`(net8.0 纯净,无 Windows);TCP/IO/序列化/ID 通用化。
|
||||
|
||||
### M12 Core 根 God-files(Commons 653 / Heuristic / LadderLogic / StandardCADTool / WebApi)
|
||||
- **职责**:`Commons` 万能工具+调度(`NearestTask`)、`Heuristic` 启发式、`LadderLogic` 梯形逻辑、`StandardCADTool` CAD(硬编码盘符路径、`async void`)。
|
||||
- **问题**:`Commons.AddOrUpdateXxxField` 重复 4 份、`CarValue` 忽略 key、`GoSite` 假重试(详见审查报告 P2-1);根目录堆放无归属大文件。
|
||||
- **目标处置**:`Commons` 按职责拆(字段服务/调度服务/控制台辅助);`StandardCADTool` 路径配置化、归 CAD 子模块。
|
||||
|
||||
---
|
||||
|
||||
## 四、目标架构
|
||||
|
||||
### 4.1 分层与程序集边界(目标)
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
subgraph L0[抽象层 net8.0 纯净]
|
||||
Abstractions[StandardScene.Abstractions<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.WebApi(Nancy 隔离)]
|
||||
UI[StandardScene.UI.WinForms(临时·弃用,待 migu)]
|
||||
end
|
||||
Core2 --> Abstractions
|
||||
Core2 --> Infra
|
||||
Core2 --> Config
|
||||
Charge2 --> Abstractions
|
||||
Devices2 --> Abstractions
|
||||
VDA --> Abstractions
|
||||
Nav --> Abstractions
|
||||
Charge2 -. 受限 .-> Core2
|
||||
Web --> Core2
|
||||
UI --> Core2
|
||||
Devices2 -. NuGet .-> Modbus
|
||||
VDA -. NuGet .-> MQTT
|
||||
Web -. NuGet .-> Nancy
|
||||
```
|
||||
|
||||
**关键规则**:
|
||||
1. **依赖只向下**:卫星/宿主 → Abstractions(+受限 Core);**Core 不得依赖任何具体协议/设备 NuGet**(MQTT/Modbus/IoTClient/OpenXml/Nancy 全部下放到对应卫星/宿主)。
|
||||
2. **抽象先行**:接口、注册特性、字段袋、纯领域模型、`ITrackCoder`/设备契约统一进 `Abstractions`(net8.0,无 Windows),卫星只认 Abstractions。
|
||||
3. **UI 与协议 = 边缘**:WinForms 全部收口到 `UI.WinForms`(标 deprecated,仅过渡,迁 migu 后删);Nancy 收口到 `WebApi`。如此 Abstractions/Infrastructure/Core/卫星可去 `-windows`,回到纯 `net8.0`。
|
||||
4. **发现统一**:设备/车型/协议统一"`[XxxType]` 特性 + `UiTypeDiscovery.AllTypes()`",`scene.json` 声明 `provides`。
|
||||
|
||||
### 4.2 边界接口(最小集)
|
||||
- `ILogger`(取代 `Console.WriteLine`/直连 Diagnosis):业务只依赖抽象。
|
||||
- `IDeviceDriver`/`IDoorController`/`IChargeStation`/`IButtonBox`(下沉 Abstractions),`IDisposable` 释放。
|
||||
- `MissionRunnerBase`(CancellationToken + 状态枚举 + 统一异常/日志):统一所有后台任务线程模型。
|
||||
- `ApiResult<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/S9(WebApi)。
|
||||
- **H 巨类拆分**:AbstractLoopMission/ChainedDeliveryMission/AbstractChargeLogicMission/Kiva 按职责拆分。解 S3。
|
||||
- **I 切 net8.0 + 清死文件/绝对路径**:TFM 收敛;删 `AbstractChainedDeliveryMission` 等死文件;`HintPath` 改相对/包变量。解 S2/S9/S10。
|
||||
- **J 导航卫星(依赖内核)**:待内核放开 Coder 注册表(S8),抽磁/二维码/激光导航卫星。
|
||||
|
||||
每阶段出口标准:`dotnet build` 0 错误、警告不增、关键路径冒烟可过。
|
||||
|
||||
---
|
||||
|
||||
## 七、模块处置矩阵(速查)
|
||||
|
||||
| 模块 | 现位置 | 目标位置 | 关键动作 |
|
||||
|---|---|---|---|
|
||||
| 接口/特性/字段袋/纯模型 | Core 各处 | **Abstractions** | 纯移动 |
|
||||
| TCP/Utils/CommonTools | Core | **Infrastructure** | 移动+`ILogger`+修 TCP |
|
||||
| 全部窗体/Viewer/Monitor | 各模块 | **UI.WinForms(deprecated)** | 移动+去 MessageBox |
|
||||
| 充电(任务/抽象/配置/通信) | Core/Charge | **StandardScene.Charge** | 卫星化 |
|
||||
| 充电桩具体驱动 | Devices | Devices 或并入 Charge | 统一 IDisposable |
|
||||
| 门/按钮盒 抽象+特性 | Core | Abstractions | 下沉 |
|
||||
| 门/按钮盒 驱动 | Devices | Devices | IDisposable |
|
||||
| MQTT/Modbus/IoTClient/Nancy/OpenXml | Core NuGet | 各卫星/WebApi | 依赖下放 |
|
||||
| WebApi | Core 单文件 | **StandardScene.WebApi** | 拆分+鉴权+ApiResult |
|
||||
| 车型 | Core/CarTypes | Core(领域) | 剥离通信/UI 协作者 |
|
||||
| Coders | Core/Coders | Core(→导航卫星 J 阶段) | 继续收敛 |
|
||||
| Commons/Heuristic/LadderLogic/CAD | Core 根 | 按职责归子模块 | 拆分+配置化 |
|
||||
|
||||
---
|
||||
|
||||
## 八、风险与约束
|
||||
|
||||
1. **内核耦合(SimpleLite/SimpleCore)**:Coder 注册表按内核程序集反射(S8)→导航卫星热插拔需内核改造;`UiTypeDiscovery.AllTypes()` 已支持跨程序集发现(设备/车型可行)。
|
||||
2. **无 Git 基线**:大量文件未纳入版本控制 → **强烈建议先建 Git 基线**再执行 A~J,保证可回滚。
|
||||
3. **行为等价**:车型/充电/VDA5050 含设备协议时序,移动需保持时序与字段语义(沿用"先移动后去重")。
|
||||
4. **绝对 HintPath/本地 dll**:迁移期保持 HintPath 可用,I 阶段统一相对化,避免中途断链。
|
||||
5. **migu 平台 UI**:UI.WinForms 仅过渡;接口层(Abstractions/WebApi)应面向 migu 提供稳定契约,UI 迁移后整包删除。
|
||||
|
||||
---
|
||||
|
||||
## 九、近期可立即执行(已具备条件,低风险)
|
||||
1. 删除/归档死文件 `AbstractChainedDeliveryMission.cs`(已 Compile Remove)。
|
||||
2. 新建 `StandardScene.Abstractions`,先迁 `BasicFields`、`*TypeAttribute`、`Loop/ILoopRules`(解 S6/S11,且不改逻辑)。
|
||||
3. `MissionRunnerBase` 抽取(承接 P0-1 协作式停止成果,统一 Scheduler 四任务)。
|
||||
4. `ApiResult` 帮助器(先在 WebApi 内消重,零行为变化)。
|
||||
Reference in New Issue
Block a user