Files
WC-CZGKJ/SCADA/Pages/UC_WeighingAnalysis.cs

546 lines
25 KiB
C#
Raw Permalink 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 System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Globalization;
using System.Linq;
using System.Windows.Forms;
using MesWork;
namespace MesWork.Pages
{
/// <summary>
/// 称重分析页面:查询称重记录,查看曲线点位和最终重量取值依据。
/// </summary>
public class UC_WeighingAnalysis : UserControl
{
private readonly Panel pnl_QueryBar = new Panel();
private readonly DateTimePicker dtp_Start = new DateTimePicker();
private readonly DateTimePicker dtp_End = new DateTimePicker();
private readonly TextBox txt_ProductNo = new TextBox();
private readonly TextBox txt_ModelNo = new TextBox();
private readonly TextBox txt_OrderNo = new TextBox();
private readonly Button btn_Query = new Button();
private readonly SplitContainer split_Main = new SplitContainer();
private readonly Panel pnl_Chart = new Panel();
private readonly Panel pnl_Info = new Panel();
private readonly DataGridView dgv_Data = new DataGridView();
private readonly Label lbl_Title = new Label();
private readonly Label lbl_FinalWeight = new Label();
private readonly Label lbl_Window = new Label();
private readonly Label lbl_Method = new Label();
private readonly Label lbl_Product = new Label();
private readonly Label lbl_Sample = new Label();
private readonly Label lbl_Status = new Label();
private readonly ToolTip tip_Point = new ToolTip();
private readonly List<decimal> _points = new List<decimal>();
private PointF[] _chartPoints = new PointF[0];
private int _finalStartSeq;
private int _finalEndSeq;
private decimal? _finalWeight;
private int _sampleIntervalMs;
private int _lastTipSeq;
private static readonly Color ClrPrimary = Color.FromArgb(74, 144, 217);
private static readonly Color ClrPrimaryDark = Color.FromArgb(30, 58, 95);
private static readonly Color ClrAccent = Color.FromArgb(245, 158, 11);
private static readonly Color ClrGood = Color.FromArgb(5, 150, 105);
private static readonly Color ClrGrid = Color.FromArgb(229, 231, 235);
private static readonly Color ClrText = Color.FromArgb(55, 65, 81);
private static readonly Color ClrMuted = Color.FromArgb(107, 114, 128);
public UC_WeighingAnalysis()
{
InitializeLayout();
InitGrid();
dtp_Start.Value = DateTime.Today.AddDays(-30);
dtp_End.Value = DateTime.Today;
SetHint(txt_ProductNo, "产品编号...");
SetHint(txt_ModelNo, "机型号...");
SetHint(txt_OrderNo, "工单号...");
pnl_Chart.Paint += pnl_Chart_Paint;
pnl_Chart.Resize += (s, e) => pnl_Chart.Invalidate();
pnl_Chart.MouseMove += pnl_Chart_MouseMove;
pnl_Chart.MouseLeave += (s, e) => HidePointTip();
}
private void InitializeLayout()
{
BackColor = Color.FromArgb(245, 247, 250);
Dock = DockStyle.Fill;
pnl_QueryBar.BackColor = Color.White;
pnl_QueryBar.Dock = DockStyle.Top;
pnl_QueryBar.Height = 56;
pnl_QueryBar.Padding = new Padding(16, 0, 16, 0);
Controls.Add(split_Main);
Controls.Add(pnl_QueryBar);
AddQueryLabel("从", 16);
InitDatePicker(dtp_Start, 40);
AddQueryLabel("到", 180);
InitDatePicker(dtp_End, 204);
InitTextBox(txt_ProductNo, 365, 150);
InitTextBox(txt_ModelNo, 525, 130);
InitTextBox(txt_OrderNo, 665, 150);
InitButton();
split_Main.Dock = DockStyle.Fill;
split_Main.Orientation = Orientation.Horizontal;
split_Main.SplitterDistance = 430;
split_Main.SplitterWidth = 6;
split_Main.Panel1.Padding = new Padding(12, 8, 12, 4);
split_Main.Panel2.Padding = new Padding(12, 4, 12, 12);
split_Main.Panel1.Controls.Add(pnl_Chart);
split_Main.Panel1.Controls.Add(pnl_Info);
split_Main.Panel2.Controls.Add(dgv_Data);
pnl_Info.BackColor = Color.FromArgb(248, 250, 252);
pnl_Info.Dock = DockStyle.Right;
pnl_Info.Width = 340;
pnl_Info.Padding = new Padding(20, 12, 20, 12);
pnl_Info.Controls.Add(lbl_Status);
pnl_Info.Controls.Add(lbl_Sample);
pnl_Info.Controls.Add(lbl_Product);
pnl_Info.Controls.Add(lbl_Method);
pnl_Info.Controls.Add(lbl_Window);
pnl_Info.Controls.Add(lbl_FinalWeight);
InitInfoLabel(lbl_FinalWeight, 18, FontStyle.Bold, ClrGood, 18);
InitInfoLabel(lbl_Window, 11, FontStyle.Bold, ClrPrimaryDark, 78);
InitInfoLabel(lbl_Method, 10, FontStyle.Regular, ClrText, 124);
InitInfoLabel(lbl_Product, 10, FontStyle.Regular, ClrText, 210);
InitInfoLabel(lbl_Sample, 10, FontStyle.Regular, ClrText, 246);
InitInfoLabel(lbl_Status, 10, FontStyle.Regular, ClrMuted, 282);
pnl_Chart.BackColor = Color.White;
pnl_Chart.Dock = DockStyle.Fill;
pnl_Chart.Padding = new Padding(18, 8, 18, 8);
pnl_Chart.Controls.Add(lbl_Title);
lbl_Title.Dock = DockStyle.Top;
lbl_Title.Height = 34;
lbl_Title.Font = new Font("微软雅黑", 12F, FontStyle.Bold);
lbl_Title.ForeColor = ClrPrimaryDark;
lbl_Title.Text = "称重曲线与最终取值依据";
lbl_Title.TextAlign = ContentAlignment.MiddleLeft;
}
private void AddQueryLabel(string text, int x)
{
var label = new Label
{
AutoSize = true,
Font = new Font("微软雅黑", 10F),
ForeColor = ClrText,
Location = new Point(x, 18),
Text = text
};
pnl_QueryBar.Controls.Add(label);
}
private void InitDatePicker(DateTimePicker picker, int x)
{
picker.Font = new Font("微软雅黑", 10F);
picker.Format = DateTimePickerFormat.Short;
picker.Location = new Point(x, 13);
picker.Size = new Size(130, 25);
pnl_QueryBar.Controls.Add(picker);
}
private void InitTextBox(TextBox textBox, int x, int width)
{
textBox.BorderStyle = BorderStyle.FixedSingle;
textBox.Font = new Font("微软雅黑", 10F);
textBox.Location = new Point(x, 13);
textBox.Size = new Size(width, 27);
pnl_QueryBar.Controls.Add(textBox);
}
private void InitButton()
{
btn_Query.BackColor = ClrPrimary;
btn_Query.Cursor = Cursors.Hand;
btn_Query.FlatAppearance.BorderSize = 0;
btn_Query.FlatStyle = FlatStyle.Flat;
btn_Query.Font = new Font("微软雅黑", 10F, FontStyle.Bold);
btn_Query.ForeColor = Color.White;
btn_Query.Location = new Point(830, 12);
btn_Query.Size = new Size(110, 30);
btn_Query.Text = "查询分析";
btn_Query.UseVisualStyleBackColor = false;
btn_Query.Click += btn_Query_Click;
pnl_QueryBar.Controls.Add(btn_Query);
}
private void InitInfoLabel(Label label, float fontSize, FontStyle style, Color color, int y)
{
label.Font = new Font("微软雅黑", fontSize, style);
label.ForeColor = color;
label.Location = new Point(20, y);
label.Size = new Size(300, y == 124 ? 72 : 32);
label.TextAlign = ContentAlignment.MiddleLeft;
}
private void InitGrid()
{
dgv_Data.AllowUserToAddRows = false;
dgv_Data.AllowUserToDeleteRows = false;
dgv_Data.AllowUserToResizeRows = false;
dgv_Data.AutoGenerateColumns = false;
dgv_Data.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.Fill;
dgv_Data.BackgroundColor = Color.White;
dgv_Data.BorderStyle = BorderStyle.None;
dgv_Data.CellBorderStyle = DataGridViewCellBorderStyle.SingleHorizontal;
dgv_Data.ColumnHeadersHeight = 40;
dgv_Data.ColumnHeadersHeightSizeMode = DataGridViewColumnHeadersHeightSizeMode.DisableResizing;
dgv_Data.ColumnHeadersDefaultCellStyle.BackColor = Color.FromArgb(232, 237, 242);
dgv_Data.ColumnHeadersDefaultCellStyle.ForeColor = ClrPrimaryDark;
dgv_Data.ColumnHeadersDefaultCellStyle.Font = new Font("微软雅黑", 10F, FontStyle.Bold);
dgv_Data.DefaultCellStyle.Font = new Font("微软雅黑", 10F);
dgv_Data.DefaultCellStyle.ForeColor = Color.FromArgb(31, 41, 55);
dgv_Data.DefaultCellStyle.SelectionBackColor = Color.FromArgb(219, 234, 254);
dgv_Data.DefaultCellStyle.SelectionForeColor = ClrPrimaryDark;
dgv_Data.DefaultCellStyle.Padding = new Padding(6, 0, 6, 0);
dgv_Data.AlternatingRowsDefaultCellStyle.BackColor = Color.FromArgb(248, 250, 252);
dgv_Data.Dock = DockStyle.Fill;
dgv_Data.EnableHeadersVisualStyles = false;
dgv_Data.GridColor = ClrGrid;
dgv_Data.MultiSelect = false;
dgv_Data.ReadOnly = true;
dgv_Data.RowHeadersVisible = false;
dgv_Data.RowTemplate.Height = 36;
dgv_Data.SelectionMode = DataGridViewSelectionMode.FullRowSelect;
dgv_Data.SelectionChanged += dgv_Data_SelectionChanged;
dgv_Data.Columns.Add(new DataGridViewTextBoxColumn { HeaderText = "检测时间", DataPropertyName = "测试时间", FillWeight = 95 });
dgv_Data.Columns.Add(new DataGridViewTextBoxColumn { HeaderText = "产品编号", DataPropertyName = "发动机号", FillWeight = 110 });
dgv_Data.Columns.Add(new DataGridViewTextBoxColumn { HeaderText = "工单号", DataPropertyName = "订单号", FillWeight = 95 });
dgv_Data.Columns.Add(new DataGridViewTextBoxColumn { HeaderText = "机型号", DataPropertyName = "机型号", FillWeight = 65 });
dgv_Data.Columns.Add(new DataGridViewTextBoxColumn { HeaderText = "称重类型", DataPropertyName = "称重类型", FillWeight = 70 });
dgv_Data.Columns.Add(new DataGridViewTextBoxColumn { HeaderText = "工位号", DataPropertyName = "设备编号", FillWeight = 60 });
dgv_Data.Columns.Add(new DataGridViewTextBoxColumn { HeaderText = "最终重量", DataPropertyName = "最终重量", FillWeight = 70, DefaultCellStyle = new DataGridViewCellStyle { Format = "F3" } });
dgv_Data.Columns.Add(new DataGridViewTextBoxColumn { HeaderText = "取点区间", DataPropertyName = "取点区间", FillWeight = 70 });
dgv_Data.Columns.Add(new DataGridViewTextBoxColumn { HeaderText = "采样点数", DataPropertyName = "采样点数", FillWeight = 60 });
dgv_Data.Columns.Add(new DataGridViewTextBoxColumn { HeaderText = "操作员", DataPropertyName = "操作员", FillWeight = 60 });
}
private void SetHint(TextBox txt, string hint)
{
txt.Tag = hint;
txt.Text = hint;
txt.ForeColor = Color.FromArgb(156, 163, 175);
txt.GotFocus += (s, e) => { if (txt.Text == hint) { txt.Text = ""; txt.ForeColor = ClrPrimaryDark; } };
txt.LostFocus += (s, e) => { if (string.IsNullOrWhiteSpace(txt.Text)) { txt.Text = hint; txt.ForeColor = Color.FromArgb(156, 163, 175); } };
}
private string GetHintValue(TextBox txt)
{
return txt.Text == txt.Tag?.ToString() ? "" : txt.Text.Trim();
}
private void btn_Query_Click(object sender, EventArgs e)
{
QueryData();
}
private void QueryData()
{
try
{
var parms = new SqlParameter[]
{
new SqlParameter("@发动机号", GetHintValue(txt_ProductNo)),
new SqlParameter("@机型号", GetHintValue(txt_ModelNo)),
new SqlParameter("@工单号", GetHintValue(txt_OrderNo)),
new SqlParameter("@开始时间", dtp_Start.Value.ToString("yyyy-MM-dd")),
new SqlParameter("@结束时间", dtp_End.Value.AddDays(1).ToString("yyyy-MM-dd"))
};
SqlOperation.ExecuteStoredProcedure("称重分析_查询", parms, out DataTable dt, out string err);
if (!string.IsNullOrWhiteSpace(err))
throw new Exception(err);
dgv_Data.DataSource = dt;
if (dt == null || dt.Rows.Count == 0)
{
ClearCurve("查询范围内无曲线数据");
MessageBox.Show("查询范围内无称重曲线记录", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
}
catch (Exception ex)
{
MessageBox.Show($"查询失败:{ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
private void dgv_Data_SelectionChanged(object sender, EventArgs e)
{
if (dgv_Data.CurrentRow?.DataBoundItem is DataRowView rowView)
{
LoadCurve(rowView.Row);
}
}
private void LoadCurve(DataRow row)
{
_points.Clear();
HidePointTip();
string pointValues = row.Table.Columns.Contains("点位值") ? row["点位值"]?.ToString() : "";
foreach (string item in (pointValues ?? "").Split('|'))
{
if (decimal.TryParse(item, NumberStyles.Number, CultureInfo.InvariantCulture, out decimal value))
_points.Add(value);
}
_finalStartSeq = ToInt(row, "最终取点开始序号");
_finalEndSeq = ToInt(row, "最终取点结束序号");
_sampleIntervalMs = ToInt(row, "采样间隔ms");
_finalWeight = ToNullableDecimal(row, "最终重量");
string productNo = GetText(row, "发动机号");
string stationNo = GetText(row, "设备编号");
lbl_FinalWeight.Text = _finalWeight.HasValue ? $"最终重量 {_finalWeight.Value:F3} kg" : "最终重量 —";
lbl_Window.Text = _finalStartSeq > 0 && _finalEndSeq > 0
? $"依据点位 第 {_finalStartSeq} - {_finalEndSeq} 点"
: "依据点位 未记录";
lbl_Method.Text = "计算方式 取连续20点中极差最小的一段去掉一个最大值和一个最小值剩余18点求平均。";
lbl_Product.Text = $"产品编号 {productNo}";
lbl_Sample.Text = $"工位号 {stationNo} 采样点数 {_points.Count} 间隔 {_sampleIntervalMs}ms";
lbl_Status.Text = _points.Count > 0 ? "曲线状态 已加载,可追溯最终取值区间" : "曲线状态 无点位数据";
pnl_Chart.Invalidate();
}
private void ClearCurve(string msg)
{
_points.Clear();
HidePointTip();
_finalWeight = null;
_finalStartSeq = 0;
_finalEndSeq = 0;
lbl_FinalWeight.Text = "最终重量 —";
lbl_Window.Text = "依据点位 —";
lbl_Method.Text = "计算方式 —";
lbl_Product.Text = "";
lbl_Sample.Text = "";
lbl_Status.Text = msg;
pnl_Chart.Invalidate();
}
private int ToInt(DataRow row, string column)
{
if (!row.Table.Columns.Contains(column) || row[column] == DBNull.Value) return 0;
int.TryParse(row[column].ToString(), out int value);
return value;
}
private decimal? ToNullableDecimal(DataRow row, string column)
{
if (!row.Table.Columns.Contains(column) || row[column] == DBNull.Value) return null;
return decimal.TryParse(row[column].ToString(), out decimal value) ? value : (decimal?)null;
}
private string GetText(DataRow row, string column)
{
return row.Table.Columns.Contains(column) && row[column] != DBNull.Value ? row[column].ToString() : "";
}
private void pnl_Chart_Paint(object sender, PaintEventArgs e)
{
Graphics g = e.Graphics;
g.SmoothingMode = SmoothingMode.AntiAlias;
g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.ClearTypeGridFit;
Rectangle rect = pnl_Chart.ClientRectangle;
using (var bg = new SolidBrush(Color.White))
g.FillRectangle(bg, rect);
int padL = 70, padR = 36, padT = 54, padB = 54;
int chartW = rect.Width - padL - padR;
int chartH = rect.Height - padT - padB;
if (chartW < 80 || chartH < 80) return;
if (_points.Count == 0)
{
_chartPoints = new PointF[0];
DrawCenterText(g, rect, "选择一条称重记录后显示曲线");
return;
}
decimal minDec = _points.Min();
decimal maxDec = _points.Max();
double minVal = (double)minDec;
double maxVal = (double)maxDec;
double range = maxVal - minVal;
if (range < 0.001) range = Math.Max(Math.Abs(maxVal) * 0.1, 1);
minVal -= range * 0.15;
maxVal += range * 0.15;
DrawGrid(g, padL, padT, chartW, chartH, minVal, maxVal);
_chartPoints = BuildPointLocations(padL, padT, chartW, chartH, minVal, maxVal);
DrawHighlightWindow(g, _chartPoints, padT, chartH);
DrawCurve(g, _chartPoints);
DrawPoints(g, _chartPoints);
DrawFinalLine(g, padL, padT, chartW, chartH, minVal, maxVal);
}
private void DrawGrid(Graphics g, int padL, int padT, int chartW, int chartH, double minVal, double maxVal)
{
using (var axisPen = new Pen(Color.FromArgb(200, 210, 220), 1))
using (var gridPen = new Pen(ClrGrid, 1) { DashStyle = DashStyle.Dot })
using (var font = new Font("微软雅黑", 9F))
using (var brush = new SolidBrush(ClrMuted))
{
g.DrawLine(axisPen, padL, padT, padL, padT + chartH);
g.DrawLine(axisPen, padL, padT + chartH, padL + chartW, padT + chartH);
for (int i = 0; i <= 5; i++)
{
int y = padT + chartH - (int)(chartH * i / 5.0);
if (i > 0) g.DrawLine(gridPen, padL + 1, y, padL + chartW, y);
string text = (minVal + (maxVal - minVal) * i / 5.0).ToString("F2");
SizeF size = g.MeasureString(text, font);
g.DrawString(text, font, brush, padL - size.Width - 6, y - size.Height / 2);
}
}
}
private PointF[] BuildPointLocations(int padL, int padT, int chartW, int chartH, double minVal, double maxVal)
{
var points = new PointF[_points.Count];
for (int i = 0; i < _points.Count; i++)
{
float x = _points.Count == 1 ? padL + chartW / 2f : padL + chartW * i / (float)(_points.Count - 1);
float y = padT + chartH - (float)(chartH * (((double)_points[i] - minVal) / (maxVal - minVal)));
points[i] = new PointF(x, y);
}
return points;
}
private void DrawHighlightWindow(Graphics g, PointF[] points, int padT, int chartH)
{
if (_finalStartSeq <= 0 || _finalEndSeq <= 0 || _finalStartSeq > points.Length) return;
int startIndex = Math.Max(0, _finalStartSeq - 1);
int endIndex = Math.Min(points.Length - 1, _finalEndSeq - 1);
float x1 = points[startIndex].X;
float x2 = points[endIndex].X;
using (var brush = new SolidBrush(Color.FromArgb(46, 245, 158, 11)))
g.FillRectangle(brush, x1, padT, Math.Max(2, x2 - x1), chartH);
using (var pen = new Pen(ClrAccent, 1.6f) { DashStyle = DashStyle.Dash })
{
g.DrawLine(pen, x1, padT, x1, padT + chartH);
g.DrawLine(pen, x2, padT, x2, padT + chartH);
}
}
private void DrawCurve(Graphics g, PointF[] points)
{
if (points.Length < 2) return;
using (var shadow = new Pen(Color.FromArgb(34, 74, 144, 217), 5f) { LineJoin = LineJoin.Round })
using (var line = new Pen(ClrPrimary, 2.2f) { LineJoin = LineJoin.Round })
{
g.DrawLines(shadow, points);
g.DrawLines(line, points);
}
}
private void DrawPoints(Graphics g, PointF[] points)
{
int step = Math.Max(1, points.Length / 120);
for (int i = 0; i < points.Length; i += step)
{
Brush brush = IsInFinalWindow(i + 1) ? new SolidBrush(ClrAccent) : new SolidBrush(ClrPrimary);
using (brush)
g.FillEllipse(brush, points[i].X - 3, points[i].Y - 3, 6, 6);
}
}
private void DrawFinalLine(Graphics g, int padL, int padT, int chartW, int chartH, double minVal, double maxVal)
{
if (!_finalWeight.HasValue) return;
float y = padT + chartH - (float)(chartH * (((double)_finalWeight.Value - minVal) / (maxVal - minVal)));
if (y < padT || y > padT + chartH) return;
using (var pen = new Pen(ClrGood, 1.8f) { DashStyle = DashStyle.Dash })
using (var font = new Font("微软雅黑", 9F, FontStyle.Bold))
using (var brush = new SolidBrush(ClrGood))
{
g.DrawLine(pen, padL, y, padL + chartW, y);
string text = $"最终 {_finalWeight.Value:F3} kg";
SizeF size = g.MeasureString(text, font);
g.DrawString(text, font, brush, padL + chartW - size.Width - 4, y - size.Height - 4);
}
}
private bool IsInFinalWindow(int seq)
{
return _finalStartSeq > 0 && _finalEndSeq > 0 && seq >= _finalStartSeq && seq <= _finalEndSeq;
}
private void pnl_Chart_MouseMove(object sender, MouseEventArgs e)
{
int index = FindNearestPointIndex(e.Location);
if (index < 0)
{
HidePointTip();
return;
}
int seq = index + 1;
if (seq == _lastTipSeq) return;
_lastTipSeq = seq;
string finalText = IsInFinalWindow(seq) ? "\r\n取值依据最终采用区间内" : "";
string tipText = $"第 {seq} 点\r\n重量{_points[index]:F3} kg\r\n时间{FormatElapsedTime(seq)}{finalText}";
tip_Point.Show(tipText, pnl_Chart, e.Location.X + 16, e.Location.Y + 16, 3000);
}
private int FindNearestPointIndex(Point mouse)
{
if (_chartPoints.Length == 0 || _points.Count == 0) return -1;
int nearestIndex = -1;
double nearestDistance = double.MaxValue;
for (int i = 0; i < _chartPoints.Length; i++)
{
double dx = _chartPoints[i].X - mouse.X;
double dy = _chartPoints[i].Y - mouse.Y;
double distance = dx * dx + dy * dy;
if (distance < nearestDistance)
{
nearestDistance = distance;
nearestIndex = i;
}
}
return nearestDistance <= 144 ? nearestIndex : -1; // 鼠标离点 12px 内才显示,避免误触。
}
private string FormatElapsedTime(int seq)
{
int interval = _sampleIntervalMs > 0 ? _sampleIntervalMs : 200;
int totalMs = Math.Max(0, seq - 1) * interval; // 第1点按采集起点0ms估算。
int seconds = totalMs / 1000;
int milliseconds = totalMs % 1000;
return $"{seconds}秒{milliseconds:D3}毫秒";
}
private void HidePointTip()
{
_lastTipSeq = 0;
tip_Point.Hide(pnl_Chart);
}
private void DrawCenterText(Graphics g, Rectangle rect, string text)
{
using (var font = new Font("微软雅黑", 14F))
using (var brush = new SolidBrush(Color.FromArgb(156, 163, 175)))
{
SizeF size = g.MeasureString(text, font);
g.DrawString(text, font, brush, (rect.Width - size.Width) / 2, (rect.Height - size.Height) / 2);
}
}
}
}