Files
ctbjrj/成套报价软件/GCS母排计算/GCS推荐铜排窗口.cs
T

473 lines
21 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.Drawing;
using System.Globalization;
using System.IO;
using System.Windows.Forms;
using Smart.CustomComponents;
namespace 成套报价软件
{
/// <summary>
/// 推荐铜排窗口 —— 按每回路原始电流,选 35°C 水平载流量下的最小合适铜排规格,
/// 统计每个规格被多少回路使用,再乘以"每回路长度"得到该规格需要的总米数。
/// 从独立项目 GCS铜排壳体计算 集成进成套报价软件。
/// </summary>
public partial class GCS推荐铜排窗口
{
// ============================================================
// 常量:35°C 水平铜排载流量对照表 (TMY 系列,铜排,单根水平铺设)
// 数据来源:用户提供的 35°C 水平载流量图片。
// 规格 / 载流量(A) 按截面积从小到大排列,选型时取第一个 ≥ 需求电流的规格。
// ============================================================
private static readonly string[] BusbarSpecs35C =
{
"TMY-15×3", "TMY-20×3", "TMY-25×3", "TMY-30×3", "TMY-30×4",
"TMY-30×5", "TMY-30×6", "TMY-40×4", "TMY-40×5", "TMY-40×6",
"TMY-50×5", "TMY-50×6", "TMY-60×6"
};
private static readonly double[] BusbarRatings35C =
{
184, 242, 299, 339, 418,
475, 536, 550, 616, 708,
756, 840, 990
};
// ============================================================
// 表格列索引 (gridResult)
// ============================================================
private const int COL_SPEC = 0; // 铜排规格
private const int COL_RATE = 1; // 载流量 (A)
private const int COL_COUNT = 2; // 回路数
private const int COL_METERS = 3; // 需要米数
/// <summary>推荐行高亮色 (与主窗口一致)。</summary>
private static readonly Color RecommendBackColor = Color.FromArgb(146, 208, 80);
// ============================================================
// 状态
// ============================================================
/// <summary>主窗口传来的所有回路原始电流列表。</summary>
private List<double> _circuitCurrents;
/// <summary>宿主(GCS 母排计算窗口):点"插入"前实时重取回路电流用。</summary>
internal GCS母排计算窗口 宿主;
// ============================================================
// 入口
// ============================================================
public GCS推荐铜排窗口()
{
InitializeComponent();
MainForm.写日志("[推荐铜排] 窗口已构造");
LoadAppIcon(); // 设窗口/任务栏图标(从 exe 自身提取嵌入的图标)
LoadSavedLength(); // 从 铜排长度.cfg 读取上次保存的每回路长度
BuildResultGrid(); // 重建表格列/行(覆盖 ui.cs 可能预建的列)
WireEvents();
Recalculate();
}
/// <summary>把 exe 内嵌入的图标加载为窗口/任务栏图标。
/// 与主窗口一致,从 exe 自身资源提取,不依赖外部文件。</summary>
private void LoadAppIcon()
{
try
{
this.Icon = System.Drawing.Icon.ExtractAssociatedIcon(
System.Windows.Forms.Application.ExecutablePath);
}
catch { /* 提取失败不影响运行,只是用默认图标 */ }
}
// ============================================================
// 每回路长度 自动保存 (铜排长度.cfg,与 exe 同目录)
// ============================================================
/// <summary>每回路铜排长度的配置文件路径。
/// 存放在用户应用数据目录 %APPDATA%\GCS铜排壳体计算\铜排长度.cfg,
/// 而不是 exe 同目录(安装到 Program Files 后 exe 目录无写入权限)。</summary>
private string LengthFilePath
{
get
{
string dir = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
"GCS铜排壳体计算");
if (!Directory.Exists(dir)) Directory.CreateDirectory(dir);
return Path.Combine(dir, "铜排长度.cfg");
}
}
/// <summary>启动时读取上次保存的每回路长度。文件不存在或无效则保持 txtLength 默认值(1.5)。</summary>
private void LoadSavedLength()
{
try
{
if (!File.Exists(LengthFilePath)) return;
string s = File.ReadAllText(LengthFilePath, System.Text.Encoding.UTF8).Trim();
double v;
if (double.TryParse(s, NumberStyles.Any, CultureInfo.InvariantCulture, out v) && v >= 0)
{
txtLength.Text = v.ToString("0.####", CultureInfo.InvariantCulture);
}
}
catch { /* 读取失败不影响运行,用默认值 */ }
}
/// <summary>把当前 txtLength 的值写入 铜排长度.cfg。
/// 在 TextChanged 事件里调用,实现"修改后自动保存"。无效值不保存。</summary>
private void SaveLength()
{
try
{
double v;
if (double.TryParse(txtLength.Text.Trim(), NumberStyles.Any,
CultureInfo.InvariantCulture, out v) && v >= 0)
{
File.WriteAllText(LengthFilePath,
v.ToString("0.####", CultureInfo.InvariantCulture),
System.Text.Encoding.UTF8);
}
}
catch { /* 保存失败静默忽略,不影响使用 */ }
}
/// <summary>主窗口调用:把回路的原始电流传进来,并触发首次计算。
/// scopeLabel:当前统计范围(如 "全部" 或 "2#"),显示在窗口标题上,让用户知道算的是哪列。</summary>
public void SetCircuitCurrents(List<double> currents, string scopeLabel)
{
_circuitCurrents = currents;
// 标题显示当前统计范围
if (!string.IsNullOrEmpty(scopeLabel))
{
this.Text = "每回路推荐铜排 (" + scopeLabel + " · 35°C 水平)";
}
Recalculate();
}
// ============================================================
// 构建表格
// ============================================================
/// <summary>构建结果表格:4 列 (规格/载流量/回路数/需要米数)。
/// 无论 ui.cs 是否预建列,这里先 Clear 再重建,确保列结构一致。
/// 初始 18 行对应全部铜排规格(载流量预填),未使用的回路数=0。</summary>
private void BuildResultGrid()
{
gridResult.SuspendLayout();
gridResult.Columns.Clear();
gridResult.Rows.Clear();
var colSpec = new DataGridViewTextBoxColumn
{
Name = "colSpec",
HeaderText = "铜排规格",
Width = 130,
SortMode = DataGridViewColumnSortMode.NotSortable
};
var colRate = new DataGridViewTextBoxColumn
{
Name = "colRate",
HeaderText = "载流量(A) 35°C水平",
Width = 150,
SortMode = DataGridViewColumnSortMode.NotSortable
};
var colCount = new DataGridViewTextBoxColumn
{
Name = "colCount",
HeaderText = "回路数",
Width = 80,
SortMode = DataGridViewColumnSortMode.NotSortable
};
var colMeters = new DataGridViewTextBoxColumn
{
Name = "colMeters",
HeaderText = "需要米数",
Width = 120,
SortMode = DataGridViewColumnSortMode.NotSortable
};
gridResult.Columns.Add(colSpec);
gridResult.Columns.Add(colRate);
gridResult.Columns.Add(colCount);
gridResult.Columns.Add(colMeters);
// 预填 18 行:规格 + 载流量,回路数和米数先置 0
for (int i = 0; i < BusbarSpecs35C.Length; i++)
{
int idx = gridResult.Rows.Add();
gridResult.Rows[idx].Cells[COL_SPEC].Value = BusbarSpecs35C[i];
gridResult.Rows[idx].Cells[COL_RATE].Value = BusbarRatings35C[i].ToString("0") + "A";
gridResult.Rows[idx].Cells[COL_COUNT].Value = "0";
gridResult.Rows[idx].Cells[COL_METERS].Value = "0";
}
gridResult.ColumnHeadersHeightSizeMode = DataGridViewColumnHeadersHeightSizeMode.EnableResizing;
gridResult.ColumnHeadersHeight = 32;
gridResult.AllowUserToResizeRows = false;
// 平均分配列宽(适应窗口)
RelayoutColumns();
// 关闭 ADGV 列头筛选按钮
DisableAdgvFilterButtons(gridResult);
gridResult.ClearSelection();
gridResult.ResumeLayout();
}
/// <summary>按 gridResult 当前宽度平均分配 4 列宽度。</summary>
private void RelayoutColumns()
{
if (gridResult.Columns.Count < 4) return;
int totalW = gridResult.ClientSize.Width;
if (totalW <= 20) return;
// 按 130:150:80:120 ≈ 规格/载流量/回路数/米数 的比例分配
int[] weights = { 130, 150, 80, 120 };
int sum = 0;
for (int i = 0; i < weights.Length; i++) sum += weights[i];
int used = 0;
for (int i = 0; i < weights.Length - 1; i++)
{
int w = totalW * weights[i] / sum;
gridResult.Columns[i].Width = w;
used += w;
}
gridResult.Columns[weights.Length - 1].Width = totalW - used;
}
// ============================================================
// 事件绑定
// ============================================================
private void WireEvents()
{
// 米数输入框:值变化即重算 + 自动保存
txtLength.TextChanged += (s, e) => { Recalculate(); SaveLength(); };
txtLength.Validating += (s, e) =>
{
double v;
if (!double.TryParse(txtLength.Text.Trim(), NumberStyles.Any,
CultureInfo.InvariantCulture, out v) || v < 0)
{
// 输入无效不强制阻止,但给提示并恢复默认
txtLength.Text = "1.5";
}
};
// 窗口尺寸变化时重排列宽
this.SizeChanged += (s, e) => RelayoutColumns();
// "插入铜排"按钮:把已选型的铜排(规格+米数)插入主窗口元件表
button1.Click += (s, e) => { _上次插入执行 = DateTime.Now; button1_Click(s, e); };
// ★MouseUp 直触发兜底(2026-09-26 23:36 实测:特定状态下 Click 会被吞而 MouseDown 正常)——
// 左键且弹起点在按钮内即执行插入;与 Click 双通道并存,1 秒防重避免双插
button1.MouseUp += button1_MouseUp兜底;
// ★鼠标落点记录仪(2026-09-26 排"点击未达按钮"专项):MouseDown 在任何控件 stealing
// Click 之前触发——下次用户点击后,日志精确显示点击落在窗口哪个坐标/哪个控件上
button1.MouseDown += (s, e) => MainForm.写日志("[推荐铜排] button1 MouseDown(" + e.X + "," + e.Y + ")");
this.MouseClick += (s, e) => MainForm.写日志("[推荐铜排] 窗体空白处点击(" + e.X + "," + e.Y + ")");
gridResult.MouseClick += (s, e) => MainForm.写日志("[推荐铜排] 表格区点击(" + e.X + "," + e.Y + ")");
// 回车键=插入(键盘通道对照/备用)
this.AcceptButton = button1;
// ★不要在这里加 Activated 自动刷新——SetCircuitCurrents 改标题/重算表格会再次触发
// Activated,形成每 200ms 一轮的无限刷新风暴把界面拖死(2026-09-26 22:57 实测 37 轮)。
// 数据同步只在点"插入"时做一次(见 button1_Click 开头)。
}
/// <summary>上次插入执行时刻(Click 与 MouseUp 双通道的 1 秒防重)。</summary>
private DateTime _上次插入执行 = DateTime.MinValue;
/// <summary>MouseUp 直触发兜底:左键弹起点在按钮内即执行插入(Click 被吞时保底)。</summary>
private void button1_MouseUp兜底(object sender, MouseEventArgs e)
{
if (e.Button != System.Windows.Forms.MouseButtons.Left) return;
if (!button1.ClientRectangle.Contains(e.Location)) return;
if (DateTime.Now - _上次插入执行 < TimeSpan.FromSeconds(1))
{
MainForm.写日志("[推荐铜排] MouseUp 兜底:1秒防重跳过(Click 通道已执行)");
return;
}
MainForm.写日志("[推荐铜排] MouseUp 兜底触发(Click 通道未达)");
_上次插入执行 = DateTime.Now;
button1_Click(sender, e);
}
/// <summary>
/// 在打开的窗体中查找主窗口(MyApp.MainForm)实例。
/// 推荐铜排窗口由 GCS母排计算窗口 弹出,GCS母排计算窗口 又由主窗口弹出,
/// 通过遍历 OpenForms 找主窗口最可靠。
/// </summary>
private MainForm FindMainForm()
{
foreach (Form f in Application.OpenForms)
{
if (f is MainForm) return (MainForm)f;
}
return null;
}
/// <summary>
/// "插入铜排"按钮:遍历结果表中所有"回路数 > 0"的行(即被选用的规格),
/// 把每行的铜排规格 + 需要米数 插入到主窗口的元件表(advancedDataGridView5)。
/// 表价由主窗口按母排界面当前铜价自动计算。
/// ★插入前先向 GCS 窗口实时重取回路电流重算(2026-09-26 修:先开推荐窗后输电流时
/// 旧快照回路数全 0,插入被"没有铜排"挡住=用户报"无法正常插入")。
/// </summary>
private void button1_Click(object sender, EventArgs e)
{
MainForm main = FindMainForm();
if (main == null)
{
MainForm.写日志("[推荐铜排] 插入失败:找不到 MainForm");
MessageBox.Show("未找到MainForm,无法插入。", "提示",
MessageBoxButtons.OK, MessageBoxIcon.Warning);
return;
}
// ★先实时刷新:GCS 窗口当前电流表 → 重算结果表(用户可能在打开推荐窗后才输入/修改电流)
bool 宿主可用 = 宿主 != null && !宿主.IsDisposed;
if (宿主可用)
宿主.刷新推荐窗口数据();
MainForm.写日志("[推荐铜排] 点插入: 宿主可用=" + 宿主可用);
// 收集所有被选用的铜排(回路数 > 0)
var items = new List<KeyValuePair<string, double>>();
for (int i = 0; i < gridResult.Rows.Count; i++)
{
var row = gridResult.Rows[i];
int cnt;
int.TryParse((row.Cells[COL_COUNT].Value ?? "").ToString(), out cnt);
if (cnt <= 0) continue;
string 规格 = (row.Cells[COL_SPEC].Value ?? "").ToString().Trim();
double 米数;
double.TryParse((row.Cells[COL_METERS].Value ?? "").ToString(),
NumberStyles.Any, CultureInfo.InvariantCulture, out 米数);
if (string.IsNullOrWhiteSpace(规格) || 米数 <= 0) continue;
items.Add(new KeyValuePair<string, double>(规格, 米数));
}
MainForm.写日志("[推荐铜排] 收集到 " + items.Count + " 种铜排(结果表 " + gridResult.Rows.Count + " 行)");
if (items.Count == 0)
{
MessageBox.Show("当前没有计算出的铜排(回路数都为 0)。请先在垂直母排计算窗口输入回路电流,再点插入。",
"提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
return;
}
// 逐条插入主窗口元件表
int okCount = 0;
foreach (var kv in items)
{
if (main.InsertGcs母排(kv.Key, kv.Value)) okCount++;
}
MainForm.写日志("[推荐铜排] 插入完成: 成功 " + okCount + "/" + items.Count + " 种");
MessageBox.Show("已插入 " + okCount + " 种铜排到报价元件表。", "插入完成",
MessageBoxButtons.OK, MessageBoxIcon.Information);
}
// ============================================================
// 核心计算
// ============================================================
/// <summary>重算:对每个回路电流选最小合适铜排规格,统计每规格回路数,乘米数得总米数。
/// 只更新被使用的行(回路数>0),高亮显示;未使用的规格保持 0 并取消高亮。</summary>
private void Recalculate()
{
double lengthPerCircuit = GetLengthPerCircuit();
// 计数桶:每规格被多少回路使用
int[] counts = new int[BusbarSpecs35C.Length];
int overflowCount = 0; // 电流超过最大规格(1120A)的回路数
if (_circuitCurrents != null)
{
foreach (double current in _circuitCurrents)
{
if (current <= 0) continue;
int idx = PickBusbarIndex(current);
if (idx >= 0)
counts[idx]++;
else
overflowCount++;
}
}
// 回写表格
gridResult.SuspendLayout();
for (int i = 0; i < BusbarSpecs35C.Length; i++)
{
var row = gridResult.Rows[i];
int cnt = counts[i];
row.Cells[COL_COUNT].Value = cnt.ToString("0");
double meters = cnt * lengthPerCircuit;
row.Cells[COL_METERS].Value = meters.ToString("0.##");
// 被使用的规格高亮
bool used = cnt > 0;
for (int c = 0; c < row.Cells.Count; c++)
{
row.Cells[c].Style.BackColor = used ? RecommendBackColor : Color.White;
row.Cells[c].Style.SelectionBackColor = used ? RecommendBackColor : Color.White;
row.Cells[c].Style.Font = used
? new Font("宋体", 9, FontStyle.Bold)
: new Font("宋体", 9);
}
}
gridResult.ResumeLayout();
// 若有超规格电流,在标题提示
if (overflowCount > 0)
{
lblInfo.Text = "提示: 有 " + overflowCount + " 个回路电流超过最大规格 (TMY-60×6, 990A),无法选型!";
}
else
{
lblInfo.Text = "说明: 按每回路原始电流,选 35°C 水平下载流量 ≥ 该电流的最小铜排规格。相同规格的回路数 × 每回路长度 = 需要总米数。";
}
}
/// <summary>选型:返回第一个载流量 ≥ need 的规格索引;都不够返回 -1。</summary>
private int PickBusbarIndex(double needCurrent)
{
if (needCurrent <= 0) return -1;
for (int i = 0; i < BusbarRatings35C.Length; i++)
{
if (BusbarRatings35C[i] >= needCurrent)
return i;
}
return -1; // 超过最大规格
}
/// <summary>解析"每回路长度"输入框,默认 1.5。无效输入返回 1.5。</summary>
private double GetLengthPerCircuit()
{
try
{
double v;
if (double.TryParse(txtLength.Text.Trim(), NumberStyles.Any,
CultureInfo.InvariantCulture, out v))
{
if (v >= 0) return v;
}
}
catch { }
return 1.5;
}
// ============================================================
// 辅助:关闭 ADGV 列头筛选按钮 (与主窗口逻辑一致)
// ============================================================
/// <summary>彻底关闭 ADGV 列头的筛选/排序按钮:
/// 把每列列头换成标准 DataGridViewColumnHeaderCell 并复位 MinimumWidth。</summary>
private void DisableAdgvFilterButtons(Smart.CustomComponents.EnhancedDataGridView grid)
{
// Smart EnhancedDataGridView 没有内置筛选按钮,无需禁用
}
}
}