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

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

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

构建:dotnet build StandardScene.sln → 0 错误,30 警告(均为历史遗留)。
注:static 单例状态重构(审核第 8 项)暂未处理,留待单独任务。
This commit is contained in:
zhaowei.huang
2026-06-26 15:00:53 +08:00
parent c8e540d272
commit a0dc1e6cd0
91 changed files with 3946 additions and 15419 deletions
@@ -0,0 +1,37 @@
using System;
using System.Diagnostics;
namespace StandardScene.Utils
{
/// <summary>
/// 车辆远程访问辅助工具。
/// </summary>
public static class CarRemoteHelper
{
/// <summary>
/// 使用系统默认浏览器打开车辆 Web 管理页面。
/// </summary>
/// <param name="ip">车辆 IP 地址</param>
/// <param name="port">Web 服务端口,默认 8081</param>
public static void OpenVehicleWebPage(string ip, int port = 8081)
{
if (string.IsNullOrWhiteSpace(ip))
{
return;
}
try
{
Process.Start(new ProcessStartInfo
{
FileName = $"http://{ip}:{port}",
UseShellExecute = true
});
}
catch (Exception ex)
{
Console.WriteLine($"打开车辆 Web 页面失败: {ex.Message}");
}
}
}
}
+91
View File
@@ -0,0 +1,91 @@
using CycleGUI;
namespace StandardScene.Utils
{
/// <summary>
/// CycleGUI 通用 UI 小工具:把多个界面都会用到的轻量对话框收敛到一处,避免各处各写一套(不造重复轮子)。
/// <para>注意:同一 <c>Panel.Define</c> 内所有控件的 label 文本必须唯一(含 <c>pb.Table</c> 列头),
/// 否则 ImGui 会抛 <c>Duplicated id</c>。编辑区 label 建议加 ASCII 序号前缀(如 <c>1. IP</c>),
/// 且勿与表格列头同名。</para>
/// </summary>
public static class CycleUiHelper
{
/// <summary>
/// 非阻塞二次确认对话框:用户点「确认」后,在<b>当前(渲染)线程</b>同步执行 <paramref name="onConfirm"/>。
/// 若 <paramref name="onConfirm"/> 含文件 IO / 锁等耗时操作,调用方应自行用 <c>Task.Run</c> 包裹,
/// 避免阻塞渲染线程导致界面卡死。
/// </summary>
public static void ConfirmThen(string message, System.Action onConfirm)
{
// 不用 ModalCycleGUI 原生「模态弹窗 + 标题栏关闭X」存在 BeginPopupModal/EndPopup 配对 bug
//X 关闭时 BeginPopupModal 返回 false 仍调用 EndPopup → ImGui 断言 "Calling End() too many times!" 崩溃)。
// 改为置顶非模态:用 Begin/End 路径,X 关闭干净,效果等同点「取消」。
var dlg = GUI.DeclarePanel()
.ShowTitle("确认")
.TopMost(true)
.InitSize(380, 150)
.InitPos(false, 0, 0, 0.5f, 0.5f, 0.5f, 0.5f);
dlg.Define(pb =>
{
if (pb.Closing())
{
dlg.Exit();
return;
}
pb.Label(message);
pb.Separator();
if (pb.Button("确认", distinct: "cycleui-confirm-ok"))
{
dlg.Exit();
onConfirm();
}
pb.SameLine(8);
if (pb.Button("取消", distinct: "cycleui-confirm-cancel"))
dlg.Exit();
});
}
/// <summary>非阻塞文件选择对话框,替代 WinForms <c>OpenFileDialog</c>。</summary>
public static void PickOpenFile(string label, string filter, System.Action<string> onPicked)
{
var dlg = GUI.DeclarePanel()
.ShowTitle("选择文件")
.TopMost(true)
.InitSize(420, 120)
.InitPos(false, 0, 0, 0.5f, 0.5f, 0.5f, 0.5f);
dlg.Define(pb =>
{
if (pb.Closing()) { dlg.Exit(); return; }
if (pb.OpenFile(label, filter, out var path))
{
dlg.Exit();
onPicked?.Invoke(path);
}
});
}
/// <summary>非阻塞提示对话框。</summary>
public static void Alert(string title, string message)
{
var dlg = GUI.DeclarePanel()
.ShowTitle(title)
.TopMost(true)
.InitSize(380, 150)
.InitPos(false, 0, 0, 0.5f, 0.5f, 0.5f, 0.5f);
dlg.Define(pb =>
{
if (pb.Closing())
{
dlg.Exit();
return;
}
pb.Label(message);
pb.Separator();
if (pb.Button("确定", distinct: "cycleui-alert-ok"))
dlg.Exit();
});
}
}
}