530 lines
20 KiB
C#
530 lines
20 KiB
C#
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();
|
||
}
|
||
}
|
||
}
|
||
} |