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 moduleReferences = new HashSet(ReferenceEqualityComparer.Instance); foreach (WinFormsControl control in moduleControls) if (control != null) moduleReferences.Add(control); if (gridViews != null) foreach (GridView gridView in gridViews) if (gridView != null) moduleReferences.Add(gridView); HashSet> moduleCaches = new HashSet>(ReferenceEqualityComparer>.Instance); HashSet moduleModels = new HashSet(ReferenceEqualityComparer.Instance); foreach (WinFormsControl control in moduleControls.ToList()) { try { CollectDynamicModels(control, moduleCaches, moduleModels); } catch (Exception exception) { TraceCleanupFailure("收集动态缓存引用", 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 caches in moduleCaches) { 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 CollectDynamicModels( object instance, HashSet> caches, HashSet models) { if (instance == null || caches == null || models == null) return; BindingFlags flags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic; for (Type type = instance.GetType(); type != null; type = type.BaseType) foreach (FieldInfo field in type.GetFields(flags | BindingFlags.DeclaredOnly)) try { DynamicModel model = field.GetValue(instance) as DynamicModel; if (model == null || model.DataCaches == null) continue; caches.Add(model.DataCaches); models.Add(model); } catch (Exception exception) { Debug.WriteLine("收集动态缓存字段失败:" + exception); } } private static void RemoveModuleCacheEntries( Dictionary caches, HashSet moduleReferences) { if (caches == null || moduleReferences == null || moduleReferences.Count == 0) return; // 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 (object key in caches.Keys.Cast().ToList()) if (moduleReferences.Contains(key)) caches.Remove(key); } } private sealed class ReferenceEqualityComparer : IEqualityComparer where T : class { public static readonly ReferenceEqualityComparer Instance = new ReferenceEqualityComparer(); public bool Equals(T x, T y) { return ReferenceEquals(x, y); } public int GetHashCode(T value) { return value == null ? 0 : RuntimeHelpers.GetHashCode(value); } } /// /// 记录当前U盾权限窗口 /// public static List DogVerifyModuleForms = new List(); public static List DogVerifyNoPageForms = new List(); /// ///模块打开时的条件界面 /// public static Dictionary ConditionsPanelDic = new Dictionary(); /// /// 表格拖拽类,key值为源表 /// public static Dictionary> GridViewDragGridDic = new Dictionary>(); /// /// 表格拖拽类,key值为目标表 /// public static Dictionary> TargetViewDragGridDic = new Dictionary>(); /// /// 当前拖拽源表格 /// public static GridView SourceDragGridView; /// /// 指纹识别模块 /// public static FingerHelper fingerHelper; ///// ///// 外部exe进程 ///// //public static Dictionary loadExeProcess = new Dictionary(); /// /// 打开的Add模块对应的父表格控件(模块编号,GridControlEx) /// public static KeyValuePair AddParentGrid = new KeyValuePair(); /// /// 当前右键点击时的表格 /// public static GridView _RightMenuGridView; public static GridView RightMenuGridView { get { return StaticControl._RightMenuGridView; } set { StaticControl._RightMenuGridView = value; StaticControl.Comprefix = ""; StaticControl.ReturnRowLisy.Clear(); } } /// /// bom附件修改时的关联页签 /// public static XtraTabPage BomUnionPage; /// /// 当前右键点击时的控件对象 /// public static MyControl RightMenuMyControl; /// /// 当前右键点击时的表格的列前缀 /// public static string Comprefix; /// /// 浏览器返回数据 /// public static List ReturnRowLisy = new List(); } }