fix(legacy): release hosted module resources safely

This commit is contained in:
2026-08-30 18:12:09 +08:00
parent 641d3c232d
commit 769aa52241
7 changed files with 362 additions and 12 deletions
+66
View File
@@ -634,6 +634,7 @@ namespace Lskj.Control
// 模块级清理只解除事件和菜单引用。GridControl/GridView 的生命周期 // 模块级清理只解除事件和菜单引用。GridControl/GridView 的生命周期
// 由真正的 WinForms 父容器负责,并在 Dispose(bool) 中统一结束。 // 由真正的 WinForms 父容器负责,并在 Dispose(bool) 中统一结束。
DetachGridViewEventHandlers(); DetachGridViewEventHandlers();
DisposeOwnedLookupFormsForDispose();
if (gridViewRightMenu != null) if (gridViewRightMenu != null)
{ {
gridViewRightMenu.ReleaseResourcesForDispose(); gridViewRightMenu.ReleaseResourcesForDispose();
@@ -683,6 +684,70 @@ namespace Lskj.Control
LabPicUrlPreview.Detach(gridView); LabPicUrlPreview.Detach(gridView);
} }
private void DisposeOwnedLookupFormsForDispose()
{
HashSet<Form> forms = new HashSet<Form>();
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<RepositoryItem>()
.ToArray();
}
}
catch
{
// The inner control may already be disposing its repository collection.
}
if (repositoryItems != null)
{
foreach (RepositoryItemButtonEdit buttonEditor in
repositoryItems.OfType<RepositoryItemButtonEdit>())
{
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() private void DisposeGridResources()
{ {
if (_gridResourcesDisposed) if (_gridResourcesDisposed)
@@ -723,6 +788,7 @@ namespace Lskj.Control
mControlList.Clear(); mControlList.Clear();
ColumnList.Clear(); ColumnList.Clear();
AutoPadDataReleColList.Clear(); AutoPadDataReleColList.Clear();
DisposeOwnedLookupFormsForDispose();
FrozenColumns.Clear(); FrozenColumns.Clear();
ImageList.Clear(); ImageList.Clear();
RepeatedVerificationNames.Clear(); RepeatedVerificationNames.Clear();
@@ -13,6 +13,10 @@
/// <param name="disposing">如果应释放托管资源,为 true;否则为 false。</param> /// <param name="disposing">如果应释放托管资源,为 true;否则为 false。</param>
protected override void Dispose(bool disposing) protected override void Dispose(bool disposing)
{ {
if (disposing)
{
ReleaseLookupFormForDispose();
}
if (disposing && (components != null)) if (disposing && (components != null))
{ {
components.Dispose(); components.Dispose();
@@ -315,6 +315,7 @@ namespace Lskj.Control
/// <param name="dataSource">数据源</param> /// <param name="dataSource">数据源</param>
public void InitialFrame() public void InitialFrame()
{ {
ReleaseLookupFormForDispose();
_lookUpForm = new FrmModelLookUp2(UnionModuleCodel, IsRadio); _lookUpForm = new FrmModelLookUp2(UnionModuleCodel, IsRadio);
_lookUpForm.ConfigureColumnMode = ConfigureColumnMode; _lookUpForm.ConfigureColumnMode = ConfigureColumnMode;
_lookUpForm.ValueField = ValueField; _lookUpForm.ValueField = ValueField;
@@ -332,6 +333,25 @@ namespace Lskj.Control
/// <para>修改备注:</para> /// <para>修改备注:</para>
/// <para>版本:1.0</para> /// <para>版本:1.0</para>
/// </summary> /// </summary>
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() public void ClearSelectForm()
{ {
// _lookUpForm.ClearSelect(); // _lookUpForm.ClearSelect();
@@ -126,6 +126,7 @@ namespace Lskj.Control.Model
// 控件,不调用 Control.Dispose,也不扫描全局控件集合,因而 // 控件,不调用 Control.Dispose,也不扫描全局控件集合,因而
// 不会影响仍然打开的模块或共享的 GridView 生命周期。 // 不会影响仍然打开的模块或共享的 GridView 生命周期。
ReleaseGridControlRuntimeReferences(moduleControls); ReleaseGridControlRuntimeReferences(moduleControls);
ReleaseLookupControlRuntimeReferences(moduleControls);
foreach (GridView gridView in gridViews) foreach (GridView gridView in gridViews)
{ {
@@ -204,6 +205,33 @@ namespace Lskj.Control.Model
} }
} }
private static void ReleaseLookupControlRuntimeReferences(
IEnumerable<WinFormsControl> moduleControls)
{
if (moduleControls == null)
{
return;
}
foreach (Lskj.Control.LabelMultiAutoTextEdit4 lookupControl in
moduleControls.OfType<Lskj.Control.LabelMultiAutoTextEdit4>())
{
if (lookupControl == null)
{
continue;
}
try
{
lookupControl.ReleaseLookupFormForDispose();
}
catch (Exception exception)
{
TraceCleanupFailure("release lookup helper form", exception);
}
}
}
private static List<WinFormsControl> CollectModuleControls( private static List<WinFormsControl> CollectModuleControls(
WinFormsControl moduleRoot, WinFormsControl moduleRoot,
XtraTabPage modulePage) XtraTabPage modulePage)
@@ -13,6 +13,10 @@
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param> /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing) protected override void Dispose(bool disposing)
{ {
if (disposing)
{
DisposeDragResources();
}
if (disposing && (components != null)) if (disposing && (components != null))
{ {
components.Dispose(); components.Dispose();
@@ -174,6 +174,10 @@ namespace Lskj.Control.MultiModelLookUp
public bool ReturnCollection; public bool ReturnCollection;
#endregion #endregion
private GridDragGrid _dragGrid;
private GridDragGrid _backDragGrid;
private bool _dragResourcesDisposed;
public FrmModelLookUp2(string ModuleCodel) public FrmModelLookUp2(string ModuleCodel)
{ {
InitializeComponent(); InitializeComponent();
@@ -184,8 +188,8 @@ namespace Lskj.Control.MultiModelLookUp
this.gcMain.GridControl.ContextMenuStrip = contextMenuStrip1; this.gcMain.GridControl.ContextMenuStrip = contextMenuStrip1;
this.gcMain.GridView.DoubleClick += new EventHandler(OnRowCellClick); this.gcMain.GridView.DoubleClick += new EventHandler(OnRowCellClick);
this.gcDetail.GridView.DoubleClick += new EventHandler(OnDoubleClick); this.gcDetail.GridView.DoubleClick += new EventHandler(OnDoubleClick);
GridDragGrid dragGrid = new GridDragGrid(this.gcMain.GridView, this.gcDetail.GridView); _dragGrid = new GridDragGrid(this.gcMain.GridView, this.gcDetail.GridView);
GridDragGrid backdragGrid = new GridDragGrid(this.gcDetail.GridView, this.gcMain.GridView); _backDragGrid = new GridDragGrid(this.gcDetail.GridView, this.gcMain.GridView);
//设置关联模块 //设置关联模块
bool result = SetAssociatedModules(); bool result = SetAssociatedModules();
this.pl_top.Height = 0; this.pl_top.Height = 0;
@@ -210,8 +214,8 @@ namespace Lskj.Control.MultiModelLookUp
this.gcMain.GridView.OptionsBehavior.EditorShowMode = DevExpress.Utils.EditorShowMode.MouseDown; this.gcMain.GridView.OptionsBehavior.EditorShowMode = DevExpress.Utils.EditorShowMode.MouseDown;
this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.OnFormClosing); this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.OnFormClosing);
this.gcMain.GridControl.ContextMenuStrip = contextMenuStrip1; this.gcMain.GridControl.ContextMenuStrip = contextMenuStrip1;
GridDragGrid dragGrid = new GridDragGrid(this.gcMain.GridView, this.gcDetail.GridView); _dragGrid = new GridDragGrid(this.gcMain.GridView, this.gcDetail.GridView);
GridDragGrid backdragGrid = new GridDragGrid(this.gcDetail.GridView, this.gcMain.GridView); _backDragGrid = new GridDragGrid(this.gcDetail.GridView, this.gcMain.GridView);
//设置关联模块 //设置关联模块
bool result = SetAssociatedModules(); bool result = SetAssociatedModules();
@@ -1108,13 +1112,50 @@ namespace Lskj.Control.MultiModelLookUp
/// <para>修改日期:</para> /// <para>修改日期:</para>
/// <para>修改备注:</para> /// <para>修改备注:</para>
/// <para>版本:1.0</para> /// <para>版本:1.0</para>
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) private void OnFrmModelLookUp_Disposed(object sender, EventArgs e)
{ {
DisposeDragResources();
if (this.DataSource != null) if (this.DataSource != null)
{ {
this.DataSource = null; this.DataSource = null;
} }
GC.Collect();
} }
#endregion #endregion
+194 -7
View File
@@ -91,6 +91,9 @@ namespace Lskj.PubBill
private DataTable _detailColumns; private DataTable _detailColumns;
private bool IsCopying = false; private bool IsCopying = false;
private bool _moduleResourcesReleased; private bool _moduleResourcesReleased;
private int _preferredSourcePanelWidth;
private bool _sourcePanelWidthRestorePending;
private bool _restoringSourcePanelWidth;
protected DataRow BillRowItem; protected DataRow BillRowItem;
@@ -260,6 +263,7 @@ namespace Lskj.PubBill
public BillModule() public BillModule()
{ {
InitializeComponent(); InitializeComponent();
this.ssc_main_top.SizeChanged += OnTopSplitterSizeChanged;
} }
/// <summary> /// <summary>
@@ -275,6 +279,7 @@ namespace Lskj.PubBill
TryRelease(() => PrintUtil.OnAfterPrint -= new EventHandler(OnReportPrintAfter), "打印回调"); TryRelease(() => PrintUtil.OnAfterPrint -= new EventHandler(OnReportPrintAfter), "打印回调");
TryRelease(ReleaseModuleEvents, "模块事件"); TryRelease(ReleaseModuleEvents, "模块事件");
TryRelease(ReleaseModuleDataSources, "模块数据源"); TryRelease(ReleaseModuleDataSources, "模块数据源");
TryRelease(DisposeDetachedOwnedSourceControls, "已脱离父容器的来源控件");
TryRelease(ReleaseRuntimeCollections, "运行时集合"); TryRelease(ReleaseRuntimeCollections, "运行时集合");
BillRowItem = null; BillRowItem = null;
@@ -343,6 +348,7 @@ namespace Lskj.PubBill
splitContainer.SplitterMoved -= OnDetailsSplitterMoved; splitContainer.SplitterMoved -= OnDetailsSplitterMoved;
splitContainer.SplitterMoved -= OnSpecialMainSplitterMoved; splitContainer.SplitterMoved -= OnSpecialMainSplitterMoved;
splitContainer.SplitterMoved -= OnSpecialLeftSplitterMoved; splitContainer.SplitterMoved -= OnSpecialLeftSplitterMoved;
splitContainer.SizeChanged -= OnTopSplitterSizeChanged;
} }
SimpleButton commonButton = control as SimpleButton; 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<System.Windows.Forms.Control>();
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<System.Windows.Forms.Control> 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( private static void AddModuleControlSnapshot(
List<System.Windows.Forms.Control> target, List<System.Windows.Forms.Control> target,
System.Windows.Forms.Control control) System.Windows.Forms.Control control)
@@ -1015,7 +1096,11 @@ namespace Lskj.PubBill
} }
#endregion #endregion
#region #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)) if (!string.IsNullOrWhiteSpace(model.DetailModeCode))
{ {
@@ -1126,14 +1211,24 @@ namespace Lskj.PubBill
// 设置单据来源左侧宽度 // 设置单据来源左侧宽度
if (this.BillModel.PanelWidth == 0) if (this.BillModel.PanelWidth == 0)
{ {
this._preferredSourcePanelWidth = 0;
this._sourcePanelWidthRestorePending = false;
// 隐藏来源 // 隐藏来源
this.ssc_main_top.PanelVisibility = SplitPanelVisibility.Panel2; this.ssc_main_top.PanelVisibility = SplitPanelVisibility.Panel2;
} }
else else
{ {
string width = IniHelper.Read(string.Format("bill_width_{0}", this.SysModel.ModuleCode)); string width = IniHelper.Read(string.Format("bill_width_{0}", this.SysModel.ModuleCode));
if (!string.IsNullOrEmpty(width)) int savedWidth;
this.ssc_main_top.SplitterPosition = Convert.ToInt32(width); 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;
}
}
/// <summary> /// <summary>
/// <para>说明:初始化系统配置</para> /// <para>说明:初始化系统配置</para>
/// <para>创建人:龚宇超</para> /// <para>创建人:龚宇超</para>
@@ -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) if (modelRow == null)
{ {
MessageUtil.Show($"没有找到编号为 {model.DetailModeCode} 的模块信息"); MessageUtil.Show($"没有找到编号为 {model.DetailModeCode} 的模块信息");
gridDetail.Parent = gridDetail;
continue; continue;
} }
@@ -8252,11 +8425,25 @@ namespace Lskj.PubBill
/// <param name="e">The <see cref="EventArgs"/> instance containing the event data.</param> /// <param name="e">The <see cref="EventArgs"/> instance containing the event data.</param>
private void OnTopSplitterMoved(object sender, EventArgs e) 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 + ""); 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();
}
} }
/// <summary> /// <summary>
/// <para>说明:分割条移动保存位置</para> /// <para>说明:分割条移动保存位置</para>