# -*- coding: utf-8 -*- """方案窗口编辑区替换为主窗口完整保存链(虚拟模式版)。heredoc 太长,用文件执行。""" import io P = r"C:\Users\099978\Documents\SharpDevelop Projects\成套报价软件\成套报价软件\DockWindows\DockWindow8\DockWindow8.cs" s = io.open(P, encoding="utf-8-sig").read() start = s.find("\t\t// ============ 方案三表编辑直写") end_anchor = "\t\tprivate string 查方案表2(System.Data.SQLite.SQLiteConnection conn, string 类型, string 图号, string 箱号)" end = s.find(end_anchor) assert start > 0 and end > start, "编辑区定位失败" NEW = '''\t\t// ============================================================ \t\t// 方案三表实时保存 —— 主窗口报价页保存链的完整复制 \t\t// (虚拟模式:CellValueNeeded 供数 / CellValuePushed 写回 / \t\t// 内存重算链乐观显示 / 300ms 防抖队列 / 后台单事务写库+聚合) \t\t// ============================================================ \t\t/// 虚拟模式供数(主窗口 Dgv_CellValueNeeded 同款)。 \t\tprivate void 方案表_CellValueNeeded(object sender, DataGridViewCellValueEventArgs e) \t\t{ \t\t\tvar dgv = sender as DataGridView; \t\t\tvar table = 取方案数据表(dgv); \t\t\tif (table == null || e.RowIndex < 0 || e.RowIndex >= table.Rows.Count) { e.Value = ""; return; } \t\t\tif (e.ColumnIndex < 0 || e.ColumnIndex >= dgv.Columns.Count) { e.Value = ""; return; } \t\t\tstring colName = dgv.Columns[e.ColumnIndex].DataPropertyName; \t\t\tif (string.IsNullOrEmpty(colName) || !table.Columns.Contains(colName)) { e.Value = ""; return; } \t\t\tobject v = table.Rows[e.RowIndex][colName]; \t\t\te.Value = (v == null || v == DBNull.Value) ? "" : v; \t\t} \t\t/// 编辑立即提交(主窗口 Grid_DirtyStateChanged 同款)。 \t\tprivate void 方案表_DirtyStateChanged(object sender, EventArgs e) \t\t{ \t\t\tvar dgv = sender as DataGridView; \t\t\tif (dgv != null && dgv.IsCurrentCellDirty) \t\t\t\tdgv.CommitEdit(DataGridViewDataErrorContexts.Commit); \t\t} \t\t/// 虚拟模式写回:写内存表 + 内存重算链(乐观显示) + 入队直写方案文件。 \t\tprivate void 方案表_CellValuePushed(object sender, DataGridViewCellValueEventArgs e) \t\t{ \t\t\tvar dgv = sender as DataGridView; \t\t\tvar table = 取方案数据表(dgv); \t\t\tif (table == null || e.RowIndex < 0 || e.RowIndex >= table.Rows.Count) return; \t\t\tif (e.ColumnIndex < 0 || e.ColumnIndex >= dgv.Columns.Count) return; \t\t\tstring colName = dgv.Columns[e.ColumnIndex].DataPropertyName; \t\t\tif (string.IsNullOrEmpty(colName) || colName == "_rowid_" || !table.Columns.Contains(colName)) return; \t\t\ttable.Rows[e.RowIndex][colName] = (e.Value == null || e.Value == DBNull.Value) ? DBNull.Value : e.Value; \t\t\t// ★ 内存重算链(乐观显示):元件→箱号→图号 \t\t\tif (_联动层 == 0 && !_方案派生列.Contains(colName)) \t\t\t{ \t\t\t\t_联动层++; \t\t\t\ttry \t\t\t\t{ \t\t\t\t\tif (dgv == advancedDataGridView元件) 方案重算元件行(e.RowIndex); \t\t\t\t\telse if (dgv == advancedDataGridView箱号) 方案重算箱号行(e.RowIndex); \t\t\t\t\telse if (dgv == advancedDataGridView图号) 方案重算图号行(e.RowIndex); \t\t\t\t} \t\t\t\tfinally { _联动层--; } \t\t\t\tdgv.Invalidate(); \t\t\t\tif (dgv == advancedDataGridView元件) \t\t\t\t{ \t\t\t\t\tadvancedDataGridView箱号.Invalidate(); \t\t\t\t\tadvancedDataGridView图号.Invalidate(); \t\t\t\t} \t\t\t\telse if (dgv == advancedDataGridView箱号) advancedDataGridView图号.Invalidate(); \t\t\t} \t\t\t// ★ 入队直写(主窗口 入队直写库 同款:300ms 防抖聚合) \t\t\tif (!_方案派生列.Contains(colName)) \t\t\t\t入队方案直写(dgv, e.RowIndex, colName, e.Value); \t\t} \t\t// ---- 内存重算链(主窗口 RecalcYuanjianRow/RecalcXianghaoRowFromYuanjian/RecalcTuhaoRowFromXianghao 的方案版) ---- \t\tprivate static decimal 方案数值(object v) \t\t{ \t\t\tdecimal d; \t\t\treturn v == null || v == DBNull.Value ? 0 : \t\t\t\t(decimal.TryParse(Convert.ToString(v).Trim(), System.Globalization.NumberStyles.Float, \t\t\t\tSystem.Globalization.CultureInfo.InvariantCulture, out d) ? d : 0); \t\t} \t\tprivate void 方案重算元件行(int rowIndex) \t\t{ \t\t\tvar t = _方案元件表; \t\t\tif (t == null || rowIndex < 0 || rowIndex >= t.Rows.Count) return; \t\t\tvar r = t.Rows[rowIndex]; \t\t\tdecimal 表价 = 方案数值(r["表价"]), 采购系数 = 方案数值(r["采购系数"]), \t\t\t\t报出系数 = 方案数值(r["报出系数"]), 数量 = 方案数值(r["数量"]); \t\t\tdecimal 采购单价 = 表价 * 采购系数; \t\t\tif (t.Columns.Contains("采购单价")) r["采购单价"] = 采购单价; \t\t\tif (t.Columns.Contains("采购价")) r["采购价"] = 采购单价 * 数量; \t\t\tif (t.Columns.Contains("报出单价")) r["报出单价"] = 采购单价 * 报出系数; \t\t\tif (t.Columns.Contains("报出价")) r["报出价"] = 采购单价 * 报出系数 * 数量; \t\t\t方案重算箱号行全部(); // 所属箱号聚合 → 图号聚合 \t\t} \t\tprivate void 方案重算箱号行(int rowIndex) \t\t{ \t\t\t// 箱号行数量变:按内存元件表 SUM 重算本行 + 图号聚合 \t\t\t方案重算箱号行全部(); \t\t} \t\tprivate void 方案重算箱号行全部() \t\t{ \t\t\tvar xh = _方案箱号表; \t\t\tvar yj = _方案元件表; \t\t\tif (xh == null) return; \t\t\tdecimal 采购合计 = 0, 报出合计 = 0; \t\t\tif (yj != null) \t\t\t{ \t\t\t\tforeach (DataRow r in yj.Rows) \t\t\t\t{ \t\t\t\t\t采购合计 += 方案数值(r["采购价"]); \t\t\t\t\t报出合计 += 方案数值(r["报出价"]); \t\t\t\t} \t\t\t} \t\t\t// 写到当前选中箱号行(方案库每箱号一张元件表,当前元件表即当前箱号) \t\t\tif (advancedDataGridView箱号.CurrentRow != null) \t\t\t{ \t\t\t\tint idx = advancedDataGridView箱号.CurrentRow.Index; \t\t\t\tif (idx >= 0 && idx < xh.Rows.Count) \t\t\t\t{ \t\t\t\t\tvar row = xh.Rows[idx]; \t\t\t\t\tdecimal 数量 = 方案数值(row["数量"]); \t\t\t\t\tif (xh.Columns.Contains("单台成本")) row["单台成本"] = 采购合计; \t\t\t\t\tif (xh.Columns.Contains("成本价")) row["成本价"] = 采购合计 * 数量; \t\t\t\t\tif (xh.Columns.Contains("单台报出")) row["单台报出"] = 报出合计; \t\t\t\t\tif (xh.Columns.Contains("报出价")) row["报出价"] = 报出合计 * 数量; \t\t\t\t\tdecimal 成本价 = 采购合计 * 数量, 报出价 = 报出合计 * 数量; \t\t\t\t\tif (xh.Columns.Contains("利润")) row["利润"] = 报出价 - 成本价; \t\t\t\t\tif (xh.Columns.Contains("利润率")) row["利润率"] = 成本价 == 0 ? 0 : (报出价 - 成本价) / 成本价; \t\t\t\t\tif (xh.Columns.Contains("毛利率")) row["毛利率"] = 报出价 == 0 ? 0 : (报出价 - 成本价) / 报出价; \t\t\t\t} \t\t\t} \t\t\t方案重算图号行全部(); \t\t} \t\tprivate void 方案重算图号行(int rowIndex) \t\t{ \t\t\t方案重算图号行全部(); \t\t} \t\tprivate void 方案重算图号行全部() \t\t{ \t\t\tvar th = _方案图号表; \t\t\tvar xh = _方案箱号表; \t\t\tif (th == null || xh == null) return; \t\t\tdecimal 箱柜数量 = 0, 报出合计 = 0, 成本合计 = 0; \t\t\tforeach (DataRow r in xh.Rows) \t\t\t{ \t\t\t\t箱柜数量 += 方案数值(r["数量"]); \t\t\t\t报出合计 += 方案数值(r["报出价"]); \t\t\t\t成本合计 += 方案数值(r["成本价"]); \t\t\t} \t\t\tif (advancedDataGridView图号.CurrentRow != null) \t\t\t{ \t\t\t\tint idx = advancedDataGridView图号.CurrentRow.Index; \t\t\t\tif (idx >= 0 && idx < th.Rows.Count) \t\t\t\t{ \t\t\t\t\tvar row = th.Rows[idx]; \t\t\t\t\tdecimal 台数 = 方案数值(row["台数"]); \t\t\t\t\tdecimal 总价 = 报出合计 * 台数, 成本总价 = 成本合计 * 台数; \t\t\t\t\tif (th.Columns.Contains("箱柜数量")) row["箱柜数量"] = 箱柜数量; \t\t\t\t\tif (th.Columns.Contains("单价")) row["单价"] = 报出合计; \t\t\t\t\tif (th.Columns.Contains("总价")) row["总价"] = 总价; \t\t\t\t\tif (th.Columns.Contains("成本总价")) row["成本总价"] = 成本总价; \t\t\t\t\tif (th.Columns.Contains("毛利")) row["毛利"] = 总价 - 成本总价; \t\t\t\t\tif (th.Columns.Contains("毛利率")) row["毛利率"] = 总价 == 0 ? 0 : (总价 - 成本总价) / 总价; \t\t\t\t} \t\t\t} \t\t} \t\t// ---- 防抖队列 + 后台事务(主窗口 编辑直写库.cs 的方案版) ---- \t\t/// 派生列集合(由计算维护;用户编辑这些列不触发直写)。 \t\tprivate static readonly System.Collections.Generic.HashSet _方案派生列 = \t\t\tnew System.Collections.Generic.HashSet( \t\t\t\tnew[] { "采购单价", "采购价", "报出单价", "报出价", "单台成本", "成本价", "单台报出", \t\t\t\t\t"利润", "利润率", "毛利率", "箱柜数量", "单价", "总价", "成本总价", "毛利" }); \t\t/// 待写库项:表名|rowid|列 → 值(后写覆盖先写)。主窗口 _待写项 同款。 \t\tprivate readonly System.Collections.Generic.Dictionary _方案待写项 = \t\t\tnew System.Collections.Generic.Dictionary(); \t\t/// 防抖定时器(300ms)。主窗口 _直写Timer 同款。 \t\tprivate Timer _方案直写Timer; \t\tprivate void 入队方案直写(DataGridView dgv, int rowIndex, string colName, object value) \t\t{ \t\t\tvar table = 取方案数据表(dgv); \t\t\tif (table == null || !table.Columns.Contains("_rowid_")) return; \t\t\tif (rowIndex < 0 || rowIndex >= table.Rows.Count) return; \t\t\tlong rowid = table.Rows[rowIndex]["_rowid_"] is long ? Convert.ToInt64(table.Rows[rowIndex]["_rowid_"]) : 0; \t\t\tif (rowid <= 0) return; \t\t\tlock (_方案待写项) \t\t\t{ \t\t\t\t_方案待写项[table.TableName + "|" + rowid + "|" + colName] = \t\t\t\t\tnew[] { table.TableName, rowid.ToString(), colName, value == null ? "" : value.ToString() }; \t\t\t} \t\t\tif (_方案直写Timer == null) \t\t\t{ \t\t\t\t_方案直写Timer = new Timer(); \t\t\t\t_方案直写Timer.Interval = 300; \t\t\t\t_方案直写Timer.Tick += 方案直写到期; \t\t\t} \t\t\t_方案直写Timer.Stop(); \t\t\t_方案直写Timer.Start(); \t\t} \t\t/// 防抖到期:批量直写方案文件(后台单事务)+按公式刷聚合。 \t\tprivate void 方案直写到期(object sender, EventArgs e) \t\t{ \t\t\t_方案直写Timer.Stop(); \t\t\tif (_方案待写项.Count == 0 || string.IsNullOrEmpty(_当前方案文件)) return; \t\t\tSystem.Collections.Generic.List 批; \t\t\tlock (_方案待写项) \t\t\t{ \t\t\t\t批 = new System.Collections.Generic.List(_方案待写项.Values); \t\t\t\t_方案待写项.Clear(); \t\t\t} \t\t\tstring dbPath = _当前方案文件; \t\t\tSystem.Threading.Tasks.Task.Run(delegate \t\t\t{ \t\t\t\ttry \t\t\t\t{ \t\t\t\t\tusing (var conn = new System.Data.SQLite.SQLiteConnection(连接串(dbPath))) \t\t\t\t\t{ \t\t\t\t\t\tconn.Open(); \t\t\t\t\t\tusing (var tx = conn.BeginTransaction()) \t\t\t\t\t\t{ \t\t\t\t\t\t\tforeach (var item in 批) \t\t\t\t\t\t\t{ \t\t\t\t\t\t\t\tusing (var cmd = new System.Data.SQLite.SQLiteCommand( \t\t\t\t\t\t\t\t\t"UPDATE \\"" + item[0].Replace("\\"", "") + "\\" SET \\"" + item[2].Replace("\\"", "") + \t\t\t\t\t\t\t\t\t"\\"=@v WHERE rowid=@r;", conn, tx)) \t\t\t\t\t\t\t\t{ \t\t\t\t\t\t\t\t\tcmd.Parameters.AddWithValue("@v", item[3]); \t\t\t\t\t\t\t\t\tcmd.Parameters.AddWithValue("@r", long.Parse(item[1])); \t\t\t\t\t\t\t\t\tcmd.ExecuteNonQuery(); \t\t\t\t\t\t\t\t} \t\t\t\t\t\t\t\t// 元件行:重算本行派生价 \t\t\t\t\t\t\t\tif (item[0].StartsWith("yj_")) \t\t\t\t\t\t\t\t{ \t\t\t\t\t\t\t\t\tstring yjSet = 计算引擎.生成Set片段(计算引擎.加载公式(GetFak连接(), "元件"), "ROW", null); \t\t\t\t\t\t\t\t\tif (yjSet.Length > 0) \t\t\t\t\t\t\t\t\t\tExec(conn, tx, "UPDATE \\"" + item[0] + "\\" SET " + yjSet + " WHERE rowid=" + item[1] + ";"); \t\t\t\t\t\t\t\t} \t\t\t\t\t\t\t} \t\t\t\t\t\t\t// 聚合链:反查 图号/箱号 → 箱号行聚合 + 图号行聚合 \t\t\t\t\t\t\tstring 图号 = null, 箱号 = null; \t\t\t\t\t\t\tforeach (var item in 批) \t\t\t\t\t\t\t{ \t\t\t\t\t\t\t\tif (item[0].StartsWith("yj_")) \t\t\t\t\t\t\t\t{ \t\t\t\t\t\t\t\t\tusing (var cmd = new System.Data.SQLite.SQLiteCommand( \t\t\t\t\t\t\t\t\t\t"SELECT 图号,箱号 FROM 表索引 WHERE 类型='元件' AND 实际表名=@t LIMIT 1;", conn, tx)) \t\t\t\t\t\t\t\t\t{ \t\t\t\t\t\t\t\t\t\tcmd.Parameters.AddWithValue("@t", item[0]); \t\t\t\t\t\t\t\t\t\tusing (var r = cmd.ExecuteReader()) \t\t\t\t\t\t\t\t\t\t\tif (r.Read()) { 图号 = r.IsDBNull(0) ? null : r.GetString(0); 箱号 = r.IsDBNull(1) ? null : r.GetString(1); } \t\t\t\t\t\t\t\t\t} \t\t\t\t\t\t\t\t} \t\t\t\t\t\t\t\telse if (item[0].StartsWith("xh_")) \t\t\t\t\t\t\t\t{ \t\t\t\t\t\t\t\t\tusing (var cmd = new System.Data.SQLite.SQLiteCommand( \t\t\t\t\t\t\t\t\t\t"SELECT 图号 FROM 表索引 WHERE 类型='箱号' AND 实际表名=@t LIMIT 1;", conn, tx)) \t\t\t\t\t\t\t\t\t{ \t\t\t\t\t\t\t\t\t\tcmd.Parameters.AddWithValue("@t", item[0]); \t\t\t\t\t\t\t\t\t\tusing (var r = cmd.ExecuteReader()) \t\t\t\t\t\t\t\t\t\t\tif (r.Read()) { 图号 = r.IsDBNull(0) ? null : r.GetString(0); 箱号 = null; } \t\t\t\t\t\t\t\t\t} \t\t\t\t\t\t\t\t} \t\t\t\t\t\t\t} \t\t\t\t\t\t\tif (!string.IsNullOrEmpty(图号)) \t\t\t\t\t\t\t{ \t\t\t\t\t\t\t\tstring xhTbl = 查方案表2(conn, tx, "箱号", 图号, null); \t\t\t\t\t\t\t\tif (!string.IsNullOrEmpty(xhTbl)) \t\t\t\t\t\t\t\t{ \t\t\t\t\t\t\t\t\tif (!string.IsNullOrEmpty(箱号)) \t\t\t\t\t\t\t\t\t{ \t\t\t\t\t\t\t\t\t\tstring yjTbl = 查方案表2(conn, tx, "元件", 图号, 箱号); \t\t\t\t\t\t\t\t\t\tif (!string.IsNullOrEmpty(yjTbl)) \t\t\t\t\t\t\t\t\t\t{ \t\t\t\t\t\t\t\t\t\t\tstring xhSet = 计算引擎.生成Set片段(计算引擎.加载公式(GetFak连接(), "箱号"), "ROW", yjTbl); \t\t\t\t\t\t\t\t\t\t\t\tif (xhSet.Length > 0) \t\t\t\t\t\t\t\t\t\t\t\t\tExec(conn, tx, "UPDATE \\"" + xhTbl + "\\" SET " + xhSet + " WHERE \\"内部ID\\"='" + 箱号.Replace("'", "''") + "';"); \t\t\t\t\t\t\t\t\t\t} \t\t\t\t\t\t\t\t\t} \t\t\t\t\t\t\t\t\tstring thSet = 计算引擎.生成Set片段(计算引擎.加载公式(GetFak连接(), "图号"), "ROW", xhTbl); \t\t\t\t\t\t\t\t\tif (thSet.Length > 0) \t\t\t\t\t\t\t\t\t\tExec(conn, tx, "UPDATE \\"" + 图号表名 + "\\" SET " + thSet + " WHERE \\"内部ID\\"='" + 图号.Replace("'", "''") + "';"); \t\t\t\t\t\t\t\t} \t\t\t\t\t\t\t} \t\t\t\t\t\t\ttx.Commit(); \t\t\t\t\t\t} \t\t\t\t\t} \t\t\t\t\t写联动日志("方案直写完成: " + 批.Count + " 项"); \t\t\t\t} \t\t\t\tcatch (Exception ex) \t\t\t\t{ \t\t\t\t\t写联动日志("方案直写异常: " + ex.Message); \t\t\t\t} \t\t\t}); \t\t} \t\t/// 简单 SQL 执行。 \t\tprivate static void Exec(System.Data.SQLite.SQLiteConnection conn, System.Data.SQLite.SQLiteTransaction tx, string sql) \t\t{ \t\t\tusing (var cmd = new System.Data.SQLite.SQLiteCommand(sql, conn, tx)) \t\t\t\tcmd.ExecuteNonQuery(); \t\t} \t\t/// 方案窗口自己的 fak.db 连接(公式表来源;已打开)。 \t\tprivate System.Data.SQLite.SQLiteConnection GetFak连接() \t\t{ \t\t\tvar conn = new System.Data.SQLite.SQLiteConnection("Data Source=" + \t\t\t\tPath.Combine(AppDomain.CurrentDomain.BaseDirectory, "sjk", "fak.db") + ";Version=3;"); \t\t\tconn.Open(); \t\t\treturn conn; \t\t} ''' s = s[:start] + NEW + s[end:] io.open(P, "w", encoding="utf-8-sig").write(s) print("编辑直写区已替换为主窗口完整链路")