1047 lines
60 KiB
C#
1047 lines
60 KiB
C#
/*
|
||
* 主窗体构造函数(由旧项目 主窗口.cs 迁移)。
|
||
*/
|
||
using System;
|
||
using System.Collections.Generic;
|
||
using System.Data;
|
||
using System.Diagnostics;
|
||
using System.Drawing;
|
||
using System.IO;
|
||
using System.Windows.Forms;
|
||
using Microsoft.Win32;
|
||
using Smart.CustomComponents;
|
||
|
||
namespace 成套报价软件
|
||
{
|
||
/// <summary>
|
||
/// 主窗体:构造函数迁移自旧项目 主窗口.cs(事件挂接全部保留)。
|
||
/// </summary>
|
||
public partial class MainForm : Form
|
||
{
|
||
/// <summary>启动时待导入的项目 zip 路径(cadbt 导出联动传入;空=正常启动)。</summary>
|
||
private string _startupZip;
|
||
|
||
// ===== 会员登录状态(登录成功后全程保持;会员信息/修改密码窗自动带手机号) =====
|
||
public static string 当前登录手机号 = "";
|
||
public static string 当前会员类型 = "";
|
||
public static string 当前有效期 = "";
|
||
/// <summary>当前登录密码(仅内存,不落盘):新版会员协议要求项目云上传/下载/列表带密码参数。</summary>
|
||
public static string 当前登录密码 = "";
|
||
|
||
/// <summary>改密重启标记:修改密码成功后关闭主窗口,Program 在释放单实例锁后启动新实例
|
||
/// (新实例启动先弹登录窗,客户用新密码重新登录)。</summary>
|
||
internal static bool 改密重启;
|
||
|
||
public MainForm() : this(null) { }
|
||
|
||
public MainForm(string startupZip)
|
||
{
|
||
_startupZip = startupZip;
|
||
InitializeComponent();
|
||
this.BackColor = System.Drawing.Color.White; // ★ 全窗口纯白背景(2026-09-24 用户要求)
|
||
// 窗体/任务栏图标:从 exe 内嵌图标提取(编译时 /win32icon 嵌入)
|
||
try {
|
||
this.Icon = System.Drawing.Icon.ExtractAssociatedIcon(
|
||
System.Windows.Forms.Application.ExecutablePath);
|
||
} catch { }
|
||
CreateDockWindows_dockSurface1();
|
||
// 恢复上次保存的停靠布局(设置页「保存布局」按钮保存的),无布局文件时用默认布局
|
||
初始化停靠布局();
|
||
|
||
// 旧 ui.cs 设计器注入的 SQLite 数据加载(Load 事件)
|
||
this.Load += (s, e) => LoadAllSqliteData();
|
||
|
||
// ★ 启动登录成功过的(Program.cs 先弹登录窗):状态栏显示已登录
|
||
if (!string.IsNullOrWhiteSpace(当前登录手机号))
|
||
this.Load += (s, e) => SetStatus("已登录: " + 当前登录手机号 + " (" + 当前会员类型 + ",有效期至 " + 当前有效期 + ")");
|
||
|
||
// ★ 项目云每日自动上传(每天首次启动把全部项目静默传到会员服务器;本地模式不传)
|
||
this.Load += (s, e) => 启动项目云自动上传();
|
||
|
||
// ★ AI助手本地接口(127.0.0.1:18790,令牌鉴权;AI×选型页断路器选型替换用)
|
||
this.Load += (s, e) => 启动AI助手接口();
|
||
|
||
// ★ AI引擎随软件启动:Load 后台拉起(已运行则跳过;双击exe/快捷方式/启动器任何入口都生效)
|
||
this.Load += (s, e) => System.Threading.ThreadPool.QueueUserWorkItem(delegate { OpenClawHost.启动引擎(); });
|
||
|
||
|
||
// ★ 性能优化:ui.cs 已经自带优化版选中效果(HashSet 缓存 _esSelMatrix,O(1) 查询),
|
||
// 不需要摘除。这里只补做一件事——开启 DoubleBuffered(双缓冲),
|
||
// 消除多选拖动时的重绘闪烁。ui.cs 没设这个属性(它是 protected,设计器够不到)。
|
||
EnableDoubleBuffered(this.advancedDataGridView9);
|
||
EnableDoubleBuffered(this.advancedDataGridView4);
|
||
EnableDoubleBuffered(this.advancedDataGridView5);
|
||
|
||
// ★ 功能区按钮图标(exe 旁 icons\ 目录,缺失时跳过不影响启动)
|
||
加载功能区图标();
|
||
|
||
// ★ 所有数据表格:行高不可拖动调整(高度值保持各表现状)
|
||
禁止行高调整(this);
|
||
|
||
// ★ 虚拟模式:三表不再存数据(DataSource 弃用),由内存 DataTable 按需供数。
|
||
// 行数再多也秒开,刷新只更新可见区域,粘贴/重算管线可移入后台线程。
|
||
this.advancedDataGridView9.VirtualMode = true;
|
||
this.advancedDataGridView4.VirtualMode = true;
|
||
this.advancedDataGridView5.VirtualMode = true;
|
||
this.advancedDataGridView9.CellValueNeeded += Dgv_CellValueNeeded;
|
||
this.advancedDataGridView4.CellValueNeeded += Dgv_CellValueNeeded;
|
||
this.advancedDataGridView5.CellValueNeeded += Dgv_CellValueNeeded;
|
||
this.advancedDataGridView9.CellValuePushed += Dgv_CellValuePushed;
|
||
this.advancedDataGridView4.CellValuePushed += Dgv_CellValuePushed;
|
||
this.advancedDataGridView5.CellValuePushed += Dgv_CellValuePushed;
|
||
// ★ 母排元件行不受"小数位/取整方式"设置限制(价格列按自然值显示),见 Grid5_母排CellFormatting
|
||
this.advancedDataGridView5.CellFormatting += Grid5_母排CellFormatting;
|
||
|
||
// ★ 状态栏:时钟启动(Load 后再刷一次项目名等信息)
|
||
启动状态栏时钟();
|
||
|
||
// ★ 状态栏:选区求和(三表选中数值自动求和,Excel 风格)
|
||
this.advancedDataGridView9.SelectionChanged += 选区求和_Refresh;
|
||
this.advancedDataGridView4.SelectionChanged += 选区求和_Refresh;
|
||
this.advancedDataGridView5.SelectionChanged += 选区求和_Refresh;
|
||
this.advancedDataGridView9.CellValueChanged += 选区求和_Refresh;
|
||
this.advancedDataGridView4.CellValueChanged += 选区求和_Refresh;
|
||
this.advancedDataGridView5.CellValueChanged += 选区求和_Refresh;
|
||
this.advancedDataGridView9.CellValuePushed += 选区求和_Refresh;
|
||
this.advancedDataGridView4.CellValuePushed += 选区求和_Refresh;
|
||
this.advancedDataGridView5.CellValuePushed += 选区求和_Refresh;
|
||
|
||
// ★ 按 Excel 模板(advancedDataGridView9Table.xlsx 第3行"不可编辑"标记)
|
||
// 设置各表的只读列。公式列(采购价/报出价/毛利/利润等)和序号列不允许双击编辑,
|
||
// 只有用户输入列(图号/箱号/数量/表价/系数等)可编辑。
|
||
// ui.cs 不改,运行时按列名(DataPropertyName)查找设置 ReadOnly。
|
||
ApplyReadOnlyColumns(this.advancedDataGridView6, new string[] {
|
||
// 项目表:全部13列不可编辑(项目信息通过新建/修改对话框改)
|
||
"项目编号", "项目名称", "客户名称", "客户地址", "联系人", "联系电话",
|
||
"项目日期", "箱柜台数", "成本总价", "报出总价", "利润", "利润率", "备注"
|
||
});
|
||
ApplyReadOnlyColumns(this.advancedDataGridView9, new string[] {
|
||
// 图号表:序号 + 6个公式列不可编辑;可编辑:图号/图名/台数
|
||
"序号", "箱柜数量", "单价", "总价", "成本总价", "毛利", "毛利率"
|
||
});
|
||
ApplyReadOnlyColumns(this.advancedDataGridView4, new string[] {
|
||
// 箱号表:序号 + 7个公式列不可编辑;可编辑:图号/箱号/型号/规格/单位/数量/箱柜类型/备注/图纸
|
||
"序号", "单台成本", "成本价", "单台报出", "报出价", "利润", "利润率", "毛利率"
|
||
});
|
||
ApplyReadOnlyColumns(this.advancedDataGridView5, new string[] {
|
||
// 元件表:序号 + 4个公式列不可编辑;可编辑:图号/箱号/元件名称/规格/单位/数量/表价/采购系数/报出系数/...
|
||
"序号", "采购单价", "采购价", "报出单价", "报出价"
|
||
});
|
||
|
||
// 启动时窗口显示在屏幕正中间(ui.cs 默认是 WindowsDefaultLocation)
|
||
this.StartPosition = FormStartPosition.CenterScreen;
|
||
|
||
// 把三个报价表格的选择模式从 RowHeaderSelect(点单元格选整行)
|
||
// 改成 CellSelect(点单元格只选那一个单元格)。
|
||
// 这样右键/点击时只选中一个单元格,不会高亮整行。
|
||
this.advancedDataGridView9.SelectionMode = DataGridViewSelectionMode.CellSelect;
|
||
this.advancedDataGridView4.SelectionMode = DataGridViewSelectionMode.CellSelect;
|
||
this.advancedDataGridView5.SelectionMode = DataGridViewSelectionMode.CellSelect;
|
||
|
||
// 原有按钮事件
|
||
|
||
|
||
// 企业信息按钮:打开企业信息设置窗口(信息持久化到 shezhi\企业信息.config.xml)
|
||
|
||
// 备份/恢复按钮:备份和恢复库数据(用户库/柜体库/方案库/收藏/企业信息)
|
||
|
||
// CAD扒图(button11):检测 CAD 插件是否已安装,未安装则用 PowerShell 运行安装脚本,
|
||
// 然后启动 AutoCAD 2018。
|
||
|
||
// ===== 项目表搜索(button28):根据 comboBox2 条件 + textBox1 关键词筛选 =====
|
||
this.button28.Click += button28_Click;
|
||
// 回车直接触发搜索
|
||
this.textBox1.KeyDown += (s, e) =>
|
||
{
|
||
if (e.KeyCode == Keys.Enter)
|
||
{
|
||
e.SuppressKeyPress = true;
|
||
button28_Click(s, e);
|
||
}
|
||
};
|
||
|
||
// ===== 用户库工具栏 toolStrip1 的事件绑定 =====
|
||
// 插入/替换:把用户库(advancedDataGridView7)选中行的数据
|
||
// 插入或替换到元件表(advancedDataGridView5)
|
||
this.toolStripButton1.Click += UserLib_Insert_Click; // 插入
|
||
this.toolStripButton2.Click += UserLib_Replace_Click; // 替换
|
||
// 新增/修改/删除:对用户库(advancedDataGridView7)本身的编辑操作
|
||
this.toolStripButton3.Click += UserLib_New_Click; // 新增
|
||
this.toolStripButton4.Click += UserLib_Modify_Click; // 修改
|
||
this.toolStripButton5.Click += UserLib_Delete_Click; // 删除
|
||
// 导入/导出/打开模板:用户库的批量数据交换
|
||
this.toolStripButton6.Click += UserLib_Import_Click; // 导入
|
||
this.toolStripButton7.Click += UserLib_Export_Click; // 导出
|
||
this.toolStripButton8.Click += UserLib_OpenTemplate_Click;// 打开模板
|
||
// toolStripButton11: 清除(清空用户库表所有数据)
|
||
this.toolStripButton11.Click += UserLib_Clear_Click;
|
||
// 用户库编辑后自动保存(改完单元格失焦时触发,避免数据丢失)
|
||
this.advancedDataGridView7.CellEndEdit += (s2, e2) => SaveUserLibToDatabase();
|
||
|
||
// ===== 用户库筛选/查找(comboBox3 + textBox2 + button30) =====
|
||
// comboBox3:选择元件名称 → 用户库只显示该名称的行(选"全部"恢复)
|
||
this.comboBox3.SelectedIndexChanged += UserLib_NameFilter_Changed;
|
||
// button30:按"元件名称"和"规格"两列不区分大小写模糊查找(参考项目表 button28)
|
||
this.button30.Click += UserLib_Search_Click;
|
||
// textBox2:回车也触发查找
|
||
this.textBox2.KeyDown += (s, e) =>
|
||
{
|
||
if (e.KeyCode == System.Windows.Forms.Keys.Enter)
|
||
{
|
||
e.SuppressKeyPress = true;
|
||
UserLib_Search_Click(s, EventArgs.Empty);
|
||
}
|
||
};
|
||
|
||
// ===== 柜体库工具栏 toolStrip3 的事件绑定 =====
|
||
// 插入/替换:把柜体库(advancedDataGridView10)选中行的数据
|
||
// 插入或替换到元件表(advancedDataGridView5)
|
||
this.toolStripButton12.Click += GuiTi_Insert_Click; // 插入
|
||
this.toolStripButton13.Click += GuiTi_Replace_Click; // 替换
|
||
// 新增/修改/删除:对柜体库(advancedDataGridView10)本身的编辑操作
|
||
this.toolStripButton14.Click += GuiTi_New_Click; // 新增
|
||
this.toolStripButton15.Click += GuiTi_Modify_Click; // 修改
|
||
this.toolStripButton16.Click += GuiTi_Delete_Click; // 删除
|
||
// 柜体库编辑后自动保存(改完单元格失焦时触发,避免数据丢失)
|
||
this.advancedDataGridView10.CellEndEdit += (s2, e2) => SaveGuiTiToDatabase();
|
||
|
||
// ===== 柜体库筛选/查找(comboBox4 + textBox5 + button31) =====
|
||
// comboBox4:选择元件名称 → 柜体库只显示该名称的行(选"全部"恢复)
|
||
this.comboBox4.SelectedIndexChanged += GuiTi_NameFilter_Changed;
|
||
// button31:按"元件名称"和"规格"两列不区分大小写模糊查找
|
||
this.button31.Click += GuiTi_Search_Click;
|
||
// textBox5:回车也触发查找
|
||
this.textBox5.KeyDown += (s, e) =>
|
||
{
|
||
if (e.KeyCode == System.Windows.Forms.Keys.Enter)
|
||
{
|
||
e.SuppressKeyPress = true;
|
||
GuiTi_Search_Click(s, EventArgs.Empty);
|
||
}
|
||
};
|
||
|
||
// ===== 母排页(tabPage12)铜价刷新按钮:从长江有色网抓取 1#铜 均价和日期 =====
|
||
this.btn刷新.Click += btn刷新_Click;
|
||
// txt加工费:失焦时自动保存到配置文件(下次启动恢复)
|
||
this.txt加工费.Leave += (s, e) => Save铜排参数();
|
||
// txt铜价 自动联动:当前铜价(元/吨) 或 加工费(元/吨) 变化时,重算 (铜价+加工费)/1000 → 元/Kg
|
||
// (1 吨 = 1000 公斤,所以除以 1000)
|
||
this.txt当前铜价.TextChanged += (s, e) => Recalc铜价PerKg();
|
||
this.txt加工费.TextChanged += (s, e) => Recalc铜价PerKg();
|
||
|
||
// txt单价(元/米) 自动联动:铜价(kg)、规格(层/宽/厚)、材质(铜排/铝排) 任一变化时重算
|
||
// 公式: 单价(元/米) = 铜价(元/kg) × 层数 × 宽(mm) × 厚(mm) × 密度 / 1000
|
||
// (铜排 TMY 密度 8.9 g/cm³,铝排 LMY 密度 2.7 g/cm³)
|
||
this.txt铜价.TextChanged += (s, e) => Recalc单价PerMeter();
|
||
this.txt层数.TextChanged += (s, e) => Recalc单价PerMeter();
|
||
this.txt宽度.TextChanged += (s, e) => Recalc单价PerMeter();
|
||
this.txt厚度.TextChanged += (s, e) => Recalc单价PerMeter();
|
||
// 材质切换:重新加载规格表、切换标签/参数/价格(CheckedChanged 会在切换瞬间旧/新按钮各触发一次,
|
||
// 只在 Checked=true 时执行切换,避免重复)
|
||
this.rb铜排.CheckedChanged += (s, e) => { if (this.rb铜排.Checked) 切换母排材质(); };
|
||
this.rb铝排.CheckedChanged += (s, e) => { if (this.rb铝排.Checked) 切换母排材质(); };
|
||
|
||
// ===== 母排页 btn插入 / btn替换:把当前规格作为一行元件插入/替换到元件表(advancedDataGridView5) =====
|
||
// 字段对应:元件名称=母排,规格=txt规格,单位=comboBox1,数量=txt长度,表价=txt单价,类型=母排
|
||
this.btn插入.Click += btn插入_Click;
|
||
this.btn替换.Click += btn替换_Click;
|
||
|
||
// ===== GCS 铜排壳体计算窗口(停靠主窗口右侧) =====
|
||
// ★ G2-2(2026-10-01):原为母排停靠窗 button14 的闭包,提取为公共方法
|
||
// 打开GCS母排计算窗口()——功能区「计算」组按钮调用;母排页 button14 已删
|
||
|
||
// ===== 双击 advancedDataGridView6 单元格跳转到报价页 =====
|
||
this.advancedDataGridView6.CellDoubleClick += advancedDataGridView6_CellDoubleClick;
|
||
|
||
// ===== 三个增强表格的联动(SelectionChanged) =====
|
||
this.advancedDataGridView9.SelectionChanged += advancedDataGridView9_SelectionChanged;
|
||
this.advancedDataGridView4.SelectionChanged += advancedDataGridView4_SelectionChanged;
|
||
this.advancedDataGridView5.SelectionChanged += advancedDataGridView5_SelectionChanged;
|
||
|
||
// ===== 铜排规格表(tabPage12 / advancedDataGridView8)选中变化:解析规格填入 txt层数/txt宽度/txt厚度 =====
|
||
// 规则:
|
||
// 无括号 "TMY-15×3" → 层数=1, 宽度=15, 厚度=3
|
||
// 有括号 "TMY-2(60×6)" → 层数=2, 宽度=60, 厚度=6
|
||
this.advancedDataGridView8.SelectionChanged += advancedDataGridView8_SelectionChanged;
|
||
|
||
// ===== 项目表右键:加入方案库(整项目) =====
|
||
var _menuProject = new ContextMenuStrip();
|
||
_menuProject.Items.Add("加入方案库", null, MenuProject_AddToFak_Click);
|
||
this.advancedDataGridView6.ContextMenuStrip = _menuProject;
|
||
Log(" 项目表菜单已挂到 advancedDataGridView6(加入方案库)");
|
||
|
||
// ===== 方案窗口:项目页/三表联动/右键菜单已全部自治于 DockWindow8(数据源=方案库目录的方案文件) =====
|
||
|
||
// ===== 三个增强表格的公式实时重算(CellValueChanged) =====
|
||
// 编辑单元格后立即提交,触发 CellValueChanged,自底向上重算派生列。
|
||
this.advancedDataGridView5.CurrentCellDirtyStateChanged += Grid_DirtyStateChanged;
|
||
this.advancedDataGridView5.CellValueChanged += Yuanjian_CellValueChanged;
|
||
this.advancedDataGridView4.CurrentCellDirtyStateChanged += Grid_DirtyStateChanged;
|
||
this.advancedDataGridView4.CellValueChanged += Xianghao_CellValueChanged;
|
||
this.advancedDataGridView9.CurrentCellDirtyStateChanged += Grid_DirtyStateChanged;
|
||
this.advancedDataGridView9.CellValueChanged += Tuhao_CellValueChanged;
|
||
|
||
// ===== 调试日志:元件表编辑开始/结束 =====
|
||
this.advancedDataGridView5.CellBeginEdit += (s, e) =>
|
||
{
|
||
string cn = (e.ColumnIndex >= 0 && e.ColumnIndex < this.advancedDataGridView5.Columns.Count)
|
||
? this.advancedDataGridView5.Columns[e.ColumnIndex].HeaderText : "?";
|
||
Log("[BeginEdit] 元件表 行" + e.RowIndex + " 列[" + cn + "]");
|
||
};
|
||
this.advancedDataGridView5.CellEndEdit += (s, e) =>
|
||
{
|
||
if (e.ColumnIndex < 0 || e.ColumnIndex >= this.advancedDataGridView5.Columns.Count) return;
|
||
string cn = this.advancedDataGridView5.Columns[e.ColumnIndex].HeaderText;
|
||
string dp = this.advancedDataGridView5.Columns[e.ColumnIndex].DataPropertyName;
|
||
object v = this.advancedDataGridView5.Rows[e.RowIndex].Cells[e.ColumnIndex].Value;
|
||
Log("[EndEdit] 元件表 行" + e.RowIndex + " 列[" + cn + "] 值=" + (v ?? "(null)"));
|
||
|
||
// ★ 只转换公式列集合中的列(用户输入的数值列),其他列保持原样
|
||
string colKey = !string.IsNullOrEmpty(dp) ? dp : cn;
|
||
if (!s_公式列集合.Contains(colKey)) return;
|
||
if (v == null || v is double) return;
|
||
string str = v.ToString().Trim();
|
||
if (string.IsNullOrEmpty(str)) return;
|
||
double d;
|
||
if (double.TryParse(str, out d))
|
||
{
|
||
// ★ 虚拟模式:写内存表+刷新该行(写 Cells.Value 无效)
|
||
string colName = this.advancedDataGridView5.Columns[e.ColumnIndex].DataPropertyName;
|
||
if (string.IsNullOrEmpty(colName)) colName = this.advancedDataGridView5.Columns[e.ColumnIndex].HeaderText;
|
||
写单元格(this.advancedDataGridView5, e.RowIndex, colName, d);
|
||
}
|
||
};
|
||
|
||
// ===== 其他表格也加 CellEndEdit,确保公式列格式化一致生效 =====
|
||
this.advancedDataGridView4.CellEndEdit += (s, e) =>
|
||
{
|
||
if (e.ColumnIndex < 0 || e.ColumnIndex >= this.advancedDataGridView4.Columns.Count) return;
|
||
string dp = this.advancedDataGridView4.Columns[e.ColumnIndex].DataPropertyName;
|
||
string cn = this.advancedDataGridView4.Columns[e.ColumnIndex].HeaderText;
|
||
string colKey = !string.IsNullOrEmpty(dp) ? dp : cn;
|
||
if (!s_公式列集合.Contains(colKey)) return;
|
||
object v = this.advancedDataGridView4.Rows[e.RowIndex].Cells[e.ColumnIndex].Value;
|
||
if (v == null || v is double) return;
|
||
string str = v.ToString().Trim();
|
||
if (string.IsNullOrEmpty(str)) return;
|
||
double d;
|
||
if (double.TryParse(str, out d))
|
||
{
|
||
// ★ 虚拟模式:写内存表+刷新该行(写 Cells.Value 无效)
|
||
string colName = this.advancedDataGridView4.Columns[e.ColumnIndex].DataPropertyName;
|
||
if (string.IsNullOrEmpty(colName)) colName = this.advancedDataGridView4.Columns[e.ColumnIndex].HeaderText;
|
||
写单元格(this.advancedDataGridView4, e.RowIndex, colName, d);
|
||
}
|
||
};
|
||
this.advancedDataGridView9.CellEndEdit += (s, e) =>
|
||
{
|
||
if (e.ColumnIndex < 0 || e.ColumnIndex >= this.advancedDataGridView9.Columns.Count) return;
|
||
string dp = this.advancedDataGridView9.Columns[e.ColumnIndex].DataPropertyName;
|
||
string cn = this.advancedDataGridView9.Columns[e.ColumnIndex].HeaderText;
|
||
string colKey = !string.IsNullOrEmpty(dp) ? dp : cn;
|
||
if (!s_公式列集合.Contains(colKey)) return;
|
||
object v = this.advancedDataGridView9.Rows[e.RowIndex].Cells[e.ColumnIndex].Value;
|
||
if (v == null || v is double) return;
|
||
string str = v.ToString().Trim();
|
||
if (string.IsNullOrEmpty(str)) return;
|
||
double d;
|
||
if (double.TryParse(str, out d))
|
||
{
|
||
// ★ 虚拟模式:写内存表+刷新该行(写 Cells.Value 无效)
|
||
string colName = this.advancedDataGridView9.Columns[e.ColumnIndex].DataPropertyName;
|
||
if (string.IsNullOrEmpty(colName)) colName = this.advancedDataGridView9.Columns[e.ColumnIndex].HeaderText;
|
||
写单元格(this.advancedDataGridView9, e.RowIndex, colName, d);
|
||
}
|
||
};
|
||
|
||
// 吞掉 DataError(设了 ValueType=double 后,空值/非数字值可能触发)
|
||
this.advancedDataGridView5.DataError += (s, e) => { e.Cancel = true; };
|
||
this.advancedDataGridView4.DataError += (s, e) => { e.Cancel = true; };
|
||
this.advancedDataGridView9.DataError += (s, e) => { e.Cancel = true; };
|
||
this.advancedDataGridView6.DataError += (s, e) => { e.Cancel = true; };
|
||
|
||
// ★ 修复(2026-09-30):项目页选中行变化 → 刷新状态栏"项目:"标签
|
||
// (未进项目时按选中行显示项目名;进项目后按 _currentProjectId 显示)
|
||
this.advancedDataGridView6.SelectionChanged += (s, e) =>
|
||
{
|
||
if (!_isReloading) 刷新状态栏();
|
||
};
|
||
|
||
// ★ 多选移动适配(2026-09-30):报价三表挂"右键多选保持"钩子——
|
||
// 右键点已选中的行不清空多选集合(否则右键弹菜单瞬间多选被清成一行,移动只动一行)
|
||
HookKeepMulti(this.advancedDataGridView9);
|
||
HookKeepMulti(this.advancedDataGridView4);
|
||
HookKeepMulti(this.advancedDataGridView5);
|
||
|
||
// ★ 行头选择(2026-09-30):全部表格改 RowHeaderSelect(Excel 风格)——
|
||
// 点单元格=选格(不影响编辑),点行头=选中整行,Ctrl/Shift+点行头=多选整行。
|
||
// 递归扫描全控件树(主窗口所有页/方案页/选型页/项目页一次覆盖)。
|
||
ApplyRowHeaderSelectAll(this);
|
||
|
||
// ===== 创建并挂载右键菜单 =====
|
||
BuildContextMenus();
|
||
|
||
// ===== 箱体计算面板(tabPage14)事件绑定 =====
|
||
Init箱体计算面板();
|
||
|
||
// ===== 窗体关闭时把当前项目的报价数据回写数据库 =====
|
||
this.FormClosing += 主窗口_FormClosing;
|
||
|
||
// ===== 窗体加载完成后,由主窗口.cs 完全接管初始化状态 =====
|
||
// ui.cs 的 LoadAllSqliteData() 会先加载旧共享库数据(设计器自动生成,不能改),
|
||
// 这里在它执行完毕后重新接管:
|
||
// 1) 重新从项目库加载 advancedDataGridView6(覆盖 ui.cs 的结果)
|
||
// 2) 清空三个报价表(每个项目独立库,启动时不显示任何报价数据)
|
||
// 3) 报价数据只有在双击项目行时才从该项目的独立库加载
|
||
this.Load += (s, e) =>
|
||
{
|
||
// 重新加载项目表(由主窗口.cs 控制,确保数据来源正确)
|
||
ReloadProjectsFromDatabase();
|
||
// 项目表公式列设 Format(REAL 类型加载后是 double,Format 直接生效)
|
||
ApplyPriceFormat(this.advancedDataGridView6, _项目小数位文本);
|
||
|
||
// ★ 行头选择补扫(2026-09-30):覆盖 Load 后才挂载/绑定的表格
|
||
// (构造时的首扫覆盖不到延迟创建的 DockWindow)
|
||
try { ApplyRowHeaderSelectAll(this); } catch { }
|
||
|
||
// 清空三个报价表,避免显示旧共享库的历史数据
|
||
// ★ 虚拟模式:清空用 RowCount=0(设置 DataSource 会抛异常)
|
||
this.advancedDataGridView9.RowCount = 0;
|
||
this.advancedDataGridView4.RowCount = 0;
|
||
this.advancedDataGridView5.RowCount = 0;
|
||
|
||
// 刷新状态栏(项目/定位/数据统计)
|
||
刷新状态栏();
|
||
|
||
// 用户库启动后,把不重复的元件名称填入 comboBox3
|
||
// (ui.cs 的 LoadSqliteData_advancedDataGridView7 已在 InitializeComponent 里加载完毕)
|
||
LoadUserLibDistinctNames();
|
||
|
||
// 柜体库启动后,把不重复的元件名称填入 comboBox4
|
||
// (ui.cs 的 LoadSqliteData_advancedDataGridView10 已在 InitializeComponent 里加载完毕)
|
||
LoadGuiTiDistinctNames();
|
||
|
||
// 方案库只有一个 fak.db,启动时直接加载图号表数据
|
||
ReloadFakData();
|
||
};
|
||
|
||
// ===== 监听主区域 tabControl2 切换,离开报价页触发保存 =====
|
||
this.tabControl2.SelectedIndexChanged += (s, e) =>
|
||
{
|
||
// 诊断:确认 tabControl2 切换事件触发,记录当前页
|
||
string t2name = (this.tabControl2 != null && this.tabControl2.SelectedTab != null)
|
||
? (this.tabControl2.SelectedTab.Text ?? "(null)") : "(null)";
|
||
Log("[TabSync] tabControl2.SelectedIndexChanged 触发, 当前页=[" + t2name +
|
||
"] _isTabSyncing=" + _isTabSyncing + " _isReloading=" + _isReloading);
|
||
|
||
// 切换标签时同步启用/禁用网页上的插入/替换按钮(仅在报价页可用)
|
||
SyncQuoteButtonsState();
|
||
|
||
// 切到 tabControl2 某页时,tabControl1 同步切到同名页(按 Text 匹配)
|
||
// 用 guard 防止反向触发死循环(下方的 tabControl1.SelectedIndexChanged 也会做同样判断)
|
||
if (!_isTabSyncing)
|
||
{
|
||
SyncTab1ToTab2ByText();
|
||
}
|
||
|
||
// 切到"报价"页(tabPage4)时,如果当前没有打开任何项目,自动恢复最近打开过的项目
|
||
// (用户首次启动 / 主动清空 _currentProjectId 后再切回报价页时触发)
|
||
if (this.tabControl2 != null && this.tabControl2.SelectedTab == this.tabPage4
|
||
&& string.IsNullOrWhiteSpace(_currentProjectId))
|
||
{
|
||
AutoEnterLastProject();
|
||
}
|
||
|
||
// 离开报价页(从 tabPage4 切到别的 tab)时触发保存。
|
||
// _isReloading=true 表示是程序化切换(双击进项目等),跳过避免重复保存。
|
||
// ★ 停靠版修正:仅"报价 → 其他"的切换才保存(与旧版一致);
|
||
// 停靠面板任意两个非报价窗口之间切换不应触发全量保存。
|
||
if (_isReloading) return;
|
||
if (this.tabControl2 != null && this.tabControl2.SelectedTab != this.tabPage4
|
||
&& this.tabControl2.PreviousSelectedTab == this.tabPage4)
|
||
{
|
||
SaveAllToDatabase();
|
||
}
|
||
};
|
||
|
||
// ===== 功能区(ribbonStrip1):页签切换联动 =====
|
||
// ★ G1-2(2026-10-01):按钮点击路由已二合一——Designer 绑定的 RibbonStrip1ButtonClick
|
||
// 直接转调统一路由,不再手工二次绑定(避免双处理器)
|
||
// 页签切换检测:基库 RibbonStrip 的页签在 OnMouseDown 内切换且无事件,
|
||
// MouseUp 时索引必已更新,在此检测变化并联动停靠文档(等效原 tabControl1 联动)
|
||
this.ribbonStrip1.MouseUp += ribbonStrip1_MouseUpTabChanged;
|
||
|
||
// ★ G4(2026-10-01):状态栏"保存"标签点击=立即保存(与 Ctrl+S/功能区保存按钮同一实现)
|
||
this.lbl保存.Click += (s, e) =>
|
||
{
|
||
try { 强制保存_快捷键(); } catch (Exception exSv) { Log("[状态栏保存] " + exSv.Message); }
|
||
};
|
||
|
||
|
||
// ★ 启动参数导入:cadbt/pdfbt 导出联动(命令行带 zip 路径)→自动导入并打开该项目
|
||
if (!string.IsNullOrWhiteSpace(_startupZip) && System.IO.File.Exists(_startupZip))
|
||
{
|
||
this.Load += (s, e) => 执行启动导入(_startupZip);
|
||
}
|
||
|
||
// ★ 导入命令文件轮询:主程序已运行时,cadbt/pdfbt 的二次导出经
|
||
// Program.cs 单实例转发写命令文件,这里收命令并导入(不新开窗口)
|
||
启动导入命令轮询();
|
||
|
||
// ===== 启动价格查询网页(tabPage5 里的 webView21) =====
|
||
StartPriceWebView();
|
||
|
||
// 主窗口关闭时,确保 python 子进程被清理
|
||
this.FormClosing += (s, e) => StopProxyServer();
|
||
|
||
// ===== 元件库收藏功能已随本地选型页退役(2026-09-13 用户要求删控件;
|
||
// 控件 comboBox6/7、button6/12 已从 DockWindow4 移除,收藏方法群留在 主窗口.cs 备查) =====
|
||
|
||
// ===== 选型页(tabPage10):元件汇总 + 箱号明细 =====
|
||
InitSelectionPage();
|
||
}
|
||
|
||
// ============================================================
|
||
// 单实例导出联动:启动参数导入 + 命令文件轮询
|
||
// ============================================================
|
||
|
||
/// <summary>导入并打开一个项目 zip(cadbt/pdfbt 导出联动共用)。</summary>
|
||
private void 执行启动导入(string zipPath)
|
||
{
|
||
try
|
||
{
|
||
Log("[启动导入] 收到导出: " + zipPath);
|
||
var pids = ImportAllProjectsFromZip(zipPath);
|
||
if (pids != null && pids.Count > 0)
|
||
{
|
||
// 重算导入项目的汇总列 + 刷新项目列表显示(抄手动导入按钮/保存按钮链路,
|
||
// 否则项目页列表看不到新项目,要重启才能看到)
|
||
foreach (string pid in pids)
|
||
{
|
||
try { 计算引擎.刷新项目汇总(GetQuoteDbPath(pid), pid, TuhaoTableName); }
|
||
catch (Exception exSum) { Log("[启动导入] 汇总刷新失败 " + pid + ": " + exSum.Message); }
|
||
}
|
||
ReloadProjectsFromDatabase();
|
||
|
||
// ★ 打开前先保存上一个项目并清缓存、设 _currentProjectId(逐行抄双击项目行链路;
|
||
// 这是 EnterProjectCore 的调用契约,缺了这步箱号/元件联动还挂在旧项目缓存上,
|
||
// 元件表会空/错,要重启才正常)
|
||
string newPid = pids[0];
|
||
if (!string.IsNullOrWhiteSpace(_currentProjectId) && _currentProjectId != newPid)
|
||
{
|
||
try
|
||
{
|
||
// 只存明细,不调 RecalcAllFromDatabase(它会在新项目上跑)
|
||
if (this.advancedDataGridView5 != null) this.advancedDataGridView5.EndEdit();
|
||
if (this.advancedDataGridView4 != null) this.advancedDataGridView4.EndEdit();
|
||
if (this.advancedDataGridView9 != null) this.advancedDataGridView9.EndEdit();
|
||
if (_tuhaoTable != null)
|
||
SaveTableToDatabase(_tuhaoTable, _currentProjectId, TuhaoTableName, null, null, true);
|
||
foreach (var kv in _xianghaoCache)
|
||
if (kv.Value != null && !string.IsNullOrWhiteSpace(kv.Value.TableName))
|
||
SaveTableToDatabase(kv.Value, _currentProjectId, "\"" + kv.Value.TableName + "\"", null, null, true);
|
||
foreach (var kv in _yuanjianCache)
|
||
if (kv.Value != null && !string.IsNullOrWhiteSpace(kv.Value.TableName))
|
||
SaveTableToDatabase(kv.Value, _currentProjectId, "\"" + kv.Value.TableName + "\"", null, null, true);
|
||
}
|
||
catch (Exception exSave) { Log("[启动导入] 切项目保存异常: " + exSave.Message); }
|
||
_xianghaoCache.Clear();
|
||
_yuanjianCache.Clear();
|
||
_tuhaoTable = null;
|
||
}
|
||
_currentProjectId = newPid;
|
||
|
||
Log("[启动导入] 已导入 " + pids.Count + " 个项目, 打开 " + newPid);
|
||
EnterProjectCore(newPid);
|
||
RestoreProjectSelection(newPid);
|
||
SetStatus("已导入并打开项目 " + newPid);
|
||
}
|
||
}
|
||
catch (Exception exImp) { Log("[启动导入] 异常: " + exImp.Message); }
|
||
}
|
||
|
||
/// <summary>命令文件轮询 Timer(300ms):二次实例转发来的导出 zip 路径写在该文件,读到即导入。</summary>
|
||
private System.Windows.Forms.Timer _导入命令Timer;
|
||
private DateTime _导入命令上次时间 = DateTime.MinValue;
|
||
|
||
private void 启动导入命令轮询()
|
||
{
|
||
_导入命令Timer = new System.Windows.Forms.Timer();
|
||
_导入命令Timer.Interval = 300;
|
||
_导入命令Timer.Tick += (s, e) =>
|
||
{
|
||
try
|
||
{
|
||
string cmdPath = Program.导入命令文件路径;
|
||
if (!System.IO.File.Exists(cmdPath)) return;
|
||
var wt = System.IO.File.GetLastWriteTime(cmdPath);
|
||
if (wt <= _导入命令上次时间) return; // 无新命令
|
||
|
||
string zip = (System.IO.File.ReadAllText(cmdPath) ?? "").Trim();
|
||
_导入命令上次时间 = wt;
|
||
if (zip.Length == 0) return;
|
||
System.IO.File.WriteAllText(cmdPath, "", System.Text.Encoding.UTF8); // 清空防重复
|
||
|
||
// 带窗口到前台再导入
|
||
if (WindowState == FormWindowState.Minimized) WindowState = FormWindowState.Normal;
|
||
Activate();
|
||
执行启动导入(zip);
|
||
}
|
||
catch (Exception exPoll) { Log("[启动导入] 轮询异常: " + exPoll.Message); }
|
||
};
|
||
_导入命令Timer.Start();
|
||
}
|
||
|
||
// ============================================================
|
||
// 功能区(ribbonStrip1)按钮路由与辅助
|
||
// ============================================================
|
||
|
||
/// <summary>打开项目云窗体(上传/下载还原/删除云端项目)。</summary>
|
||
private void 打开项目云窗口()
|
||
{
|
||
using (var dlg = new 项目云窗体(this))
|
||
{
|
||
dlg.ShowDialog(this);
|
||
// 关闭后本地列表可能因"下载还原"变化,刷新项目页
|
||
try { ReloadProjectsFromDatabase(); } catch (Exception exRld) { Log("[项目页] 下载还原后刷新项目列表失败: " + exRld.Message); }
|
||
}
|
||
}
|
||
|
||
/// <summary>项目云每日自动上传启动(远程模式+已登录才生效;后台静默)。</summary>
|
||
private void 启动项目云自动上传()
|
||
{
|
||
项目云自动上传.启动(this);
|
||
}
|
||
|
||
/// <summary>功能区统一按钮点击:按 RibbonButton.Name(保留原按钮字段名)分发到原处理器。</summary>
|
||
private void ribbonStrip1_ButtonClick(object sender, Smart.CustomComponents.RibbonButtonEventArgs e)
|
||
{
|
||
if (e.Button == null || string.IsNullOrEmpty(e.Button.Name)) return;
|
||
switch (e.Button.Name)
|
||
{
|
||
case "button1": button1_Click(sender, e); break; // 新建项目
|
||
case "button2": button2_Click(sender, e); break; // 修改项目(项目页)
|
||
case "button3": button3_Click(sender, e); break; // 删除项目
|
||
case "button5": button5_Click(sender, e); break; // 复制项目
|
||
case "button7": button7_Click(sender, e); break; // 导出项目
|
||
case "button8": button8_Click(sender, e); break; // 导入项目
|
||
case "button37": 打开项目云窗口(); break; // 项目云(上传/下载还原到会员服务器)
|
||
case "button11": button11_Click(sender, e); break; // CAD扒图
|
||
case "button34": button34_Click(sender, e); break; // PDF扒图
|
||
case "button13": button13_Click(sender, e); break; // 调系数
|
||
case "button15": 打开调母排窗口(); break; // 调母排
|
||
case "btn母排计算": 打开GCS母排计算窗口(); break; // G2-2:GCS垂直母排计算(自母排页button14移入)
|
||
case "button16": button16_Click(sender, e); break; // 甲方报表
|
||
case "button17": 强制保存_快捷键(); break; // G2-1:保存(与 Ctrl+S 同一实现,状态栏提示不弹窗)
|
||
case "button18": button18_Click(sender, e); break; // 导入报价
|
||
case "button29": 打开费用模板窗口(); break; // G1-1:模板费用(费用行按公式计入箱号合计)
|
||
case "button33": button33_Click(sender, e); break; // 元件排序
|
||
case "btnAI设置": // G2-3:自AI助手窗口工具栏移入
|
||
using (var dlgAI = new AI助手设置()) { dlgAI.ShowDialog(this); }
|
||
break;
|
||
case "btnAI重连": // G2-3:自AI助手窗口工具栏移入(引擎重连)
|
||
try
|
||
{
|
||
AI助手窗口兜底显示();
|
||
if (this.w10 != null) this.w10.重新连接();
|
||
}
|
||
catch (Exception exAI) { Log("[功能区·AI重连] " + exAI.Message); }
|
||
break;
|
||
case "btn登录":
|
||
using (var dlg登录 = new 登录())
|
||
{
|
||
if (dlg登录.ShowDialog(this) == DialogResult.OK)
|
||
{
|
||
当前登录手机号 = dlg登录.登录手机号;
|
||
当前会员类型 = dlg登录.登录会员类型;
|
||
当前有效期 = dlg登录.登录有效期;
|
||
当前登录密码 = dlg登录.登录密码; // 新协议:项目云上传/下载/列表需带密码(仅内存)
|
||
SetStatus("已登录: " + 当前登录手机号 + " (" + 当前会员类型 + ",有效期至 " + 当前有效期 + ")");
|
||
Log("[会员] 登录成功: " + 当前登录手机号 + " " + 当前会员类型);
|
||
}
|
||
}
|
||
break; // 登录
|
||
case "btn修改密码":
|
||
{
|
||
var dlg改密 = new 修改密码();
|
||
dlg改密.txt手机号码.Text = 当前登录手机号; // 自动带已登录手机号
|
||
if (dlg改密.ShowDialog(this) == DialogResult.OK)
|
||
{
|
||
// ★改密成功=关闭整个主窗口并重启软件重走登录流程(用户 2026-09-26 要求:
|
||
// 不保留原窗口,直接关窗→弹登录窗)。退出时释放单实例锁后由 Program 启动新实例
|
||
Log("[会员] 密码已修改,主窗口关闭并重启软件等待重新登录");
|
||
改密重启 = true;
|
||
Close();
|
||
}
|
||
}
|
||
break; // 修改密码
|
||
case "btn企业信息": btn企业信息_Click(sender, e); break; // 企业信息
|
||
case "btn会员信息":
|
||
{
|
||
var dlg会员 = new 会员信息();
|
||
dlg会员.txt手机号码.Text = 当前登录手机号; // 自动带已登录手机号
|
||
dlg会员.ShowDialog(this);
|
||
}
|
||
break; // 会员信息
|
||
case "btn保存布局": btn保存布局_Click(sender, e); break; // 保存布局
|
||
case "btn关于软件":
|
||
{
|
||
var dlg关于 = new 关于软件();
|
||
dlg关于.ShowDialog(this);
|
||
}
|
||
break; // 关于软件
|
||
case "btn备份": btn备份_Click(sender, e); break; // 备份
|
||
case "btn恢复": btn恢复_Click(sender, e); break; // 恢复
|
||
// ---- CAD扒图页签(原左侧报价窗口工具栏 7 钮,未开工作区自动打开) ----
|
||
case "btnCad导出数据": Cad工作区调用(f => f.功能区_导出数据()); break;
|
||
case "btnCad新建项目": Cad工作区调用(f => f.功能区_新建项目()); break;
|
||
case "btnCad删除项目": Cad工作区调用(f => f.功能区_删除项目()); break;
|
||
case "btnCad扒图号": Cad工作区调用(f => f.功能区_扒图号()); break;
|
||
case "btnCad扒箱号": Cad工作区调用(f => f.功能区_扒箱号()); break;
|
||
case "btnCad扒元件": Cad工作区调用(f => f.功能区_扒元件()); break;
|
||
case "btnCad设置": Cad工作区调用(f => f.功能区_设置()); break;
|
||
// ---- PDF扒图页签(原左侧报价窗口工具栏 7 钮,未开工作区自动打开) ----
|
||
case "btnPdf导出数据": Pdf工作区调用(f => f.功能区_导出数据()); break;
|
||
case "btnPdf新建项目": Pdf工作区调用(f => f.功能区_新建项目()); break;
|
||
case "btnPdf删除项目": Pdf工作区调用(f => f.功能区_删除项目()); break;
|
||
case "btnPdf扒图号": Pdf工作区调用(f => f.功能区_扒图号()); break;
|
||
case "btnPdf扒箱号": Pdf工作区调用(f => f.功能区_扒箱号()); break;
|
||
case "btnPdf扒元件": Pdf工作区调用(f => f.功能区_扒元件()); break;
|
||
case "btnPdf设置": Pdf工作区调用(f => f.功能区_设置()); break;
|
||
}
|
||
}
|
||
|
||
/// <summary>CAD扒图页签按钮统一入口:工作区未开先自动打开,再对内嵌报价表单执行动作。</summary>
|
||
private void Cad工作区调用(System.Action<MyApp.Form3> action)
|
||
{
|
||
if (_cadWin == null || _cadWin.IsDisposed) button11_Click(null, null);
|
||
var f = _cadWin != null ? _cadWin.报价表单 : null;
|
||
if (f == null)
|
||
{
|
||
MessageBox.Show(this, "CAD扒图工作区未就绪,请稍候重试。", "CAD扒图",
|
||
MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
||
return;
|
||
}
|
||
try { action(f); }
|
||
catch (Exception ex) { Log("[功能区·CAD扒图] " + ex.Message); }
|
||
}
|
||
|
||
/// <summary>PDF扒图页签按钮统一入口:工作区未开先自动打开,再对内嵌报价表单执行动作。</summary>
|
||
private void Pdf工作区调用(System.Action<MyAppPdf.Form3> action)
|
||
{
|
||
if (_pdfWin == null || _pdfWin.IsDisposed) button34_Click(null, null);
|
||
var f = _pdfWin != null ? _pdfWin.报价表单 : null;
|
||
if (f == null)
|
||
{
|
||
MessageBox.Show(this, "PDF扒图工作区未就绪,请稍候重试。", "PDF扒图",
|
||
MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
||
return;
|
||
}
|
||
try { action(f); }
|
||
catch (Exception ex) { Log("[功能区·PDF扒图] " + ex.Message); }
|
||
}
|
||
|
||
/// <summary>功能区页签切换的基准索引(检测用户点击页签后的变化)。</summary>
|
||
private int _lastRibbonActiveIndex = 0;
|
||
|
||
/// <summary>MouseUp 时检测功能区活动页签变化:用户点了"项目"/"报价"页签 → 激活对应停靠文档。</summary>
|
||
private async void ribbonStrip1_MouseUpTabChanged(object sender, MouseEventArgs e)
|
||
{
|
||
if (this.ribbonStrip1 == null) return;
|
||
if (this.ribbonStrip1.ActiveTabIndex == _lastRibbonActiveIndex) return;
|
||
_lastRibbonActiveIndex = this.ribbonStrip1.ActiveTabIndex;
|
||
string tabName = (this.ribbonStrip1.ActiveTab != null) ? this.ribbonStrip1.ActiveTab.Text : "";
|
||
Log("[TabSync] 功能区页签切换: [" + tabName + "]");
|
||
if (tabName == "项目" && this.w1 != null)
|
||
{
|
||
// ★ 先强制提交报价页当前编辑控件,再等直写批提交,最后才读项目库
|
||
// 报出系数编辑尤其容易停留在编辑控件里,若不 EndEdit 项目页会抢读旧值
|
||
try
|
||
{
|
||
if (advancedDataGridView5 != null) advancedDataGridView5.EndEdit();
|
||
if (advancedDataGridView4 != null) advancedDataGridView4.EndEdit();
|
||
if (advancedDataGridView9 != null) advancedDataGridView9.EndEdit();
|
||
}
|
||
catch { }
|
||
await 等待直写完成();
|
||
try
|
||
{
|
||
if (!string.IsNullOrWhiteSpace(_currentProjectId))
|
||
计算引擎.刷新项目汇总(GetQuoteDbPath(_currentProjectId), _currentProjectId, TuhaoTableName);
|
||
ReloadProjectsFromDatabase();
|
||
}
|
||
catch (Exception exSyncTab) { Log("[TabSync] 项目页切换刷新异常: " + exSyncTab.Message); }
|
||
this.w1.Show(this.dockSurface1);
|
||
this.w1.Activate();
|
||
}
|
||
else if (tabName == "报价" && this.w2 != null)
|
||
{
|
||
this.w2.Show(this.dockSurface1);
|
||
this.w2.Activate();
|
||
}
|
||
// ★ 扒图页签联动:切到 CAD扒图/PDF扒图 页签 = 打开(或激活)对应工作区窗口
|
||
else if (tabName == "CAD扒图")
|
||
{
|
||
Cad工作区调用(f => { });
|
||
}
|
||
else if (tabName == "PDF扒图")
|
||
{
|
||
Pdf工作区调用(f => { });
|
||
}
|
||
// "设置"页只显示按钮面板,无停靠窗口
|
||
}
|
||
|
||
/// <summary>按页签文字找功能区索引(找不到返回 -1)。</summary>
|
||
private int 功能区页索引(string 文字)
|
||
{
|
||
if (this.ribbonStrip1 == null || 文字 == null) return -1;
|
||
for (int i = 0; i < this.ribbonStrip1.Tabs.Count; i++)
|
||
if (string.Equals(this.ribbonStrip1.Tabs[i].Text, 文字, StringComparison.OrdinalIgnoreCase))
|
||
return i;
|
||
return -1;
|
||
}
|
||
|
||
/// <summary>RibbonButton.Name → icons\ 目录下的图标文件名(与 tools\make_ribbon_icons_pillow.py 一致)。</summary>
|
||
private static readonly System.Collections.Generic.Dictionary<string, string> 功能区图标表 =
|
||
new System.Collections.Generic.Dictionary<string, string>
|
||
{
|
||
{ "button1", "new-project.png" }, { "button2", "edit-project.png" },
|
||
{ "button3", "delete-project.png" },
|
||
{ "button5", "copy-project.png" }, { "button7", "export-project.png" },
|
||
{ "button8", "import-project.png" },
|
||
{ "button37", "backup.png" }, // 项目云
|
||
// ★ G1-3(2026-10-01):button4/9/10 幽灵按钮图标映射已删(按钮早已不存在)
|
||
{ "button17", "save.png" },
|
||
{ "button18", "import-quote.png" }, { "button16", "report.png" },
|
||
{ "button15", "busbar.png" }, { "button13", "factor.png" },
|
||
{ "btn母排计算", "busbar.png" }, // G2-2:GCS垂直母排计算
|
||
{ "button33", "sort-components.png" }, { "button29", "template-cost.png" },
|
||
{ "button11", "cad-grab.png" }, { "button34", "pdf-grab.png" },
|
||
{ "btnAI设置", "ai-assistant.png" }, { "btnAI重连", "ai-assistant.png" }, // G2-3
|
||
{ "btnCad导出数据", "pa-export.png" }, { "btnCad新建项目", "pa-new-project.png" },
|
||
{ "btnCad删除项目", "pa-del-project.png" }, { "btnCad扒图号", "pa-tuhao.png" },
|
||
{ "btnCad扒箱号", "pa-xianghao.png" }, { "btnCad扒元件", "pa-yuanjian.png" },
|
||
{ "btnCad设置", "pa-settings.png" },
|
||
{ "btnPdf导出数据", "pa-export.png" }, { "btnPdf新建项目", "pa-new-project.png" },
|
||
{ "btnPdf删除项目", "pa-del-project.png" }, { "btnPdf扒图号", "pa-tuhao.png" },
|
||
{ "btnPdf扒箱号", "pa-xianghao.png" }, { "btnPdf扒元件", "pa-yuanjian.png" },
|
||
{ "btnPdf设置", "pa-settings.png" },
|
||
{ "btn登录", "login.png" },
|
||
{ "btn修改密码", "password.png" }, { "btn会员信息", "member.png" },
|
||
{ "btn企业信息", "company.png" }, { "btn保存布局", "save-layout.png" },
|
||
{ "btn关于软件", "about.png" },
|
||
{ "btn备份", "backup.png" }, { "btn恢复", "restore.png" }
|
||
};
|
||
|
||
/// <summary>给功能区 24 个按钮设置图标(exe 旁 icons\ 目录,绝对路径;文件缺失跳过)。</summary>
|
||
private void 加载功能区图标()
|
||
{
|
||
try
|
||
{
|
||
string dir = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "icons");
|
||
foreach (var kv in 功能区图标表)
|
||
{
|
||
var btn = FindRibbonButton(kv.Key);
|
||
if (btn == null) continue;
|
||
string p = System.IO.Path.Combine(dir, kv.Value);
|
||
if (!System.IO.File.Exists(p)) continue;
|
||
btn.Image = Smart.CustomComponents.RibbonButton.FromFile(p);
|
||
}
|
||
if (this.ribbonStrip1 != null) this.ribbonStrip1.Invalidate();
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Log("[功能区] 图标加载异常(忽略): " + ex.Message);
|
||
}
|
||
}
|
||
|
||
/// <summary>按 Name 在功能区三层集合里找 RibbonButton(找不到返回 null)。</summary>
|
||
private Smart.CustomComponents.RibbonButton FindRibbonButton(string name)
|
||
{
|
||
if (this.ribbonStrip1 == null) return null;
|
||
foreach (Smart.CustomComponents.RibbonTab tab in this.ribbonStrip1.Tabs)
|
||
foreach (Smart.CustomComponents.RibbonGroup grp in tab.Groups)
|
||
foreach (Smart.CustomComponents.RibbonButton btn in grp.Buttons)
|
||
if (btn.Name == name) return btn;
|
||
return null;
|
||
}
|
||
|
||
/// <summary>调母排(button15):普通非模态弹窗(原构造器内 lambda 抽出)。
|
||
/// 不自动居中、不贴靠右侧、不跟随主窗口移动、不限制大小,直接弹出。</summary>
|
||
private 调母排 _调母排窗;
|
||
private void 打开调母排窗口()
|
||
{
|
||
if (_调母排窗 != null && !_调母排窗.IsDisposed)
|
||
{
|
||
_调母排窗.Activate();
|
||
return;
|
||
}
|
||
if (string.IsNullOrWhiteSpace(_currentProjectId))
|
||
{
|
||
MessageBox.Show(this, "请先在项目列表双击打开一个项目。", "调母排",
|
||
MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||
return;
|
||
}
|
||
_调母排窗 = new 调母排(this);
|
||
// 不设置 StartPosition/Location/Size/MaximumSize/MinimumSize,使用窗体自身默认值
|
||
_调母排窗.Show(this);
|
||
_调母排窗.FormClosed += (s2, e2) => { _调母排窗 = null; };
|
||
}
|
||
|
||
// ============================================================
|
||
// GCS 铜排壳体计算窗口(2026-10-01 G2-2:自母排页 button14 闭包提取,功能区按钮调用)
|
||
// ============================================================
|
||
|
||
private GCS母排计算窗口 _gcsForm = null;
|
||
private System.Windows.Forms.Timer _gcsSyncTimer = null;
|
||
private bool _gcsDocked = false; // GCS 窗口是否正在停靠
|
||
private const int gcsFixedWidth = 471;
|
||
|
||
/// <summary>打开/激活 GCS 铜排壳体计算窗口(停靠主窗口右侧,宽度固定 471 只跟高度和位置;
|
||
/// _gcsDocked=true 时主窗口"最大化"实际是占屏幕左半边,GCS 窗口占右半边)。</summary>
|
||
private void 打开GCS母排计算窗口()
|
||
{
|
||
if (_gcsForm != null && !_gcsForm.IsDisposed)
|
||
{
|
||
_gcsForm.Activate();
|
||
同步GCS窗口();
|
||
return;
|
||
}
|
||
_gcsForm = new GCS母排计算窗口();
|
||
// 固定宽度(最小=最大宽度),高度不限
|
||
_gcsForm.MinimumSize = new Size(gcsFixedWidth, 200);
|
||
_gcsForm.MaximumSize = new Size(gcsFixedWidth, Screen.PrimaryScreen.Bounds.Height);
|
||
_gcsForm.FormBorderStyle = FormBorderStyle.Sizable;
|
||
_gcsForm.StartPosition = FormStartPosition.Manual;
|
||
_gcsForm.Show(this);
|
||
_gcsDocked = true;
|
||
同步GCS窗口();
|
||
|
||
// 用 Timer 低频同步位置(拖动/调整大小时)
|
||
if (_gcsSyncTimer == null)
|
||
{
|
||
_gcsSyncTimer = new System.Windows.Forms.Timer();
|
||
_gcsSyncTimer.Interval = 50;
|
||
_gcsSyncTimer.Tick += (s2, e2) => 同步GCS窗口();
|
||
}
|
||
_gcsSyncTimer.Start();
|
||
|
||
// 主窗口最小化/最大化/还原时立即同步
|
||
EventHandler onStateChanged = (s2, e2) =>
|
||
{
|
||
if (_gcsForm == null || _gcsForm.IsDisposed) return;
|
||
if (this.WindowState == FormWindowState.Minimized)
|
||
{
|
||
_gcsForm.WindowState = FormWindowState.Minimized;
|
||
}
|
||
else if (this.WindowState == FormWindowState.Maximized && _gcsDocked)
|
||
{
|
||
// 停靠模式:拦截最大化,主窗口占左侧(减去GCS宽度),GCS 右侧固定宽度
|
||
var screen = Screen.FromHandle(this.Handle).WorkingArea;
|
||
int mainW = screen.Width - gcsFixedWidth;
|
||
_gcsDocked = false; // 临时关闭,避免递归
|
||
this.WindowState = FormWindowState.Normal;
|
||
this.Bounds = new Rectangle(screen.X, screen.Y, mainW, screen.Height);
|
||
_gcsDocked = true;
|
||
_gcsForm.WindowState = FormWindowState.Normal;
|
||
_gcsForm.Bounds = new Rectangle(screen.X + mainW, screen.Y, gcsFixedWidth, screen.Height);
|
||
}
|
||
else
|
||
{
|
||
// 还原到正常窗口:重新停靠
|
||
_gcsForm.WindowState = FormWindowState.Normal;
|
||
同步GCS窗口();
|
||
}
|
||
};
|
||
this.SizeChanged -= onStateChanged;
|
||
this.SizeChanged += onStateChanged;
|
||
this.LocationChanged -= onStateChanged;
|
||
this.LocationChanged += onStateChanged;
|
||
|
||
_gcsForm.FormClosed += (s2, e2) =>
|
||
{
|
||
if (_gcsSyncTimer != null) { _gcsSyncTimer.Stop(); _gcsSyncTimer.Dispose(); _gcsSyncTimer = null; }
|
||
_gcsDocked = false;
|
||
_gcsForm = null;
|
||
};
|
||
}
|
||
|
||
/// <summary>同步 GCS 窗口位置/大小到主窗口右侧。</summary>
|
||
private void 同步GCS窗口()
|
||
{
|
||
if (_gcsForm == null || _gcsForm.IsDisposed) return;
|
||
if (this.WindowState == FormWindowState.Minimized)
|
||
{
|
||
_gcsForm.WindowState = FormWindowState.Minimized;
|
||
return;
|
||
}
|
||
var screen = Screen.FromHandle(this.Handle).WorkingArea;
|
||
if (this.WindowState == FormWindowState.Maximized && _gcsDocked)
|
||
{
|
||
// 停靠模式下的"最大化":主窗口左半屏(减去GCS固定宽度),GCS 右侧固定宽度
|
||
_gcsForm.WindowState = FormWindowState.Normal;
|
||
_gcsForm.Bounds = new Rectangle(screen.Right - gcsFixedWidth, screen.Y, gcsFixedWidth, screen.Height);
|
||
_gcsForm.BringToFront();
|
||
return;
|
||
}
|
||
// 正常状态:贴主窗口右侧显示,宽度固定
|
||
_gcsForm.WindowState = FormWindowState.Normal;
|
||
_gcsForm.Location = new Point(this.Right, this.Top);
|
||
_gcsForm.Size = new Size(gcsFixedWidth, this.Height);
|
||
}
|
||
|
||
|
||
async void DockSurface1ActiveContentChanged(object sender, EventArgs e)
|
||
{
|
||
// ★ 反向联动:点击项目/报价窗口激活 → 功能区页签跟随切换(与 ribbonStrip1_MouseUpTabChanged 正向联动对称)
|
||
// 只认 w1(项目)/w2(报价)两个文档窗口,其他窗口(元件库/方案等)不动功能区
|
||
try
|
||
{
|
||
var active = this.dockSurface1.ActiveContent as WeifenLuo.WinFormsUI.Docking.DockContent;
|
||
if (active != null && this.ribbonStrip1 != null)
|
||
{
|
||
string 目标页 = null;
|
||
if (active == this.w1) 目标页 = "项目";
|
||
else if (active == this.w2) 目标页 = "报价";
|
||
else if (active is Cad扒图窗口) 目标页 = "CAD扒图"; // ★ 扒图工作区:点窗口标签→功能区跳对应页签
|
||
else if (active is Pdf扒图窗口) 目标页 = "PDF扒图";
|
||
if (目标页 == "项目")
|
||
{
|
||
// ★ 直接点项目窗口标签:先提交报价页当前编辑控件,再等批提交,最后读项目库
|
||
try
|
||
{
|
||
if (advancedDataGridView5 != null) advancedDataGridView5.EndEdit();
|
||
if (advancedDataGridView4 != null) advancedDataGridView4.EndEdit();
|
||
if (advancedDataGridView9 != null) advancedDataGridView9.EndEdit();
|
||
}
|
||
catch { }
|
||
await 等待直写完成();
|
||
try
|
||
{
|
||
if (!string.IsNullOrWhiteSpace(_currentProjectId))
|
||
计算引擎.刷新项目汇总(GetQuoteDbPath(_currentProjectId), _currentProjectId, TuhaoTableName);
|
||
ReloadProjectsFromDatabase();
|
||
}
|
||
catch (Exception exPg) { Log("[TabSync] 切项目页刷新汇总失败: " + exPg.Message); }
|
||
}
|
||
if (目标页 != null)
|
||
{
|
||
int idx = 功能区页索引(目标页);
|
||
if (idx >= 0 && this.ribbonStrip1.ActiveTabIndex != idx)
|
||
{
|
||
this.ribbonStrip1.ActiveTabIndex = idx;
|
||
_lastRibbonActiveIndex = idx; // 同步基准,防 MouseUp 正向联动再触发抖动
|
||
Log("[TabSync] 文档激活→功能区页签: [" + 目标页 + "]");
|
||
}
|
||
}
|
||
}
|
||
}
|
||
catch (Exception exTab) { Log("[TabSync] 活动文档联动异常: " + exTab.Message); }
|
||
|
||
// 停靠面板活动文档变化 → 触发旧 tabControl2.SelectedIndexChanged 兼容事件
|
||
if (_tabControl2兼容 != null) {
|
||
_tabControl2兼容.RaiseSelectedIndexChanged();
|
||
}
|
||
}
|
||
}
|
||
} |