using DevExpress.XtraGrid.Views.Grid;
using DevExpress.XtraGrid.Views.BandedGrid;
using DevExpress.XtraTab;
using Lskj.Control.ZKFinger;
using Lskj.Util;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Diagnostics;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading;
using System.Windows.Forms;
using Lskj.Model;
using System.Reflection;
using WinFormsControl = System.Windows.Forms.Control;
namespace Lskj.Control.Model
{
public static class StaticControl
{
///
/// 在窗体关闭前捕获模块控件树及其 GridView。WinForms 的 Close/Dispose
/// 流程可能会先清空 Controls 或重建 GridView;保留这个快照可以让后续
/// 引用解绑仍然针对原始实例执行。
///
public sealed class ModuleReferenceSnapshot
{
internal readonly List Controls;
internal readonly List GridViews;
internal ModuleReferenceSnapshot(
List controls,
List gridViews)
{
Controls = controls ?? new List();
GridViews = gridViews ?? new List();
}
}
public static ModuleReferenceSnapshot CaptureModuleReferences(
WinFormsControl moduleRoot,
XtraTabPage modulePage)
{
List moduleControls = new List();
try
{
moduleControls = CollectModuleControls(moduleRoot, modulePage);
}
catch (Exception exception)
{
TraceCleanupFailure("收集模块控件引用", exception);
}
List gridViews = new List();
try
{
gridViews = CollectGridViews(moduleControls);
}
catch (Exception exception)
{
TraceCleanupFailure("收集模块表格引用", exception);
}
return new ModuleReferenceSnapshot(moduleControls, gridViews);
}
///
/// Releases static references owned by a closed legacy module. Cleanup is
/// best-effort so it cannot change the module close result.
///
public static void ReleaseModuleReferences(WinFormsControl moduleRoot, XtraTabPage modulePage, string moduleCode)
{
ReleaseModuleReferences(
CaptureModuleReferences(moduleRoot, modulePage),
modulePage,
moduleCode);
}
///
/// 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.
///
public static void ReleaseModuleReferences(
ModuleReferenceSnapshot snapshot,
XtraTabPage modulePage,
string moduleCode)
{
List moduleControls = snapshot == null || snapshot.Controls == null
? new List()
: snapshot.Controls;
List gridViews = snapshot == null || snapshot.GridViews == null
? new List()
: snapshot.GridViews;
List tabControls = new List();
try
{
tabControls = moduleControls.OfType().ToList();
}
catch (Exception exception)
{
TraceCleanupFailure("收集模块引用", exception);
}
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)
{
try
{
ReleaseGridViewReferences(gridView);
}
catch (Exception exception)
{
TraceCleanupFailure("释放表格拖拽引用", exception);
}
}
TryCleanup(
() => RemoveConditionPanels(moduleCode, moduleControls),
"释放条件面板引用");
TryCleanup(
() => RemoveProtectedReferences(moduleControls, modulePage),
"释放权限保护引用");
TryCleanup(
() => ClearRightMenuReferences(moduleControls, gridViews),
"释放右键菜单引用");
TryCleanup(
() => StaticBandedControl.ReleaseModuleReferences(moduleControls, modulePage, moduleCode),
"释放带状表格引用");
}
finally
{
// Only remove cache entries owned by this module. The cached
// values are owned by their module/control and must not be
// disposed from a global static cleanup routine.
TryCleanup(
() => RemoveModuleCacheReferences(moduleControls, gridViews),
"移除动态数据缓存引用");
}
}
private static void TryCleanup(Action cleanup, string operation)
{
try
{
cleanup();
}
catch (Exception exception)
{
TraceCleanupFailure(operation, exception);
}
}
private static void TraceCleanupFailure(string operation, Exception exception)
{
Debug.WriteLine("释放旧模块引用失败[" + operation + "]:" + exception);
}
private static void ReleaseGridControlRuntimeReferences(
IEnumerable moduleControls)
{
if (moduleControls == null)
return;
HashSet released =
new HashSet(ReferenceEqualityComparer.Instance);
foreach (GridControlEx gridControl in moduleControls.OfType())
{
if (gridControl == null || !released.Add(gridControl))
continue;
try
{
gridControl.ReleaseResourcesForDispose();
}
catch (Exception exception)
{
TraceCleanupFailure("释放表格控件运行时引用", exception);
}
}
}
private static List CollectModuleControls(
WinFormsControl moduleRoot,
XtraTabPage modulePage)
{
List result = new List();
AddModuleControls(result, moduleRoot);
if (!ReferenceEquals(moduleRoot, modulePage))
AddModuleControls(result, modulePage);
return result;
}
private static void AddModuleControls(
List target,
WinFormsControl root)
{
if (root == null)
return;
foreach (WinFormsControl control in EnumerateControls(root))
if (!ContainsControl(target, control))
target.Add(control);
}
private static List CollectGridViews(IEnumerable controls)
{
List result = new List();
if (controls == null)
return result;
foreach (WinFormsControl control in controls)
{
DevExpress.XtraGrid.GridControl grid = control as DevExpress.XtraGrid.GridControl;
if (grid == null)
continue;
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;
}
private static void AddGridView(List target, GridView view)
{
if (view != null && !ContainsGridView(target, view))
target.Add(view);
}
private static bool ContainsGridView(
IEnumerable 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 EnumerateControls(WinFormsControl root)
{
yield return root;
foreach (WinFormsControl child in root.Controls)
foreach (WinFormsControl nested in EnumerateControls(child))
yield return nested;
}
internal static void ReleaseGridViewReferences(GridView gridView)
{
if (gridView == null)
return;
GridDragGrid.DisposeRegistrations(GridViewDragGridDic, TargetViewDragGridDic, gridView);
// BandedGridDragGrid uses a separate source dictionary but shares
// the GridView target dictionary. Clean both sides whenever a
// GridView is released so control-level Dispose also removes
// banded drag helpers without disposing the view itself.
BandedGridDragGrid.DisposeRegistrations(
StaticBandedControl.BandedGridViewDragGridDic,
StaticBandedControl.BandedTargetViewDragGridDic,
gridView as BandedGridView,
gridView);
GridDragGrid.ClearSourceReferenceIfUnused(gridView);
if (ReferenceEquals(_RightMenuGridView, gridView))
RightMenuGridView = null;
}
private static void RemoveConditionPanels(
string moduleCode,
IEnumerable moduleControls)
{
foreach (string key in ConditionsPanelDic.Keys.ToList())
{
ModuleConditionsPanelEx panel;
if (!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);
}
}
private static bool IsConditionPanelOwnedByModule(
ModuleConditionsPanelEx panel,
IEnumerable 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 moduleControls,
XtraTabPage page)
{
while (page != null && DogVerifyModuleForms.Remove(page))
{
}
foreach (XtraTabPage item in DogVerifyModuleForms.ToList())
if (item == null || item.IsDisposed || ContainsControl(moduleControls, item))
DogVerifyModuleForms.Remove(item);
foreach (IForm item in DogVerifyNoPageForms.ToList())
if (item == null || item.SubForm == null || item.SubForm.IsDisposed ||
ContainsControl(moduleControls, item.SubForm))
DogVerifyNoPageForms.Remove(item);
}
private static bool ContainsControl(
IEnumerable moduleControls,
WinFormsControl value)
{
if (moduleControls == null || value == null)
return false;
foreach (WinFormsControl control in moduleControls)
if (ReferenceEquals(control, value))
return true;
return false;
}
private static void ClearRightMenuReferences(
IEnumerable moduleControls,
List gridViews)
{
if (_RightMenuGridView != null && ContainsGridView(gridViews, _RightMenuGridView))
RightMenuGridView = null;
if (RightMenuMyControl != null && RightMenuMyControl.MainGridEx != null &&
(RightMenuMyControl.MainGridEx.IsDisposed ||
ContainsControl(moduleControls, RightMenuMyControl.MainGridEx)))
RightMenuMyControl = null;
if (BomUnionPage != null &&
(BomUnionPage.IsDisposed || ContainsControl(moduleControls, BomUnionPage)))
BomUnionPage = null;
if (AddParentGrid.Value != null &&
(AddParentGrid.Value.IsDisposed || ContainsControl(moduleControls, AddParentGrid.Value)))
AddParentGrid = new KeyValuePair();
}
private static void RemoveModuleCacheReferences(
IEnumerable moduleControls,
IEnumerable gridViews)
{
if (moduleControls == null)
return;
HashSet