基线:全量源码首提(D1 版本控制落地,含第一轮优化 B1-B4/A1/A4/缩放修复)

This commit is contained in:
dqb-dev
2026-10-01 22:15:22 +08:00
commit 1e65792b09
499 changed files with 204579 additions and 0 deletions
@@ -0,0 +1,472 @@
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 没有内置筛选按钮,无需禁用
}
}
}