fix(legacy): finalize hosted module lifecycle cleanup
This commit is contained in:
@@ -631,7 +631,15 @@ namespace Lskj.Control
|
|||||||
|
|
||||||
internal void ReleaseResourcesForDispose()
|
internal void ReleaseResourcesForDispose()
|
||||||
{
|
{
|
||||||
DisposeGridResources();
|
// 模块级清理只解除事件和菜单引用。GridControl/GridView 的生命周期
|
||||||
|
// 由真正的 WinForms 父容器负责,并在 Dispose(bool) 中统一结束。
|
||||||
|
DetachGridViewEventHandlers();
|
||||||
|
if (gridViewRightMenu != null)
|
||||||
|
{
|
||||||
|
gridViewRightMenu.ReleaseResourcesForDispose();
|
||||||
|
gridViewRightMenu = null;
|
||||||
|
}
|
||||||
|
ParentControl = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DetachGridViewEventHandlers()
|
private void DetachGridViewEventHandlers()
|
||||||
@@ -658,6 +666,13 @@ namespace Lskj.Control
|
|||||||
gridView.ShowFilterPopupCheckedListBox -= OnShowFilterPopupCheckedListBox;
|
gridView.ShowFilterPopupCheckedListBox -= OnShowFilterPopupCheckedListBox;
|
||||||
gridView.DataSourceChanged -= OnGridDataSourceChanged;
|
gridView.DataSourceChanged -= OnGridDataSourceChanged;
|
||||||
gridView.RowCellStyle -= GridView_RowCellStyle;
|
gridView.RowCellStyle -= GridView_RowCellStyle;
|
||||||
|
// These handlers are attached dynamically by SetGridRowColors and
|
||||||
|
// AddHeadquarters. They are not part of the constructor hookup, so
|
||||||
|
// removing only the static handlers above leaves a GridView event
|
||||||
|
// delegate rooted to this GridControlEx after module close.
|
||||||
|
gridView.RowCellStyle -= OnGridViewRowCellStyle;
|
||||||
|
gridView.RowStyle -= OnGridViewRowStyle;
|
||||||
|
gridView.CellValueChanged -= onCellValueChanged;
|
||||||
gridView.CustomRowFilter -= GridView_CustomRowFilter;
|
gridView.CustomRowFilter -= GridView_CustomRowFilter;
|
||||||
gridView.EndGrouping -= GridView_EndGrouping;
|
gridView.EndGrouping -= GridView_EndGrouping;
|
||||||
gridView.ShowingEditor -= GridView_ShowingEditor;
|
gridView.ShowingEditor -= GridView_ShowingEditor;
|
||||||
@@ -735,6 +750,52 @@ namespace Lskj.Control
|
|||||||
customFont.Dispose();
|
customFont.Dispose();
|
||||||
customFont = null;
|
customFont = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Dispose 完成后,包装对象不应继续保留已释放的 DevExpress 控件、
|
||||||
|
// 数据表和模块字典。尤其是左右联动事件链仍可能短暂持有包装对象,
|
||||||
|
// 这里主动断开引用可以避免已关闭页签继续保留整棵控件对象图。
|
||||||
|
gridView = null;
|
||||||
|
gridControl = null;
|
||||||
|
GridRowColorsTable = null;
|
||||||
|
ChartTable = null;
|
||||||
|
rightMenuButtonTable = null;
|
||||||
|
InitGridColumsTab = null;
|
||||||
|
ModuleEnclosure?.Clear();
|
||||||
|
ModuleEnclosure = null;
|
||||||
|
mControlSourceDic?.Clear();
|
||||||
|
mControlSourceDic = null;
|
||||||
|
mControlList?.Clear();
|
||||||
|
mControlList = null;
|
||||||
|
ColumnList?.Clear();
|
||||||
|
ColumnList = null;
|
||||||
|
AutoPadDataReleColList?.Clear();
|
||||||
|
AutoPadDataReleColList = null;
|
||||||
|
ImageList?.Clear();
|
||||||
|
ImageList = null;
|
||||||
|
RepeatedVerificationNames?.Clear();
|
||||||
|
RepeatedVerificationNames = null;
|
||||||
|
PromptMessage?.Clear();
|
||||||
|
PromptMessage = null;
|
||||||
|
_CheckedList?.Clear();
|
||||||
|
_CheckedList = null;
|
||||||
|
modifyThePosition?.Clear();
|
||||||
|
modifyThePosition = null;
|
||||||
|
ColumnsList?.Clear();
|
||||||
|
ColumnsList = null;
|
||||||
|
SelectedDataSource?.Clear();
|
||||||
|
SelectedDataSource = null;
|
||||||
|
ProgressBarColumn?.Clear();
|
||||||
|
ProgressBarColumn = null;
|
||||||
|
ManuallyAddData?.Clear();
|
||||||
|
ManuallyAddData = null;
|
||||||
|
FrozenColumns = null;
|
||||||
|
CurrentOperGridView = null;
|
||||||
|
CurrentOperModel = null;
|
||||||
|
HeaderColumnSift = null;
|
||||||
|
sumPanel = null;
|
||||||
|
PopupEdit = null;
|
||||||
|
CustomGroupBandEx = null;
|
||||||
|
CustomGroupTreeBandEx = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void PrepareForColumnRebuild()
|
private void PrepareForColumnRebuild()
|
||||||
@@ -3825,7 +3886,7 @@ namespace Lskj.Control
|
|||||||
DllName = "",
|
DllName = "",
|
||||||
Id = this.Model.ModuleCode,
|
Id = this.Model.ModuleCode,
|
||||||
Name = this.Model.FormText,
|
Name = this.Model.FormText,
|
||||||
BeforePage = ERPInfo.Instance.PageControl.SelectedTabPage
|
BeforePage = ERPInfo.Instance.CurrentModulePage
|
||||||
};
|
};
|
||||||
module.ModuleForm = frmGridSift;
|
module.ModuleForm = frmGridSift;
|
||||||
string guid = Guid.NewGuid().ToString();
|
string guid = Guid.NewGuid().ToString();
|
||||||
@@ -3842,8 +3903,8 @@ namespace Lskj.Control
|
|||||||
tp.Controls.Add(frmGridSift);
|
tp.Controls.Add(frmGridSift);
|
||||||
tp.ShowCloseButton = DefaultBoolean.True;
|
tp.ShowCloseButton = DefaultBoolean.True;
|
||||||
tp.Dock = DockStyle.Fill;
|
tp.Dock = DockStyle.Fill;
|
||||||
ERPInfo.Instance.PageControl.TabPages.Add(tp);
|
ERPInfo.Instance.AddModulePage(tp);
|
||||||
ERPInfo.Instance.PageControl.SelectedTabPage = tp;
|
ERPInfo.Instance.CurrentModulePage = tp;
|
||||||
ERPInfo.Instance.ModuleForms.Add(guid, module);
|
ERPInfo.Instance.ModuleForms.Add(guid, module);
|
||||||
frmGridSift.Show();
|
frmGridSift.Show();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,19 +44,39 @@ namespace Lskj.Control.Model
|
|||||||
Dictionary<BandedGridView, List<BandedGridDragGrid>> registrations,
|
Dictionary<BandedGridView, List<BandedGridDragGrid>> registrations,
|
||||||
BandedGridView gridView)
|
BandedGridView gridView)
|
||||||
{
|
{
|
||||||
|
if (registrations == null)
|
||||||
|
return;
|
||||||
|
|
||||||
foreach (KeyValuePair<BandedGridView, List<BandedGridDragGrid>> entry in registrations.ToList())
|
foreach (KeyValuePair<BandedGridView, List<BandedGridDragGrid>> entry in registrations.ToList())
|
||||||
{
|
{
|
||||||
bool removeKey = ReferenceEquals(entry.Key, gridView);
|
bool removeKey = ReferenceEquals(entry.Key, gridView);
|
||||||
foreach (BandedGridDragGrid registration in entry.Value.ToList())
|
List<BandedGridDragGrid> registrationsForView = entry.Value;
|
||||||
|
if (registrationsForView == null)
|
||||||
{
|
{
|
||||||
if (removeKey || registration == null || registration._disposed || registration.References(gridView))
|
registrations.Remove(entry.Key);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (BandedGridDragGrid registration in registrationsForView.ToList())
|
||||||
|
{
|
||||||
|
if (removeKey || registration == null || registration._disposed ||
|
||||||
|
registration.References(gridView))
|
||||||
{
|
{
|
||||||
if (registration != null)
|
if (registration != null)
|
||||||
registration.Dispose();
|
{
|
||||||
entry.Value.Remove(registration);
|
try
|
||||||
|
{
|
||||||
|
registration.Dispose();
|
||||||
|
}
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
System.Diagnostics.Debug.WriteLine("清理带状表格拖拽注册失败:" + exception);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
registrationsForView.Remove(registration);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (removeKey || entry.Value.Count == 0)
|
if (removeKey || registrationsForView.Count == 0)
|
||||||
registrations.Remove(entry.Key);
|
registrations.Remove(entry.Key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -65,23 +85,71 @@ namespace Lskj.Control.Model
|
|||||||
Dictionary<GridView, List<BandedGridDragGrid>> registrations,
|
Dictionary<GridView, List<BandedGridDragGrid>> registrations,
|
||||||
GridView gridView)
|
GridView gridView)
|
||||||
{
|
{
|
||||||
|
if (registrations == null)
|
||||||
|
return;
|
||||||
|
|
||||||
foreach (KeyValuePair<GridView, List<BandedGridDragGrid>> entry in registrations.ToList())
|
foreach (KeyValuePair<GridView, List<BandedGridDragGrid>> entry in registrations.ToList())
|
||||||
{
|
{
|
||||||
bool removeKey = ReferenceEquals(entry.Key, gridView);
|
bool removeKey = ReferenceEquals(entry.Key, gridView);
|
||||||
foreach (BandedGridDragGrid registration in entry.Value.ToList())
|
List<BandedGridDragGrid> registrationsForView = entry.Value;
|
||||||
|
if (registrationsForView == null)
|
||||||
{
|
{
|
||||||
if (removeKey || registration == null || registration._disposed || registration.References(gridView))
|
registrations.Remove(entry.Key);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (BandedGridDragGrid registration in registrationsForView.ToList())
|
||||||
|
{
|
||||||
|
if (removeKey || registration == null || registration._disposed ||
|
||||||
|
registration.References(gridView))
|
||||||
{
|
{
|
||||||
if (registration != null)
|
if (registration != null)
|
||||||
registration.Dispose();
|
{
|
||||||
entry.Value.Remove(registration);
|
try
|
||||||
|
{
|
||||||
|
registration.Dispose();
|
||||||
|
}
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
System.Diagnostics.Debug.WriteLine("清理带状表格拖拽注册失败:" + exception);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
registrationsForView.Remove(registration);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (removeKey || entry.Value.Count == 0)
|
if (removeKey || registrationsForView.Count == 0)
|
||||||
registrations.Remove(entry.Key);
|
registrations.Remove(entry.Key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static bool IsDisposedView(BandedGridView view)
|
||||||
|
{
|
||||||
|
if (view == null)
|
||||||
|
return true;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return view.GridControl == null || view.GridControl.IsDisposed;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsDisposedView(GridView view)
|
||||||
|
{
|
||||||
|
if (view == null)
|
||||||
|
return true;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return view.GridControl == null || view.GridControl.IsDisposed;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
internal bool References(GridView gridView)
|
internal bool References(GridView gridView)
|
||||||
{
|
{
|
||||||
return gridView != null &&
|
return gridView != null &&
|
||||||
@@ -124,8 +192,14 @@ namespace Lskj.Control.Model
|
|||||||
/// <param name="tarGridView">目标GridView.</param>
|
/// <param name="tarGridView">目标GridView.</param>
|
||||||
public BandedGridDragGrid(BandedGridView sourceView, GridView targetView)
|
public BandedGridDragGrid(BandedGridView sourceView, GridView targetView)
|
||||||
{
|
{
|
||||||
|
// Do not retain an incomplete registration in the global dictionaries.
|
||||||
|
if (IsDisposedView(sourceView) || IsDisposedView(targetView))
|
||||||
|
return;
|
||||||
|
|
||||||
this._sourceGridView = sourceView;
|
this._sourceGridView = sourceView;
|
||||||
this._targetGridView = targetView;
|
this._targetGridView = targetView;
|
||||||
|
try
|
||||||
|
{
|
||||||
//记录所有拖拽类到来源表中,后每次进入目标表禁用其他拖拽类的拖拽事件
|
//记录所有拖拽类到来源表中,后每次进入目标表禁用其他拖拽类的拖拽事件
|
||||||
if (StaticBandedControl.BandedGridViewDragGridDic.ContainsKey(sourceView))
|
if (StaticBandedControl.BandedGridViewDragGridDic.ContainsKey(sourceView))
|
||||||
{
|
{
|
||||||
@@ -171,6 +245,21 @@ namespace Lskj.Control.Model
|
|||||||
{
|
{
|
||||||
this._targetGridView.Disposed += OnGridViewDisposed;
|
this._targetGridView.Disposed += OnGridViewDisposed;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// Roll back dictionary entries and event handlers from a partial
|
||||||
|
// registration before surfacing the original failure.
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Dispose();
|
||||||
|
}
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
System.Diagnostics.Debug.WriteLine("回滚带状表格拖拽注册失败:" + exception);
|
||||||
|
}
|
||||||
|
throw;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnGridViewDisposed(object sender, EventArgs e)
|
private void OnGridViewDisposed(object sender, EventArgs e)
|
||||||
@@ -225,7 +314,8 @@ namespace Lskj.Control.Model
|
|||||||
|
|
||||||
if (ReferenceEquals(
|
if (ReferenceEquals(
|
||||||
StaticBandedControl.SourceDragBandedGridView,
|
StaticBandedControl.SourceDragBandedGridView,
|
||||||
sourceGridView))
|
sourceGridView) &&
|
||||||
|
!HasRegistrationForView(sourceGridView))
|
||||||
{
|
{
|
||||||
StaticBandedControl.SourceDragBandedGridView = null;
|
StaticBandedControl.SourceDragBandedGridView = null;
|
||||||
}
|
}
|
||||||
@@ -237,6 +327,37 @@ namespace Lskj.Control.Model
|
|||||||
_targetGridView = null;
|
_targetGridView = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static bool HasRegistrationForView(BandedGridView gridView)
|
||||||
|
{
|
||||||
|
if (gridView == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
List<BandedGridDragGrid> items;
|
||||||
|
if (StaticBandedControl.BandedGridViewDragGridDic != null &&
|
||||||
|
StaticBandedControl.BandedGridViewDragGridDic.TryGetValue(gridView, out items) &&
|
||||||
|
ContainsLiveRegistration(items, gridView))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if (StaticBandedControl.BandedTargetViewDragGridDic != null)
|
||||||
|
foreach (KeyValuePair<GridView, List<BandedGridDragGrid>> entry in
|
||||||
|
StaticBandedControl.BandedTargetViewDragGridDic)
|
||||||
|
if (ContainsLiveRegistration(entry.Value, gridView))
|
||||||
|
return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool ContainsLiveRegistration(
|
||||||
|
IEnumerable<BandedGridDragGrid> registrations,
|
||||||
|
GridView gridView)
|
||||||
|
{
|
||||||
|
if (registrations == null || gridView == null)
|
||||||
|
return false;
|
||||||
|
foreach (BandedGridDragGrid item in registrations)
|
||||||
|
if (item != null && !item._disposed && item.References(gridView))
|
||||||
|
return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
private static void RemoveRegistration<TKey>(
|
private static void RemoveRegistration<TKey>(
|
||||||
Dictionary<TKey, List<BandedGridDragGrid>> registrations,
|
Dictionary<TKey, List<BandedGridDragGrid>> registrations,
|
||||||
TKey key,
|
TKey key,
|
||||||
@@ -248,12 +369,23 @@ namespace Lskj.Control.Model
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (registrations == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
List<BandedGridDragGrid> registrationsForView;
|
List<BandedGridDragGrid> registrationsForView;
|
||||||
if (!registrations.TryGetValue(key, out registrationsForView))
|
if (!registrations.TryGetValue(key, out registrationsForView))
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (registrationsForView == null)
|
||||||
|
{
|
||||||
|
registrations.Remove(key);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
registrationsForView.Remove(registration);
|
registrationsForView.Remove(registration);
|
||||||
if (registrationsForView.Count == 0)
|
if (registrationsForView.Count == 0)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -46,23 +46,62 @@ namespace Lskj.Control.Model
|
|||||||
Dictionary<GridView, List<GridDragGrid>> registrations,
|
Dictionary<GridView, List<GridDragGrid>> registrations,
|
||||||
GridView gridView)
|
GridView gridView)
|
||||||
{
|
{
|
||||||
|
if (registrations == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// A module-close operation must only remove registrations that
|
||||||
|
// reference the exact view being closed. Never infer ownership
|
||||||
|
// from GridControl.IsDisposed here: another open tab can be in a
|
||||||
|
// transient initialization/dispose state and its registration
|
||||||
|
// must remain untouched.
|
||||||
foreach (KeyValuePair<GridView, List<GridDragGrid>> entry in registrations.ToList())
|
foreach (KeyValuePair<GridView, List<GridDragGrid>> entry in registrations.ToList())
|
||||||
{
|
{
|
||||||
bool removeKey = ReferenceEquals(entry.Key, gridView);
|
bool removeKey = ReferenceEquals(entry.Key, gridView);
|
||||||
foreach (GridDragGrid registration in entry.Value.ToList())
|
List<GridDragGrid> registrationsForView = entry.Value;
|
||||||
|
if (registrationsForView == null)
|
||||||
{
|
{
|
||||||
if (removeKey || registration == null || registration._disposed || registration.References(gridView))
|
registrations.Remove(entry.Key);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (GridDragGrid registration in registrationsForView.ToList())
|
||||||
|
{
|
||||||
|
if (removeKey || registration == null || registration._disposed ||
|
||||||
|
registration.References(gridView))
|
||||||
{
|
{
|
||||||
if (registration != null)
|
if (registration != null)
|
||||||
registration.Dispose();
|
{
|
||||||
entry.Value.Remove(registration);
|
try
|
||||||
|
{
|
||||||
|
registration.Dispose();
|
||||||
|
}
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
System.Diagnostics.Debug.WriteLine("清理表格拖拽注册失败:" + exception);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
registrationsForView.Remove(registration);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (removeKey || entry.Value.Count == 0)
|
if (removeKey || registrationsForView.Count == 0)
|
||||||
registrations.Remove(entry.Key);
|
registrations.Remove(entry.Key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static bool IsDisposedView(GridView view)
|
||||||
|
{
|
||||||
|
if (view == null)
|
||||||
|
return true;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return view.GridControl == null || view.GridControl.IsDisposed;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
internal bool References(GridView gridView)
|
internal bool References(GridView gridView)
|
||||||
{
|
{
|
||||||
return gridView != null &&
|
return gridView != null &&
|
||||||
@@ -115,8 +154,16 @@ namespace Lskj.Control.Model
|
|||||||
/// <param name="tarGridView">目标GridView.</param>
|
/// <param name="tarGridView">目标GridView.</param>
|
||||||
public GridDragGrid(GridView sourceView, GridView targetView)
|
public GridDragGrid(GridView sourceView, GridView targetView)
|
||||||
{
|
{
|
||||||
|
// Do not put an incomplete registration in the global dictionaries.
|
||||||
|
// A GridView without an owner GridControl cannot receive the drag
|
||||||
|
// events below and would otherwise remain a strong static reference.
|
||||||
|
if (IsDisposedView(sourceView) || IsDisposedView(targetView))
|
||||||
|
return;
|
||||||
|
|
||||||
this._sourceGridView = sourceView;
|
this._sourceGridView = sourceView;
|
||||||
this._targetGridView = targetView;
|
this._targetGridView = targetView;
|
||||||
|
try
|
||||||
|
{
|
||||||
//记录所有拖拽类到来源表中,后每次进入目标表禁用其他拖拽类的拖拽事件
|
//记录所有拖拽类到来源表中,后每次进入目标表禁用其他拖拽类的拖拽事件
|
||||||
if (StaticControl.GridViewDragGridDic.ContainsKey(sourceView))
|
if (StaticControl.GridViewDragGridDic.ContainsKey(sourceView))
|
||||||
{
|
{
|
||||||
@@ -162,6 +209,21 @@ namespace Lskj.Control.Model
|
|||||||
{
|
{
|
||||||
this._targetGridView.Disposed += OnGridViewDisposed;
|
this._targetGridView.Disposed += OnGridViewDisposed;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// Registration is a two-dictionary operation. If event wiring
|
||||||
|
// fails, undo everything that was attached before rethrowing.
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Dispose();
|
||||||
|
}
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
System.Diagnostics.Debug.WriteLine("回滚表格拖拽注册失败:" + exception);
|
||||||
|
}
|
||||||
|
throw;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnGridViewDisposed(object sender, EventArgs e)
|
private void OnGridViewDisposed(object sender, EventArgs e)
|
||||||
@@ -214,7 +276,8 @@ namespace Lskj.Control.Model
|
|||||||
targetGridView,
|
targetGridView,
|
||||||
this);
|
this);
|
||||||
|
|
||||||
if (ReferenceEquals(StaticControl.SourceDragGridView, sourceGridView))
|
if (ReferenceEquals(StaticControl.SourceDragGridView, sourceGridView) &&
|
||||||
|
!HasRegistrationForView(sourceGridView))
|
||||||
{
|
{
|
||||||
StaticControl.SourceDragGridView = null;
|
StaticControl.SourceDragGridView = null;
|
||||||
}
|
}
|
||||||
@@ -226,6 +289,45 @@ namespace Lskj.Control.Model
|
|||||||
_targetGridView = null;
|
_targetGridView = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static bool HasRegistrationForView(GridView gridView)
|
||||||
|
{
|
||||||
|
if (gridView == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return HasRegistration(StaticControl.GridViewDragGridDic, gridView) ||
|
||||||
|
HasRegistration(StaticControl.TargetViewDragGridDic, gridView);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static void ClearSourceReferenceIfUnused(GridView gridView)
|
||||||
|
{
|
||||||
|
if (gridView != null &&
|
||||||
|
ReferenceEquals(StaticControl.SourceDragGridView, gridView) &&
|
||||||
|
!HasRegistrationForView(gridView))
|
||||||
|
{
|
||||||
|
StaticControl.SourceDragGridView = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool HasRegistration<TKey>(
|
||||||
|
Dictionary<TKey, List<GridDragGrid>> registrations,
|
||||||
|
GridView gridView)
|
||||||
|
where TKey : class
|
||||||
|
{
|
||||||
|
if (registrations == null || gridView == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
foreach (KeyValuePair<TKey, List<GridDragGrid>> entry in registrations)
|
||||||
|
{
|
||||||
|
List<GridDragGrid> items = entry.Value;
|
||||||
|
if (items == null)
|
||||||
|
continue;
|
||||||
|
foreach (GridDragGrid item in items)
|
||||||
|
if (item != null && !item._disposed && item.References(gridView))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
private static void RemoveRegistration<TKey>(
|
private static void RemoveRegistration<TKey>(
|
||||||
Dictionary<TKey, List<GridDragGrid>> registrations,
|
Dictionary<TKey, List<GridDragGrid>> registrations,
|
||||||
TKey key,
|
TKey key,
|
||||||
@@ -237,12 +339,23 @@ namespace Lskj.Control.Model
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (registrations == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
List<GridDragGrid> registrationsForView;
|
List<GridDragGrid> registrationsForView;
|
||||||
if (!registrations.TryGetValue(key, out registrationsForView))
|
if (!registrations.TryGetValue(key, out registrationsForView))
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (registrationsForView == null)
|
||||||
|
{
|
||||||
|
registrations.Remove(key);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
registrationsForView.Remove(registration);
|
registrationsForView.Remove(registration);
|
||||||
if (registrationsForView.Count == 0)
|
if (registrationsForView.Count == 0)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using DevExpress.XtraGrid.Views.Grid;
|
using DevExpress.XtraGrid.Views.Grid;
|
||||||
|
using DevExpress.XtraGrid.Views.BandedGrid;
|
||||||
using DevExpress.XtraTab;
|
using DevExpress.XtraTab;
|
||||||
using Lskj.Control.ZKFinger;
|
using Lskj.Control.ZKFinger;
|
||||||
using Lskj.Util;
|
using Lskj.Util;
|
||||||
@@ -8,7 +9,9 @@ using System.Collections.Generic;
|
|||||||
using System.Data;
|
using System.Data;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using System.Threading;
|
||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
using Lskj.Model;
|
using Lskj.Model;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
@@ -18,24 +21,112 @@ namespace Lskj.Control.Model
|
|||||||
{
|
{
|
||||||
public static class StaticControl
|
public static class StaticControl
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 在窗体关闭前捕获模块控件树及其 GridView。WinForms 的 Close/Dispose
|
||||||
|
/// 流程可能会先清空 Controls 或重建 GridView;保留这个快照可以让后续
|
||||||
|
/// 引用解绑仍然针对原始实例执行。
|
||||||
|
/// </summary>
|
||||||
|
public sealed class ModuleReferenceSnapshot
|
||||||
|
{
|
||||||
|
internal readonly List<WinFormsControl> Controls;
|
||||||
|
internal readonly List<GridView> GridViews;
|
||||||
|
|
||||||
|
internal ModuleReferenceSnapshot(
|
||||||
|
List<WinFormsControl> controls,
|
||||||
|
List<GridView> gridViews)
|
||||||
|
{
|
||||||
|
Controls = controls ?? new List<WinFormsControl>();
|
||||||
|
GridViews = gridViews ?? new List<GridView>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ModuleReferenceSnapshot CaptureModuleReferences(
|
||||||
|
WinFormsControl moduleRoot,
|
||||||
|
XtraTabPage modulePage)
|
||||||
|
{
|
||||||
|
List<WinFormsControl> moduleControls = new List<WinFormsControl>();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
moduleControls = CollectModuleControls(moduleRoot, modulePage);
|
||||||
|
}
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
TraceCleanupFailure("收集模块控件引用", exception);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<GridView> gridViews = new List<GridView>();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
gridViews = CollectGridViews(moduleControls);
|
||||||
|
}
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
TraceCleanupFailure("收集模块表格引用", exception);
|
||||||
|
}
|
||||||
|
return new ModuleReferenceSnapshot(moduleControls, gridViews);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Releases static references owned by a closed legacy module. Cleanup is
|
/// Releases static references owned by a closed legacy module. Cleanup is
|
||||||
/// best-effort so it cannot change the module close result.
|
/// best-effort so it cannot change the module close result.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static void ReleaseModuleReferences(WinFormsControl moduleRoot, XtraTabPage modulePage, string moduleCode)
|
public static void ReleaseModuleReferences(WinFormsControl moduleRoot, XtraTabPage modulePage, string moduleCode)
|
||||||
{
|
{
|
||||||
List<GridView> gridViews = new List<GridView>();
|
ReleaseModuleReferences(
|
||||||
|
CaptureModuleReferences(moduleRoot, modulePage),
|
||||||
|
modulePage,
|
||||||
|
moduleCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Releases references using a snapshot captured before Close/Dispose.
|
||||||
|
/// This overload is used by the WPF host, where the legacy form can be
|
||||||
|
/// detached from its Controls collection during Form.Close.
|
||||||
|
/// </summary>
|
||||||
|
public static void ReleaseModuleReferences(
|
||||||
|
ModuleReferenceSnapshot snapshot,
|
||||||
|
XtraTabPage modulePage,
|
||||||
|
string moduleCode)
|
||||||
|
{
|
||||||
|
List<WinFormsControl> moduleControls = snapshot == null || snapshot.Controls == null
|
||||||
|
? new List<WinFormsControl>()
|
||||||
|
: snapshot.Controls;
|
||||||
|
List<GridView> gridViews = snapshot == null || snapshot.GridViews == null
|
||||||
|
? new List<GridView>()
|
||||||
|
: snapshot.GridViews;
|
||||||
|
List<TabControlEx> tabControls = new List<TabControlEx>();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
gridViews = CollectGridViews(moduleRoot);
|
tabControls = moduleControls.OfType<TabControlEx>().ToList();
|
||||||
}
|
}
|
||||||
catch (Exception exception)
|
catch (Exception exception)
|
||||||
{
|
{
|
||||||
TraceCleanupFailure("收集表格引用", exception);
|
TraceCleanupFailure("收集模块引用", exception);
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
// WPF 承载旧 WinForms 页面时,移除页签不一定会同步触发
|
||||||
|
// TabControlEx.Disposed。先解除左右联动事件,避免 GridView
|
||||||
|
// 的事件委托继续闭包持有已经关闭的 TabControlEx/模块。
|
||||||
|
foreach (TabControlEx tabControl in tabControls)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
tabControl?.ReleaseRuntimeReferences();
|
||||||
|
}
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
TraceCleanupFailure("释放多页签运行时引用", exception);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GridControlEx 自身也会把事件处理器、右键菜单和父控件
|
||||||
|
// 引用挂在 GridView/菜单对象上。这里只处理关闭前快照中的
|
||||||
|
// 控件,不调用 Control.Dispose,也不扫描全局控件集合,因而
|
||||||
|
// 不会影响仍然打开的模块或共享的 GridView 生命周期。
|
||||||
|
ReleaseGridControlRuntimeReferences(moduleControls);
|
||||||
|
|
||||||
foreach (GridView gridView in gridViews)
|
foreach (GridView gridView in gridViews)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@@ -48,18 +139,27 @@ namespace Lskj.Control.Model
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TryCleanup(() => RemoveConditionPanels(moduleCode), "释放条件面板引用");
|
|
||||||
TryCleanup(() => RemoveProtectedReferences(moduleRoot, modulePage), "释放权限保护引用");
|
|
||||||
TryCleanup(() => ClearRightMenuReferences(moduleRoot, gridViews), "释放右键菜单引用");
|
|
||||||
TryCleanup(
|
TryCleanup(
|
||||||
() => StaticBandedControl.ReleaseModuleReferences(moduleRoot, modulePage, moduleCode),
|
() => RemoveConditionPanels(moduleCode, moduleControls),
|
||||||
|
"释放条件面板引用");
|
||||||
|
TryCleanup(
|
||||||
|
() => RemoveProtectedReferences(moduleControls, modulePage),
|
||||||
|
"释放权限保护引用");
|
||||||
|
TryCleanup(
|
||||||
|
() => ClearRightMenuReferences(moduleControls, gridViews),
|
||||||
|
"释放右键菜单引用");
|
||||||
|
TryCleanup(
|
||||||
|
() => StaticBandedControl.ReleaseModuleReferences(moduleControls, modulePage, moduleCode),
|
||||||
"释放带状表格引用");
|
"释放带状表格引用");
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
// DynamicModel caches retain completed tasks, result tables and
|
// Only remove cache entries owned by this module. The cached
|
||||||
// their closures. Always release them after other references.
|
// values are owned by their module/control and must not be
|
||||||
TryCleanup(() => ClearDataCaches(moduleRoot), "释放动态数据缓存");
|
// disposed from a global static cleanup routine.
|
||||||
|
TryCleanup(
|
||||||
|
() => RemoveModuleCacheReferences(moduleControls, gridViews),
|
||||||
|
"移除动态数据缓存引用");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -80,21 +180,104 @@ namespace Lskj.Control.Model
|
|||||||
Debug.WriteLine("释放旧模块引用失败[" + operation + "]:" + exception);
|
Debug.WriteLine("释放旧模块引用失败[" + operation + "]:" + exception);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static List<GridView> CollectGridViews(WinFormsControl root)
|
private static void ReleaseGridControlRuntimeReferences(
|
||||||
|
IEnumerable<WinFormsControl> moduleControls)
|
||||||
|
{
|
||||||
|
if (moduleControls == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
HashSet<GridControlEx> released =
|
||||||
|
new HashSet<GridControlEx>(ReferenceEqualityComparer<GridControlEx>.Instance);
|
||||||
|
foreach (GridControlEx gridControl in moduleControls.OfType<GridControlEx>())
|
||||||
|
{
|
||||||
|
if (gridControl == null || !released.Add(gridControl))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
gridControl.ReleaseResourcesForDispose();
|
||||||
|
}
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
TraceCleanupFailure("释放表格控件运行时引用", exception);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<WinFormsControl> CollectModuleControls(
|
||||||
|
WinFormsControl moduleRoot,
|
||||||
|
XtraTabPage modulePage)
|
||||||
|
{
|
||||||
|
List<WinFormsControl> result = new List<WinFormsControl>();
|
||||||
|
AddModuleControls(result, moduleRoot);
|
||||||
|
if (!ReferenceEquals(moduleRoot, modulePage))
|
||||||
|
AddModuleControls(result, modulePage);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void AddModuleControls(
|
||||||
|
List<WinFormsControl> target,
|
||||||
|
WinFormsControl root)
|
||||||
|
{
|
||||||
|
if (root == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
foreach (WinFormsControl control in EnumerateControls(root))
|
||||||
|
if (!ContainsControl(target, control))
|
||||||
|
target.Add(control);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<GridView> CollectGridViews(IEnumerable<WinFormsControl> controls)
|
||||||
{
|
{
|
||||||
List<GridView> result = new List<GridView>();
|
List<GridView> result = new List<GridView>();
|
||||||
if (root == null)
|
if (controls == null)
|
||||||
return result;
|
return result;
|
||||||
foreach (WinFormsControl control in EnumerateControls(root))
|
|
||||||
|
foreach (WinFormsControl control in controls)
|
||||||
{
|
{
|
||||||
DevExpress.XtraGrid.GridControl grid = control as DevExpress.XtraGrid.GridControl;
|
DevExpress.XtraGrid.GridControl grid = control as DevExpress.XtraGrid.GridControl;
|
||||||
GridView view = grid == null ? null : grid.MainView as GridView;
|
if (grid == null)
|
||||||
if (view != null && !result.Contains(view))
|
continue;
|
||||||
result.Add(view);
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
GridView mainView = grid.MainView as GridView;
|
||||||
|
AddGridView(result, mainView);
|
||||||
|
|
||||||
|
// A GridControl can retain detail views in ViewCollection even
|
||||||
|
// when they are not the current MainView.
|
||||||
|
foreach (DevExpress.XtraGrid.Views.Base.BaseView baseView in grid.ViewCollection)
|
||||||
|
AddGridView(result, baseView as GridView);
|
||||||
|
}
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
// A form can already be partially disposed when the shell
|
||||||
|
// releases its static references. Keep the views collected
|
||||||
|
// from the remaining controls.
|
||||||
|
TraceCleanupFailure("读取表格视图", exception);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void AddGridView(List<GridView> target, GridView view)
|
||||||
|
{
|
||||||
|
if (view != null && !ContainsGridView(target, view))
|
||||||
|
target.Add(view);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool ContainsGridView(
|
||||||
|
IEnumerable<GridView> gridViews,
|
||||||
|
GridView value)
|
||||||
|
{
|
||||||
|
if (gridViews == null || value == null)
|
||||||
|
return false;
|
||||||
|
foreach (GridView gridView in gridViews)
|
||||||
|
if (ReferenceEquals(gridView, value))
|
||||||
|
return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
private static IEnumerable<WinFormsControl> EnumerateControls(WinFormsControl root)
|
private static IEnumerable<WinFormsControl> EnumerateControls(WinFormsControl root)
|
||||||
{
|
{
|
||||||
yield return root;
|
yield return root;
|
||||||
@@ -109,114 +292,222 @@ namespace Lskj.Control.Model
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
GridDragGrid.DisposeRegistrations(GridViewDragGridDic, TargetViewDragGridDic, gridView);
|
GridDragGrid.DisposeRegistrations(GridViewDragGridDic, TargetViewDragGridDic, gridView);
|
||||||
if (ReferenceEquals(SourceDragGridView, gridView) ||
|
// BandedGridDragGrid uses a separate source dictionary but shares
|
||||||
(SourceDragGridView != null && SourceDragGridView.GridControl != null && SourceDragGridView.GridControl.IsDisposed))
|
// the GridView target dictionary. Clean both sides whenever a
|
||||||
SourceDragGridView = null;
|
// GridView is released so control-level Dispose also removes
|
||||||
if (ReferenceEquals(_RightMenuGridView, gridView) ||
|
// banded drag helpers without disposing the view itself.
|
||||||
(_RightMenuGridView != null && _RightMenuGridView.GridControl != null && _RightMenuGridView.GridControl.IsDisposed))
|
BandedGridDragGrid.DisposeRegistrations(
|
||||||
|
StaticBandedControl.BandedGridViewDragGridDic,
|
||||||
|
StaticBandedControl.BandedTargetViewDragGridDic,
|
||||||
|
gridView as BandedGridView,
|
||||||
|
gridView);
|
||||||
|
GridDragGrid.ClearSourceReferenceIfUnused(gridView);
|
||||||
|
if (ReferenceEquals(_RightMenuGridView, gridView))
|
||||||
RightMenuGridView = null;
|
RightMenuGridView = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void RemoveConditionPanels(string moduleCode)
|
private static void RemoveConditionPanels(
|
||||||
|
string moduleCode,
|
||||||
|
IEnumerable<WinFormsControl> moduleControls)
|
||||||
{
|
{
|
||||||
foreach (string key in ConditionsPanelDic.Keys.ToList())
|
foreach (string key in ConditionsPanelDic.Keys.ToList())
|
||||||
{
|
{
|
||||||
ModuleConditionsPanelEx panel;
|
ModuleConditionsPanelEx panel;
|
||||||
if (string.Equals(key, moduleCode, StringComparison.OrdinalIgnoreCase) ||
|
if (!ConditionsPanelDic.TryGetValue(key, out panel) || panel == null || panel.IsDisposed)
|
||||||
!ConditionsPanelDic.TryGetValue(key, out panel) || panel == null || panel.IsDisposed)
|
{
|
||||||
|
ConditionsPanelDic.Remove(key);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The dictionary is keyed only by module code. Two instances of
|
||||||
|
// the same module can therefore share that key. Remove a live
|
||||||
|
// panel only when its owner can be tied to the closing instance;
|
||||||
|
// otherwise leave it for the still-open module.
|
||||||
|
if (string.Equals(key, moduleCode, StringComparison.OrdinalIgnoreCase) &&
|
||||||
|
IsConditionPanelOwnedByModule(panel, moduleControls))
|
||||||
ConditionsPanelDic.Remove(key);
|
ConditionsPanelDic.Remove(key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void RemoveProtectedReferences(WinFormsControl root, XtraTabPage page)
|
private static bool IsConditionPanelOwnedByModule(
|
||||||
|
ModuleConditionsPanelEx panel,
|
||||||
|
IEnumerable<WinFormsControl> moduleControls)
|
||||||
|
{
|
||||||
|
if (panel == null)
|
||||||
|
return false;
|
||||||
|
if (ContainsControl(moduleControls, panel) || ContainsControl(moduleControls, panel.Owner))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
MyControl parentControl = panel.ParentControlObj;
|
||||||
|
return parentControl != null &&
|
||||||
|
parentControl.MainGridEx != null &&
|
||||||
|
ContainsControl(moduleControls, parentControl.MainGridEx);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void RemoveProtectedReferences(
|
||||||
|
IEnumerable<WinFormsControl> moduleControls,
|
||||||
|
XtraTabPage page)
|
||||||
{
|
{
|
||||||
while (page != null && DogVerifyModuleForms.Remove(page))
|
while (page != null && DogVerifyModuleForms.Remove(page))
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
foreach (XtraTabPage item in DogVerifyModuleForms.ToList())
|
foreach (XtraTabPage item in DogVerifyModuleForms.ToList())
|
||||||
if (item == null || item.IsDisposed || IsContained(root, item))
|
if (item == null || item.IsDisposed || ContainsControl(moduleControls, item))
|
||||||
DogVerifyModuleForms.Remove(item);
|
DogVerifyModuleForms.Remove(item);
|
||||||
foreach (IForm item in DogVerifyNoPageForms.ToList())
|
foreach (IForm item in DogVerifyNoPageForms.ToList())
|
||||||
if (item == null || item.SubForm == null || item.SubForm.IsDisposed || IsContained(root, item.SubForm))
|
if (item == null || item.SubForm == null || item.SubForm.IsDisposed ||
|
||||||
|
ContainsControl(moduleControls, item.SubForm))
|
||||||
DogVerifyNoPageForms.Remove(item);
|
DogVerifyNoPageForms.Remove(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool IsContained(WinFormsControl root, WinFormsControl value)
|
private static bool ContainsControl(
|
||||||
|
IEnumerable<WinFormsControl> moduleControls,
|
||||||
|
WinFormsControl value)
|
||||||
{
|
{
|
||||||
if (root == null || value == null)
|
if (moduleControls == null || value == null)
|
||||||
return false;
|
return false;
|
||||||
if (ReferenceEquals(root, value))
|
foreach (WinFormsControl control in moduleControls)
|
||||||
return true;
|
if (ReferenceEquals(control, value))
|
||||||
foreach (WinFormsControl child in root.Controls)
|
|
||||||
if (IsContained(child, value))
|
|
||||||
return true;
|
return true;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void ClearRightMenuReferences(WinFormsControl root, List<GridView> gridViews)
|
private static void ClearRightMenuReferences(
|
||||||
|
IEnumerable<WinFormsControl> moduleControls,
|
||||||
|
List<GridView> gridViews)
|
||||||
{
|
{
|
||||||
if (_RightMenuGridView != null && (gridViews.Contains(_RightMenuGridView) ||
|
if (_RightMenuGridView != null && ContainsGridView(gridViews, _RightMenuGridView))
|
||||||
(_RightMenuGridView.GridControl != null && _RightMenuGridView.GridControl.IsDisposed)))
|
|
||||||
RightMenuGridView = null;
|
RightMenuGridView = null;
|
||||||
if (RightMenuMyControl != null && RightMenuMyControl.MainGridEx != null &&
|
if (RightMenuMyControl != null && RightMenuMyControl.MainGridEx != null &&
|
||||||
(RightMenuMyControl.MainGridEx.IsDisposed || IsContained(root, RightMenuMyControl.MainGridEx)))
|
(RightMenuMyControl.MainGridEx.IsDisposed ||
|
||||||
|
ContainsControl(moduleControls, RightMenuMyControl.MainGridEx)))
|
||||||
RightMenuMyControl = null;
|
RightMenuMyControl = null;
|
||||||
if (BomUnionPage != null && (BomUnionPage.IsDisposed || IsContained(root, BomUnionPage)))
|
if (BomUnionPage != null &&
|
||||||
|
(BomUnionPage.IsDisposed || ContainsControl(moduleControls, BomUnionPage)))
|
||||||
BomUnionPage = null;
|
BomUnionPage = null;
|
||||||
if (AddParentGrid.Value != null && (AddParentGrid.Value.IsDisposed || IsContained(root, AddParentGrid.Value)))
|
if (AddParentGrid.Value != null &&
|
||||||
|
(AddParentGrid.Value.IsDisposed || ContainsControl(moduleControls, AddParentGrid.Value)))
|
||||||
AddParentGrid = new KeyValuePair<string, GridControlEx>();
|
AddParentGrid = new KeyValuePair<string, GridControlEx>();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void ClearDataCaches(WinFormsControl root)
|
private static void RemoveModuleCacheReferences(
|
||||||
|
IEnumerable<WinFormsControl> moduleControls,
|
||||||
|
IEnumerable<GridView> gridViews)
|
||||||
{
|
{
|
||||||
if (root == null)
|
if (moduleControls == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// Clear the root first because child enumeration can fail while a
|
HashSet<object> moduleReferences = new HashSet<object>(ReferenceEqualityComparer<object>.Instance);
|
||||||
// DevExpress control is disposing its child collection.
|
foreach (WinFormsControl control in moduleControls)
|
||||||
ClearDynamicModels(root);
|
if (control != null)
|
||||||
try
|
moduleReferences.Add(control);
|
||||||
|
if (gridViews != null)
|
||||||
|
foreach (GridView gridView in gridViews)
|
||||||
|
if (gridView != null)
|
||||||
|
moduleReferences.Add(gridView);
|
||||||
|
|
||||||
|
HashSet<Dictionary<object, Hashtable>> moduleCaches =
|
||||||
|
new HashSet<Dictionary<object, Hashtable>>(ReferenceEqualityComparer<Dictionary<object, Hashtable>>.Instance);
|
||||||
|
HashSet<DynamicModel> moduleModels =
|
||||||
|
new HashSet<DynamicModel>(ReferenceEqualityComparer<DynamicModel>.Instance);
|
||||||
|
foreach (WinFormsControl control in moduleControls.ToList())
|
||||||
{
|
{
|
||||||
foreach (WinFormsControl control in EnumerateControls(root).Skip(1).ToList())
|
try
|
||||||
ClearDynamicModels(control);
|
{
|
||||||
|
CollectDynamicModels(control, moduleCaches, moduleModels);
|
||||||
|
}
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
TraceCleanupFailure("收集动态缓存引用", exception);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (Exception exception)
|
|
||||||
|
foreach (DynamicModel model in moduleModels)
|
||||||
|
if (model != null)
|
||||||
|
moduleReferences.Add(model);
|
||||||
|
|
||||||
|
// A DataCaches dictionary is normally module-local, but the legacy
|
||||||
|
// code can share one dictionary between the form, its child controls
|
||||||
|
// and model objects. Never clear a whole dictionary here: a module.
|
||||||
|
// can be opening concurrently and may not have been registered in
|
||||||
|
// ERPInfo.ModuleForms yet. Removing only keys that belong to the
|
||||||
|
// closing controls/views keeps every other module's cache intact.
|
||||||
|
foreach (Dictionary<object, Hashtable> caches in moduleCaches)
|
||||||
{
|
{
|
||||||
TraceCleanupFailure("遍历动态数据缓存", exception);
|
try
|
||||||
|
{
|
||||||
|
RemoveModuleCacheEntries(caches, moduleReferences);
|
||||||
|
}
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
TraceCleanupFailure("移除动态缓存引用", exception);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
foreach (DynamicModel model in moduleModels)
|
||||||
|
if (model != null && model.DataCaches != null)
|
||||||
|
model.DataCaches = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void ClearDynamicModels(object instance)
|
private static void CollectDynamicModels(
|
||||||
|
object instance,
|
||||||
|
HashSet<Dictionary<object, Hashtable>> caches,
|
||||||
|
HashSet<DynamicModel> models)
|
||||||
{
|
{
|
||||||
if (instance == null)
|
if (instance == null || caches == null || models == null)
|
||||||
return;
|
return;
|
||||||
BindingFlags flags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
|
BindingFlags flags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
|
||||||
for (Type type = instance.GetType(); type != null; type = type.BaseType)
|
for (Type type = instance.GetType(); type != null; type = type.BaseType)
|
||||||
foreach (FieldInfo field in type.GetFields(flags | BindingFlags.DeclaredOnly))
|
foreach (FieldInfo field in type.GetFields(flags | BindingFlags.DeclaredOnly))
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
ClearDynamicModel(field.GetValue(instance));
|
DynamicModel model = field.GetValue(instance) as DynamicModel;
|
||||||
|
if (model == null || model.DataCaches == null)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
caches.Add(model.DataCaches);
|
||||||
|
models.Add(model);
|
||||||
}
|
}
|
||||||
catch (Exception exception)
|
catch (Exception exception)
|
||||||
{
|
{
|
||||||
Debug.WriteLine("清理动态缓存字段失败:" + exception);
|
Debug.WriteLine("收集动态缓存字段失败:" + exception);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void ClearDynamicModel(object value)
|
private static void RemoveModuleCacheEntries(
|
||||||
|
Dictionary<object, Hashtable> caches,
|
||||||
|
HashSet<object> moduleReferences)
|
||||||
{
|
{
|
||||||
DynamicModel model = value as DynamicModel;
|
if (caches == null || moduleReferences == null || moduleReferences.Count == 0)
|
||||||
if (model == null || model.DataCaches == null)
|
|
||||||
return;
|
return;
|
||||||
Dictionary<object, Hashtable> caches = model.DataCaches;
|
|
||||||
foreach (Hashtable cache in caches.Values.OfType<Hashtable>().ToList())
|
// DataCaches is shared by several controls in a module. Remove only
|
||||||
|
// entries keyed by controls/views in the closing module and leave
|
||||||
|
// all cached values untouched. Their actual owner decides when and
|
||||||
|
// how to dispose them.
|
||||||
|
lock (caches)
|
||||||
{
|
{
|
||||||
foreach (IDisposable item in cache.Values.OfType<IDisposable>().ToList())
|
foreach (object key in caches.Keys.Cast<object>().ToList())
|
||||||
try { item.Dispose(); } catch { }
|
if (moduleReferences.Contains(key))
|
||||||
cache.Clear();
|
caches.Remove(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class ReferenceEqualityComparer<T> : IEqualityComparer<T>
|
||||||
|
where T : class
|
||||||
|
{
|
||||||
|
public static readonly ReferenceEqualityComparer<T> Instance =
|
||||||
|
new ReferenceEqualityComparer<T>();
|
||||||
|
|
||||||
|
public bool Equals(T x, T y)
|
||||||
|
{
|
||||||
|
return ReferenceEquals(x, y);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int GetHashCode(T value)
|
||||||
|
{
|
||||||
|
return value == null ? 0 : RuntimeHelpers.GetHashCode(value);
|
||||||
}
|
}
|
||||||
caches.Clear();
|
|
||||||
model.DataCaches = null;
|
|
||||||
}
|
}
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 记录当前U盾权限窗口
|
/// 记录当前U盾权限窗口
|
||||||
|
|||||||
@@ -160,6 +160,19 @@ namespace Lskj.Control
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private readonly HashSet<GridControlEx> _rightTabRefreshRegisteredGrids = new HashSet<GridControlEx>();
|
private readonly HashSet<GridControlEx> _rightTabRefreshRegisteredGrids = new HashSet<GridControlEx>();
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
/// 右侧联动事件委托。必须保存委托实例,Dispose 时才能从 GridView 正确解绑。
|
||||||
|
/// 匿名 lambda 如果不保存实例,会把 TabControlEx 通过事件闭包继续挂在已关闭的表格上。
|
||||||
|
/// </summary>
|
||||||
|
private readonly Dictionary<GridControlEx, DevExpress.XtraGrid.Views.Base.FocusedRowObjectChangedEventHandler> _rightTabRefreshHandlers =
|
||||||
|
new Dictionary<GridControlEx, DevExpress.XtraGrid.Views.Base.FocusedRowObjectChangedEventHandler>();
|
||||||
|
/// <summary>
|
||||||
|
/// 保存注册事件时的 GridView 实例。GridControlEx 在 Dispose 过程中可能
|
||||||
|
/// 清空或替换 GridView 属性;解绑时必须使用当初注册事件的同一个实例,
|
||||||
|
/// 不能再次通过 GridControlEx.GridView 取值。
|
||||||
|
/// </summary>
|
||||||
|
private readonly Dictionary<GridControlEx, GridView> _rightTabRefreshHandlerViews =
|
||||||
|
new Dictionary<GridControlEx, GridView>();
|
||||||
|
/// <summary>
|
||||||
/// 左右结构中的右侧页签集合。
|
/// 左右结构中的右侧页签集合。
|
||||||
/// 只有这些页签会根据数据行数禁用 ModuleGridEx 底部操作栏。
|
/// 只有这些页签会根据数据行数禁用 ModuleGridEx 底部操作栏。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -435,9 +448,14 @@ namespace Lskj.Control
|
|||||||
if (leftGrid == null || rightTab == null) return;
|
if (leftGrid == null || rightTab == null) return;
|
||||||
|
|
||||||
_rightTabsByLeftGrid[leftGrid] = rightTab;
|
_rightTabsByLeftGrid[leftGrid] = rightTab;
|
||||||
if (!_rightTabRefreshRegisteredGrids.Contains(leftGrid))
|
GridView leftView = leftGrid.GridView;
|
||||||
|
if (!_rightTabRefreshRegisteredGrids.Contains(leftGrid) && leftView != null)
|
||||||
{
|
{
|
||||||
leftGrid.GridView.FocusedRowObjectChanged += (s, e) => RefreshRightTabAfterLeftBind(leftGrid);
|
DevExpress.XtraGrid.Views.Base.FocusedRowObjectChangedEventHandler handler =
|
||||||
|
(s, e) => RefreshRightTabAfterLeftBind(leftGrid);
|
||||||
|
leftView.FocusedRowObjectChanged += handler;
|
||||||
|
_rightTabRefreshHandlers[leftGrid] = handler;
|
||||||
|
_rightTabRefreshHandlerViews[leftGrid] = leftView;
|
||||||
_rightTabRefreshRegisteredGrids.Add(leftGrid);
|
_rightTabRefreshRegisteredGrids.Add(leftGrid);
|
||||||
}
|
}
|
||||||
RefreshRightTab(rightTab, leftGrid);// 首次加载
|
RefreshRightTab(rightTab, leftGrid);// 首次加载
|
||||||
@@ -480,18 +498,30 @@ namespace Lskj.Control
|
|||||||
{
|
{
|
||||||
if (ParentGridEx != null && ParentGridEx.CustomGroupBandEx != null)
|
if (ParentGridEx != null && ParentGridEx.CustomGroupBandEx != null)
|
||||||
{
|
{
|
||||||
|
ParentGridEx.CustomGroupBandEx.BandedView.LeftCoordChanged -= OnBandedViewLeftCoordChanged;
|
||||||
|
ParentGridEx.CustomGroupBandEx.BandedView.ColumnWidthChanged -= OnBandedViewColumnWidthChanged;
|
||||||
|
ParentGridEx.OnBuildGroupedColumnCallBack -= OnBandedViewColumnWidthChanged;
|
||||||
ParentGridEx.CustomGroupBandEx.BandedView.LeftCoordChanged += OnBandedViewLeftCoordChanged;
|
ParentGridEx.CustomGroupBandEx.BandedView.LeftCoordChanged += OnBandedViewLeftCoordChanged;
|
||||||
ParentGridEx.CustomGroupBandEx.BandedView.ColumnWidthChanged += OnBandedViewColumnWidthChanged;
|
ParentGridEx.CustomGroupBandEx.BandedView.ColumnWidthChanged += OnBandedViewColumnWidthChanged;
|
||||||
ParentGridEx.OnBuildGroupedColumnCallBack += OnBandedViewColumnWidthChanged;
|
ParentGridEx.OnBuildGroupedColumnCallBack += OnBandedViewColumnWidthChanged;
|
||||||
}
|
}
|
||||||
if (ParentGridEx != null && ParentGridEx.CustomGroupTreeBandEx != null)
|
if (ParentGridEx != null && ParentGridEx.CustomGroupTreeBandEx != null)
|
||||||
{
|
{
|
||||||
|
ParentGridEx.CustomGroupTreeBandEx.TreeListObj.LeftCoordChanged -= OnBandedViewLeftCoordChanged;
|
||||||
|
ParentGridEx.CustomGroupTreeBandEx.TreeListObj.ColumnWidthChanged -= OnBandedViewColumnWidthChanged;
|
||||||
|
ParentGridEx.OnBuildGroupedColumnCallBack -= OnBandedViewColumnWidthChanged;
|
||||||
ParentGridEx.CustomGroupTreeBandEx.TreeListObj.LeftCoordChanged += OnBandedViewLeftCoordChanged; ;
|
ParentGridEx.CustomGroupTreeBandEx.TreeListObj.LeftCoordChanged += OnBandedViewLeftCoordChanged; ;
|
||||||
ParentGridEx.CustomGroupTreeBandEx.TreeListObj.ColumnWidthChanged += OnBandedViewColumnWidthChanged;
|
ParentGridEx.CustomGroupTreeBandEx.TreeListObj.ColumnWidthChanged += OnBandedViewColumnWidthChanged;
|
||||||
ParentGridEx.OnBuildGroupedColumnCallBack += OnBandedViewColumnWidthChanged;
|
ParentGridEx.OnBuildGroupedColumnCallBack += OnBandedViewColumnWidthChanged;
|
||||||
}
|
}
|
||||||
this._gridDetail = gridDetail;
|
this._gridDetail = gridDetail;
|
||||||
if (!isAddPages) this.xtc_container.TabPages.Clear();
|
if (!isAddPages)
|
||||||
|
{
|
||||||
|
// 重建明细页签前先解除旧左右联动关系。仅清空 TabPages 不会移除
|
||||||
|
// GridView 上的事件,因此旧页签仍可能通过事件闭包存活。
|
||||||
|
DetachRightTabRefreshHandlers();
|
||||||
|
this.xtc_container.TabPages.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
// 1️⃣ 预分组:Rightdockid > 0 的明细
|
// 1️⃣ 预分组:Rightdockid > 0 的明细
|
||||||
var rightGroups = gridDetail
|
var rightGroups = gridDetail
|
||||||
@@ -549,7 +579,12 @@ namespace Lskj.Control
|
|||||||
rightTab.ShowTabHeader = DevExpress.Utils.DefaultBoolean.False;
|
rightTab.ShowTabHeader = DevExpress.Utils.DefaultBoolean.False;
|
||||||
|
|
||||||
|
|
||||||
GridControlEx leftControl = new GridControlEx();
|
// Only reuse an existing grid from the left detail. The
|
||||||
|
// previous unconditional `new GridControlEx()` created an
|
||||||
|
// orphan control whenever leftCtrl was a non-grid control;
|
||||||
|
// it was never added to the visual tree or disposed, and
|
||||||
|
// its GridView event handlers kept the whole wrapper alive.
|
||||||
|
GridControlEx leftControl = null;
|
||||||
if (leftCtrl is GridControlEx left1)
|
if (leftCtrl is GridControlEx left1)
|
||||||
{
|
{
|
||||||
leftControl = left1;
|
leftControl = left1;
|
||||||
@@ -646,6 +681,7 @@ namespace Lskj.Control
|
|||||||
this._gridDetail = gridDetail;
|
this._gridDetail = gridDetail;
|
||||||
if (!isAddPages)
|
if (!isAddPages)
|
||||||
{
|
{
|
||||||
|
DetachRightTabRefreshHandlers();
|
||||||
this.xtc_container.TabPages.Clear();
|
this.xtc_container.TabPages.Clear();
|
||||||
}
|
}
|
||||||
string DetaiName = string.Empty;
|
string DetaiName = string.Empty;
|
||||||
@@ -1865,6 +1901,7 @@ namespace Lskj.Control
|
|||||||
public void InitTabPages(List<GridDetailModel> gridDetail, string parentValue)
|
public void InitTabPages(List<GridDetailModel> gridDetail, string parentValue)
|
||||||
{
|
{
|
||||||
this._gridDetail = gridDetail;
|
this._gridDetail = gridDetail;
|
||||||
|
DetachRightTabRefreshHandlers();
|
||||||
this.xtc_container.TabPages.Clear();
|
this.xtc_container.TabPages.Clear();
|
||||||
|
|
||||||
bool ProcessExists = (Lskj.Data.Caches.CacheSYSConfig.Instance().JudgeProcessExists() && SystemInfo.Instance.isExecload);
|
bool ProcessExists = (Lskj.Data.Caches.CacheSYSConfig.Instance().JudgeProcessExists() && SystemInfo.Instance.isExecload);
|
||||||
@@ -2685,8 +2722,92 @@ namespace Lskj.Control
|
|||||||
/// <param name="e"></param>
|
/// <param name="e"></param>
|
||||||
private void OnTabControlExDisposed(object sender, EventArgs e)
|
private void OnTabControlExDisposed(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
|
ReleaseRuntimeReferences();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 在模块关闭但控件尚未进入 WinForms Dispose 流程时,主动解除
|
||||||
|
/// TabControlEx 持有的运行时引用。这里只解除事件和清空本控件的
|
||||||
|
/// 映射,不销毁共享的 GridControl/GridView。
|
||||||
|
/// </summary>
|
||||||
|
internal void ReleaseRuntimeReferences()
|
||||||
|
{
|
||||||
|
DetachRightTabRefreshHandlers();
|
||||||
|
DetachParentGridHandlers();
|
||||||
PageControlsDic?.Clear();
|
PageControlsDic?.Clear();
|
||||||
DetailTabPagesDic?.Clear();
|
DetailTabPagesDic?.Clear();
|
||||||
|
_gridDetail?.Clear();
|
||||||
|
_gridIntoDetail?.Clear();
|
||||||
|
ParentGridEx = null;
|
||||||
|
MrpParentGridEx = null;
|
||||||
|
MrpDetailGridEx = null;
|
||||||
|
ParentControlEx = null;
|
||||||
|
ModuleGridObj = null;
|
||||||
|
ReplaceControlEx = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 解除左右联动刷新事件。事件委托由字典保存,保证能够与注册时使用同一个实例。
|
||||||
|
/// </summary>
|
||||||
|
private void DetachRightTabRefreshHandlers()
|
||||||
|
{
|
||||||
|
foreach (KeyValuePair<GridControlEx, DevExpress.XtraGrid.Views.Base.FocusedRowObjectChangedEventHandler> item in
|
||||||
|
_rightTabRefreshHandlers.ToArray())
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
GridView registeredView;
|
||||||
|
if (_rightTabRefreshHandlerViews.TryGetValue(item.Key, out registeredView) &&
|
||||||
|
registeredView != null && item.Value != null)
|
||||||
|
{
|
||||||
|
registeredView.FocusedRowObjectChanged -= item.Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// 表格可能已先于 TabControlEx 进入 Dispose,解绑失败不应阻断剩余清理。
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_rightTabRefreshHandlers.Clear();
|
||||||
|
_rightTabRefreshHandlerViews.Clear();
|
||||||
|
_rightTabsByLeftGrid.Clear();
|
||||||
|
_rightTabRefreshRegisteredGrids.Clear();
|
||||||
|
_rightSideTabPages.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 解除父表聚合视图的滚动/列宽事件,避免父表事件链反向持有当前 TabControlEx。
|
||||||
|
/// </summary>
|
||||||
|
private void DetachParentGridHandlers()
|
||||||
|
{
|
||||||
|
GridControlEx parentGrid = ParentGridEx;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (parentGrid != null && parentGrid.CustomGroupBandEx != null)
|
||||||
|
{
|
||||||
|
if (parentGrid.CustomGroupBandEx.BandedView != null)
|
||||||
|
{
|
||||||
|
parentGrid.CustomGroupBandEx.BandedView.LeftCoordChanged -= OnBandedViewLeftCoordChanged;
|
||||||
|
parentGrid.CustomGroupBandEx.BandedView.ColumnWidthChanged -= OnBandedViewColumnWidthChanged;
|
||||||
|
}
|
||||||
|
parentGrid.OnBuildGroupedColumnCallBack -= OnBandedViewColumnWidthChanged;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parentGrid != null && parentGrid.CustomGroupTreeBandEx != null)
|
||||||
|
{
|
||||||
|
if (parentGrid.CustomGroupTreeBandEx.TreeListObj != null)
|
||||||
|
{
|
||||||
|
parentGrid.CustomGroupTreeBandEx.TreeListObj.LeftCoordChanged -= OnBandedViewLeftCoordChanged;
|
||||||
|
parentGrid.CustomGroupTreeBandEx.TreeListObj.ColumnWidthChanged -= OnBandedViewColumnWidthChanged;
|
||||||
|
}
|
||||||
|
parentGrid.OnBuildGroupedColumnCallBack -= OnBandedViewColumnWidthChanged;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// 父表可能已经释放,事件清理应保持 best-effort。
|
||||||
|
}
|
||||||
}
|
}
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 水平滚动条改变时
|
/// 水平滚动条改变时
|
||||||
|
|||||||
Reference in New Issue
Block a user