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
@@ -15,7 +15,6 @@ using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace StandardScene.Chained
{
@@ -1510,7 +1509,7 @@ namespace StandardScene.Chained
public JsonFileTaskStrategy(string jsonPath = null)
{
JsonPath = string.IsNullOrWhiteSpace(jsonPath)
? Path.Combine(Application.StartupPath, "tasklist.json")
? Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "tasklist.json")
: jsonPath;
EnsureWatcher();
+165 -161
View File
@@ -1,13 +1,10 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using CycleGUI;
using StandardScene.Model;
using SimpleLite;
using SimpleCore;
@@ -17,35 +14,162 @@ using static StandardScene.Chained.ChainedDeliveryMission;
namespace StandardScene.Chained
{
public partial class DeliveryViewer : Form
/// <summary>
/// 搬运任务管理界面(CycleGUI 版,替代原 WinForms <c>DeliveryViewer</c> 窗体)。
/// <list type="bullet">
/// <item>单实例:再次打开则把已有面板置前。</item>
/// <item>约每 1s 节流刷新任务快照(在渲染线程内节流,避免并发),面板 500ms 准实时重绘。</item>
/// <item>每行提供「取消 / 重发 / 换车重发」按钮(带二次确认),超时任务整行高亮。</item>
/// </list>
/// 保留可实例化 + <see cref="Show"/> 以兼容既有调用 <c>new DeliveryViewer().Show()</c>。
/// </summary>
public class DeliveryViewer
{
private const int OverdueMinutesThreshold = 10000; // 约7天视为超时
private const int DisplayColumnIndexOverdueFlag = 9;
private const int OverdueMinutesThreshold = 10000; // 约 7 天视为超时
private const string TableId = "delivery-task-list";
private static readonly HttpClient SharedHttpClient = new HttpClient();
/// <summary>选中行的背景色</summary>
private static readonly Color SelectedRowBackColor = Color.FromArgb(220, 230, 250);
/// <summary>缓存选中行索引,避免在 RetrieveVirtualItem 中访问 SelectedIndices 引发递归</summary>
private readonly HashSet<int> _selectedIndicesCache = new HashSet<int>();
/// <summary>超时任务整行底色(深色主题下的暗红,醒目但不刺眼)。</summary>
private static readonly Color OverdueRowColor = Color.FromArgb(255, 90, 36, 36);
private ListViewItem _item = null;
private static Panel _panel;
private static bool _showFinished = true; // 显示已完成任务
private static bool _showAbolished = true; // 显示废止任务(Error / Canceled / Terminated
public DeliveryViewer()
// 渲染快照:由后台线程按 FlushInterval 刷新,渲染线程只读引用;锁/文件 IO 绝不放在渲染线程,避免界面卡死。
private static volatile List<Delivery> _snapshot = new List<Delivery>();
private static volatile bool _refreshing;
private static DateTime _lastFlush = DateTime.MinValue;
private static readonly TimeSpan FlushInterval = TimeSpan.FromSeconds(1);
private static volatile string _status = "";
/// <summary>打开(或置前)任务管理面板。兼容原 <c>new DeliveryViewer().Show()</c> 调用方式。</summary>
public void Show() => Open();
/// <summary>打开(或置前)任务管理面板。</summary>
public static void Open()
{
InitializeComponent();
if (_panel != null)
{
try
{
_panel.BringToFront();
return;
}
catch
{
_panel = null;
}
}
var panel = GUI.DeclarePanel()
.ShowTitle("任务列表")
.SetDefaultDocking(Panel.Docking.None)
.InitSize(1500, 620) // 列宽按内容自适应(SizingFixedFit),给足初始宽度避免 10 列横向拥挤
.InitPos(false, 0, 0, 0.5f, 0.5f, 0.5f, 0.5f);
_panel = panel;
panel.IfTerminalQuit(() => _panel = null);
panel.Define(pb =>
{
if (pb.Closing())
{
panel.Exit();
_panel = null;
return;
}
// 过滤开关:改变时强制立即刷新一次(不必等节流窗口)。
if (pb.CheckBox("显示已完成任务", ref _showFinished)) _lastFlush = DateTime.MinValue;
pb.SameLine(16);
if (pb.CheckBox("显示废止的任务(Error / Canceled / Terminated", ref _showAbolished)) _lastFlush = DateTime.MinValue;
EnsureSnapshotFresh();
var items = _snapshot;
pb.Label($"共 {items.Count} 个任务(超时任务高亮置顶)");
pb.Table(TableId,
new[] { "任务号", "小车", "取货点", "放货点", "任务状态", "下发时间", "执行时间", "结束时间", "优先级", "操作" },
items.Count, (row, i) =>
{
var dd = items[i];
if (IsOverdue(dd)) row.SetColor(OverdueRowColor);
row.Label($"{dd.Id}");
row.Label(dd.UsingCar?.name ?? "");
row.Label($"{dd.Src}-{SafeSiteName(dd.Src)}");
row.Label($"{dd.Dst}-{SafeSiteName(dd.Dst)}");
row.Label($"{dd.GetStatus()}");
row.Label($"{dd.CreateTime:yyyy-MM-dd HH:mm:ss}");
row.Label($"{dd.StartTime:yyyy-MM-dd HH:mm:ss}");
row.Label($"{dd.FinishTime:yyyy-MM-dd HH:mm:ss}");
row.Label($"{dd.Priority}");
var op = row.ButtonGroup(
new[] { "取消", "重发", "换车" },
new[] { "取消任务", "重发任务", "换车重发任务" });
var taskCode = dd.Id;
// 业务操作含文件 IO 与锁竞争,统一用 Task.Run 放后台执行,绝不阻塞渲染线程(否则界面卡死)。
if (op == 0) CycleUiHelper.ConfirmThen($"是否结束任务 {taskCode}", () => Task.Run(() => CancelDelivery(taskCode)));
else if (op == 1) CycleUiHelper.ConfirmThen($"是否重发任务 {taskCode}", () => Task.Run(() => ResendDelivery(taskCode)));
else if (op == 2) CycleUiHelper.ConfirmThen($"是否换车重发任务 {taskCode}", () => Task.Run(() => ChangeCarResendDelivery(taskCode)));
}, height: 18, enableSearch: true);
if (!string.IsNullOrEmpty(_status))
{
pb.Separator();
pb.Label(_status);
}
// 节流重绘:任务监控无需高帧率,约 500ms 刷新一次即可保持准实时,显著降低 CPU。
pb.Panel.Repaint(repaintTimeMs: 500);
});
}
private readonly ContextMenuStrip strip = new ContextMenuStrip();
private static bool IsOverdue(Delivery dd) =>
(DateTime.Now - dd.CreateTime).TotalMinutes > OverdueMinutesThreshold;
private void DeliveryViewer_Load(object sender, EventArgs e)
/// <summary>
/// 渲染线程调用:到达刷新间隔且无在途刷新时,<b>在后台线程</b>重新拉取任务快照(超时任务置顶)。
/// 业务侧的锁与文件 IO 一律放到后台,渲染线程只读 <see cref="_snapshot"/> 引用,避免界面卡死。
/// </summary>
private static void EnsureSnapshotFresh()
{
strip.Items.Clear();
strip.Items.Add("取消任务", null, CancelClick);
strip.Items.Add("重发任务", null, ResendClick);
strip.Items.Add("换车重发任务", null, ChangeCarResendClick);
currentTaskList.ContextMenuStrip = strip;
if (_refreshing) return;
if (DateTime.Now - _lastFlush < FlushInterval) return;
_lastFlush = DateTime.Now;
_refreshing = true;
// 捕获当前过滤条件,避免后台读取过程中被 UI 改动。
bool showFinished = _showFinished, showAbolished = _showAbolished;
Task.Run(() =>
{
try
{
var list = new List<Delivery>();
foreach (var cdm in SimpleProject.proj.Missions.OfType<ChainedDeliveryMission>())
list.AddRange(cdm.GetDeliveries(showFinished, showAbolished, showAbolished, showAbolished));
// 与原窗体一致:超时任务排在最前。
_snapshot = list.OrderByDescending(d => IsOverdue(d) ? 1 : 0).ToList();
}
catch (Exception ex)
{
Diagnosis.Post($"DeliveryViewer 刷新异常: {ExceptionFormatter.FormatEx(ex)}");
}
finally
{
_refreshing = false;
}
});
}
private List<string[]> _listDeliveries = new List<string[]>();
private static string SafeSiteName(int siteId)
{
try { return SimpleLib.GetSite(siteId)?.name ?? ""; }
catch { return ""; }
}
/// <summary>将任务标记为已取消(Canceled)。</summary>
private static void MarkDeliveryCanceled(Delivery d)
@@ -93,93 +217,8 @@ namespace StandardScene.Chained
}
}
protected virtual string[] GetDisplayContent(Delivery dd)
private static void ResendDelivery(string taskCode)
{
var srcName =SimpleLib.GetSite(dd.Src).name;
var dstName =SimpleLib.GetSite(dd.Dst).name;
var now = DateTime.Now;
var usingCar = dd.UsingCar == null ? string.Empty : dd.UsingCar.name;
return
[
$"{dd.Id}",
$"{usingCar}",
$"{dd.Src}-{srcName}",
$"{dd.Dst}-{dstName}",
$"{dd.GetStatus()}",
$"{dd.CreateTime:yyyy-mm-dd HH:mm:ss:fff}",
$"{dd.StartTime:yyyy-mm-dd HH:mm:ss:fff}",
$"{dd.FinishTime:yyyy-mm-dd HH:mm:ss:fff}",
$"{dd.Priority}",
$"{((now - dd.CreateTime).TotalMinutes > OverdueMinutesThreshold ? 1 : 0)}",
$"{dd.Id}"
];
}
private void TaskFlush()
{
_listDeliveries.Clear();
try
{
foreach (var cdm in SimpleProject.proj.Missions.OfType<ChainedDeliveryMission>())
foreach (var dd in cdm.GetDeliveries(checkBox1.Checked, checkBox2.Checked,checkBox2.Checked,checkBox2.Checked))
_listDeliveries.Add(GetDisplayContent(dd));
if (_listDeliveries.Count > 0)
{
var len = _listDeliveries[0].Length;
if (len > 0) _listDeliveries = _listDeliveries.OrderByDescending(p => int.Parse(p[len - 2])).ToList();
}
}
catch (Exception ex)
{
Diagnosis.Post($"TaskFlush 异常: {ExceptionFormatter.FormatEx(ex)}");
}
}
private void timer1_Tick(object sender, EventArgs e)
{
try
{
TaskFlush();
currentTaskList.VirtualListSize = _listDeliveries.Count;
currentTaskList.Invalidate();
}
catch (Exception ex)
{
Diagnosis.Post($"timer1_Tick 异常: {ExceptionFormatter.FormatEx(ex)}");
}
}
private void currentTaskList_RetrieveVirtualItem(object sender, RetrieveVirtualItemEventArgs e)
{
try
{
var n = e.ItemIndex;
e.Item = new ListViewItem(_listDeliveries[n]);
if (_listDeliveries[n].Length > DisplayColumnIndexOverdueFlag && _listDeliveries[n][DisplayColumnIndexOverdueFlag] == "1")
e.Item.ForeColor = Color.Red;
if (_selectedIndicesCache.Contains(n))
e.Item.BackColor = SelectedRowBackColor;
}
catch (Exception)
{
e.Item = new ListViewItem(["", "", "", "", "", "", "", "", ""]);
}
}
private void currentTaskList_MouseClick(object sender, MouseEventArgs e)
{
if (e.Button != MouseButtons.Right) return;
_item = currentTaskList.GetItemAt(e.X, e.Y);
}
private void ResendClick(object sender, EventArgs e)
{
if (_item == null) return;
string taskCode = _item.Text;
try
{
var cdm = SimpleProject.proj.Missions.OfType<TransportMission>().FirstOrDefault();
@@ -189,44 +228,40 @@ namespace StandardScene.Chained
.FirstOrDefault(s => s.Id == taskCode);
if (d == null)
{
MessageBox.Show("列表中不存在目标任务", "提示", MessageBoxButtons.OK, MessageBoxIcon.Warning);
_status = $"重发失败:列表中不存在任务 {taskCode}";
return;
}
var ms = MessageBox.Show($"是否重发任务--{taskCode}", "提示", MessageBoxButtons.OKCancel, MessageBoxIcon.Question);
if (ms != System.Windows.Forms.DialogResult.OK) return;
if (!MarkDeliveryWaiting(d, clearCarForChange: false))
{
MessageBox.Show("重发任务失败:当前状态不允许重发", "提示", MessageBoxButtons.OK, MessageBoxIcon.Warning);
_status = "重发任务失败:当前状态不允许重发";
return;
}
// 状态已改为 Waiting,持久化
cdm.PersistDelivery(d);
_status = $"已重发任务 {taskCode}";
}
catch (Exception)
catch (Exception ex)
{
MessageBox.Show("列表中不存在目标任务", "提示", MessageBoxButtons.OK, MessageBoxIcon.Warning);
_status = $"重发任务 {taskCode} 异常,详见日志";
Diagnosis.Post($"重发任务 {taskCode} 异常: {ExceptionFormatter.FormatEx(ex)}");
}
}
private void CancelClick(object sender, EventArgs e)
private static void CancelDelivery(string taskCode)
{
if (_item == null) return;
string str = _item.Text;
try
{
var cdm = SimpleProject.proj.Missions.OfType<TransportMission>().FirstOrDefault();
if (cdm == null) return;
var d = cdm.GetDeliveries(true, true, true, true)
.OfType<TransportDelivery>()
.FirstOrDefault(s => s.Id == str);
.FirstOrDefault(s => s.Id == taskCode);
if (d == null)
{
MessageBox.Show("列表中不存在目标任务", "提示", MessageBoxButtons.OK, MessageBoxIcon.Warning);
_status = $"取消失败:列表中不存在任务 {taskCode}";
return;
}
var ms = MessageBox.Show($"是否结束任务--{str}", "提示", MessageBoxButtons.OKCancel, MessageBoxIcon.Question);
if (ms != System.Windows.Forms.DialogResult.OK || d.IsFinished()) return;
if (d.IsFinished()) return;
// 1) 状态上将任务标记为已取消
MarkDeliveryCanceled(d);
@@ -242,17 +277,17 @@ namespace StandardScene.Chained
// 3) 持久化已取消状态
cdm.PersistDelivery(d);
_status = $"已结束任务 {taskCode}";
}
catch (Exception ex)
{
Diagnosis.Post($"结束任务 {str} 异常: {ExceptionFormatter.FormatEx(ex)}");
_status = $"结束任务 {taskCode} 异常,详见日志";
Diagnosis.Post($"结束任务 {taskCode} 异常: {ExceptionFormatter.FormatEx(ex)}");
}
}
private void ChangeCarResendClick(object sender, EventArgs e)
private static void ChangeCarResendDelivery(string taskCode)
{
if (_item == null) return;
string taskCode = _item.Text;
try
{
var cdm = SimpleProject.proj.Missions.OfType<TransportMission>().FirstOrDefault();
@@ -262,55 +297,24 @@ namespace StandardScene.Chained
.FirstOrDefault(s => s.Id == taskCode);
if (d == null)
{
MessageBox.Show("列表中不存在目标任务", "提示", MessageBoxButtons.OK, MessageBoxIcon.Warning);
_status = $"换车重发失败:列表中不存在任务 {taskCode}";
return;
}
var ms = MessageBox.Show($"是否换车重发任务--{taskCode}", "提示", MessageBoxButtons.OKCancel, MessageBoxIcon.Question);
if (ms != System.Windows.Forms.DialogResult.OK) return;
if (!MarkDeliveryWaiting(d, clearCarForChange: true))
{
MessageBox.Show("换车重发失败:仅当任务状态为 Suspended 或 Waiting 且未处于放货阶段时才允许换车重发", "提示", MessageBoxButtons.OK, MessageBoxIcon.Warning);
_status = "换车重发失败:仅当任务状态为 Suspended 或 Waiting 且未处于放货阶段时才允许换车重发";
return;
}
// 状态已改为 Waiting 且 UsingCar 已清空,持久化
cdm.PersistDelivery(d);
_status = $"已换车重发任务 {taskCode}";
}
catch (Exception ex)
{
_status = $"换车重发任务 {taskCode} 异常,详见日志";
Diagnosis.Post($"换车重发任务 {taskCode} 异常: {ExceptionFormatter.FormatEx(ex)}");
MessageBox.Show("换车重发任务异常,请查看日志", "提示", MessageBoxButtons.OK, MessageBoxIcon.Warning);
}
}
private void DeliveryViewer_FormClosing(object sender, FormClosingEventArgs e)
{
if (e.CloseReason == CloseReason.UserClosing)
{
e.Cancel = true;
this.Visible = false;
timer1.Stop();
}
}
protected override void SetVisibleCore(bool value)
{
if (!IsHandleCreated && value)
CreateHandle();
bool wasVisible = Visible;
base.SetVisibleCore(value);
if (value && !wasVisible)
timer1.Start();
}
private void currentTaskList_SelectedIndexChanged(object sender, EventArgs e)
{
_selectedIndicesCache.Clear();
foreach (int i in currentTaskList.SelectedIndices)
_selectedIndicesCache.Add(i);
this.BeginInvoke(() => currentTaskList.Invalidate());
}
}
}
-206
View File
@@ -1,206 +0,0 @@
using System.Windows.Forms;
namespace StandardScene.Chained
{
partial class DeliveryViewer
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
if (disposing)
{
strip?.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.components = new System.ComponentModel.Container();
this.currentTaskList = new System.Windows.Forms.ListView();
this.columnHeader8 = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader()));
this.columnHeader1 = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader()));
this.columnHeader4 = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader()));
this.columnHeader5 = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader()));
this.columnHeader9 = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader()));
this.columnHeader6 = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader()));
this.columnHeader2 = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader()));
this.columnHeader7 = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader()));
this.columnHeader3 = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader()));
this.label2 = new System.Windows.Forms.Label();
this.timer1 = new System.Windows.Forms.Timer(this.components);
this.checkBox1 = new System.Windows.Forms.CheckBox();
this.checkBox2 = new System.Windows.Forms.CheckBox();
this.SuspendLayout();
//
// currentTaskList
//
this.currentTaskList.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
| System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.currentTaskList.Columns.AddRange(new System.Windows.Forms.ColumnHeader[] {
this.columnHeader8,
this.columnHeader1,
this.columnHeader4,
this.columnHeader5,
this.columnHeader9,
this.columnHeader6,
this.columnHeader2,
this.columnHeader7,
this.columnHeader3});
this.currentTaskList.Font = new System.Drawing.Font("微软雅黑", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134)));
this.currentTaskList.FullRowSelect = true;
this.currentTaskList.GridLines = true;
this.currentTaskList.HideSelection = false;
this.currentTaskList.Location = new System.Drawing.Point(38, 62);
this.currentTaskList.Name = "currentTaskList";
this.currentTaskList.Size = new System.Drawing.Size(1146, 429);
this.currentTaskList.TabIndex = 2;
this.currentTaskList.UseCompatibleStateImageBehavior = false;
this.currentTaskList.View = System.Windows.Forms.View.Details;
this.currentTaskList.VirtualMode = true;
this.currentTaskList.RetrieveVirtualItem += new System.Windows.Forms.RetrieveVirtualItemEventHandler(this.currentTaskList_RetrieveVirtualItem);
this.currentTaskList.SelectedIndexChanged += new System.EventHandler(this.currentTaskList_SelectedIndexChanged);
this.currentTaskList.MouseClick += new System.Windows.Forms.MouseEventHandler(this.currentTaskList_MouseClick);
//
// columnHeader8
//
this.columnHeader8.Text = "任务号";
this.columnHeader8.Width = 130;
//
// columnHeader1
//
this.columnHeader1.Text = "小车";
this.columnHeader1.Width = 100;
//
// columnHeader4
//
this.columnHeader4.Text = "取货点";
this.columnHeader4.Width = 130;
//
// columnHeader5
//
this.columnHeader5.Text = "放货点";
this.columnHeader5.Width = 130;
//
// columnHeader9
//
this.columnHeader9.Text = "任务状态";
this.columnHeader9.Width = 100;
//
// columnHeader6
//
this.columnHeader6.Text = "下发时间";
this.columnHeader6.Width = 130;
//
// columnHeader2
//
this.columnHeader2.Text = "执行时间";
this.columnHeader2.Width = 130;
//
// columnHeader7
//
this.columnHeader7.Text = "结束时间";
this.columnHeader7.Width = 130;
//
// columnHeader3
//
this.columnHeader3.Text = "优先级";
this.columnHeader3.Width = 83;
//
// label2
//
this.label2.AutoSize = true;
this.label2.Font = new System.Drawing.Font("微软雅黑", 14.25F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(134)));
this.label2.Location = new System.Drawing.Point(33, 7);
this.label2.Name = "label2";
this.label2.Size = new System.Drawing.Size(88, 26);
this.label2.TabIndex = 3;
this.label2.Text = "任务列表";
//
// timer1
//
this.timer1.Enabled = true;
this.timer1.Interval = 1000;
this.timer1.Tick += new System.EventHandler(this.timer1_Tick);
//
// checkBox1
//
this.checkBox1.AutoSize = true;
this.checkBox1.Checked = true;
this.checkBox1.CheckState = System.Windows.Forms.CheckState.Checked;
this.checkBox1.Location = new System.Drawing.Point(127, 15);
this.checkBox1.Margin = new System.Windows.Forms.Padding(2, 2, 2, 2);
this.checkBox1.Name = "checkBox1";
this.checkBox1.Size = new System.Drawing.Size(108, 16);
this.checkBox1.TabIndex = 4;
this.checkBox1.Text = "显示已完成任务";
this.checkBox1.UseVisualStyleBackColor = true;
//
// checkBox2
//
this.checkBox2.AutoSize = true;
this.checkBox2.Checked = true;
this.checkBox2.CheckState = System.Windows.Forms.CheckState.Checked;
this.checkBox2.Location = new System.Drawing.Point(239, 14);
this.checkBox2.Margin = new System.Windows.Forms.Padding(2, 2, 2, 2);
this.checkBox2.Name = "checkBox2";
this.checkBox2.Size = new System.Drawing.Size(318, 16);
this.checkBox2.TabIndex = 5;
this.checkBox2.Text = "显示废止的任务(包括Error、Canceled、Terminated";
this.checkBox2.UseVisualStyleBackColor = true;
//
// DeliveryViewer
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(1199, 551);
this.Controls.Add(this.checkBox2);
this.Controls.Add(this.checkBox1);
this.Controls.Add(this.label2);
this.Controls.Add(this.currentTaskList);
this.Name = "DeliveryViewer";
this.Text = "DeliveryViewer";
this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.DeliveryViewer_FormClosing);
this.Load += new System.EventHandler(this.DeliveryViewer_Load);
this.ResumeLayout(false);
this.PerformLayout();
}
#endregion
private System.Windows.Forms.Label label2;
private System.Windows.Forms.ColumnHeader columnHeader4;
private System.Windows.Forms.ColumnHeader columnHeader5;
private System.Windows.Forms.ColumnHeader columnHeader6;
private System.Windows.Forms.ColumnHeader columnHeader7;
private System.Windows.Forms.ColumnHeader columnHeader8;
private System.Windows.Forms.Timer timer1;
private System.Windows.Forms.ColumnHeader columnHeader1;
private System.Windows.Forms.ColumnHeader columnHeader2;
private System.Windows.Forms.ColumnHeader columnHeader9;
private System.Windows.Forms.CheckBox checkBox1;
private System.Windows.Forms.CheckBox checkBox2;
public System.Windows.Forms.ListView currentTaskList;
private System.Windows.Forms.ColumnHeader columnHeader3;
}
}
@@ -1,123 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<metadata name="timer1.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>17, 17</value>
</metadata>
</root>
-570
View File
@@ -1,570 +0,0 @@
using System;
using System.Drawing;
using System.Windows.Forms;
namespace LoopViewerApp
{
partial class LoopViewer
{
private System.ComponentModel.IContainer components = null;
private ComboBox cmbTaskKind;
private NumericUpDown numCurrent;
private NumericUpDown numTarget;
private NumericUpDown numTraffic;
private CheckBox chkViaPoint;
private ComboBox cmbStartType;
private NumericUpDown numPriority;
private Button btnEdit; // 保留字段以供代码逻辑/样式使用(在界面上隐藏)
private Button btnDelete; // 保留字段以供代码逻辑/样式使用(在界面上隐藏)
private Button btnSave;
private Button btnCancel;
private ListView lstTasks;
private GroupBox grpEdit;
// 布局控件
private SplitContainer splitContainer;
private TableLayoutPanel tlpEdit;
private FlowLayoutPanel flpButtons;
// 中间竖向按钮(列表与编辑区之间)
private Panel pnlMiddle;
private FlowLayoutPanel flpMiddle;
private Button btnMiddleEdit;
private Button btnMiddleDelete;
// 列头
private ColumnHeader colId;
private ColumnHeader colTaskType;
private ColumnHeader colCurrent;
private ColumnHeader colTarget;
private ColumnHeader colTraffic;
private ColumnHeader colPriority;
private ColumnHeader colViaPoint;
private ColumnHeader colStartType;
// 标签字段(编辑区)
private Label lblKind;
private Label lblCurrent;
private Label lblTarget;
private Label lblTraffic;
private Label lblPriority;
private Label lblVia;
private Label lblStartType;
private Label lblEditingId; // 显示当前编辑的任务ID
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
private void InitializeComponent()
{
this.splitContainer = new System.Windows.Forms.SplitContainer();
this.pnlMiddle = new System.Windows.Forms.Panel();
this.flpMiddle = new System.Windows.Forms.FlowLayoutPanel();
this.btnMiddleEdit = new System.Windows.Forms.Button();
this.btnMiddleDelete = new System.Windows.Forms.Button();
this.lstTasks = new System.Windows.Forms.ListView();
this.colId = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader()));
this.colTaskType = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader()));
this.colCurrent = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader()));
this.colTarget = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader()));
this.colTraffic = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader()));
this.colPriority = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader()));
this.colViaPoint = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader()));
this.colStartType = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader()));
this.grpEdit = new System.Windows.Forms.GroupBox();
this.tlpEdit = new System.Windows.Forms.TableLayoutPanel();
this.lblEditingId = new System.Windows.Forms.Label();
this.lblKind = new System.Windows.Forms.Label();
this.cmbTaskKind = new System.Windows.Forms.ComboBox();
this.lblCurrent = new System.Windows.Forms.Label();
this.numCurrent = new System.Windows.Forms.NumericUpDown();
this.lblTarget = new System.Windows.Forms.Label();
this.numTarget = new System.Windows.Forms.NumericUpDown();
this.lblTraffic = new System.Windows.Forms.Label();
this.numTraffic = new System.Windows.Forms.NumericUpDown();
this.lblPriority = new System.Windows.Forms.Label();
this.numPriority = new System.Windows.Forms.NumericUpDown();
this.lblVia = new System.Windows.Forms.Label();
this.chkViaPoint = new System.Windows.Forms.CheckBox();
this.lblStartType = new System.Windows.Forms.Label();
this.cmbStartType = new System.Windows.Forms.ComboBox();
this.flpButtons = new System.Windows.Forms.FlowLayoutPanel();
this.btnSave = new System.Windows.Forms.Button();
this.btnCancel = new System.Windows.Forms.Button();
this.btnEdit = new System.Windows.Forms.Button();
this.btnDelete = new System.Windows.Forms.Button();
((System.ComponentModel.ISupportInitialize)(this.splitContainer)).BeginInit();
this.splitContainer.Panel1.SuspendLayout();
this.splitContainer.Panel2.SuspendLayout();
this.splitContainer.SuspendLayout();
this.pnlMiddle.SuspendLayout();
this.flpMiddle.SuspendLayout();
this.grpEdit.SuspendLayout();
this.tlpEdit.SuspendLayout();
((System.ComponentModel.ISupportInitialize)(this.numCurrent)).BeginInit();
((System.ComponentModel.ISupportInitialize)(this.numTarget)).BeginInit();
((System.ComponentModel.ISupportInitialize)(this.numTraffic)).BeginInit();
((System.ComponentModel.ISupportInitialize)(this.numPriority)).BeginInit();
this.flpButtons.SuspendLayout();
this.SuspendLayout();
//
// splitContainer
//
this.splitContainer.Dock = System.Windows.Forms.DockStyle.Fill;
this.splitContainer.Location = new System.Drawing.Point(0, 0);
this.splitContainer.Name = "splitContainer";
//
// splitContainer.Panel1
//
this.splitContainer.Panel1.Controls.Add(this.pnlMiddle);
this.splitContainer.Panel1.Controls.Add(this.lstTasks);
//
// splitContainer.Panel2
//
this.splitContainer.Panel2.Controls.Add(this.grpEdit);
this.splitContainer.Size = new System.Drawing.Size(1200, 600);
this.splitContainer.SplitterDistance = 680;
this.splitContainer.SplitterWidth = 6;
this.splitContainer.TabIndex = 0;
//
// pnlMiddle
//
this.pnlMiddle.Controls.Add(this.flpMiddle);
this.pnlMiddle.Dock = System.Windows.Forms.DockStyle.Right;
this.pnlMiddle.Location = new System.Drawing.Point(614, 0);
this.pnlMiddle.Name = "pnlMiddle";
this.pnlMiddle.Padding = new System.Windows.Forms.Padding(6);
this.pnlMiddle.Size = new System.Drawing.Size(66, 600);
this.pnlMiddle.TabIndex = 0;
//
// flpMiddle
//
this.flpMiddle.Anchor = System.Windows.Forms.AnchorStyles.None;
this.flpMiddle.Controls.Add(this.btnMiddleEdit);
this.flpMiddle.Controls.Add(this.btnMiddleDelete);
this.flpMiddle.FlowDirection = System.Windows.Forms.FlowDirection.TopDown;
this.flpMiddle.Location = new System.Drawing.Point(0, 220);
this.flpMiddle.Name = "flpMiddle";
this.flpMiddle.Padding = new System.Windows.Forms.Padding(2);
this.flpMiddle.Size = new System.Drawing.Size(63, 160);
this.flpMiddle.TabIndex = 0;
this.flpMiddle.WrapContents = false;
//
// btnMiddleEdit
//
this.btnMiddleEdit.BackColor = System.Drawing.SystemColors.Control;
this.btnMiddleEdit.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
this.btnMiddleEdit.Font = new System.Drawing.Font("微软雅黑", 9F);
this.btnMiddleEdit.Location = new System.Drawing.Point(6, 12);
this.btnMiddleEdit.Margin = new System.Windows.Forms.Padding(4, 10, 4, 4);
this.btnMiddleEdit.Name = "btnMiddleEdit";
this.btnMiddleEdit.Size = new System.Drawing.Size(50, 40);
this.btnMiddleEdit.TabIndex = 0;
this.btnMiddleEdit.Text = "编辑";
this.btnMiddleEdit.UseVisualStyleBackColor = false;
this.btnMiddleEdit.Click += new System.EventHandler(this.btnEdit_Click);
//
// btnMiddleDelete
//
this.btnMiddleDelete.BackColor = System.Drawing.Color.LightCoral;
this.btnMiddleDelete.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
this.btnMiddleDelete.Font = new System.Drawing.Font("微软雅黑", 9F);
this.btnMiddleDelete.Location = new System.Drawing.Point(6, 62);
this.btnMiddleDelete.Margin = new System.Windows.Forms.Padding(4, 6, 4, 4);
this.btnMiddleDelete.Name = "btnMiddleDelete";
this.btnMiddleDelete.Size = new System.Drawing.Size(50, 40);
this.btnMiddleDelete.TabIndex = 1;
this.btnMiddleDelete.Text = "删除";
this.btnMiddleDelete.UseVisualStyleBackColor = false;
this.btnMiddleDelete.Click += new System.EventHandler(this.btnDelete_Click);
//
// lstTasks
//
this.lstTasks.Columns.AddRange(new System.Windows.Forms.ColumnHeader[] {
this.colId,
this.colTaskType,
this.colCurrent,
this.colTarget,
this.colTraffic,
this.colPriority,
this.colViaPoint,
this.colStartType});
this.lstTasks.Dock = System.Windows.Forms.DockStyle.Fill;
this.lstTasks.FullRowSelect = true;
this.lstTasks.HideSelection = false;
this.lstTasks.Location = new System.Drawing.Point(0, 0);
this.lstTasks.Name = "lstTasks";
this.lstTasks.OwnerDraw = true;
this.lstTasks.Size = new System.Drawing.Size(680, 600);
this.lstTasks.TabIndex = 0;
this.lstTasks.UseCompatibleStateImageBehavior = false;
this.lstTasks.View = System.Windows.Forms.View.Details;
this.lstTasks.DrawColumnHeader += new System.Windows.Forms.DrawListViewColumnHeaderEventHandler(this.lstTasks_DrawColumnHeader);
this.lstTasks.DrawItem += new System.Windows.Forms.DrawListViewItemEventHandler(this.lstTasks_DrawItem);
this.lstTasks.DrawSubItem += new System.Windows.Forms.DrawListViewSubItemEventHandler(this.lstTasks_DrawSubItem);
this.lstTasks.MouseDoubleClick += new System.Windows.Forms.MouseEventHandler(this.lstTasks_MouseDoubleClick);
//
// colId
//
this.colId.Text = "ID";
this.colId.Width = 40;
//
// colTaskType
//
this.colTaskType.Text = "任务类别";
this.colTaskType.Width = 110;
//
// colCurrent
//
this.colCurrent.Text = "当前站点";
this.colCurrent.Width = 90;
//
// colTarget
//
this.colTarget.Text = "目标站点";
this.colTarget.Width = 90;
//
// colTraffic
//
this.colTraffic.Text = "流量控制";
this.colTraffic.Width = 90;
//
// colPriority
//
this.colPriority.Text = "优先级";
this.colPriority.Width = 80;
//
// colViaPoint
//
this.colViaPoint.Text = "途径点";
this.colViaPoint.Width = 70;
//
// colStartType
//
this.colStartType.Text = "启动类型";
this.colStartType.Width = 100;
//
// grpEdit
//
this.grpEdit.Controls.Add(this.tlpEdit);
this.grpEdit.Dock = System.Windows.Forms.DockStyle.Fill;
this.grpEdit.Font = new System.Drawing.Font("微软雅黑", 10F, System.Drawing.FontStyle.Bold);
this.grpEdit.Location = new System.Drawing.Point(0, 0);
this.grpEdit.Name = "grpEdit";
this.grpEdit.Size = new System.Drawing.Size(514, 600);
this.grpEdit.TabIndex = 1;
this.grpEdit.TabStop = false;
this.grpEdit.Text = "任务信息(选中列表项后可编辑)";
//
// tlpEdit
//
this.tlpEdit.ColumnCount = 2;
this.tlpEdit.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 120F));
this.tlpEdit.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F));
this.tlpEdit.Controls.Add(this.lblEditingId, 0, 0);
this.tlpEdit.Controls.Add(this.lblKind, 0, 1);
this.tlpEdit.Controls.Add(this.cmbTaskKind, 1, 1);
this.tlpEdit.Controls.Add(this.lblCurrent, 0, 2);
this.tlpEdit.Controls.Add(this.numCurrent, 1, 2);
this.tlpEdit.Controls.Add(this.lblTarget, 0, 3);
this.tlpEdit.Controls.Add(this.numTarget, 1, 3);
this.tlpEdit.Controls.Add(this.lblTraffic, 0, 4);
this.tlpEdit.Controls.Add(this.numTraffic, 1, 4);
this.tlpEdit.Controls.Add(this.lblPriority, 0, 5);
this.tlpEdit.Controls.Add(this.numPriority, 1, 5);
this.tlpEdit.Controls.Add(this.lblVia, 0, 6);
this.tlpEdit.Controls.Add(this.chkViaPoint, 1, 6);
this.tlpEdit.Controls.Add(this.lblStartType, 0, 7);
this.tlpEdit.Controls.Add(this.cmbStartType, 1, 7);
this.tlpEdit.Controls.Add(this.flpButtons, 1, 8);
this.tlpEdit.Dock = System.Windows.Forms.DockStyle.Fill;
this.tlpEdit.Location = new System.Drawing.Point(3, 25);
this.tlpEdit.Name = "tlpEdit";
this.tlpEdit.Padding = new System.Windows.Forms.Padding(8);
this.tlpEdit.RowCount = 9;
this.tlpEdit.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 36F));
this.tlpEdit.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 36F));
this.tlpEdit.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 36F));
this.tlpEdit.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 36F));
this.tlpEdit.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 36F));
this.tlpEdit.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 36F));
this.tlpEdit.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 36F));
this.tlpEdit.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 36F));
this.tlpEdit.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F));
this.tlpEdit.Size = new System.Drawing.Size(508, 572);
this.tlpEdit.TabIndex = 0;
//
// lblEditingId
//
this.lblEditingId.Anchor = System.Windows.Forms.AnchorStyles.Left;
this.lblEditingId.AutoSize = true;
this.tlpEdit.SetColumnSpan(this.lblEditingId, 2);
this.lblEditingId.Font = new System.Drawing.Font("微软雅黑", 10F, System.Drawing.FontStyle.Bold);
this.lblEditingId.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(0)))), ((int)(((byte)(120)))), ((int)(((byte)(215)))));
this.lblEditingId.Location = new System.Drawing.Point(11, 14);
this.lblEditingId.Name = "lblEditingId";
this.lblEditingId.Size = new System.Drawing.Size(78, 24);
this.lblEditingId.TabIndex = 0;
this.lblEditingId.Text = "新增任务";
//
// lblKind
//
this.lblKind.Dock = System.Windows.Forms.DockStyle.Fill;
this.lblKind.Font = new System.Drawing.Font("微软雅黑", 10F);
this.lblKind.Location = new System.Drawing.Point(11, 44);
this.lblKind.Name = "lblKind";
this.lblKind.Size = new System.Drawing.Size(114, 36);
this.lblKind.TabIndex = 1;
this.lblKind.Text = "任务类别:";
this.lblKind.TextAlign = System.Drawing.ContentAlignment.MiddleRight;
//
// cmbTaskKind
//
this.cmbTaskKind.Dock = System.Windows.Forms.DockStyle.Fill;
this.cmbTaskKind.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
this.cmbTaskKind.Font = new System.Drawing.Font("微软雅黑", 10F);
this.cmbTaskKind.Items.AddRange(new object[] {
"Loop",
"BranchPoint",
"JoinPoint"});
this.cmbTaskKind.Location = new System.Drawing.Point(131, 47);
this.cmbTaskKind.Name = "cmbTaskKind";
this.cmbTaskKind.Size = new System.Drawing.Size(366, 31);
this.cmbTaskKind.TabIndex = 2;
//
// lblCurrent
//
this.lblCurrent.Dock = System.Windows.Forms.DockStyle.Fill;
this.lblCurrent.Font = new System.Drawing.Font("微软雅黑", 10F);
this.lblCurrent.Location = new System.Drawing.Point(11, 80);
this.lblCurrent.Name = "lblCurrent";
this.lblCurrent.Size = new System.Drawing.Size(114, 36);
this.lblCurrent.TabIndex = 3;
this.lblCurrent.Text = "当前站点:";
this.lblCurrent.TextAlign = System.Drawing.ContentAlignment.MiddleRight;
//
// numCurrent
//
this.numCurrent.Dock = System.Windows.Forms.DockStyle.Left;
this.numCurrent.Font = new System.Drawing.Font("微软雅黑", 10F);
this.numCurrent.Location = new System.Drawing.Point(131, 83);
this.numCurrent.Maximum = new decimal(new int[] {
1000000,
0,
0,
0});
this.numCurrent.Name = "numCurrent";
this.numCurrent.Size = new System.Drawing.Size(120, 29);
this.numCurrent.TabIndex = 4;
//
// lblTarget
//
this.lblTarget.Dock = System.Windows.Forms.DockStyle.Fill;
this.lblTarget.Font = new System.Drawing.Font("微软雅黑", 10F);
this.lblTarget.Location = new System.Drawing.Point(11, 116);
this.lblTarget.Name = "lblTarget";
this.lblTarget.Size = new System.Drawing.Size(114, 36);
this.lblTarget.TabIndex = 5;
this.lblTarget.Text = "目标站点:";
this.lblTarget.TextAlign = System.Drawing.ContentAlignment.MiddleRight;
//
// numTarget
//
this.numTarget.Dock = System.Windows.Forms.DockStyle.Left;
this.numTarget.Font = new System.Drawing.Font("微软雅黑", 10F);
this.numTarget.Location = new System.Drawing.Point(131, 119);
this.numTarget.Maximum = new decimal(new int[] {
1000000,
0,
0,
0});
this.numTarget.Name = "numTarget";
this.numTarget.Size = new System.Drawing.Size(120, 29);
this.numTarget.TabIndex = 6;
//
// lblTraffic
//
this.lblTraffic.Dock = System.Windows.Forms.DockStyle.Fill;
this.lblTraffic.Font = new System.Drawing.Font("微软雅黑", 10F);
this.lblTraffic.Location = new System.Drawing.Point(11, 152);
this.lblTraffic.Name = "lblTraffic";
this.lblTraffic.Size = new System.Drawing.Size(114, 36);
this.lblTraffic.TabIndex = 7;
this.lblTraffic.Text = "流量控制:";
this.lblTraffic.TextAlign = System.Drawing.ContentAlignment.MiddleRight;
//
// numTraffic
//
this.numTraffic.Dock = System.Windows.Forms.DockStyle.Left;
this.numTraffic.Font = new System.Drawing.Font("微软雅黑", 10F);
this.numTraffic.Location = new System.Drawing.Point(131, 155);
this.numTraffic.Maximum = new decimal(new int[] {
1000,
0,
0,
0});
this.numTraffic.Name = "numTraffic";
this.numTraffic.Size = new System.Drawing.Size(120, 29);
this.numTraffic.TabIndex = 8;
//
// lblPriority
//
this.lblPriority.Dock = System.Windows.Forms.DockStyle.Fill;
this.lblPriority.Font = new System.Drawing.Font("微软雅黑", 10F);
this.lblPriority.Location = new System.Drawing.Point(11, 188);
this.lblPriority.Name = "lblPriority";
this.lblPriority.Size = new System.Drawing.Size(114, 36);
this.lblPriority.TabIndex = 9;
this.lblPriority.Text = "优先级:";
this.lblPriority.TextAlign = System.Drawing.ContentAlignment.MiddleRight;
//
// numPriority
//
this.numPriority.Dock = System.Windows.Forms.DockStyle.Left;
this.numPriority.Font = new System.Drawing.Font("微软雅黑", 10F);
this.numPriority.Location = new System.Drawing.Point(131, 191);
this.numPriority.Name = "numPriority";
this.numPriority.Size = new System.Drawing.Size(120, 29);
this.numPriority.TabIndex = 10;
this.numPriority.Value = new decimal(new int[] {
1,
0,
0,
0});
//
// lblVia
//
this.lblVia.Dock = System.Windows.Forms.DockStyle.Fill;
this.lblVia.Font = new System.Drawing.Font("微软雅黑", 10F);
this.lblVia.Location = new System.Drawing.Point(11, 224);
this.lblVia.Name = "lblVia";
this.lblVia.Size = new System.Drawing.Size(114, 36);
this.lblVia.TabIndex = 11;
this.lblVia.Text = "途径点:";
this.lblVia.TextAlign = System.Drawing.ContentAlignment.MiddleRight;
//
// chkViaPoint
//
this.chkViaPoint.Dock = System.Windows.Forms.DockStyle.Left;
this.chkViaPoint.Font = new System.Drawing.Font("微软雅黑", 10F);
this.chkViaPoint.Location = new System.Drawing.Point(131, 227);
this.chkViaPoint.Name = "chkViaPoint";
this.chkViaPoint.Size = new System.Drawing.Size(104, 30);
this.chkViaPoint.TabIndex = 12;
this.chkViaPoint.Text = "是";
//
// lblStartType
//
this.lblStartType.Dock = System.Windows.Forms.DockStyle.Fill;
this.lblStartType.Font = new System.Drawing.Font("微软雅黑", 10F);
this.lblStartType.Location = new System.Drawing.Point(11, 260);
this.lblStartType.Name = "lblStartType";
this.lblStartType.Size = new System.Drawing.Size(114, 36);
this.lblStartType.TabIndex = 13;
this.lblStartType.Text = "启动类型:";
this.lblStartType.TextAlign = System.Drawing.ContentAlignment.MiddleRight;
//
// cmbStartType
//
this.cmbStartType.Dock = System.Windows.Forms.DockStyle.Fill;
this.cmbStartType.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
this.cmbStartType.Font = new System.Drawing.Font("微软雅黑", 10F);
this.cmbStartType.Items.AddRange(new object[] {
"Api",
"Plc",
"ButtonBox",
"AutoLoop",
"Charge"});
this.cmbStartType.Location = new System.Drawing.Point(131, 263);
this.cmbStartType.Name = "cmbStartType";
this.cmbStartType.Size = new System.Drawing.Size(366, 31);
this.cmbStartType.TabIndex = 14;
//
// flpButtons
//
this.flpButtons.AutoSize = true;
this.flpButtons.Controls.Add(this.btnSave);
this.flpButtons.Controls.Add(this.btnCancel);
this.flpButtons.Dock = System.Windows.Forms.DockStyle.Left;
this.flpButtons.Location = new System.Drawing.Point(131, 299);
this.flpButtons.Name = "flpButtons";
this.flpButtons.Size = new System.Drawing.Size(292, 262);
this.flpButtons.TabIndex = 15;
//
// btnSave
//
this.btnSave.BackColor = System.Drawing.Color.LightBlue;
this.btnSave.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
this.btnSave.Font = new System.Drawing.Font("微软雅黑", 11F, System.Drawing.FontStyle.Bold);
this.btnSave.Location = new System.Drawing.Point(3, 3);
this.btnSave.Name = "btnSave";
this.btnSave.Size = new System.Drawing.Size(140, 40);
this.btnSave.TabIndex = 0;
this.btnSave.Text = "保存";
this.btnSave.UseVisualStyleBackColor = false;
this.btnSave.Click += new System.EventHandler(this.btnSave_Click);
//
// btnCancel
//
this.btnCancel.BackColor = System.Drawing.SystemColors.Control;
this.btnCancel.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
this.btnCancel.Font = new System.Drawing.Font("微软雅黑", 11F);
this.btnCancel.Location = new System.Drawing.Point(149, 3);
this.btnCancel.Name = "btnCancel";
this.btnCancel.Size = new System.Drawing.Size(140, 40);
this.btnCancel.TabIndex = 1;
this.btnCancel.Text = "取消";
this.btnCancel.UseVisualStyleBackColor = false;
this.btnCancel.Click += new System.EventHandler(this.btnCancel_Click);
//
// btnEdit
//
this.btnEdit.Location = new System.Drawing.Point(0, 0);
this.btnEdit.Name = "btnEdit";
this.btnEdit.Size = new System.Drawing.Size(75, 23);
this.btnEdit.TabIndex = 0;
this.btnEdit.Visible = false;
//
// btnDelete
//
this.btnDelete.Location = new System.Drawing.Point(0, 0);
this.btnDelete.Name = "btnDelete";
this.btnDelete.Size = new System.Drawing.Size(75, 23);
this.btnDelete.TabIndex = 0;
this.btnDelete.Visible = false;
//
// LoopViewer
//
this.ClientSize = new System.Drawing.Size(1200, 600);
this.Controls.Add(this.splitContainer);
this.Font = new System.Drawing.Font("微软雅黑", 9F);
this.MinimumSize = new System.Drawing.Size(1000, 420);
this.Name = "LoopViewer";
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
this.Text = "任务列表管理器";
this.splitContainer.Panel1.ResumeLayout(false);
this.splitContainer.Panel2.ResumeLayout(false);
((System.ComponentModel.ISupportInitialize)(this.splitContainer)).EndInit();
this.splitContainer.ResumeLayout(false);
this.pnlMiddle.ResumeLayout(false);
this.flpMiddle.ResumeLayout(false);
this.grpEdit.ResumeLayout(false);
this.tlpEdit.ResumeLayout(false);
this.tlpEdit.PerformLayout();
((System.ComponentModel.ISupportInitialize)(this.numCurrent)).EndInit();
((System.ComponentModel.ISupportInitialize)(this.numTarget)).EndInit();
((System.ComponentModel.ISupportInitialize)(this.numTraffic)).EndInit();
((System.ComponentModel.ISupportInitialize)(this.numPriority)).EndInit();
this.flpButtons.ResumeLayout(false);
this.ResumeLayout(false);
}
}
}
+278 -534
View File
@@ -1,595 +1,339 @@
using Newtonsoft.Json;
using StandardScene.Model;
using System;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Windows.Forms;
using System.Threading.Tasks;
using CycleGUI;
using Newtonsoft.Json;
using SimpleCore.Library;
using StandardScene.Model;
using StandardScene.Utils;
namespace LoopViewerApp
{
public partial class LoopViewer : Form
/// <summary>
/// 环线/循环任务配置管理界面(CycleGUI 版,替代原 WinForms <c>LoopViewer</c> 窗体)。
/// <list type="bullet">
/// <item>维护 <c>tasklist.json</c><see cref="List{T}"/> of <see cref="LoopTask"/>)的增 / 改 / 删;与 <c>AbstractLoopMission</c> 读取同一文件。</item>
/// <item>单实例:再次打开则把已有面板置前。</item>
/// <item>勾选多行后「删除选中」可批量删除(保留原 ListView 多选删除能力);每行「编辑」按钮打开编辑对话框。</item>
/// <item>文件写入放后台线程,绝不阻塞渲染线程(避免界面卡死)。</item>
/// </list>
/// 沿用 <c>DeliveryViewer</c> 的同套模式(单实例面板、<c>pb.Table</c>、<c>CycleUiHelper.ConfirmThen</c>),不另造轮子。
/// 保留可实例化 + <see cref="Show"/> 以兼容既有调用 <c>new LoopViewer().Show()</c>。
/// </summary>
public class LoopViewer
{
private readonly string jsonPath =
Path.Combine(Application.StartupPath, "tasklist.json");
private const string TableId = "loop-task-list";
private List<LoopTask> tasks = new List<LoopTask>();
// 与 AbstractLoopMission 完全一致的读取路径,保证“写哪儿、它就读哪儿”。
private static string JsonPath => Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "tasklist.json");
// -1 表示新增模式;>=0 表示正在编辑对应索引
private int editingIndex = -1;
private static readonly object SaveLock = new object();
// 直接取自枚举,自动与 TaskKind / TaskStartType 保持同步(含 Charge),无需手写列表。
private static readonly string[] KindNames = Enum.GetNames(typeof(TaskKind));
private static readonly string[] StartTypeNames = Enum.GetNames(typeof(TaskStartType));
public LoopViewer()
private static Panel _panel;
private static Panel _dialog; // 新增/编辑对话框,限单实例
private static List<LoopTask> _tasks = new List<LoopTask>(); // 仅渲染线程读写
private static readonly HashSet<int> _selected = new HashSet<int>(); // 仅渲染线程读写,存被勾选任务的 Id
private static volatile string _status = "";
/// <summary>打开(或置前)任务管理面板。兼容原 <c>new LoopViewer().Show()</c> 调用方式。</summary>
public void Show() => Open();
/// <summary>打开(或置前)任务管理面板。</summary>
public static void Open()
{
InitializeComponent();
if (_panel != null)
{
try
{
_panel.BringToFront();
return;
}
catch
{
_panel = null;
}
}
if (LicenseManager.UsageMode == LicenseUsageMode.Designtime)
_selected.Clear();
LoadTasks();
var panel = GUI.DeclarePanel()
.ShowTitle("任务列表管理器")
.SetDefaultDocking(Panel.Docking.None)
.InitSize(1080, 620)
.InitPos(false, 0, 0, 0.5f, 0.5f, 0.5f, 0.5f);
_panel = panel;
panel.IfTerminalQuit(() => _panel = null);
panel.Define(pb =>
{
if (pb.Closing())
{
panel.Exit();
_panel = null;
return;
}
if (pb.Button("新增任务", distinct: "loop-add"))
OpenEditDialog(null);
pb.SameLine(12);
if (pb.Button("删除选中", distinct: "loop-del-selected"))
ConfirmDeleteSelected();
pb.SameLine(16);
pb.Label($"共 {_tasks.Count} 个任务,已选 {_selected.Count} 个");
pb.Table(TableId,
new[] { "选择", "ID", "任务类别", "当前站点", "目标站点", "流量控制", "优先级", "途径点", "启动类型", "操作" },
_tasks.Count, (row, i) =>
{
var t = _tasks[i];
var id = t.Id;
var sel = _selected.Contains(id);
if (row.Checkbox(ref sel, "勾选以批量删除"))
{
if (sel) _selected.Add(id);
else _selected.Remove(id);
}
row.Label($"{t.Id}");
row.Label($"{t.Kind}");
row.Label($"{t.CurrentStationId}");
row.Label($"{t.TargetStationId}");
row.Label($"{t.TrafficControl}");
row.Label($"{t.Priority}");
row.Label(t.IsViaPoint ? "是" : "否");
row.Label($"{t.StartType}");
if (row.ButtonGroup(new[] { "编辑" }, new[] { "编辑该任务" }) == 0)
OpenEditDialog(t);
}, height: 18, enableSearch: true);
if (!string.IsNullOrEmpty(_status))
{
pb.Separator();
pb.Label(_status);
}
// 事件驱动为主,配合较慢的节流重绘即可保证后台保存结果/状态及时反映。
pb.Panel.Repaint(repaintTimeMs: 500);
});
}
/// <summary>对选中项发起二次确认后删除(保留原多选删除的提示文案)。</summary>
private static void ConfirmDeleteSelected()
{
if (_selected.Count == 0)
{
_status = "未选择任何任务";
_panel?.Repaint();
return;
// 应用 ChargeStationManagementForm 风格的运行时样式调整
ApplyChargeStyle();
EnsureComboItems();
// 启用多选并绑定右键菜单与 Delete 键删除功能
try
{
if (lstTasks != null)
{
lstTasks.MultiSelect = true;
// 右键菜单:删除
var ctx = new ContextMenuStrip();
ctx.Items.Add("删除", null, (s, e) => OnDeleteSelectedTasks());
lstTasks.ContextMenuStrip = ctx;
// 键盘删除键绑定
lstTasks.KeyDown += lstTasks_KeyDown;
}
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"LoopViewer context menu init error: {ex}");
}
try
{
InitOrLoadJson();
RenderListView();
UpdateSaveButtonText();
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"LoopViewer initialization error: {ex}");
}
string prompt;
if (_selected.Count == 1)
prompt = $"确认删除任务 ID={_selected.First()}";
else
prompt = $"确认删除所选 {_selected.Count} 个任务?";
// 删除仅做内存列表增删(极快,可在渲染线程执行);真正的文件写入在 SaveTasks 内部放后台线程。
CycleUiHelper.ConfirmThen(prompt, DeleteSelected);
}
private void lstTasks_KeyDown(object sender, KeyEventArgs e)
private static void DeleteSelected()
{
try
{
if (e.KeyCode == Keys.Delete)
{
OnDeleteSelectedTasks();
e.Handled = true;
}
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"lstTasks_KeyDown error: {ex}");
}
var removed = _tasks.RemoveAll(t => _selected.Contains(t.Id));
_selected.Clear();
SaveTasks();
_status = $"已删除 {removed} 个任务";
_panel?.Repaint();
}
/// <summary>
/// 删除 ListView 中选中的任务(支持多选)
/// 打开「新增 / 编辑」对话框(置顶非模态、限单实例)。<paramref name="existing"/> 为 null 表示新增,否则编辑该任务(保留其 Id)。
/// 每次打开都是全新面板:<c>defaultText</c> 能正确初始化,规避立即模式下文本框缓冲难以重置的问题。
/// </summary>
private void OnDeleteSelectedTasks()
private static void OpenEditDialog(LoopTask existing)
{
try
if (_dialog != null)
{
if (lstTasks == null || lstTasks.SelectedIndices.Count == 0)
try { _dialog.BringToFront(); return; }
catch { _dialog = null; }
}
bool isAdd = existing == null;
int kindIdx = isAdd ? 0 : Math.Max(0, Array.IndexOf(KindNames, existing.Kind.ToString()));
int startIdx = Math.Max(0, Array.IndexOf(StartTypeNames,
(isAdd ? TaskStartType.AutoLoop : existing.StartType).ToString()));
string curText = (isAdd ? 0 : Clamp(existing.CurrentStationId, 0, 1000000)).ToString();
string tgtText = (isAdd ? 0 : Clamp(existing.TargetStationId, 0, 1000000)).ToString();
string trafficText = (isAdd ? 0 : Clamp(existing.TrafficControl, 0, 1000)).ToString();
string priText = (isAdd ? 1 : Clamp(existing.Priority, 0, 100)).ToString();
bool via = !isAdd && existing.IsViaPoint;
string err = "";
// 不用 Modal:原生「模态弹窗 + 标题栏关闭X」的 EndPopup 配对 bug 会断言崩溃。
// 也不用 TopMost:置顶视口带 NoAutoMerge,会让 DropdownBox 的下拉弹窗落到独立非置顶视口里、被对话框挡在后面(看不到选项)。
// 故采用与 DeliveryViewer 相同的普通浮动面板(非模态、不停靠):Begin/End 路径,X 关闭干净,下拉弹窗 z 序正常。
var dlg = GUI.DeclarePanel()
.ShowTitle(isAdd ? "新增任务" : $"编辑任务 ID: {existing.Id}")
.SetDefaultDocking(Panel.Docking.None)
.InitSize(420, 380)
.InitPos(false, 0, 0, 0.5f, 0.5f, 0.5f, 0.5f);
_dialog = dlg;
dlg.IfTerminalQuit(() => _dialog = null);
dlg.Define(pb =>
{
if (pb.Closing())
{
dlg.Exit();
_dialog = null;
return;
// 收集被选中的索引并按降序删除,避免索引移动问题
var selectedIndices = lstTasks.SelectedIndices.Cast<int>().OrderByDescending(i => i).ToList();
// 构造确认提示
string prompt;
if (selectedIndices.Count == 1)
{
int idx = selectedIndices[0];
if (idx >= 0 && idx < tasks.Count)
prompt = $"确认删除任务 ID={tasks[idx].Id}";
else
prompt = "确认删除选中任务?";
}
else
{
prompt = $"确认删除所选 {selectedIndices.Count} 个任务?";
}
if (MessageBox.Show(prompt, "确认", MessageBoxButtons.YesNo, MessageBoxIcon.Question) != DialogResult.Yes)
return;
// 控件 id 由 ImHashStr(prompt) 经 Encoding.ASCII 计算:中文会被压成 '?',导致同字数纯中文标签
// (如“当前站点/目标站点”“任务类别/启动类型”)哈希相同而抛 Duplicated id。故各标签加唯一 ASCII 序号前缀以区分。
pb.DropdownBox("1. 任务类别", KindNames, ref kindIdx);
var (c, _) = pb.TextInput("2. 当前站点 (0~1000000)", curText, alwaysReturnString: true);
curText = c;
var (tg, _) = pb.TextInput("3. 目标站点 (0~1000000)", tgtText, alwaysReturnString: true);
tgtText = tg;
var (tf, _) = pb.TextInput("4. 流量控制 (0~1000)", trafficText, alwaysReturnString: true);
trafficText = tf;
var (pr, _) = pb.TextInput("5. 优先级 (0~100)", priText, alwaysReturnString: true);
priText = pr;
pb.CheckBox("6. 途径点", ref via);
pb.DropdownBox("7. 启动类型", StartTypeNames, ref startIdx);
// 删除任务
foreach (var idx in selectedIndices)
if (!string.IsNullOrEmpty(err))
{
if (idx >= 0 && idx < tasks.Count)
pb.Separator();
pb.Label(err);
}
pb.Separator();
if (pb.Button("保存", distinct: "loop-edit-save"))
{
if (!TryParseClamp(curText, 0, 1000000, out var cur)) { err = "当前站点需为 0~1000000 的整数"; return; }
if (!TryParseClamp(tgtText, 0, 1000000, out var tgt)) { err = "目标站点需为 0~1000000 的整数"; return; }
if (!TryParseClamp(trafficText, 0, 1000, out var traffic)) { err = "流量控制需为 0~1000 的整数"; return; }
if (!TryParseClamp(priText, 0, 100, out var pri)) { err = "优先级需为 0~100 的整数"; return; }
Enum.TryParse<TaskKind>(KindNames[kindIdx], out var kind);
Enum.TryParse<TaskStartType>(StartTypeNames[startIdx], out var st);
if (isAdd)
{
tasks.RemoveAt(idx);
}
}
// 如果被删除项包含当前正在编辑的项,退出编辑状态
if (editingIndex >= 0)
{
if (editingIndex >= tasks.Count || selectedIndices.Any(i => i == editingIndex))
{
editingIndex = -1;
UpdateSaveButtonText();
ClearPanelInputs();
_tasks.Add(new LoopTask
{
Id = GetNextTaskId(),
Kind = kind,
CurrentStationId = cur,
TargetStationId = tgt,
TrafficControl = traffic,
Priority = pri,
IsViaPoint = via,
StartType = st
});
_status = "已新增任务";
}
else
{
// 重新计算编辑索引在删除后的新位置
int removedBefore = selectedIndices.Count(i => i < editingIndex);
editingIndex -= removedBefore;
existing.Kind = kind;
existing.CurrentStationId = cur;
existing.TargetStationId = tgt;
existing.TrafficControl = traffic;
existing.Priority = pri;
existing.IsViaPoint = via;
existing.StartType = st;
_status = $"已保存任务 ID={existing.Id}";
}
}
// 持久化并刷新列表视图
Save();
RenderListView();
SaveTasks();
dlg.Exit();
_dialog = null;
_panel?.Repaint();
}
pb.SameLine(8);
if (pb.Button("取消", distinct: "loop-edit-cancel"))
{
dlg.Exit();
_dialog = null;
}
});
}
/// <summary>下一个可用任务 Id(当前最大 Id + 1,空表则为 1)。</summary>
private static int GetNextTaskId() => _tasks.Count == 0 ? 1 : _tasks.Max(t => t.Id) + 1;
private static void LoadTasks()
{
try
{
var path = JsonPath;
if (!File.Exists(path))
File.WriteAllText(path, "[]");
var text = File.ReadAllText(path);
_tasks = JsonConvert.DeserializeObject<List<LoopTask>>(text) ?? new List<LoopTask>();
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"OnDeleteSelectedTasks error: {ex}");
MessageBox.Show("删除失败:" + ex.Message);
_tasks = new List<LoopTask>();
_status = "加载 tasklist.json 失败,详见日志";
Diagnosis.Post($"LoopViewer 加载 tasklist.json 异常: {ExceptionFormatter.FormatEx(ex)}");
}
}
/// <summary>
/// 将 LoopViewer 的运行时样式调整为与 ChargeStationManagementForm 接近的视觉风格:
/// - 全局字体设为微软雅黑
/// - 表头暖色替换为蓝色沉稳风格(和充电界面一致)
/// - 按钮字号、背景色与充电界面保持一致(保存/删除/取消)
/// - 列表视图设置为整行选择、无边框、交替背景等
/// 注意:不修改 Designer 文件,仅在运行时统一控件表现,避免破坏设计器生成代码。
/// </summary>
private void ApplyChargeStyle()
/// <summary>序列化在渲染线程完成(极快),文件写入放后台线程,避免阻塞渲染线程。</summary>
private static void SaveTasks()
{
string json;
try
{
// 窗体级设置
this.StartPosition = FormStartPosition.CenterScreen;
this.MinimumSize = new System.Drawing.Size(1327, 738);
this.Font = new Font("微软雅黑", 9F, FontStyle.Regular);
// 调整 ListView(如果存在)
if (lstTasks != null)
{
lstTasks.View = View.Details;
lstTasks.FullRowSelect = true;
lstTasks.GridLines = false;
lstTasks.HeaderStyle = ColumnHeaderStyle.Nonclickable;
lstTasks.OwnerDraw = true; // 已有自定义绘制
lstTasks.BackColor = Color.White;
lstTasks.ForeColor = Color.FromArgb(33, 33, 33);
// 多选由初始化时控制(这里不强制)
}
// 下拉框统一字体
if (cmbTaskKind != null) cmbTaskKind.Font = new Font("微软雅黑", 10F, FontStyle.Regular);
if (cmbStartType != null) cmbStartType.Font = new Font("微软雅黑", 10F, FontStyle.Regular);
// 数值输入框统一字体
if (numCurrent != null) numCurrent.Font = new Font("微软雅黑", 10F, FontStyle.Regular);
if (numTarget != null) numTarget.Font = new Font("微软雅黑", 10F, FontStyle.Regular);
if (numTraffic != null) numTraffic.Font = new Font("微软雅黑", 10F, FontStyle.Regular);
if (numPriority != null) numPriority.Font = new Font("微软雅黑", 10F, FontStyle.Regular);
// 标签字体统一
if (lblEditingId != null) lblEditingId.Font = new Font("微软雅黑", 10F, FontStyle.Bold);
// 按钮风格:与 ChargeStationManagementForm 保持一致的视觉优先级
if (btnSave != null)
{
btnSave.BackColor = Color.LightBlue;
btnSave.ForeColor = Color.Black;
btnSave.Font = new Font("微软雅黑", 11F, FontStyle.Bold);
btnSave.FlatStyle = FlatStyle.Flat;
}
if (btnDelete != null)
{
btnDelete.BackColor = Color.LightCoral;
btnDelete.ForeColor = Color.Black;
btnDelete.Font = new Font("微软雅黑", 11F, FontStyle.Bold);
btnDelete.FlatStyle = FlatStyle.Flat;
}
if (btnCancel != null)
{
btnCancel.BackColor = SystemColors.Control;
btnCancel.ForeColor = Color.Black;
btnCancel.Font = new Font("微软雅黑", 11F, FontStyle.Regular);
btnCancel.FlatStyle = FlatStyle.Flat;
}
// 如果存在额外的操作按钮(例如在面板上),尝试统一风格(容错)
foreach (Control ctrl in this.Controls)
{
if (ctrl is Panel pnl)
{
pnl.Padding = new Padding(12);
}
else if (ctrl is Button btn)
{
// 已设置主要按钮,其他按钮使用中性风格
if (btn == btnSave || btn == btnDelete || btn == btnCancel) continue;
btn.Font = new Font("微软雅黑", 10F, FontStyle.Regular);
}
}
json = JsonConvert.SerializeObject(_tasks, Formatting.Indented);
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"ApplyChargeStyle error: {ex}");
_status = "保存失败,详见日志";
Diagnosis.Post($"LoopViewer 序列化 tasklist.json 异常: {ExceptionFormatter.FormatEx(ex)}");
return;
}
}
private void EnsureComboItems()
{
try
var path = JsonPath;
Task.Run(() =>
{
if (cmbTaskKind != null && cmbTaskKind.Items.Count == 0)
try
{
cmbTaskKind.Items.AddRange(new object[] { "Loop", "BranchPoint", "JoinPoint" });
cmbTaskKind.SelectedIndex = 0;
lock (SaveLock)
File.WriteAllText(path, json);
}
if (cmbStartType != null && cmbStartType.Items.Count == 0)
catch (Exception ex)
{
cmbStartType.Items.AddRange(new object[] { "Api", "Plc", "ButtonBox", "AutoLoop" });
cmbStartType.SelectedIndex = 3;
_status = "保存失败,详见日志";
Diagnosis.Post($"LoopViewer 保存 tasklist.json 异常: {ExceptionFormatter.FormatEx(ex)}");
_panel?.Repaint();
}
// 确保下拉框字体一致(防止 Designer 未设置)
if (cmbTaskKind != null) cmbTaskKind.Font = new Font("微软雅黑", 10F, FontStyle.Regular);
if (cmbStartType != null) cmbStartType.Font = new Font("微软雅黑", 10F, FontStyle.Regular);
}
catch { }
});
}
#region /
private void InitOrLoadJson()
private static int Clamp(int v, int min, int max) => v < min ? min : (v > max ? max : v);
private static bool TryParseClamp(string s, int min, int max, out int value)
{
try
if (int.TryParse((s ?? "").Trim(), out value))
{
if (!File.Exists(jsonPath))
File.WriteAllText(jsonPath, "[]");
var text = File.ReadAllText(jsonPath);
tasks = JsonConvert.DeserializeObject<List<LoopTask>>(text) ?? new List<LoopTask>();
}
catch (Exception ex)
{
tasks = new List<LoopTask>();
System.Diagnostics.Debug.WriteLine($"Load tasks failed: {ex}");
value = Clamp(value, min, max);
return true;
}
value = min;
return false;
}
#endregion
#region ID
/// <summary>
/// 获取下一个可用的任务ID(当前最大ID + 1)
/// </summary>
/// <returns>新的任务ID</returns>
private int GetNextTaskId()
{
if (tasks == null || tasks.Count == 0)
return 1;
int maxId = tasks.Max(t => t.Id);
return maxId + 1;
}
#endregion
#region OwnerDraw +
private void lstTasks_DrawColumnHeader(object sender, DrawListViewColumnHeaderEventArgs e)
{
try
{
// 与 ChargeStationManagementForm 表头保持一致的深蓝背景与白色加粗字体
using (var backBrush = new SolidBrush(Color.FromArgb(63, 81, 181))) // 深蓝(与 Charge 界面一致)
using (var textBrush = new SolidBrush(Color.White)) // 白色文字
using (var font = new Font("微软雅黑", 9, FontStyle.Bold))
{
e.Graphics.FillRectangle(backBrush, e.Bounds);
var sf = new StringFormat { LineAlignment = StringAlignment.Center, Alignment = StringAlignment.Near };
var rect = e.Bounds;
rect.Inflate(-8, 0);
e.Graphics.DrawString(e.Header.Text, font, textBrush, rect, sf);
// 分隔线
using (var pen = new Pen(Color.FromArgb(200, 200, 200)))
{
e.Graphics.DrawLine(pen, e.Bounds.Left, e.Bounds.Bottom - 1, e.Bounds.Right, e.Bounds.Bottom - 1);
}
}
}
catch
{
e.DrawBackground();
e.DrawText();
}
}
private void lstTasks_DrawItem(object sender, DrawListViewItemEventArgs e)
{
// 由 DrawSubItem 绘制全部内容以保证每列对齐
}
private void lstTasks_DrawSubItem(object sender, DrawListViewSubItemEventArgs e)
{
try
{
var item = e.Item;
bool selected = item.Selected;
Rectangle bounds = e.Bounds;
// 选中行颜色:与 ChargeStationManagementForm 保持一致的蓝色强调
Color selectedBack = Color.FromArgb(0, 120, 215);
Color selectedFore = Color.White;
// 非选中行交替背景
Color evenBack = Color.White;
Color oddBack = Color.FromArgb(250, 251, 253);
Color normalFore = Color.FromArgb(33, 33, 33);
// 填充背景
if (selected)
{
using (var selBrush = new SolidBrush(selectedBack))
{
e.Graphics.FillRectangle(selBrush, bounds);
}
}
else
{
using (var back = new SolidBrush(e.ItemIndex % 2 == 0 ? evenBack : oddBack))
{
e.Graphics.FillRectangle(back, bounds);
}
}
// 绘制文本(加一点内边距)
string text = e.SubItem.Text ?? string.Empty;
Color fore = selected ? selectedFore : normalFore;
TextFormatFlags flags = TextFormatFlags.Left | TextFormatFlags.VerticalCenter;
Rectangle textRect = bounds;
textRect.Inflate(-6, 0);
using (var font = new Font("微软雅黑", 9))
{
TextRenderer.DrawText(e.Graphics, text, font, textRect, fore, flags);
}
}
catch
{
e.DrawBackground();
e.DrawText();
}
}
#endregion
#region /
private void RenderListView()
{
try
{
if (lstTasks == null) return;
lstTasks.BeginUpdate();
lstTasks.Items.Clear();
foreach (var t in tasks)
{
var lvi = new ListViewItem(new[]
{
t.Id.ToString(), // ID 列
t.Kind.ToString(),
t.CurrentStationId.ToString(),
t.TargetStationId.ToString(),
t.TrafficControl.ToString(),
t.Priority.ToString(),
t.IsViaPoint ? "是" : "否",
t.StartType.ToString()
});
lstTasks.Items.Add(lvi);
}
lstTasks.EndUpdate();
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"RenderListView failed: {ex}");
}
}
private void Save()
{
try
{
File.WriteAllText(jsonPath, JsonConvert.SerializeObject(tasks, Formatting.Indented));
}
catch (Exception ex)
{
MessageBox.Show("保存失败:" + ex.Message);
}
}
#endregion
#region /
private void UpdateSaveButtonText()
{
if (btnSave != null)
{
// 文案固定为"保存"
btnSave.Text = "保存";
}
}
private void btnSave_Click(object sender, EventArgs e)
{
try
{
// 从面板读取值,直接在界面内编辑/新增
Enum.TryParse<TaskKind>(cmbTaskKind?.SelectedItem?.ToString() ?? "Loop", out var kind);
Enum.TryParse<TaskStartType>(cmbStartType?.SelectedItem?.ToString() ?? "AutoLoop", out var st);
if (editingIndex >= 0 && editingIndex < tasks.Count)
{
// 更新模式:保留原有ID
var existingTask = tasks[editingIndex];
existingTask.Kind = kind;
existingTask.CurrentStationId = (int)(numCurrent?.Value ?? 0);
existingTask.TargetStationId = (int)(numTarget?.Value ?? 0);
existingTask.TrafficControl = (int)(numTraffic?.Value ?? 0);
existingTask.Priority = (int)(numPriority?.Value ?? 1);
existingTask.IsViaPoint = chkViaPoint?.Checked ?? false;
existingTask.StartType = st;
}
else
{
// 新增模式:自动分配新ID
var t = new LoopTask
{
Id = GetNextTaskId(), // 自增ID
Kind = kind,
CurrentStationId = (int)(numCurrent?.Value ?? 0),
TargetStationId = (int)(numTarget?.Value ?? 0),
TrafficControl = (int)(numTraffic?.Value ?? 0),
Priority = (int)(numPriority?.Value ?? 1),
IsViaPoint = chkViaPoint?.Checked ?? false,
StartType = st
};
tasks.Add(t);
}
Save();
RenderListView();
// 恢复新增状态
editingIndex = -1;
UpdateSaveButtonText();
ClearPanelInputs();
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"btnSave_Click error: {ex}");
MessageBox.Show("操作失败:" + ex.Message);
}
}
private void btnCancel_Click(object sender, EventArgs e)
{
// 取消编辑,清空面板并回到"添加"模式
editingIndex = -1;
UpdateSaveButtonText();
ClearPanelInputs();
}
private void btnEdit_Click(object sender, EventArgs e)
{
try
{
if (lstTasks == null || lstTasks.SelectedIndices.Count == 0) return;
int idx = lstTasks.SelectedIndices[0];
if (idx < 0 || idx >= tasks.Count) return;
editingIndex = idx;
LoadTaskToPanel(tasks[idx]);
UpdateSaveButtonText();
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"btnEdit_Click error: {ex}");
}
}
private void btnDelete_Click(object sender, EventArgs e)
{
// 兼容旧的删除按钮:复用统一删除逻辑
OnDeleteSelectedTasks();
}
#endregion
#region
private void lstTasks_MouseDoubleClick(object sender, MouseEventArgs e)
{
try
{
if (lstTasks == null) return;
var item = lstTasks.GetItemAt(e.X, e.Y);
if (item == null) return;
int idx = item.Index;
if (idx < 0 || idx >= tasks.Count) return;
editingIndex = idx;
LoadTaskToPanel(tasks[idx]);
UpdateSaveButtonText();
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"lstTasks_MouseDoubleClick error: {ex}");
}
}
#endregion
#region
private void LoadTaskToPanel(LoopTask t)
{
if (t == null) return;
try
{
// 显示当前编辑的任务ID(只读显示)
if (lblEditingId != null) lblEditingId.Text = $"编辑任务 ID: {t.Id}";
if (cmbTaskKind != null) cmbTaskKind.SelectedItem = t.Kind.ToString();
if (numCurrent != null) numCurrent.Value = Math.Max(numCurrent.Minimum, Math.Min(numCurrent.Maximum, t.CurrentStationId));
if (numTarget != null) numTarget.Value = Math.Max(numTarget.Minimum, Math.Min(numTarget.Maximum, t.TargetStationId));
if (numTraffic != null) numTraffic.Value = Math.Max(numTraffic.Minimum, Math.Min(numTraffic.Maximum, t.TrafficControl));
if (numPriority != null) numPriority.Value = Math.Max(numPriority.Minimum, Math.Min(numPriority.Maximum, t.Priority));
if (chkViaPoint != null) chkViaPoint.Checked = t.IsViaPoint;
if (cmbStartType != null) cmbStartType.SelectedItem = t.StartType.ToString();
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"LoadTaskToPanel error: {ex}");
}
}
private void ClearPanelInputs()
{
try
{
// 清除编辑ID显示
if (lblEditingId != null) lblEditingId.Text = "新增任务";
if (cmbTaskKind != null) cmbTaskKind.SelectedIndex = 0;
if (numCurrent != null) numCurrent.Value = 0;
if (numTarget != null) numTarget.Value = 0;
if (numTraffic != null) numTraffic.Value = 0;
if (numPriority != null) numPriority.Value = 1;
if (chkViaPoint != null) chkViaPoint.Checked = false;
if (cmbStartType != null) cmbStartType.SelectedIndex = 3;
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"ClearPanelInputs error: {ex}");
}
}
#endregion
}
}
}
-120
View File
@@ -1,120 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>
@@ -22,7 +22,6 @@ using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using Microsoft.Win32.SafeHandles;
namespace StandardScene.Chained
@@ -377,7 +376,7 @@ namespace StandardScene.Chained
{
G.pushStatus("选择小车");
var selected = SimpleMonitor.selected.ToArray();
if (selected.Length == 0) { MessageBox.Show("请先选择需要控制的小车!"); return; }
if (selected.Length == 0) { CycleUiHelper.Alert("提示", "请先选择需要控制的小车!"); return; }
var obj = selected[0];
if (obj is Car car)
{
@@ -401,7 +400,7 @@ namespace StandardScene.Chained
}
else
{
MessageBox.Show("请选择需要控制的小车!");
CycleUiHelper.Alert("提示", "请选择需要控制的小车!");
}
}
catch
@@ -428,7 +427,7 @@ namespace StandardScene.Chained
{
G.pushStatus("选择小车");
var selected = SimpleMonitor.selected.ToArray();
if (selected.Length == 0) { MessageBox.Show("请先选择需要控制的小车!"); return; }
if (selected.Length == 0) { CycleUiHelper.Alert("提示", "请先选择需要控制的小车!"); return; }
var obj = selected[0];
if (obj is Car car)
{
@@ -453,7 +452,7 @@ namespace StandardScene.Chained
}
else
{
MessageBox.Show("请选择需要控制的小车!");
CycleUiHelper.Alert("提示", "请选择需要控制的小车!");
}
}
catch