using JinYuan.Helper; using JinYuan.Models; using JinYuan.VirtualDataLibrary; using Language; using System; using System.Collections.Generic; using System.Drawing; using System.Drawing.Text; using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; using System.Windows.Forms; using System.Windows.Forms.DataVisualization.Charting; namespace LargeSquareOne { public partial class FrmMonitor : MultiLanguageForm { private static readonly object uiLock = new object(); private static FrmMonitor _instance; private readonly CancellationTokenSource _cts = new CancellationTokenSource(); // 字体资源 private readonly PrivateFontCollection _fontCollection = new PrivateFontCollection(); private Font _myFont; // 缓存图表数据点数量,用于优化更新逻辑 private int _chart12HourPointCount = 0; private int _alarmChartPointCount = 0; private int _ngChartPointCount = 0; public static FrmMonitor Instance { get { lock (uiLock) { return _instance ??= new FrmMonitor(); } } } public FrmMonitor() { InitializeComponent(); InitializeFormSettings(); // 建议:如果字体是必须的,取消下面这行的注释,确保只加载一次 // LoadCustomFont(); this.FormClosing += FrmMonitor_FormClosing; this.Load += FrmMonitor_Load; } private void InitializeFormSettings() { // 开启双缓冲,减少闪烁 this.SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.DoubleBuffer | ControlStyles.ResizeRedraw | ControlStyles.Selectable | ControlStyles.SupportsTransparentBackColor, true); this.UpdateStyles(); } private void LoadCustomFont() { try { var fontPath = Path.Combine(Environment.CurrentDirectory, "Assts", "Fonts", "FZY4JW.ttf"); if (File.Exists(fontPath)) { _fontCollection.AddFontFile(fontPath); var myFontFamily = new FontFamily(_fontCollection.Families[0].Name, _fontCollection); _myFont = new Font(myFontFamily, 20, FontStyle.Bold); } } catch (Exception ex) { CommonMethods.AddLog(true, "加载字体异常".Translated() + $":{ex.Message}"); } } private void FrmMonitor_FormClosing(object sender, FormClosingEventArgs e) { _cts.Cancel(); _myFont?.Dispose(); _fontCollection?.Dispose(); } protected override void WndProc(ref Message m) { // 禁掉清除背景消息,减少重绘开销 if (m.Msg == 0x0014) return; base.WndProc(ref m); } private async void FrmMonitor_Load(object sender, EventArgs e) { try { // 1. 一次性初始化图表结构 InitializeCharts(); // 2. 首次刷新数据 await RefreshAllDataAsync().ConfigureAwait(false); // 3. 启动定时刷新 await StartPeriodicRefreshAsync(_cts.Token).ConfigureAwait(false); } catch (Exception ex) { CommonMethods.AddLog(true, "窗体加载异常".Translated() + $":{ex.Message}"); } } /// /// 仅执行一次的图表初始化逻辑 /// 原 SetChart, ConfigureAlarmChart, ConfigureNGChart 合并于此 /// private void InitializeCharts() { try { #region 12小时统计图表初始化 Chart12HourTotal.Series.Clear(); Chart12HourTotal.ChartAreas[0].AxisY.Minimum = 0; Chart12HourTotal.ChartAreas[0].AxisY.Maximum = 2000; // 初始值,动态调整可在更新时做 ChartHelper.AddSeries(Chart12HourTotal, "投入".Translated(), SeriesChartType.Column, ChartValueType.Auto, MarkerStyle.Diamond, 8, Color.DarkTurquoise, Color.Aqua, "", false, true); ChartHelper.AddSeries(Chart12HourTotal, "产出".Translated(), SeriesChartType.Column, ChartValueType.Auto, MarkerStyle.Diamond, 8, Color.PaleGoldenrod, Color.PaleGoldenrod, "", false, true); ChartHelper.AddSeries(Chart12HourTotal, "优率".Translated(), SeriesChartType.Spline, ChartValueType.String, MarkerStyle.Circle, 8, Color.Lime, Color.Lime, "#VAL{P}", true, true); ChartHelper.SetStyle(Chart12HourTotal, Color.Transparent, Color.White); ChartHelper.SetLegend(Chart12HourTotal, Docking.Left, StringAlignment.Center, Color.Transparent, Color.White); ChartHelper.SetXY(Chart12HourTotal, "时间".Translated(), "数值".Translated(), -45, StringAlignment.Center, Color.White, Color.White, AxisArrowStyle.None, 1, true); ChartHelper.SetMajorGrid(Chart12HourTotal, Color.Transparent, 20, 2); #endregion #region 报警TOP10图表初始化 // 预配置样式,不添加具体数据系列,留待数据来时动态添加或复用 ChartHelper.SetTitle(chartAlarmData, "告警TOP10".Translated(), new Font("微软雅黑", 12), Docking.Top, Color.White); ChartHelper.SetStyle(chartAlarmData, Color.Transparent, Color.White); ChartHelper.SetLegend(chartAlarmData, Docking.Top, StringAlignment.Center, Color.Transparent, Color.White); ChartHelper.SetXY(chartAlarmData, "报警项".Translated(), "次数".Translated(), -45, StringAlignment.Center, Color.White, Color.White, AxisArrowStyle.None, 1, false); ChartHelper.SetMajorGrid(chartAlarmData, Color.Transparent, 20, 2); #endregion #region 不良项图表初始化 ChartHelper.SetTitle(chartNgShow, "各不良项".Translated(), new Font("微软雅黑", 12), Docking.Top, Color.White); ChartHelper.SetStyle(chartNgShow, Color.Transparent, Color.White); ChartHelper.SetLegend(chartNgShow, Docking.Top, StringAlignment.Center, Color.Transparent, Color.White); ChartHelper.SetXY(chartNgShow, "项名".Translated(), "数值".Translated(), 0, StringAlignment.Center, Color.White, Color.White, AxisArrowStyle.None, 1, false); ChartHelper.SetMajorGrid(chartNgShow, Color.Transparent, 1, 1); #endregion } catch (Exception ex) { CommonMethods.AddLog(true, "图表初始化异常".Translated() + $":{ex.Message}"); } } private async Task StartPeriodicRefreshAsync(CancellationToken ct) { while (!ct.IsCancellationRequested) { try { await RefreshAllDataAsync().ConfigureAwait(false); // 适当增加间隔可进一步降低CPU,如数据变化不快可改为 3000ms await Task.Delay(2000, ct).ConfigureAwait(false); } catch (OperationCanceledException) { break; } catch (Exception ex) { CommonMethods.AddLog(true, "定时刷新异常".Translated() + $":{ex.Message}"); await Task.Delay(1000, ct).ConfigureAwait(false); } } } private async Task RefreshAllDataAsync() { var tasks = new[] { Refresh12ProdQtyAsync(), RefreshXiaoLvDataAsync(), RefreshChatTop10AlarmDataAsync(), RefreshChatNGDataAsync() }; await Task.WhenAll(tasks).ConfigureAwait(false); } private async Task Refresh12ProdQtyAsync() { await Task.Run(() => { if (IsDisposed) return; var data = CommonMethods.Lst_Prod12HourData; this.InvokeIfRequired(() => { // 不再调用 SetChart(),只更新数据 UpdateChart12HourData(data); }); }).ConfigureAwait(false); } /// /// 【核心优化】高效更新图表数据,避免重建 Series /// private void UpdateChart12HourData(List data) { try { if (data == null || data.Count == 0 || Chart12HourTotal.Series.Count < 3) return; var maxValue = data.Max(x => x.ProdIn); Chart12HourTotal.ChartAreas[0].AxisY.Minimum = 0; Chart12HourTotal.ChartAreas[0].AxisY.Maximum = (int)maxValue + 1500; var s1 = Chart12HourTotal.Series[0]; var s2 = Chart12HourTotal.Series[1]; var s3 = Chart12HourTotal.Series[2]; // 优化策略:如果点数相同,直接修改值;如果不同,清空重加(但仍保留Series对象) if (_chart12HourPointCount == data.Count && s1.Points.Count == data.Count) { for (int i = 0; i < data.Count; i++) { s1.Points[i].YValues[0] = data[i].ProdIn; s1.Points[i].AxisLabel = data[i].DisplayTime; s2.Points[i].YValues[0] = data[i].ProdOut; s3.Points[i].YValues[0] = Convert.ToDouble(data[i].OKRatio); } } else { // 数量变化时重置 s1.Points.Clear(); s2.Points.Clear(); s3.Points.Clear(); _chart12HourPointCount = data.Count; for (int i = 0; i < data.Count; i++) { s1.Points.AddXY(data[i].DisplayTime, data[i].ProdIn); s2.Points.AddXY(data[i].DisplayTime, data[i].ProdOut); s3.Points.AddXY(data[i].DisplayTime, data[i].OKRatio); } } Chart12HourTotal.ChartAreas[0].AxisX.LabelStyle.Angle = 0; Chart12HourTotal.ChartAreas[0].AxisX.Interval = 1; // 强制重绘图表区域,避免全窗体重绘 Chart12HourTotal.Invalidate(); } catch (Exception ex) { CommonMethods.AddLog(true, "更新12小时数据异常".Translated() + $":{ex.Message}"); } } private async Task RefreshXiaoLvDataAsync() { await Task.Run(() => { if (IsDisposed) return; // 空值保护 if (CommonMethods.JiaDL == 0 && CommonMethods.TotalQty == 0) { // 可选:使用默认值或跳过 return; } var utilizationRate = Convert.ToDouble(CommonMethods.JiaDL) * 100; var totalQty = CommonMethods.TotalQty; var okQty = CommonMethods.ProOKQty; var yieldRate = totalQty == 0 ? 0 : Math.Round((double)okQty / totalQty * 100.0, 2); this.InvokeIfRequired(() => { UpdateEfficiencyControls(utilizationRate, yieldRate, (int)totalQty, (int)okQty); UpdateTimeDisplay(); UpdateEnergyDisplay(); }); }).ConfigureAwait(false); } private void UpdateEfficiencyControls(double utilizationRate, double yieldRate, int totalQty, int okQty) { try { if (titleModeName != null) titleModeName.Title = CommonMethods.mesConfig.ModelName ?? "测试型号"; if (titleFactory_code != null) titleFactory_code.Title = CommonMethods.mesConfig.siteCode ?? "TEST001"; if (titleLine_No != null) titleLine_No.Title = CommonMethods.mesConfig.lineCode ?? "LINE01"; if (titleEqp_code != null) titleEqp_code.Title = CommonMethods.mesConfig.equipNum ?? "EQP001"; if (proUtilizationRate != null) proUtilizationRate.FloatValue = (float)utilizationRate; if (proPPM != null) proPPM.IntValue = (int)CommonMethods.Ppm; if (proOKQty != null) proOKQty.IntValue = okQty; if (proOKRatio != null) proOKRatio.Value = yieldRate; if (proTotalQty != null) proTotalQty.TotalValue = totalQty; } catch (Exception ex) { CommonMethods.AddLog(true, "更新效率控件异常".Translated() + $":{ex.Message}"); } } private void UpdateTimeDisplay() { try { if (CommonMethods.PlcOpenAndRunTime?.Count >= 6) { if (titleStartTime != null) titleStartTime.Title = $"{CommonMethods.PlcOpenAndRunTime[0]}:{CommonMethods.PlcOpenAndRunTime[1]}:{CommonMethods.PlcOpenAndRunTime[2]}"; if (titleRunTime != null) titleRunTime.Title = $"{CommonMethods.PlcOpenAndRunTime[3]}:{CommonMethods.PlcOpenAndRunTime[4]}:{CommonMethods.PlcOpenAndRunTime[5]}"; } } catch (Exception ex) { CommonMethods.AddLog(true, "更新时间显示异常".Translated() + $":{ex.Message}"); } } private void UpdateEnergyDisplay() { try { if (CommonMethods.listElectricEnergy?.Count > 0 && EyMaterH1 != null) { EyMaterH1.ElectricEnergy = CommonMethods.listElectricEnergy[0]; } } catch (Exception ex) { CommonMethods.AddLog(true, "更新能源显示异常".Translated() + $":{ex.Message}"); } } private async Task RefreshChatTop10AlarmDataAsync() { await Task.Run(() => { if (IsDisposed) return; this.InvokeIfRequired(() => { UpdateAlarmChart(); }); }).ConfigureAwait(false); } private void UpdateAlarmChart() { try { var alarmData = CommonMethods.Lst_AlarmTop10_Qty; if (alarmData == null || alarmData.Count == 0) return; // 确保至少有一个Series if (chartAlarmData.Series.Count == 0) { ChartHelper.AddSeries(chartAlarmData, "Alarm", SeriesChartType.Column, ChartValueType.Auto, MarkerStyle.Diamond, 4, Color.DarkTurquoise, Color.White, "", false, true); } var s = chartAlarmData.Series[0]; var maxValue = alarmData.Max(x => x.DataCount); chartAlarmData.ChartAreas[0].AxisY.Minimum = 0; chartAlarmData.ChartAreas[0].AxisY.Maximum = (int)maxValue + 20; // 检查是否需要重建点 if (_alarmChartPointCount == alarmData.Count && s.Points.Count == alarmData.Count) { for (int i = 0; i < alarmData.Count; i++) { // 如果标签变了才改,减少开销 if (s.Points[i].AxisLabel != alarmData[i].DataType) s.Points[i].AxisLabel = alarmData[i].DataType; s.Points[i].YValues[0] = alarmData[i].DataCount; // 动态修改系列名称以匹配第一个数据的类型(如果需要) if (i == 0) s.Name = alarmData[i].DataType; } } else { s.Points.Clear(); _alarmChartPointCount = alarmData.Count; if (alarmData.Count > 0) s.Name = alarmData[0].DataType; foreach (var item in alarmData) { s.Points.AddXY(item.DataType, item.DataCount); } } chartAlarmData.Invalidate(); } catch (Exception ex) { CommonMethods.AddLog(true, "更新报警图表异常".Translated() + $":{ex.Message}"); } } private async Task RefreshChatNGDataAsync() { await Task.Run(() => { if (IsDisposed) return; this.InvokeIfRequired(() => { UpdateNGChart(); }); }).ConfigureAwait(false); } private void UpdateNGChart() { try { var ngData = CommonMethods.Lst_DefectTypeQty; if (ngData == null || ngData.Count == 0) return; if (chartNgShow.Series.Count == 0) { ChartHelper.AddSeries(chartNgShow, "NG", SeriesChartType.Column, ChartValueType.Auto, MarkerStyle.Diamond, 4, Color.Red, Color.White, "", false, true); } var s = chartNgShow.Series[0]; var maxValue = ngData.Max(x => x.DataCount); chartNgShow.ChartAreas[0].AxisY.Minimum = 0; chartNgShow.ChartAreas[0].AxisY.Maximum = (int)maxValue + 100; if (_ngChartPointCount == ngData.Count && s.Points.Count == ngData.Count) { for (int i = 0; i < ngData.Count; i++) { if (s.Points[i].AxisLabel != ngData[i].DataType) s.Points[i].AxisLabel = ngData[i].DataType; s.Points[i].YValues[0] = ngData[i].DataCount; if (i == 0) s.Name = ngData[i].DataType; } } else { s.Points.Clear(); _ngChartPointCount = ngData.Count; if (ngData.Count > 0) s.Name = ngData[0].DataType; foreach (var item in ngData) { s.Points.AddXY(item.DataType, item.DataCount); } } chartNgShow.Invalidate(); } catch (Exception ex) { CommonMethods.AddLog(true, "更新不良图表异常".Translated() + $":{ex.Message}"); } } } // 扩展方法 public static class ControlExtensions { public static void InvokeIfRequired(this Control control, Action action) { if (control == null || control.IsDisposed) return; if (control.InvokeRequired) { try { control.BeginInvoke(action); } catch (ObjectDisposedException) { // 忽略在销毁过程中发生的调用异常 } } else { action(); } } } }