refactor: 插件窗体由 static 单例状态改为实例字段(对齐 TextViewer)

将 10 个 CycleGUI 窗体/管理器从进程级 static 单例状态改为实例字段 + 实例方法,
消除本地端/Web 端共享同一可变状态的隐患(对齐 TextViewer 正例)。

每个类保留一个 private static _instance 单实例持有者;静态入口 Open()/OpenViewer()
与实例 Show() 均转发到该实例,调用方零改动、单实例“置前”行为不变。

涉及:ChargeStationManagementForm、ChargeStrategyConfigForm、CommunicationMonitorForm、
AlarmConfigManagementForm、ButtonBoxManager、DoorManager、DoorMonitor、
LoopViewer、DeliveryViewer、TrafficInterlockViewer。

构建:dotnet build StandardScene.sln --no-incremental → 0 错误,30 警告(与基线一致,无新增)。
This commit is contained in:
zhaowei.huang
2026-06-26 15:31:02 +08:00
parent a0dc1e6cd0
commit 54cda958db
10 changed files with 297 additions and 257 deletions
+23 -19
View File
@@ -28,26 +28,30 @@ namespace StandardScene.Chained
private const int OverdueMinutesThreshold = 10000; // 约 7 天视为超时
private const string TableId = "delivery-task-list";
private static readonly HttpClient SharedHttpClient = new HttpClient();
private readonly HttpClient SharedHttpClient = new HttpClient();
/// <summary>超时任务整行底色(深色主题下的暗红,醒目但不刺眼)。</summary>
private static readonly Color OverdueRowColor = Color.FromArgb(255, 90, 36, 36);
private readonly Color OverdueRowColor = Color.FromArgb(255, 90, 36, 36);
private static Panel _panel;
private static bool _showFinished = true; // 显示已完成任务
private static bool _showAbolished = true; // 显示废止任务(Error / Canceled / Terminated
private Panel _panel;
private bool _showFinished = true; // 显示已完成任务
private bool _showAbolished = true; // 显示废止任务(Error / Canceled / Terminated
// 渲染快照:由后台线程按 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 = "";
private volatile List<Delivery> _snapshot = new List<Delivery>();
private volatile bool _refreshing;
private DateTime _lastFlush = DateTime.MinValue;
private readonly TimeSpan FlushInterval = TimeSpan.FromSeconds(1);
private volatile string _status = "";
/// <summary>打开(或置前)任务管理面板。兼容原 <c>new DeliveryViewer().Show()</c> 调用方式。</summary>
public void Show() => Open();
/// <summary>打开(或置前)任务管理面板。</summary>
public static void Open()
public static void Open() => (_instance ??= new DeliveryViewer()).OpenCore();
private static DeliveryViewer _instance;
private void OpenCore()
{
if (_panel != null)
{
@@ -127,14 +131,14 @@ namespace StandardScene.Chained
});
}
private static bool IsOverdue(Delivery dd) =>
private bool IsOverdue(Delivery dd) =>
(DateTime.Now - dd.CreateTime).TotalMinutes > OverdueMinutesThreshold;
/// <summary>
/// 渲染线程调用:到达刷新间隔且无在途刷新时,<b>在后台线程</b>重新拉取任务快照(超时任务置顶)。
/// 业务侧的锁与文件 IO 一律放到后台,渲染线程只读 <see cref="_snapshot"/> 引用,避免界面卡死。
/// </summary>
private static void EnsureSnapshotFresh()
private void EnsureSnapshotFresh()
{
if (_refreshing) return;
if (DateTime.Now - _lastFlush < FlushInterval) return;
@@ -165,14 +169,14 @@ namespace StandardScene.Chained
});
}
private static string SafeSiteName(int siteId)
private string SafeSiteName(int siteId)
{
try { return SimpleLib.GetSite(siteId)?.name ?? ""; }
catch { return ""; }
}
/// <summary>将任务标记为已取消(Canceled)。</summary>
private static void MarkDeliveryCanceled(Delivery d)
private void MarkDeliveryCanceled(Delivery d)
{
if (d == null) return;
lock (d.SyncStatus)
@@ -187,7 +191,7 @@ namespace StandardScene.Chained
/// 当 clearCarForChange=true 时,仅当状态为 Suspended 或 Waiting 且未处于放货阶段时,
/// 才会清空 UsingCar 并返回 true;否则返回 false。
/// </summary>
private static bool MarkDeliveryWaiting(Delivery d, bool clearCarForChange)
private bool MarkDeliveryWaiting(Delivery d, bool clearCarForChange)
{
if (d == null) return false;
lock (d.SyncStatus)
@@ -217,7 +221,7 @@ namespace StandardScene.Chained
}
}
private static void ResendDelivery(string taskCode)
private void ResendDelivery(string taskCode)
{
try
{
@@ -247,7 +251,7 @@ namespace StandardScene.Chained
}
}
private static void CancelDelivery(string taskCode)
private void CancelDelivery(string taskCode)
{
try
{
@@ -286,7 +290,7 @@ namespace StandardScene.Chained
}
}
private static void ChangeCarResendDelivery(string taskCode)
private void ChangeCarResendDelivery(string taskCode)
{
try
{
+22 -18
View File
@@ -27,24 +27,28 @@ namespace LoopViewerApp
private const string TableId = "loop-task-list";
// 与 AbstractLoopMission 完全一致的读取路径,保证“写哪儿、它就读哪儿”。
private static string JsonPath => Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "tasklist.json");
private string JsonPath => Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "tasklist.json");
private static readonly object SaveLock = new object();
private 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));
private readonly string[] KindNames = Enum.GetNames(typeof(TaskKind));
private readonly string[] StartTypeNames = Enum.GetNames(typeof(TaskStartType));
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 = "";
private Panel _panel;
private Panel _dialog; // 新增/编辑对话框,限单实例
private List<LoopTask> _tasks = new List<LoopTask>(); // 仅渲染线程读写
private readonly HashSet<int> _selected = new HashSet<int>(); // 仅渲染线程读写,存被勾选任务的 Id
private volatile string _status = "";
/// <summary>打开(或置前)任务管理面板。兼容原 <c>new LoopViewer().Show()</c> 调用方式。</summary>
public void Show() => Open();
/// <summary>打开(或置前)任务管理面板。</summary>
public static void Open()
public static void Open() => (_instance ??= new LoopViewer()).OpenCore();
private static LoopViewer _instance;
private void OpenCore()
{
if (_panel != null)
{
@@ -126,7 +130,7 @@ namespace LoopViewerApp
}
/// <summary>对选中项发起二次确认后删除(保留原多选删除的提示文案)。</summary>
private static void ConfirmDeleteSelected()
private void ConfirmDeleteSelected()
{
if (_selected.Count == 0)
{
@@ -145,7 +149,7 @@ namespace LoopViewerApp
CycleUiHelper.ConfirmThen(prompt, DeleteSelected);
}
private static void DeleteSelected()
private void DeleteSelected()
{
var removed = _tasks.RemoveAll(t => _selected.Contains(t.Id));
_selected.Clear();
@@ -158,7 +162,7 @@ namespace LoopViewerApp
/// 打开「新增 / 编辑」对话框(置顶非模态、限单实例)。<paramref name="existing"/> 为 null 表示新增,否则编辑该任务(保留其 Id)。
/// 每次打开都是全新面板:<c>defaultText</c> 能正确初始化,规避立即模式下文本框缓冲难以重置的问题。
/// </summary>
private static void OpenEditDialog(LoopTask existing)
private void OpenEditDialog(LoopTask existing)
{
if (_dialog != null)
{
@@ -270,9 +274,9 @@ namespace LoopViewerApp
}
/// <summary>下一个可用任务 Id(当前最大 Id + 1,空表则为 1)。</summary>
private static int GetNextTaskId() => _tasks.Count == 0 ? 1 : _tasks.Max(t => t.Id) + 1;
private int GetNextTaskId() => _tasks.Count == 0 ? 1 : _tasks.Max(t => t.Id) + 1;
private static void LoadTasks()
private void LoadTasks()
{
try
{
@@ -292,7 +296,7 @@ namespace LoopViewerApp
}
/// <summary>序列化在渲染线程完成(极快),文件写入放后台线程,避免阻塞渲染线程。</summary>
private static void SaveTasks()
private void SaveTasks()
{
string json;
try
@@ -323,9 +327,9 @@ namespace LoopViewerApp
});
}
private static int Clamp(int v, int min, int max) => v < min ? min : (v > max ? max : v);
private 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)
private bool TryParseClamp(string s, int min, int max, out int value)
{
if (int.TryParse((s ?? "").Trim(), out value))
{