diff --git a/插件库/Lskj.Control/GridControlEx.cs b/插件库/Lskj.Control/GridControlEx.cs index 83bd89a..9523c9c 100644 --- a/插件库/Lskj.Control/GridControlEx.cs +++ b/插件库/Lskj.Control/GridControlEx.cs @@ -634,6 +634,7 @@ namespace Lskj.Control // 模块级清理只解除事件和菜单引用。GridControl/GridView 的生命周期 // 由真正的 WinForms 父容器负责,并在 Dispose(bool) 中统一结束。 DetachGridViewEventHandlers(); + DisposeOwnedLookupFormsForDispose(); if (gridViewRightMenu != null) { gridViewRightMenu.ReleaseResourcesForDispose(); @@ -683,6 +684,70 @@ namespace Lskj.Control LabPicUrlPreview.Detach(gridView); } + private void DisposeOwnedLookupFormsForDispose() + { + HashSet
forms = new HashSet(); + foreach (FrmModelLookUp2 modelLookUp in + selectReturnModelLookUpCache.Values.Distinct()) + { + if (modelLookUp != null) + { + forms.Add(modelLookUp); + } + } + + selectReturnModelLookUpCache.Clear(); + selectReturnDisplaySourceCache.Clear(); + + RepositoryItem[] repositoryItems = null; + try + { + if (gridControl != null) + { + repositoryItems = gridControl.RepositoryItems + .Cast() + .ToArray(); + } + } + catch + { + // The inner control may already be disposing its repository collection. + } + + if (repositoryItems != null) + { + foreach (RepositoryItemButtonEdit buttonEditor in + repositoryItems.OfType()) + { + foreach (EditorButton button in buttonEditor.Buttons) + { + Form form = button.Tag as Form; + if (form == null) + { + continue; + } + + button.Tag = null; + forms.Add(form); + } + } + } + + foreach (Form form in forms) + { + try + { + if (!form.IsDisposed) + { + form.Dispose(); + } + } + catch + { + // A helper form must not prevent its owning grid from closing. + } + } + } private void DisposeGridResources() { if (_gridResourcesDisposed) @@ -723,6 +788,7 @@ namespace Lskj.Control mControlList.Clear(); ColumnList.Clear(); AutoPadDataReleColList.Clear(); + DisposeOwnedLookupFormsForDispose(); FrozenColumns.Clear(); ImageList.Clear(); RepeatedVerificationNames.Clear(); diff --git a/插件库/Lskj.Control/LabelMultiAutoTextEdit4.Designer.cs b/插件库/Lskj.Control/LabelMultiAutoTextEdit4.Designer.cs index c41a457..499f1ed 100644 --- a/插件库/Lskj.Control/LabelMultiAutoTextEdit4.Designer.cs +++ b/插件库/Lskj.Control/LabelMultiAutoTextEdit4.Designer.cs @@ -13,6 +13,10 @@ /// 如果应释放托管资源,为 true;否则为 false。 protected override void Dispose(bool disposing) { + if (disposing) + { + ReleaseLookupFormForDispose(); + } if (disposing && (components != null)) { components.Dispose(); diff --git a/插件库/Lskj.Control/LabelMultiAutoTextEdit4.cs b/插件库/Lskj.Control/LabelMultiAutoTextEdit4.cs index 87dab12..e97a864 100644 --- a/插件库/Lskj.Control/LabelMultiAutoTextEdit4.cs +++ b/插件库/Lskj.Control/LabelMultiAutoTextEdit4.cs @@ -315,6 +315,7 @@ namespace Lskj.Control /// 数据源 public void InitialFrame() { + ReleaseLookupFormForDispose(); _lookUpForm = new FrmModelLookUp2(UnionModuleCodel, IsRadio); _lookUpForm.ConfigureColumnMode = ConfigureColumnMode; _lookUpForm.ValueField = ValueField; @@ -332,6 +333,25 @@ namespace Lskj.Control /// 修改备注: /// 版本:1.0 /// + internal void ReleaseLookupFormForDispose() + { + FrmModelLookUp2 lookUpForm = _lookUpForm; + _lookUpForm = null; + if (lookUpForm == null || lookUpForm.IsDisposed) + { + return; + } + + try + { + lookUpForm.Dispose(); + } + catch + { + // A helper form must not prevent its owner from closing. + } + } + public void ClearSelectForm() { // _lookUpForm.ClearSelect(); diff --git a/插件库/Lskj.Control/Model/StaticControl.cs b/插件库/Lskj.Control/Model/StaticControl.cs index 315c2cb..1fd8246 100644 --- a/插件库/Lskj.Control/Model/StaticControl.cs +++ b/插件库/Lskj.Control/Model/StaticControl.cs @@ -126,6 +126,7 @@ namespace Lskj.Control.Model // 控件,不调用 Control.Dispose,也不扫描全局控件集合,因而 // 不会影响仍然打开的模块或共享的 GridView 生命周期。 ReleaseGridControlRuntimeReferences(moduleControls); + ReleaseLookupControlRuntimeReferences(moduleControls); foreach (GridView gridView in gridViews) { @@ -204,6 +205,33 @@ namespace Lskj.Control.Model } } + private static void ReleaseLookupControlRuntimeReferences( + IEnumerable moduleControls) + { + if (moduleControls == null) + { + return; + } + + foreach (Lskj.Control.LabelMultiAutoTextEdit4 lookupControl in + moduleControls.OfType()) + { + if (lookupControl == null) + { + continue; + } + + try + { + lookupControl.ReleaseLookupFormForDispose(); + } + catch (Exception exception) + { + TraceCleanupFailure("release lookup helper form", exception); + } + } + } + private static List CollectModuleControls( WinFormsControl moduleRoot, XtraTabPage modulePage) diff --git a/插件库/Lskj.Control/MultiModelLookUp/FrmModelLookUp2.Designer.cs b/插件库/Lskj.Control/MultiModelLookUp/FrmModelLookUp2.Designer.cs index ea4146b..fbcea74 100644 --- a/插件库/Lskj.Control/MultiModelLookUp/FrmModelLookUp2.Designer.cs +++ b/插件库/Lskj.Control/MultiModelLookUp/FrmModelLookUp2.Designer.cs @@ -13,6 +13,10 @@ /// true if managed resources should be disposed; otherwise, false. protected override void Dispose(bool disposing) { + if (disposing) + { + DisposeDragResources(); + } if (disposing && (components != null)) { components.Dispose(); diff --git a/插件库/Lskj.Control/MultiModelLookUp/FrmModelLookUp2.cs b/插件库/Lskj.Control/MultiModelLookUp/FrmModelLookUp2.cs index a560725..5daacc4 100644 --- a/插件库/Lskj.Control/MultiModelLookUp/FrmModelLookUp2.cs +++ b/插件库/Lskj.Control/MultiModelLookUp/FrmModelLookUp2.cs @@ -174,6 +174,10 @@ namespace Lskj.Control.MultiModelLookUp public bool ReturnCollection; #endregion + private GridDragGrid _dragGrid; + private GridDragGrid _backDragGrid; + private bool _dragResourcesDisposed; + public FrmModelLookUp2(string ModuleCodel) { InitializeComponent(); @@ -184,8 +188,8 @@ namespace Lskj.Control.MultiModelLookUp this.gcMain.GridControl.ContextMenuStrip = contextMenuStrip1; this.gcMain.GridView.DoubleClick += new EventHandler(OnRowCellClick); this.gcDetail.GridView.DoubleClick += new EventHandler(OnDoubleClick); - GridDragGrid dragGrid = new GridDragGrid(this.gcMain.GridView, this.gcDetail.GridView); - GridDragGrid backdragGrid = new GridDragGrid(this.gcDetail.GridView, this.gcMain.GridView); + _dragGrid = new GridDragGrid(this.gcMain.GridView, this.gcDetail.GridView); + _backDragGrid = new GridDragGrid(this.gcDetail.GridView, this.gcMain.GridView); //设置关联模块 bool result = SetAssociatedModules(); this.pl_top.Height = 0; @@ -210,8 +214,8 @@ namespace Lskj.Control.MultiModelLookUp this.gcMain.GridView.OptionsBehavior.EditorShowMode = DevExpress.Utils.EditorShowMode.MouseDown; this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.OnFormClosing); this.gcMain.GridControl.ContextMenuStrip = contextMenuStrip1; - GridDragGrid dragGrid = new GridDragGrid(this.gcMain.GridView, this.gcDetail.GridView); - GridDragGrid backdragGrid = new GridDragGrid(this.gcDetail.GridView, this.gcMain.GridView); + _dragGrid = new GridDragGrid(this.gcMain.GridView, this.gcDetail.GridView); + _backDragGrid = new GridDragGrid(this.gcDetail.GridView, this.gcMain.GridView); //设置关联模块 bool result = SetAssociatedModules(); @@ -1108,13 +1112,50 @@ namespace Lskj.Control.MultiModelLookUp /// 修改日期: /// 修改备注: /// 版本:1.0 + private void DisposeDragResources() + { + if (_dragResourcesDisposed) + { + return; + } + + _dragResourcesDisposed = true; + GridDragGrid dragGrid = _dragGrid; + _dragGrid = null; + if (dragGrid != null) + { + try + { + dragGrid.Dispose(); + } + catch + { + // Continue releasing the reverse registration. + } + } + + GridDragGrid backDragGrid = _backDragGrid; + _backDragGrid = null; + if (backDragGrid != null) + { + try + { + backDragGrid.Dispose(); + } + catch + { + // Drag cleanup must not block form disposal. + } + } + } + private void OnFrmModelLookUp_Disposed(object sender, EventArgs e) { + DisposeDragResources(); if (this.DataSource != null) { this.DataSource = null; } - GC.Collect(); } #endregion diff --git a/插件库/Lskj.PubBill/BillModule.cs b/插件库/Lskj.PubBill/BillModule.cs index 923c9f7..d6b186c 100644 --- a/插件库/Lskj.PubBill/BillModule.cs +++ b/插件库/Lskj.PubBill/BillModule.cs @@ -91,6 +91,9 @@ namespace Lskj.PubBill private DataTable _detailColumns; private bool IsCopying = false; private bool _moduleResourcesReleased; + private int _preferredSourcePanelWidth; + private bool _sourcePanelWidthRestorePending; + private bool _restoringSourcePanelWidth; protected DataRow BillRowItem; @@ -260,6 +263,7 @@ namespace Lskj.PubBill public BillModule() { InitializeComponent(); + this.ssc_main_top.SizeChanged += OnTopSplitterSizeChanged; } /// @@ -275,6 +279,7 @@ namespace Lskj.PubBill TryRelease(() => PrintUtil.OnAfterPrint -= new EventHandler(OnReportPrintAfter), "打印回调"); TryRelease(ReleaseModuleEvents, "模块事件"); TryRelease(ReleaseModuleDataSources, "模块数据源"); + TryRelease(DisposeDetachedOwnedSourceControls, "已脱离父容器的来源控件"); TryRelease(ReleaseRuntimeCollections, "运行时集合"); BillRowItem = null; @@ -343,6 +348,7 @@ namespace Lskj.PubBill splitContainer.SplitterMoved -= OnDetailsSplitterMoved; splitContainer.SplitterMoved -= OnSpecialMainSplitterMoved; splitContainer.SplitterMoved -= OnSpecialLeftSplitterMoved; + splitContainer.SizeChanged -= OnTopSplitterSizeChanged; } SimpleButton commonButton = control as SimpleButton; @@ -390,6 +396,81 @@ namespace Lskj.PubBill } } + private void DisposeDetachedOwnedSourceControls() + { + // 来源页签切换会通过 Controls.Clear 临时摘下非当前页的控件。 + // 模块永久关闭时只处理当前 BillModuleModelDic 明确登记的三个 + // 顶层来源控件,不扫描其他窗体或全局控件树。释放父控件可让 + // ModuleGridEx 等控件按自身 Dispose 逻辑释放其专属 Grid/BarManager。 + var ownedSourceControls = + new HashSet(); + foreach (BillModuleModel moduleModel in BillModuleModelDic.Values.ToList()) + { + if (moduleModel == null) + continue; + + AddOwnedSourceControl(ownedSourceControls, moduleModel.SourceControl); + AddOwnedSourceControl(ownedSourceControls, moduleModel.SourceDetailControl); + AddOwnedSourceControl(ownedSourceControls, moduleModel.SourceDetailDetailControl); + } + + foreach (System.Windows.Forms.Control sourceControl in ownedSourceControls) + { + if (sourceControl == null || sourceControl.IsDisposed || + !IsDetachedFromModuleDisposeChain(sourceControl)) + { + continue; + } + + try + { + // 仅释放当前单据持有且已脱离窗体 Dispose 链的实例; + // 不会触碰仍挂在其他 BillModule/Form 下的控件。 + sourceControl.Dispose(); + } + catch (Exception exception) + { + Debug.WriteLine("释放单据来源控件失败:" + exception); + } + } + } + + private static void AddOwnedSourceControl( + HashSet target, + object sourceControl) + { + System.Windows.Forms.Control control = + sourceControl as System.Windows.Forms.Control; + if (target != null && control != null) + target.Add(control); + } + + private bool IsDetachedFromModuleDisposeChain( + System.Windows.Forms.Control control) + { + System.Windows.Forms.Control current = control; + while (current != null) + { + // 仍在当前模块中时由 FrmMain/BillModule 的正常 Dispose 链处理; + // 若被重新挂到其他单据或窗体,也绝不能随当前模块释放。 + if (current is BillModule || + current is System.Windows.Forms.Form || + current is DevExpress.XtraTab.XtraTabPage) + return false; + + try + { + current = current.Parent; + } + catch (ObjectDisposedException) + { + return true; + } + } + + return true; + } + private static void AddModuleControlSnapshot( List target, System.Windows.Forms.Control control) @@ -1015,7 +1096,11 @@ namespace Lskj.PubBill } #endregion #region 加载来源明细 - GridControlEx gridDetail = new GridControlEx(); + // The concrete source-detail control is selected below. + // Do not allocate a temporary GridControlEx first: replacing + // that unattached instance leaves its GridView subscribed to + // DevExpress global localizer events. + GridControlEx gridDetail = null; if (!string.IsNullOrWhiteSpace(model.DetailModeCode)) { @@ -1126,14 +1211,24 @@ namespace Lskj.PubBill // 设置单据来源左侧宽度 if (this.BillModel.PanelWidth == 0) { + this._preferredSourcePanelWidth = 0; + this._sourcePanelWidthRestorePending = false; // 隐藏来源 this.ssc_main_top.PanelVisibility = SplitPanelVisibility.Panel2; } else { string width = IniHelper.Read(string.Format("bill_width_{0}", this.SysModel.ModuleCode)); - if (!string.IsNullOrEmpty(width)) - this.ssc_main_top.SplitterPosition = Convert.ToInt32(width); + int savedWidth; + this._preferredSourcePanelWidth = + !string.IsNullOrEmpty(width) && + int.TryParse(width, out savedWidth) && + savedWidth > 0 + ? savedWidth + : this.BillModel.PanelWidth; + this._sourcePanelWidthRestorePending = + this._preferredSourcePanelWidth > 0; + TryRestoreSourcePanelWidth(); } // 设置单据来源底部高度 @@ -1155,6 +1250,82 @@ namespace Lskj.PubBill } } + + private void OnTopSplitterSizeChanged(object sender, EventArgs e) + { + if (!ReferenceEquals(sender, this.ssc_main_top) || + this._moduleResourcesReleased || + this.BillModel == null || + this.BillModel.PanelWidth <= 0) + { + return; + } + + if (this.ssc_main_top.ClientSize.Width <= 1) + { + if (this._preferredSourcePanelWidth > 0) + { + // WindowsFormsHost temporarily measures a projected page at + // zero while moving it between WPF content presenters. Keep + // the last valid position and restore it after reattachment. + this._sourcePanelWidthRestorePending = true; + } + return; + } + + if (this._isInitFinish && + this._sourcePanelWidthRestorePending && + !this.ssc_main_top.Collapsed) + { + TryRestoreSourcePanelWidth(); + } + } + + private void TryRestoreSourcePanelWidth() + { + if (this._restoringSourcePanelWidth || + this._preferredSourcePanelWidth <= 0 || + this.ssc_main_top == null || + this.ssc_main_top.IsDisposed || + this.ssc_main_top.Collapsed) + { + return; + } + + int availableWidth = this.ssc_main_top.ClientSize.Width; + if (availableWidth <= 1) + { + this._sourcePanelWidthRestorePending = true; + return; + } + + int targetWidth = Math.Min( + this._preferredSourcePanelWidth, + availableWidth - 1); + if (targetWidth <= 0) + { + this._sourcePanelWidthRestorePending = true; + return; + } + + this._restoringSourcePanelWidth = true; + try + { + this.ssc_main_top.SplitterPosition = targetWidth; + this._sourcePanelWidthRestorePending = + targetWidth < this._preferredSourcePanelWidth; + } + catch (ArgumentOutOfRangeException) + { + // The DevExpress panels may not have completed their minimum-size + // layout yet. The next SizeChanged event retries this instance. + this._sourcePanelWidthRestorePending = true; + } + finally + { + this._restoringSourcePanelWidth = false; + } + } /// /// 说明:初始化系统配置 /// 创建人:龚宇超 @@ -2630,7 +2801,10 @@ namespace Lskj.PubBill //明细为表格 - GridControlEx gridDetail = new GridControlEx(); + // Each branch below supplies the actual source-detail control. Avoid + // creating a placeholder GridControlEx that is immediately replaced + // and never enters a WinForms dispose chain. + GridControlEx gridDetail = null; @@ -2642,7 +2816,6 @@ namespace Lskj.PubBill if (modelRow == null) { MessageUtil.Show($"没有找到编号为 {model.DetailModeCode} 的模块信息"); - gridDetail.Parent = gridDetail; continue; } @@ -8252,11 +8425,25 @@ namespace Lskj.PubBill /// The instance containing the event data. private void OnTopSplitterMoved(object sender, EventArgs e) { - if (this._isInitFinish)// 是否为初始化创建 + if (this._restoringSourcePanelWidth) { + return; + } + + if (this._isInitFinish && + this.SysModel != null && + this.ssc_main_top.ClientSize.Width > 1 && + this.ssc_main_top.SplitterPosition > 0)// 是否为初始化创建 + { + this._preferredSourcePanelWidth = + this.ssc_main_top.SplitterPosition; + this._sourcePanelWidthRestorePending = false; IniHelper.Write(string.Format("bill_width_{0}", this.SysModel.ModuleCode), this.ssc_main_top.SplitterPosition + ""); } - this.gcMain.gridControl.Refresh(); + if (this.gcMain != null && this.gcMain.gridControl != null) + { + this.gcMain.gridControl.Refresh(); + } } /// /// 说明:分割条移动保存位置