Files
1258/LargeSquareOne/FrmMonitor.cs
T
2026-08-04 18:36:40 +08:00

530 lines
20 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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}");
}
}
/// <summary>
/// 仅执行一次的图表初始化逻辑
/// 原 SetChart, ConfigureAlarmChart, ConfigureNGChart 合并于此
/// </summary>
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);
}
/// <summary>
/// 【核心优化】高效更新图表数据,避免重建 Series
/// </summary>
private void UpdateChart12HourData(List<Chart12HourData> 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();
}
}
}
}