基线:全量源码首提(D1 版本控制落地,含第一轮优化 B1-B4/A1/A4/缩放修复)
This commit is contained in:
@@ -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 没有内置筛选按钮,无需禁用
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user