575 lines
28 KiB
C#
575 lines
28 KiB
C#
// 停靠布局保存/恢复(设置页签「保存布局」按钮)。
|
||
// 用 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 + " 保存时是关闭状态,保持隐藏");
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|