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 { /// /// 称重分析页面:查询称重记录,查看曲线点位和最终重量取值依据。 /// 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 _points = new List(); 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); } } } }