init commit
This commit is contained in:
@@ -0,0 +1,413 @@
|
||||
using SimpleCore.Traffic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using SimpleLite.RCS;
|
||||
using SimpleLite.RCS.CarTypes;
|
||||
using SimpleCore.Library;
|
||||
using SimpleCore.PropType;
|
||||
using SimpleCore;
|
||||
using System.Threading;
|
||||
using SimpleLite.CADTools;
|
||||
using SimpleLite.Props;
|
||||
using SimpleLite.UI;
|
||||
using SimpleLite;
|
||||
using System.Windows.Forms;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace StandardScene.InterLock
|
||||
{
|
||||
public class MyDictionary<TKey, TItem> where TItem : new()
|
||||
{
|
||||
private readonly Dictionary<TKey, TItem> dictionary = new();
|
||||
|
||||
public TItem this[TKey key]
|
||||
{
|
||||
get
|
||||
{
|
||||
if (dictionary.TryGetValue(key, out var item)) return item;
|
||||
var newItem = new TItem();
|
||||
dictionary[key] = newItem;
|
||||
return newItem;
|
||||
}
|
||||
set => dictionary[key] = value;
|
||||
}
|
||||
|
||||
public ICollection<TKey> Keys
|
||||
{
|
||||
get => dictionary.Keys;
|
||||
}
|
||||
}
|
||||
|
||||
public class AbstractInterlockMission : Mission
|
||||
{
|
||||
public bool GetAskEnter(int siteId)
|
||||
{
|
||||
if (!SiteFilter(siteId)) return false;
|
||||
lock (sync) return askEnter[siteId];
|
||||
}
|
||||
|
||||
public void SetAllowEnter(int siteId, bool value)
|
||||
{
|
||||
if (!SiteFilter(siteId)) return;
|
||||
lock (sync) allowEnter[siteId] = value;
|
||||
}
|
||||
|
||||
public bool GetAskExit(int siteId)
|
||||
{
|
||||
if (!SiteFilter(siteId)) return false;
|
||||
lock (sync) return askExit[siteId];
|
||||
}
|
||||
|
||||
public void SetAllowExit(int siteId, bool value)
|
||||
{
|
||||
if (!SiteFilter(siteId)) return;
|
||||
lock (sync) allowExit[siteId] = value;
|
||||
}
|
||||
|
||||
public bool GetReportLeave(int siteId)
|
||||
{
|
||||
if (!SiteFilter(siteId)) return false;
|
||||
lock (sync) return reportLeave[siteId];
|
||||
}
|
||||
|
||||
public void SetAcknowledgeLeave(int siteId, bool value)
|
||||
{
|
||||
if (!SiteFilter(siteId)) return;
|
||||
lock (sync) acknowledgeLeave[siteId] = value;
|
||||
}
|
||||
|
||||
public int GetInSiteCarId(int siteId)
|
||||
{
|
||||
if (!SiteFilter(siteId)) return -1;
|
||||
lock (sync) return inSiteCarId[siteId];
|
||||
}
|
||||
|
||||
// 请求进站(写)
|
||||
[JsonIgnore] private readonly MyDictionary<int, bool> askEnter = new();
|
||||
// 允许进站(读)
|
||||
[JsonIgnore] private readonly MyDictionary<int, bool> allowEnter = new();
|
||||
|
||||
// 请求离站(写)
|
||||
[JsonIgnore] private readonly MyDictionary<int, bool> askExit = new();
|
||||
// 允许离站(读)
|
||||
[JsonIgnore] private readonly MyDictionary<int, bool> allowExit = new();
|
||||
|
||||
// 报告离开(写)
|
||||
[JsonIgnore] private readonly MyDictionary<int, bool> reportLeave = new();
|
||||
// 确认离开(读)
|
||||
[JsonIgnore] private readonly MyDictionary<int, bool> acknowledgeLeave = new();
|
||||
|
||||
// 站点上所在的小车(状态)
|
||||
[JsonIgnore] private readonly MyDictionary<int, int> inSiteCarId = new();
|
||||
|
||||
[JsonIgnore] private object sync = new();
|
||||
|
||||
public virtual string GetSiteDisplay(int siteId)
|
||||
{
|
||||
return $"{siteId}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 返回为true的站点,是当前互锁机制关注的站点
|
||||
/// </summary>
|
||||
/// <param name="siteId"></param>
|
||||
/// <returns></returns>
|
||||
public virtual bool SiteFilter(int siteId)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 互锁站点进站条件
|
||||
/// </summary>
|
||||
/// <param name="car"></param>
|
||||
/// <param name="siteId"></param>
|
||||
/// <returns></returns>
|
||||
private bool EnterCondition(AbstractCar car, int siteId)
|
||||
{
|
||||
// todo: 是否考虑提前更多申请进入,加快节拍?
|
||||
if (car.status.pendingLocks.First() != siteId) return false;
|
||||
//DateTime startTime = DateTime.Now;
|
||||
//bool validTime = false;
|
||||
|
||||
lock (sync)
|
||||
{
|
||||
// if (askEnter[siteId] == false)
|
||||
// {
|
||||
// Diagnosis.Post($"{car.name}({car.id}) 申请进站 {GetSiteDisplay(siteId)}", "intertime", true);
|
||||
// //startTime = DateTime.Now;
|
||||
// //validTime = true;
|
||||
// }
|
||||
askEnter[siteId] = true;
|
||||
if (reportLeave[siteId])
|
||||
{
|
||||
Diagnosis.Post($"{car.name}({car.id})未被准许进站{GetSiteDisplay(siteId)},前车离开信号未被确认", "interlock", true);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (allowEnter[siteId])
|
||||
{
|
||||
Diagnosis.Post($"{car.name}({car.id})准许进站{GetSiteDisplay(siteId)}", "interlock", true);
|
||||
// Diagnosis.Post($"{car.name}({car.id}) 准许进站 {GetSiteDisplay(siteId)}", "intertime", true);
|
||||
lock (sync) askEnter[siteId] = false;
|
||||
return true;
|
||||
}
|
||||
Diagnosis.Post($"{car.name}({car.id})未被准许进站{GetSiteDisplay(siteId)}", "interlock", true);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 互锁站点离站条件
|
||||
/// </summary>
|
||||
/// <param name="car"></param>
|
||||
/// <param name="siteId"></param>
|
||||
/// <returns></returns>
|
||||
private bool ExitCondition(AbstractCar car, int siteId)
|
||||
{
|
||||
lock (sync)
|
||||
{
|
||||
askExit[siteId] = true;
|
||||
|
||||
if (allowExit[siteId])
|
||||
{
|
||||
Diagnosis.Post($"{car.name}({car.id})准许离站{GetSiteDisplay(siteId)}", "interlock", true);
|
||||
askExit[siteId] = false;
|
||||
return true;
|
||||
}
|
||||
Diagnosis.Post($"{car.name}({car.id})未被准许离站{GetSiteDisplay(siteId)}", "interlock", true);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 告知对方设备小车离开,直到对方设备确认收到离开信号
|
||||
/// </summary>
|
||||
/// <param name="car"></param>
|
||||
/// <param name="siteId"></param>
|
||||
private void LeaveEvent(AbstractCar car, int siteId)
|
||||
{
|
||||
new Thread(() =>
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
Thread.Sleep(100);
|
||||
lock (sync)
|
||||
{
|
||||
if (acknowledgeLeave[siteId]) // 已经确认小车离开
|
||||
{
|
||||
Diagnosis.Post($"{GetSiteDisplay(siteId)}确认{car.name}({car.id})离开", "interlock", true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
Diagnosis.Post($"{car.name}({car.id})告知离开{GetSiteDisplay(siteId)}", "interlock", true);
|
||||
lock (sync) reportLeave[siteId] = true;
|
||||
}
|
||||
|
||||
lock (sync) reportLeave[siteId] = false;
|
||||
}).Start();
|
||||
}
|
||||
|
||||
[JsonIgnore] private bool enableSimulation = false;
|
||||
[JsonIgnore] public bool started = false;
|
||||
[JsonIgnore] public Thread showT;
|
||||
|
||||
[MethodMember(Name = "启动进程", Description = "处理安全互锁")]
|
||||
public override void Execute()
|
||||
{
|
||||
if (started) return;
|
||||
// started = true;
|
||||
status.status = "已启动";
|
||||
|
||||
|
||||
|
||||
showT = new Thread(() =>
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
Thread.Sleep(300);
|
||||
try
|
||||
{
|
||||
if (fields.TryGetValue("enableSimulation", out var userEnableSimulation))
|
||||
enableSimulation = bool.Parse(userEnableSimulation);
|
||||
foreach (var site in SimpleLib.GetAllSites().Where(ss => SiteFilter(ss.id)))
|
||||
{
|
||||
var carId = -1;
|
||||
foreach (var car in SimpleLib.GetAllCars())
|
||||
{
|
||||
lock (TrafficControl.syncTrafficSequence)
|
||||
{
|
||||
if (car.status.holdingLocks.Length == 1 && car.status.holdingLocks[0] == site.id)
|
||||
{
|
||||
carId = car.id;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
lock (sync) inSiteCarId[site.id] = carId;
|
||||
}
|
||||
|
||||
// if (!onDisplay) continue;
|
||||
var painter = SimpleMonitor.getPainter("DemoInterlockMission");
|
||||
painter.clear();
|
||||
if (!onDisplay) continue;
|
||||
//string str;
|
||||
//lock (sync)
|
||||
//{
|
||||
// var keys = askEnter.Keys.Concat(askExit.Keys).Concat(reportLeave.Keys).ToHashSet()
|
||||
// .OrderBy(kk => int.Parse(SimpleLib.GetSite(kk).fields["ASNub"])).ToList();
|
||||
// str = $"站点\t\t请求进站\t允许进站\t请求离站\t允许离站\t上报离开\t确认离开\t小车id\n" +
|
||||
// $"{string.Join("\n", keys.Select(key =>
|
||||
// $"{GetSiteDisplay(key)}\t{askEnter[key]}\t\t{allowEnter[key]}\t\t" +
|
||||
// $"{askExit[key]}\t\t{allowExit[key]}\t\t" +
|
||||
// $"{reportLeave[key]}\t\t{acknowledgeLeave[key]}\t\t{inSiteCarId[key]}"))}";
|
||||
//}
|
||||
//painter.drawTextFixed(str, new SolidBrush(Color.Black), VirtualPainter.DrawPosition.RightTop, Color.AliceBlue);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Diagnosis.Post(ExceptionFormatter.FormatEx(ex), "interlock", true);
|
||||
}
|
||||
}
|
||||
}) { Name = "InterlockMission" };
|
||||
|
||||
showT.Start();
|
||||
|
||||
|
||||
}
|
||||
|
||||
[JsonIgnore] private bool onDisplay = true;
|
||||
|
||||
[MethodMember(Name = "切换显示", Description = "是否在右下角显示")]
|
||||
public void SwitchAlwaysOnDisplay()
|
||||
{
|
||||
onDisplay = !onDisplay;
|
||||
}
|
||||
|
||||
private async void SimButton(string title, MyDictionary<int, bool> toManipulate)
|
||||
{
|
||||
//if (!enableSimulation) return;
|
||||
G.pushStatus("请选择站点");
|
||||
var pt = await Program.UI.getPoint(new UIOps.getPointOptions() { site = true });
|
||||
var site = SimpleLib.GetSite(pt.site);
|
||||
if (SiteFilter(site.id))
|
||||
{
|
||||
var vv = await Program.UI.Input("请输入true/false", title, "true");
|
||||
if (vv == null || !bool.TryParse(vv, out var allow))
|
||||
{
|
||||
MessageBox.Show("输入错误");
|
||||
return;
|
||||
}
|
||||
lock (sync) toManipulate[site.id] = allow;
|
||||
}
|
||||
}
|
||||
|
||||
[MethodMember(Name = "模拟准许进站")]
|
||||
public void SimAllowEnter()
|
||||
{
|
||||
SimButton("模拟准许进入状态", allowEnter);
|
||||
}
|
||||
|
||||
[MethodMember(Name = "模拟准许离站")]
|
||||
public void SimAllowExit()
|
||||
{
|
||||
SimButton("模拟准许离站状态", allowExit);
|
||||
}
|
||||
|
||||
[MethodMember(Name = "模拟确认离开")]
|
||||
public void SimLeaveNoted()
|
||||
{
|
||||
SimButton("模拟确认离开状态", acknowledgeLeave);
|
||||
}
|
||||
|
||||
[MethodMember(Name = "模拟发送离开")]
|
||||
public async void SimReportLeave()
|
||||
{
|
||||
// if (!enableSimulation) return;
|
||||
G.pushStatus("请选择站点");
|
||||
var pt = await Program.UI.getPoint(new UIOps.getPointOptions() { site = true });
|
||||
var site = SimpleLib.GetSite(pt.site);
|
||||
if (SiteFilter(site.id))
|
||||
{
|
||||
lock (sync) reportLeave[site.id] = true;
|
||||
}
|
||||
new Thread(() =>
|
||||
{
|
||||
var t1 = DateTime.Now;
|
||||
while (true)
|
||||
{
|
||||
Thread.Sleep(100);
|
||||
lock (sync)
|
||||
{
|
||||
var deltaT = (DateTime.Now - t1).TotalSeconds;
|
||||
if (acknowledgeLeave[site.id]||deltaT>5) // 已经确认小车离开
|
||||
{
|
||||
Diagnosis.Post($"{GetSiteDisplay(site.id)} 停止发送离开", "simlikai", true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
Diagnosis.Post($"模拟告知离开{GetSiteDisplay(site.id)}", "simlikai", true);
|
||||
lock (sync) reportLeave[site.id] = true;
|
||||
}
|
||||
|
||||
lock (sync) reportLeave[site.id] = false;
|
||||
}).Start();
|
||||
// SimButton("模拟确认离开状态", reportLeave);
|
||||
}
|
||||
|
||||
public AbstractInterlockMission()
|
||||
{
|
||||
Diagnosis.Post($"InterlockMechanism loaded", "interlock", true);
|
||||
TrafficControl.BeforeLock += (car, siteId) =>
|
||||
{
|
||||
if (SiteFilter(siteId)) // EnterCondition
|
||||
return EnterCondition(car, siteId);
|
||||
|
||||
var site = SimpleLib.GetSite(siteId);
|
||||
if (site.fields.ContainsKey("PreAskEnterSite") && int.TryParse(site.fields["PreAskEnterSite"], out int PreAskEnterSiteId))
|
||||
{
|
||||
var presite = SimpleLib.GetSite(PreAskEnterSiteId);
|
||||
if (presite != null && SiteFilter(PreAskEnterSiteId))
|
||||
{
|
||||
Diagnosis.Post($"{car.name}({car.id})在{siteId}-{GetSiteDisplay(siteId)},提前申请 {PreAskEnterSiteId} 的请求进入", "interlock", true);
|
||||
askEnter[PreAskEnterSiteId] = true;
|
||||
}
|
||||
|
||||
}
|
||||
// ExitCondition
|
||||
lock (TrafficControl.syncTrafficSequence)
|
||||
if (!car.status.holdingLocks.Any(ss => SiteFilter(ss)))
|
||||
return true;
|
||||
|
||||
|
||||
var isNeighbor = false;
|
||||
var lockSiteId = -1;
|
||||
foreach (var trackId in site.relatedTracks)
|
||||
{
|
||||
var track = SimpleLib.GetTrack(trackId);
|
||||
if (SiteFilter(track.siteA))
|
||||
{
|
||||
isNeighbor = true;
|
||||
lockSiteId = track.siteA;
|
||||
break;
|
||||
}
|
||||
|
||||
if (SiteFilter(track.siteB))
|
||||
{
|
||||
isNeighbor = true;
|
||||
lockSiteId = track.siteB;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return !isNeighbor || ExitCondition(car, lockSiteId);
|
||||
};
|
||||
TrafficControl.AfterLeave += (car, siteId) =>
|
||||
{
|
||||
if (SiteFilter(siteId)) LeaveEvent(car, siteId);
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
using LessokajiWeaverUtilities.Utilities;
|
||||
using LoopViewerApp;
|
||||
using Newtonsoft.Json;
|
||||
using SimpleLite.RCS;
|
||||
using SimpleLite.RCS.CarTypes;
|
||||
using SimpleCore;
|
||||
using SimpleCore.PropType;
|
||||
using SimpleCore.Traffic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace StandardScene.InterLock
|
||||
{
|
||||
[MissionType(Name = "交通管制", editor = typeof(TrafficInterlockMission))]
|
||||
[I18N.DocumentTranslation(Name = "Traffic Mission", locale = "en")]
|
||||
public class TrafficInterlockMission : Mission
|
||||
{
|
||||
public static List<TrafficArea> TrafficAreaList { get; set; } = new List<TrafficArea>();
|
||||
|
||||
public TrafficInterlockMission()
|
||||
{
|
||||
TrafficControl.BeforeLock += (car, siteId) =>
|
||||
{
|
||||
return EnterArea(car, siteId);
|
||||
};
|
||||
|
||||
TrafficControl.AfterLeave += (car, siteId) =>
|
||||
{
|
||||
LeaveArea(car, siteId);
|
||||
};
|
||||
}
|
||||
|
||||
[MethodMember(Name = "启动进程", Description = "开始交通监控")]
|
||||
public void StartTrafficControl()
|
||||
{
|
||||
if (InitTrafficConfig()) { status.status = "已启动"; }
|
||||
}
|
||||
|
||||
[MethodMember(Name = "停止进程", Description = "停止交通监控")]
|
||||
public void StopTrafficControl()
|
||||
{
|
||||
status.status = "已停止";
|
||||
}
|
||||
|
||||
[MethodMember(Name = "查看管制区")]
|
||||
public void ShowViewer()
|
||||
{
|
||||
var viewer = new TrafficInterlockViewer();
|
||||
viewer.Show();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 初始化交管配置文件
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private bool InitTrafficConfig()
|
||||
{
|
||||
try
|
||||
{
|
||||
string ConfigPath = Path.Combine(Application.StartupPath, "Config/traffic.json");
|
||||
|
||||
if (File.Exists(ConfigPath))
|
||||
{
|
||||
TrafficAreaList = JsonConvert.DeserializeObject<List<TrafficArea>>(File.ReadAllText(ConfigPath));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine("初始化交管配置文件失败: " + ex.Message); return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 判断小车是否可以进入区域
|
||||
/// </summary>
|
||||
/// <param name="car"></param>
|
||||
/// <param name="siteId"></param>
|
||||
/// <returns></returns>
|
||||
public bool EnterArea(AbstractCar car, int siteId)
|
||||
{
|
||||
var TrafficAreas = TrafficAreaList.FindAll(area => area.SiteList.Contains(siteId) && area.IsEnable);
|
||||
|
||||
if (TrafficAreas.Count == 0) { return true; }
|
||||
|
||||
if (!TrafficAreas.Exists(t => t.IsOccupy && t.ControllerName != car.id.ToString()))
|
||||
{
|
||||
lock (TrafficAreaList)
|
||||
{
|
||||
TrafficAreas.ForEach(t => { t.IsOccupy = true; t.ControllerName = car.id.ToString(); });
|
||||
}
|
||||
|
||||
Console.WriteLine($"小车 {car.id} 进入了交管区域 {string.Join(",", TrafficAreas.Select(t => t.AreaName))}");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 离开区域
|
||||
/// </summary>
|
||||
/// <param name="car"></param>
|
||||
/// <param name="siteId"></param>
|
||||
/// <returns></returns>
|
||||
public bool LeaveArea(AbstractCar car, int siteId)
|
||||
{
|
||||
var TrafficAreas = TrafficAreaList.FindAll(area => area.SiteList.Contains(siteId) && area.IsEnable);
|
||||
|
||||
if (TrafficAreas.Count == 0) { return true; }
|
||||
|
||||
foreach (var item in TrafficAreas)
|
||||
{
|
||||
var Sites = SimpleLib.GetAllSites().Where(s => item.SiteList.Contains(s.id)).ToList();
|
||||
|
||||
if (!Sites.Exists(t => t.status.owner == car.id))
|
||||
{
|
||||
lock (TrafficAreaList)
|
||||
{
|
||||
item.ControllerName = string.Empty; item.IsOccupy = false;
|
||||
}
|
||||
|
||||
Console.WriteLine($"小车 {car.id} 离开了交管区域 {item.AreaName}");
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public class TrafficArea
|
||||
{
|
||||
/// <summary>
|
||||
/// 区域名称
|
||||
/// </summary>
|
||||
public string AreaName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 区域站点集合
|
||||
/// </summary>
|
||||
public List<int> SiteList { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 控制权
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public string ControllerName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否被占用
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public bool IsOccupy { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否启用
|
||||
/// </summary>
|
||||
public bool IsEnable { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,325 @@
|
||||
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);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,370 @@
|
||||
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;
|
||||
|
||||
namespace LoopViewerApp
|
||||
{
|
||||
public partial class TrafficInterlockViewer : Form
|
||||
{
|
||||
/// <summary>-1 表示新增模式;>=0 表示正在编辑对应索引</summary>
|
||||
private int _editingIndex = -1;
|
||||
|
||||
/// <summary>选中行变化时是否允许加载到编辑区(避免在保存/取消时重复刷新)</summary>
|
||||
private bool _allowLoadFromSelection = true;
|
||||
|
||||
public TrafficInterlockViewer()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
if (LicenseManager.UsageMode == LicenseUsageMode.Designtime)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
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))
|
||||
{
|
||||
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
|
||||
{
|
||||
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)
|
||||
{
|
||||
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;
|
||||
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[]
|
||||
{
|
||||
a.AreaName ?? "",
|
||||
stationStr,
|
||||
a.ControllerName ?? "",
|
||||
a.IsOccupy ? "是" : "否",
|
||||
a.IsEnable ? "是" : "否"
|
||||
});
|
||||
|
||||
lstTasks.Items.Add(lvi);
|
||||
}
|
||||
lstTasks.EndUpdate();
|
||||
_allowLoadFromSelection = true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_allowLoadFromSelection = true;
|
||||
System.Diagnostics.Debug.WriteLine($"RenderListView failed: {ex}");
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
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}");
|
||||
}
|
||||
}
|
||||
|
||||
private void ClearPanelInputs()
|
||||
{
|
||||
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;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"ClearPanelInputs error: {ex}");
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 解析站点集合字符串 "1,2,3" -> List<int>
|
||||
|
||||
private static List<int> ParseStationIds(string text)
|
||||
{
|
||||
var list = new List<int>();
|
||||
if (string.IsNullOrWhiteSpace(text)) return list;
|
||||
foreach (var part in text.Split(new[] { ',', ';', ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries))
|
||||
{
|
||||
if (int.TryParse(part.Trim(), out int id))
|
||||
list.Add(id);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 按钮:保存 / 刷新 / 新增 / 删除
|
||||
|
||||
private void btnSave_Click(object sender, EventArgs e)
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
private void btnRefresh_Click(object sender, EventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
RenderListView();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"btnRefresh_Click error: {ex}");
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
<?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>
|
||||
Reference in New Issue
Block a user