using System; using System.Collections.Generic; using System.Drawing; using System.Globalization; using System.IO; using System.Windows.Forms; using Smart.CustomComponents; namespace 成套报价软件 { /// /// 推荐铜排窗口 —— 按每回路原始电流,选 35°C 水平载流量下的最小合适铜排规格, /// 统计每个规格被多少回路使用,再乘以"每回路长度"得到该规格需要的总米数。 /// 从独立项目 GCS铜排壳体计算 集成进成套报价软件。 /// 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; // 需要米数 /// 推荐行高亮色 (与主窗口一致)。 private static readonly Color RecommendBackColor = Color.FromArgb(146, 208, 80); // ============================================================ // 状态 // ============================================================ /// 主窗口传来的所有回路原始电流列表。 private List _circuitCurrents; /// 宿主(GCS 母排计算窗口):点"插入"前实时重取回路电流用。 internal GCS母排计算窗口 宿主; // ============================================================ // 入口 // ============================================================ public GCS推荐铜排窗口() { InitializeComponent(); MainForm.写日志("[推荐铜排] 窗口已构造"); LoadAppIcon(); // 设窗口/任务栏图标(从 exe 自身提取嵌入的图标) LoadSavedLength(); // 从 铜排长度.cfg 读取上次保存的每回路长度 BuildResultGrid(); // 重建表格列/行(覆盖 ui.cs 可能预建的列) WireEvents(); Recalculate(); } /// 把 exe 内嵌入的图标加载为窗口/任务栏图标。 /// 与主窗口一致,从 exe 自身资源提取,不依赖外部文件。 private void LoadAppIcon() { try { this.Icon = System.Drawing.Icon.ExtractAssociatedIcon( System.Windows.Forms.Application.ExecutablePath); } catch { /* 提取失败不影响运行,只是用默认图标 */ } } // ============================================================ // 每回路长度 自动保存 (铜排长度.cfg,与 exe 同目录) // ============================================================ /// 每回路铜排长度的配置文件路径。 /// 存放在用户应用数据目录 %APPDATA%\GCS铜排壳体计算\铜排长度.cfg, /// 而不是 exe 同目录(安装到 Program Files 后 exe 目录无写入权限)。 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"); } } /// 启动时读取上次保存的每回路长度。文件不存在或无效则保持 txtLength 默认值(1.5)。 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 { /* 读取失败不影响运行,用默认值 */ } } /// 把当前 txtLength 的值写入 铜排长度.cfg。 /// 在 TextChanged 事件里调用,实现"修改后自动保存"。无效值不保存。 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 { /* 保存失败静默忽略,不影响使用 */ } } /// 主窗口调用:把回路的原始电流传进来,并触发首次计算。 /// scopeLabel:当前统计范围(如 "全部" 或 "2#"),显示在窗口标题上,让用户知道算的是哪列。 public void SetCircuitCurrents(List currents, string scopeLabel) { _circuitCurrents = currents; // 标题显示当前统计范围 if (!string.IsNullOrEmpty(scopeLabel)) { this.Text = "每回路推荐铜排 (" + scopeLabel + " · 35°C 水平)"; } Recalculate(); } // ============================================================ // 构建表格 // ============================================================ /// 构建结果表格:4 列 (规格/载流量/回路数/需要米数)。 /// 无论 ui.cs 是否预建列,这里先 Clear 再重建,确保列结构一致。 /// 初始 18 行对应全部铜排规格(载流量预填),未使用的回路数=0。 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(); } /// 按 gridResult 当前宽度平均分配 4 列宽度。 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 开头)。 } /// 上次插入执行时刻(Click 与 MouseUp 双通道的 1 秒防重)。 private DateTime _上次插入执行 = DateTime.MinValue; /// MouseUp 直触发兜底:左键弹起点在按钮内即执行插入(Click 被吞时保底)。 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); } /// /// 在打开的窗体中查找主窗口(MyApp.MainForm)实例。 /// 推荐铜排窗口由 GCS母排计算窗口 弹出,GCS母排计算窗口 又由主窗口弹出, /// 通过遍历 OpenForms 找主窗口最可靠。 /// private MainForm FindMainForm() { foreach (Form f in Application.OpenForms) { if (f is MainForm) return (MainForm)f; } return null; } /// /// "插入铜排"按钮:遍历结果表中所有"回路数 > 0"的行(即被选用的规格), /// 把每行的铜排规格 + 需要米数 插入到主窗口的元件表(advancedDataGridView5)。 /// 表价由主窗口按母排界面当前铜价自动计算。 /// ★插入前先向 GCS 窗口实时重取回路电流重算(2026-09-26 修:先开推荐窗后输电流时 /// 旧快照回路数全 0,插入被"没有铜排"挡住=用户报"无法正常插入")。 /// 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>(); 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(规格, 米数)); } 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); } // ============================================================ // 核心计算 // ============================================================ /// 重算:对每个回路电流选最小合适铜排规格,统计每规格回路数,乘米数得总米数。 /// 只更新被使用的行(回路数>0),高亮显示;未使用的规格保持 0 并取消高亮。 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 水平下载流量 ≥ 该电流的最小铜排规格。相同规格的回路数 × 每回路长度 = 需要总米数。"; } } /// 选型:返回第一个载流量 ≥ need 的规格索引;都不够返回 -1。 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; // 超过最大规格 } /// 解析"每回路长度"输入框,默认 1.5。无效输入返回 1.5。 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 列头筛选按钮 (与主窗口逻辑一致) // ============================================================ /// 彻底关闭 ADGV 列头的筛选/排序按钮: /// 把每列列头换成标准 DataGridViewColumnHeaderCell 并复位 MinimumWidth。 private void DisableAdgvFilterButtons(Smart.CustomComponents.EnhancedDataGridView grid) { // Smart EnhancedDataGridView 没有内置筛选按钮,无需禁用 } } }