Files
ctbjrj/成套报价软件/MainForm.停靠布局.cs
T

575 lines
28 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 停靠布局保存/恢复(设置页签「保存布局」按钮)。
// 用 DockPanelSuite 自带的 SaveAsXml/LoadFromXml:
// 保存 = 哪些窗口开着、停靠在哪(左/右/上/下/浮动/文档)、大小、比例、激活窗口;
// 恢复 = 程序启动时若存在 停靠布局.xml 就按它重排,否则用默认布局。
using System;
using System.IO;
using System.Windows.Forms;
namespace 成套报价软件
{
public partial class MainForm
{
/// <summary>布局文件路径(与 exe 同目录)。</summary>
private string 停靠布局路径 {
get {
return Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "停靠布局.xml");
}
}
/// <summary>激活窗口记录文件(独立小文件,不碰布局 XML,避免编码破坏 XML)。</summary>
private string 激活窗口路径 {
get {
return Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "停靠布局.激活.txt");
}
}
/// <summary>窗口内部分割条位置记录文件(SplitContainer 的 SplitterDistance)。</summary>
private string 分割位置路径 {
get {
return Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "停靠布局.分割.txt");
}
}
/// <summary>构造函数里 CreateDockWindows 之后调用:抓取窗口实例 + 挂按钮 + 恢复上次布局。</summary>
internal void 初始化停靠布局()
{
捕获停靠窗口实例();
// 保存布局按钮已迁到功能区(ribbonStrip1),点击统一走 ribbonStrip1_ButtonClick 路由
// 分割条位置恢复(Load 一次 + 800ms 后再补一次):
// Load 时停靠面板尚未完成首次渲染,随后的窗口尺寸调整会让 SplitContainer
// 按比例重算分割条,把已恢复的位置冲掉——延迟二次恢复在最终尺寸定型后生效
this.Load += (s, e) =>
{
恢复分割位置();
var t = new Timer();
t.Interval = 800;
t.Tick += (s2, e2) => { t.Stop(); t.Dispose(); 恢复分割位置(); 校验分割位置(); 恢复激活窗口延迟执行(); };
t.Start();
};
if (File.Exists(停靠布局路径)) {
try {
try {
this.dockSurface1.LoadFromXml(停靠布局路径, 反序列化停靠内容);
}
catch (InvalidOperationException) {
// 默认 Show 已执行时走这里:先把窗口全部隐藏,
// 让面板回到未初始化状态,再按布局加载
Log("[停靠布局] 面板已初始化,先隐藏默认窗口再按布局恢复");
foreach (var kv in 全部停靠窗口()) {
if (kv.Value != null && kv.Value.Visible) kv.Value.Hide();
}
this.dockSurface1.LoadFromXml(停靠布局路径, 反序列化停靠内容);
}
补齐缺失窗口实例();
关闭布局中不存在的窗口();
// ★ AI助手兜底显示:标签保证在(引擎后台自启,点过去即用);
// 激活窗口不在构造期切回——窗体未显示时 Activate 是哑的(实测谁最后
// Show 谁就是活动标签,AI助手抢焦点的真凶),改由 Shown+800ms 延迟恢复
AI助手窗口兜底显示();
Log("[停靠布局] 已恢复上次保存的布局");
}
catch (Exception ex) {
Log("[停靠布局] 恢复失败,回退默认布局: " + ex.Message);
// ★ 默认 Show 已被跳过,恢复失败必须补显示,否则停靠区会整个空白
按默认布局显示();
}
}
else {
// 无布局文件(默认 Show 已执行):实例已在 捕获停靠窗口实例 里抓齐
}
// 无论哪条路径,最后都把设计器里配置的窗口属性重新应用一遍
// (存在布局文件时窗口实例由本文件按需创建,不带设计器生成代码里的属性)
应用设计器窗口设置();
// ★ AI助手窗口兜底(布局文件里没有它的旧布局/无布局文件/回退默认路径):
// 标签在但不当活动标签(激活切回统一走下面的延迟恢复)
AI助手窗口兜底显示();
// ★ 活动标签延迟切回:初始化停靠布局跑在构造函数里,窗体未显示时 Activate 不生效
// (实测被"最后 Show 的窗口"覆盖,AI助手抢焦点的真凶)——Shown 首显后立即一次
// +上面 800ms 定时器再补一次,双保险幂等
this.Shown += (s5, e5) => 恢复激活窗口延迟执行();
// ★ 分割条首次显示补恢复(必须在布局恢复后挂:有布局文件时 w1~w9 在此之前是 null,
// 窗口实例由 LoadFromXml 按需创建——开头挂会扑空;此刻实例已定型)。
// 非活动标签页窗口点开前尺寸小,保存值超范围被跳过——显示/激活时按真身尺寸补赋。
// VisibleChanged+Activated 双保险:标签点开/窗口激活任一触发即恢复。
foreach (var kv in 全部停靠窗口()) {
if (kv.Value == null) { Log("[停靠布局] " + kv.Key + " 无实例,跳过补恢复挂接"); continue; }
var w = kv.Value;
string 前缀 = kv.Key.Substring(kv.Key.LastIndexOf('.') + 1);
w.VisibleChanged += (s3, e3) => { if (w.Visible) 恢复窗口分割条(前缀, w); };
w.Activated += (s4, e4) => 恢复窗口分割条(前缀, w);
}
Log("[停靠布局] 分割条补恢复挂接完成(VisibleChanged+Activated)");
// ★ 恢复自检:打印每窗口最终停靠状态(与保存的 XML 对比定位"没用布局文件"问题)
try {
foreach (var kv in 全部停靠窗口()) {
if (kv.Value == null) { Log("[布局自检] " + kv.Key + " = null"); continue; }
Log("[布局自检] " + kv.Key + " Visible=" + kv.Value.Visible +
" DockState=" + kv.Value.DockState +
" AutoHide=" + (kv.Value.DockState == WeifenLuo.WinFormsUI.Docking.DockState.DockLeftAutoHide ||
kv.Value.DockState == WeifenLuo.WinFormsUI.Docking.DockState.DockRightAutoHide ||
kv.Value.DockState == WeifenLuo.WinFormsUI.Docking.DockState.DockTopAutoHide ||
kv.Value.DockState == WeifenLuo.WinFormsUI.Docking.DockState.DockBottomAutoHide));
}
}
catch (Exception ex) { Log("[布局自检] 自检输出失败: " + ex.Message); }
}
/// <summary>AI助手窗口兜底显示:不在布局文件里(旧布局保存于窗口诞生前)也保证标签存在——
/// 引擎随软件后台自启,用户把页签点过去即用;所有调用点都在"恢复激活窗口"之前,
/// 活动标签始终按保存布局落,AI助手不当启动焦点(2026-09-21 用户要求)。</summary>
private void AI助手窗口兜底显示()
{
try {
if (this.w10 == null) this.w10 = new DockWindow10();
if (!this.w10.Visible) this.w10.Show(this.dockSurface1, WeifenLuo.WinFormsUI.Docking.DockState.DockRight);
}
catch (Exception ex) { Log("[停靠布局] AI助手窗口显示失败: " + ex.Message); }
}
/// <summary>把设计器 DockWindows 集合里配置的属性(固定位置/显示关闭按钮)
/// 应用到当前窗口实例。设计器改属性后不必关心代码路径,启动时统一回放。</summary>
private void 应用设计器窗口设置()
{
try {
foreach (Smart.CustomComponents.DockWindows.DockWindowItem item in this.dockSurface1.DockWindows) {
var w = 按类名找窗口(item.ClassName);
if (w == null) continue;
if (!item.ShowCloseButton) w.CloseButtonVisible = false;
// ★ FixedPosition 锁定窗口允许的停靠区(用户要求:右侧工具栏位置固定)。
// 注:布局保存/恢复不受此影响(SaveAsXml 记录 DockState,固定窗口恢复后仍在原区);
// 之前"保存布局不生效"的真因是分割条绝对像素问题,已改按比例保存恢复。
if (item.FixedPosition) w.DockAreas = 对应DockAreas(item.DockState);
}
Log("[停靠布局] 已应用设计器窗口设置(固定位置/关闭按钮)");
}
catch (Exception ex) {
Log("[停靠布局] 应用设计器窗口设置失败: " + ex.Message);
}
}
/// <summary>设计器集合里的 ClassName → 窗口实例。</summary>
private WeifenLuo.WinFormsUI.Docking.DockContent 按类名找窗口(string className)
{
switch ((className ?? "").Trim()) {
case "DockWindow": return this.w1;
case "DockWindow2": return this.w2;
case "DockWindow3": return this.w3;
case "DockWindow4": return this.w4;
case "DockWindow5": return this.w5;
case "DockWindow6": return this.w6;
case "DockWindow7": return this.w7;
case "DockWindow8": return this.w8;
case "DockWindow9": return this.w9;
case "DockWindow10": return this.w10;
}
return null;
}
/// <summary>FixedPosition=true 时,按设计器 DockState 锁定窗口允许的停靠区
/// (映射表与"生成停靠窗口代码"命令一致)。</summary>
private static WeifenLuo.WinFormsUI.Docking.DockAreas 对应DockAreas(WeifenLuo.WinFormsUI.Docking.DockState state)
{
switch (state) {
case WeifenLuo.WinFormsUI.Docking.DockState.DockLeft:
case WeifenLuo.WinFormsUI.Docking.DockState.DockLeftAutoHide:
return WeifenLuo.WinFormsUI.Docking.DockAreas.DockLeft;
case WeifenLuo.WinFormsUI.Docking.DockState.DockRight:
case WeifenLuo.WinFormsUI.Docking.DockState.DockRightAutoHide:
return WeifenLuo.WinFormsUI.Docking.DockAreas.DockRight;
case WeifenLuo.WinFormsUI.Docking.DockState.DockTop:
case WeifenLuo.WinFormsUI.Docking.DockState.DockTopAutoHide:
return WeifenLuo.WinFormsUI.Docking.DockAreas.DockTop;
case WeifenLuo.WinFormsUI.Docking.DockState.DockBottom:
case WeifenLuo.WinFormsUI.Docking.DockState.DockBottomAutoHide:
return WeifenLuo.WinFormsUI.Docking.DockAreas.DockBottom;
case WeifenLuo.WinFormsUI.Docking.DockState.Document:
return WeifenLuo.WinFormsUI.Docking.DockAreas.Document;
case WeifenLuo.WinFormsUI.Docking.DockState.Float:
return WeifenLuo.WinFormsUI.Docking.DockAreas.Float;
}
return WeifenLuo.WinFormsUI.Docking.DockAreas.DockLeft | WeifenLuo.WinFormsUI.Docking.DockAreas.DockRight
| WeifenLuo.WinFormsUI.Docking.DockAreas.DockTop | WeifenLuo.WinFormsUI.Docking.DockAreas.DockBottom
| WeifenLuo.WinFormsUI.Docking.DockAreas.Document;
}
/// <summary>布局里没出现的窗口(保存时被用户关掉的)也要有实例,
/// 业务代码通过别名访问它们的控件时才不会空引用——补出来,保持隐藏。</summary>
private void 补齐缺失窗口实例()
{
if (this.w1 == null) this.w1 = new DockWindow();
if (this.w2 == null) this.w2 = new DockWindow2();
if (this.w3 == null) this.w3 = new DockWindow3();
if (this.w4 == null) this.w4 = new DockWindow4();
if (this.w5 == null) this.w5 = new DockWindow5();
if (this.w6 == null) this.w6 = new DockWindow6();
if (this.w7 == null) this.w7 = new DockWindow7();
if (this.w8 == null) this.w8 = new DockWindow8();
if (this.w9 == null) this.w9 = new DockWindow9();
if (this.w10 == null) this.w10 = new DockWindow10();
}
/// <summary>从停靠面板的 Contents 里按类型抓取 9 个窗口实例,填到 w1..w9 字段。
/// 只在窗口已显示时有效(无布局文件的默认启动);
/// 有布局文件时窗口由 LoadFromXml 按需创建,见 反序列化停靠内容/补齐缺失窗口实例。</summary>
internal void 捕获停靠窗口实例()
{
foreach (WeifenLuo.WinFormsUI.Docking.IDockContent c in this.dockSurface1.Contents) {
if (this.w1 == null && c is DockWindow) this.w1 = (DockWindow)c;
else if (this.w2 == null && c is DockWindow2) this.w2 = (DockWindow2)c;
else if (this.w3 == null && c is DockWindow3) this.w3 = (DockWindow3)c;
else if (this.w4 == null && c is DockWindow4) this.w4 = (DockWindow4)c;
else if (this.w5 == null && c is DockWindow5) this.w5 = (DockWindow5)c;
else if (this.w6 == null && c is DockWindow6) this.w6 = (DockWindow6)c;
else if (this.w7 == null && c is DockWindow7) this.w7 = (DockWindow7)c;
else if (this.w8 == null && c is DockWindow8) this.w8 = (DockWindow8)c;
else if (this.w9 == null && c is DockWindow9) this.w9 = (DockWindow9)c;
else if (this.w10 == null && c is DockWindow10) this.w10 = (DockWindow10)c;
}
}
/// <summary>按默认布局显示 9 个停靠窗口(与 CreateDockWindows 的默认位置一致)。</summary>
private void 按默认布局显示()
{
try {
补齐缺失窗口实例();
this.w1.Show(this.dockSurface1, WeifenLuo.WinFormsUI.Docking.DockState.Document);
this.w2.Show(this.dockSurface1, WeifenLuo.WinFormsUI.Docking.DockState.Document);
this.w3.Show(this.dockSurface1, WeifenLuo.WinFormsUI.Docking.DockState.Document);
this.w4.Show(this.dockSurface1, WeifenLuo.WinFormsUI.Docking.DockState.DockRight);
this.w5.Show(this.dockSurface1, WeifenLuo.WinFormsUI.Docking.DockState.DockRight);
this.w6.Show(this.dockSurface1, WeifenLuo.WinFormsUI.Docking.DockState.DockRight);
this.w7.Show(this.dockSurface1, WeifenLuo.WinFormsUI.Docking.DockState.DockRight);
this.w8.Show(this.dockSurface1, WeifenLuo.WinFormsUI.Docking.DockState.DockRight);
this.w9.Show(this.dockSurface1, WeifenLuo.WinFormsUI.Docking.DockState.DockRight);
this.w10.Show(this.dockSurface1, WeifenLuo.WinFormsUI.Docking.DockState.DockRight);
// 默认布局最后 Show 的是 AI助手,不当活动标签——启动落在项目页
try { if (this.w1 != null) this.w1.Activate(); } catch { }
}
catch (Exception ex) {
Log("[停靠布局] 默认布局显示失败: " + ex.Message);
}
}
/// <summary>「保存布局」按钮:把当前停靠布局写到 停靠布局.xml。</summary>
private void btn保存布局_Click(object sender, EventArgs e)
{
try {
this.dockSurface1.SaveAsXml(停靠布局路径);
记录激活窗口();
保存分割位置();
Log("[停靠布局] 已保存当前布局");
MessageBox.Show(this,
"当前窗口布局已保存。\n" +
"(包括:哪些窗口开着、停靠位置、窗口大小、当前选中的窗口、\n" +
"以及各窗口内部左右/上下分割条的位置)\n" +
"下次启动程序时自动恢复这个布局。",
"保存布局", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
catch (Exception ex) {
MessageBox.Show(this, "保存布局失败: " + ex.Message, "保存布局",
MessageBoxButtons.OK, MessageBoxIcon.Warning);
}
}
/// <summary>把当前激活(选中)的停靠窗口写到独立小文件,恢复时切回它。
/// 不改布局 XML——重新写 XML 会破坏 SaveAsXml 的 utf-16 编码声明。</summary>
private void 记录激活窗口()
{
try {
// 点按钮时焦点在顶部选项卡上,ActiveContent 会是 null(闪断特性),
// 用兼容层记录的"最后活动窗口"兜底
var active1 = this.dockSurface1.ActiveContent as WeifenLuo.WinFormsUI.Docking.DockContent;
var active = active1 ?? this.tabControl2.最后活动窗口;
string name = active != null ? active.GetType().ToString() : "";
File.WriteAllText(激活窗口路径, name, System.Text.Encoding.UTF8);
Log("[停靠布局] 激活窗口=" + (name == "" ? "(无)" : name));
}
catch (Exception ex) {
Log("[停靠布局] 记录激活窗口失败: " + ex.Message);
}
}
/// <summary>按激活窗口记录文件,把保存时选中的窗口重新激活。</summary>
/// <summary>启动后把活动标签切回保存值(有布局按记录激活,无布局落项目页)。
/// ★ AI助手永不作启动活动标签(用户要求):记录值是它时回落项目页。
/// 由 Shown 首显后与 800ms 定时器各调一次,幂等。</summary>
private void 恢复激活窗口延迟执行()
{
try {
if (File.Exists(停靠布局路径)) {
恢复激活窗口();
}
else if (this.w1 != null) {
this.w1.Activate();
}
}
catch (Exception ex) { Log("[停靠布局] 恢复激活窗口失败: " + ex.Message); }
}
private void 恢复激活窗口()
{
try {
if (!File.Exists(激活窗口路径)) return;
string key = File.ReadAllText(激活窗口路径).Trim();
if (key == "") return;
var c = 反序列化停靠内容(key) as WeifenLuo.WinFormsUI.Docking.DockContent;
// ★ AI助手不作启动活动标签:旧记录被"常驻抢焦点"污染成它时,回落项目页
if (c is DockWindow10) c = this.w1;
if (c != null && c.Visible) {
c.Show(this.dockSurface1);
c.Activate();
Log("[停靠布局] 已切回选中的窗口: " + key);
}
}
catch (Exception ex) {
Log("[停靠布局] 恢复激活窗口失败: " + ex.Message);
}
}
// ============================================================
// 窗口内部分割条(SplitContainer)位置的保存/恢复
// 停靠库的布局 XML 只管窗口之间;分割条独立存到 停靠布局.分割.txt
// ============================================================
/// <summary>递归收集某窗口下所有 SplitContainer,行格式"窗口类名简写/控件名=距离"。</summary>
private void 收集分割条(Control parent, string 前缀, System.Collections.Generic.List<string> lines)
{
foreach (Control c in parent.Controls) {
var sc = c as SplitContainer;
if (sc != null) {
// ★ 记"距离,比例":SplitterDistance 是绝对像素,窗口宽度变化后同值视觉位置不同;
// 比例=距离/总长,恢复时按当前宽度×比例赋值,任何窗口尺寸下视觉一致
int total = sc.Orientation == Orientation.Horizontal ? sc.Height : sc.Width;
double ratio = total > 0 ? (double)sc.SplitterDistance / total : 0;
lines.Add(string.Format(System.Globalization.CultureInfo.InvariantCulture,
"{0}/{1}={2},{3:0.####}", 前缀, sc.Name, sc.SplitterDistance, ratio));
}
if (c.Controls.Count > 0) 收集分割条(c, 前缀, lines);
}
}
/// <summary>递归收集某窗口下所有 SplitContainer 实例到字典(键同上)。</summary>
private void 收集分割条实例(Control parent, string 前缀, System.Collections.Generic.Dictionary<string, SplitContainer> map)
{
foreach (Control c in parent.Controls) {
var sc = c as SplitContainer;
if (sc != null && !map.ContainsKey(前缀 + "/" + sc.Name)) map.Add(前缀 + "/" + sc.Name, sc);
if (c.Controls.Count > 0) 收集分割条实例(c, 前缀, map);
}
}
/// <summary>保存 9 个停靠窗口内所有分割条的位置。</summary>
private void 保存分割位置()
{
try {
var lines = new System.Collections.Generic.List<string>();
foreach (var kv in 全部停靠窗口()) {
if (kv.Value == null) continue;
string 前缀 = kv.Key.Substring(kv.Key.LastIndexOf('.') + 1);
收集分割条(kv.Value, 前缀, lines);
}
File.WriteAllLines(分割位置路径, lines.ToArray(), System.Text.Encoding.UTF8);
Log("[停靠布局] 已保存分割条位置 " + lines.Count + " 项");
}
catch (Exception ex) {
Log("[停靠布局] 保存分割位置失败: " + ex.Message);
}
}
/// <summary>恢复分割条位置(在 Load 事件里调用,此时窗口尺寸已就位)。</summary>
private void 恢复分割位置()
{
try {
if (!File.Exists(分割位置路径)) return;
var map = new System.Collections.Generic.Dictionary<string, SplitContainer>();
foreach (var kv in 全部停靠窗口()) {
if (kv.Value == null) continue;
string 前缀 = kv.Key.Substring(kv.Key.LastIndexOf('.') + 1);
收集分割条实例(kv.Value, 前缀, map);
}
int applied = 0;
foreach (string line in File.ReadAllLines(分割位置路径)) {
int i = line.LastIndexOf('=');
if (i <= 0) continue;
string key = line.Substring(0, i);
string val = line.Substring(i + 1).Trim();
// 格式:"距离,比例"(新) 或 "距离"(旧)——优先按比例换算(窗口宽度无关)
int d; double ratio = 0;
int comma = val.IndexOf(',');
if (comma > 0) {
int.TryParse(val.Substring(0, comma), out d);
double.TryParse(val.Substring(comma + 1), System.Globalization.NumberStyles.Float,
System.Globalization.CultureInfo.InvariantCulture, out ratio);
}
else if (!int.TryParse(val, out d)) continue;
SplitContainer sc;
if (!map.TryGetValue(key, out sc) || sc == null) continue;
try {
int total = sc.Orientation == Orientation.Horizontal ? sc.Height : sc.Width;
if (ratio > 0 && total > 20) d = (int)(total * ratio); // 按比例换算
if (total > 20 && d >= sc.Panel1MinSize && d <= total - sc.Panel2MinSize - sc.SplitterWidth) {
sc.SplitterDistance = d;
applied++;
}
}
catch { /* 单条失败不影响其余 */ }
}
Log("[停靠布局] 已恢复分割条位置 " + applied + " 项");
}
catch (Exception ex) {
Log("[停靠布局] 恢复分割位置失败: " + ex.Message);
}
}
/// <summary>单窗口分割条恢复(窗口首次显示时调用;此时句柄已建、尺寸已定,赋值能落住)。</summary>
private void 恢复窗口分割条(string 前缀, WeifenLuo.WinFormsUI.Docking.DockContent w)
{
try {
if (w == null || !File.Exists(分割位置路径)) return;
var map = new System.Collections.Generic.Dictionary<string, SplitContainer>();
收集分割条实例(w, 前缀, map);
int applied = 0;
foreach (string line in File.ReadAllLines(分割位置路径)) {
int i = line.LastIndexOf('=');
if (i <= 0) continue;
string key = line.Substring(0, i);
if (!key.StartsWith(前缀 + "/")) continue;
string val = line.Substring(i + 1).Trim();
int d; double ratio = 0;
int comma = val.IndexOf(',');
if (comma > 0) {
int.TryParse(val.Substring(0, comma), out d);
double.TryParse(val.Substring(comma + 1), System.Globalization.NumberStyles.Float,
System.Globalization.CultureInfo.InvariantCulture, out ratio);
}
else if (!int.TryParse(val, out d)) continue;
SplitContainer sc;
if (!map.TryGetValue(key, out sc) || sc == null) continue;
try {
int total = sc.Orientation == Orientation.Horizontal ? sc.Height : sc.Width;
if (ratio > 0 && total > 20) d = (int)(total * ratio);
if (total > 20 && d >= sc.Panel1MinSize && d <= total - sc.Panel2MinSize - sc.SplitterWidth) {
sc.SplitterDistance = d;
applied++;
}
else Log("[停靠布局] " + key + " 显示时仍超范围(记录=" + d + " 可用=" + total + "),跳过");
}
catch (Exception ex) { Log("[停靠布局] " + key + " 分割条恢复异常: " + ex.Message); }
}
if (applied > 0) Log("[停靠布局] " + 前缀 + " 首次显示补恢复分割条 " + applied + " 项");
}
catch (Exception ex) { Log("[停靠布局] " + 前缀 + " 补恢复异常: " + ex.Message); }
}
/// <summary>诊断:回读所有分割条实际值 vs 记录值(区分"被改回"与"显示未刷")。</summary>
private void 校验分割位置()
{
try {
if (!File.Exists(分割位置路径)) return;
var map = new System.Collections.Generic.Dictionary<string, SplitContainer>();
foreach (var kv in 全部停靠窗口()) {
if (kv.Value == null) continue;
string 前缀 = kv.Key.Substring(kv.Key.LastIndexOf('.') + 1);
收集分割条实例(kv.Value, 前缀, map);
}
foreach (string line in File.ReadAllLines(分割位置路径)) {
int i = line.LastIndexOf('=');
if (i <= 0) continue;
string key = line.Substring(0, i);
string val = line.Substring(i + 1).Trim();
int d;
int comma = val.IndexOf(',');
if (!int.TryParse(comma > 0 ? val.Substring(0, comma) : val, out d)) continue;
SplitContainer sc;
if (!map.TryGetValue(key, out sc) || sc == null) { Log("[分割校验] " + key + " 找不到实例"); continue; }
Log("[分割校验] " + key + " 记录=" + d + " 实际=" + sc.SplitterDistance +
(sc.SplitterDistance == d ? " ✓" : " ✗被改"));
}
}
catch (Exception ex) { Log("[分割校验] 异常: " + ex.Message); }
}
/// <summary>LoadFromXml 回调:布局文件里的 PersistString → 对应停靠窗口实例。
/// 实例不存在时按需创建并缓存到 wN 字段(默认 Show 可能因布局恢复被跳过)。</summary>
private WeifenLuo.WinFormsUI.Docking.IDockContent 反序列化停靠内容(string persistString)
{
// PersistString 可能带 ";窗体文本" 后缀,先取分号前的类型名
int i = persistString != null ? persistString.IndexOf(';') : -1;
string key = i > 0 ? persistString.Substring(0, i) : persistString;
switch (key) {
case "成套报价软件.DockWindow":
if (this.w1 == null) this.w1 = new DockWindow();
return this.w1;
case "成套报价软件.DockWindow2":
if (this.w2 == null) this.w2 = new DockWindow2();
return this.w2;
case "成套报价软件.DockWindow3":
if (this.w3 == null) this.w3 = new DockWindow3();
return this.w3;
case "成套报价软件.DockWindow4":
if (this.w4 == null) this.w4 = new DockWindow4();
return this.w4;
case "成套报价软件.DockWindow5":
if (this.w5 == null) this.w5 = new DockWindow5();
return this.w5;
case "成套报价软件.DockWindow6":
if (this.w6 == null) this.w6 = new DockWindow6();
return this.w6;
case "成套报价软件.DockWindow7":
if (this.w7 == null) this.w7 = new DockWindow7();
return this.w7;
case "成套报价软件.DockWindow8":
if (this.w8 == null) this.w8 = new DockWindow8();
return this.w8;
case "成套报价软件.DockWindow9":
if (this.w9 == null) this.w9 = new DockWindow9();
return this.w9;
case "成套报价软件.DockWindow10":
if (this.w10 == null) this.w10 = new DockWindow10();
return this.w10;
}
return null;
}
/// <summary>9 个停靠窗口的(类型名, 实例)清单。</summary>
private System.Collections.Generic.KeyValuePair<string, WeifenLuo.WinFormsUI.Docking.DockContent>[] 全部停靠窗口()
{
return new System.Collections.Generic.KeyValuePair<string, WeifenLuo.WinFormsUI.Docking.DockContent>[] {
new System.Collections.Generic.KeyValuePair<string, WeifenLuo.WinFormsUI.Docking.DockContent>("成套报价软件.DockWindow", this.w1),
new System.Collections.Generic.KeyValuePair<string, WeifenLuo.WinFormsUI.Docking.DockContent>("成套报价软件.DockWindow2", this.w2),
new System.Collections.Generic.KeyValuePair<string, WeifenLuo.WinFormsUI.Docking.DockContent>("成套报价软件.DockWindow3", this.w3),
new System.Collections.Generic.KeyValuePair<string, WeifenLuo.WinFormsUI.Docking.DockContent>("成套报价软件.DockWindow4", this.w4),
new System.Collections.Generic.KeyValuePair<string, WeifenLuo.WinFormsUI.Docking.DockContent>("成套报价软件.DockWindow5", this.w5),
new System.Collections.Generic.KeyValuePair<string, WeifenLuo.WinFormsUI.Docking.DockContent>("成套报价软件.DockWindow6", this.w6),
new System.Collections.Generic.KeyValuePair<string, WeifenLuo.WinFormsUI.Docking.DockContent>("成套报价软件.DockWindow7", this.w7),
new System.Collections.Generic.KeyValuePair<string, WeifenLuo.WinFormsUI.Docking.DockContent>("成套报价软件.DockWindow8", this.w8),
new System.Collections.Generic.KeyValuePair<string, WeifenLuo.WinFormsUI.Docking.DockContent>("成套报价软件.DockWindow9", this.w9),
new System.Collections.Generic.KeyValuePair<string, WeifenLuo.WinFormsUI.Docking.DockContent>("成套报价软件.DockWindow10", this.w10),
};
}
/// <summary>保存布局时被用户关掉的窗口不会出现在布局文件里,恢复后把它们隐藏,
/// 让界面和保存时完全一致。</summary>
private void 关闭布局中不存在的窗口()
{
string xml = File.ReadAllText(停靠布局路径);
foreach (var kv in 全部停靠窗口()) {
if (kv.Value == null) continue;
bool inLayout = xml.IndexOf(kv.Key, StringComparison.Ordinal) >= 0;
if (!inLayout && kv.Value.Visible) {
kv.Value.Hide();
Log("[停靠布局] " + kv.Key + " 保存时是关闭状态,保持隐藏");
}
}
}
}
}