diff --git a/插件库/Lskj.BaseAccraditation/FrmBaseAccraditation.Designer.cs b/插件库/Lskj.BaseAccraditation/FrmBaseAccraditation.Designer.cs index 98db0b0..4286383 100644 --- a/插件库/Lskj.BaseAccraditation/FrmBaseAccraditation.Designer.cs +++ b/插件库/Lskj.BaseAccraditation/FrmBaseAccraditation.Designer.cs @@ -13,9 +13,13 @@ /// true if managed resources should be disposed; otherwise, false. protected override void Dispose(bool disposing) { - if (disposing && (components != null)) + if (disposing) { - components.Dispose(); + ReleaseResourcesForDispose(); + if (components != null) + { + components.Dispose(); + } } base.Dispose(disposing); } @@ -666,4 +670,4 @@ private Control.TabControlEx tab_basicInfo; private DevExpress.XtraEditors.DropDownButton btnPrint; } -} \ No newline at end of file +} diff --git a/插件库/Lskj.BaseAccraditation/FrmBaseAccraditation.cs b/插件库/Lskj.BaseAccraditation/FrmBaseAccraditation.cs index 5c54ed4..e966fa4 100644 --- a/插件库/Lskj.BaseAccraditation/FrmBaseAccraditation.cs +++ b/插件库/Lskj.BaseAccraditation/FrmBaseAccraditation.cs @@ -64,6 +64,7 @@ namespace Lskj.BaseAccraditation /// 主表控件 /// public MyControl ControlObj; + private bool mResourcesReleased; #endregion @@ -191,6 +192,42 @@ namespace Lskj.BaseAccraditation InitializeComponent(); } + private void ReleaseResourcesForDispose() + { + if (mResourcesReleased) + { + return; + } + + mResourcesReleased = true; + PrintUtil.OnAfterPrint -= OnReportPrintAfter; + + MyControl control = ControlObj; + ControlObj = null; + if (control != null) + { + control.OnDataSourceBindCallBack -= + OnControlObjOnDataSourceBindCallBack; + control.Dispose(); + } + + try + { + if (tab_basicInfo != null) + { + tab_basicInfo.ParentControlEx = null; + } + if (SysModel != null && SysModel.DataCaches != null) + { + SysModel.DataCaches.Remove(this); + } + } + catch + { + // A partially disposed child must not stop form cleanup. + } + } + public override void SetFormSize() { this.WindowState = FormWindowState.Normal; diff --git a/插件库/Lskj.Control/AuditPanel2.Designer.cs b/插件库/Lskj.Control/AuditPanel2.Designer.cs index 980d33d..4ff6a58 100644 --- a/插件库/Lskj.Control/AuditPanel2.Designer.cs +++ b/插件库/Lskj.Control/AuditPanel2.Designer.cs @@ -13,9 +13,14 @@ /// 如果应释放托管资源,为 true;否则为 false。 protected override void Dispose(bool disposing) { - if (disposing && (components != null)) + if (disposing) { - components.Dispose(); + Lskj.Control.Model.PrintUtil.OnAfterPrint -= + new System.EventHandler(OnReportPrintAfter); + if (components != null) + { + components.Dispose(); + } } base.Dispose(disposing); } diff --git a/插件库/Lskj.Control/AuditPanelEx.Designer.cs b/插件库/Lskj.Control/AuditPanelEx.Designer.cs index 231854f..6181166 100644 --- a/插件库/Lskj.Control/AuditPanelEx.Designer.cs +++ b/插件库/Lskj.Control/AuditPanelEx.Designer.cs @@ -13,9 +13,13 @@ /// 如果应释放托管资源,为 true;否则为 false。 protected override void Dispose(bool disposing) { - if (disposing && (components != null)) + if (disposing) { - components.Dispose(); + ReleaseResourcesForDispose(); + if (components != null) + { + components.Dispose(); + } } base.Dispose(disposing); } diff --git a/插件库/Lskj.Control/AuditPanelEx.cs b/插件库/Lskj.Control/AuditPanelEx.cs index 8601439..f9e9e18 100644 --- a/插件库/Lskj.Control/AuditPanelEx.cs +++ b/插件库/Lskj.Control/AuditPanelEx.cs @@ -41,11 +41,79 @@ namespace Lskj.Control { public partial class AuditPanelEx : UserControl { + private bool mResourcesReleased; + public AuditPanelEx() { InitializeComponent(); } + internal void ReleaseResourcesForDispose() + { + if (mResourcesReleased) + { + return; + } + + mResourcesReleased = true; + MyControl searchControl = _searchContrl; + _searchContrl = null; + if (searchControl != null) + { + searchControl.Dispose(); + } + + try + { + if (gridFinish != null && gridFinish.GridView != null) + { + gridFinish.GridView.SelectionChanged -= GvFinish_SelectionChangedClick; + gridFinish.GridView.DoubleClick -= GvFinish_DoubleClick; + } + foreach (TabPageObj tabPage in _tabPageObjs) + { + foreach (TabPageItemObj item in tabPage.TabPageItemObjs) + { + if (item.GridControlObj != null && + item.GridControlObj.GridView != null) + { + item.GridControlObj.GridView.DoubleClick -= + OnGridViewDoubleClick; + item.GridControlObj.Model = null; + } + } + tabPage.TabPageItemObjs.Clear(); + } + } + catch + { + // A partially disposed DevExpress view must not stop cleanup. + } + + try + { + if (SysModel != null && SysModel.DataCaches != null) + { + SysModel.DataCaches.Remove(this); + } + } + catch + { + // Cache cleanup must not interrupt control disposal. + } + + _tabPageObjs.Clear(); + _tabPageCount.Clear(); + OnGridViewDoubleCallBack = null; + OnGridFinshViewDoubleCallBack = null; + OnBtnSearchCallBack = null; + SectionAccradit = null; + AuditComModelObj = null; + BaseAccraditModelObj = null; + PubSysModel = null; + SysModel = null; + } + #region 公有变量 /// @@ -299,17 +367,12 @@ namespace Lskj.Control { return new AccraditationModel(dataRow); })); - Task gridControlExTask = cachesDic.AddTask(dataRow, "GridControlEx", new Task(() => - { - return new GridControlEx(); - })); - GridControlEx gridControlEx = gridControlExTask.Result; string key = "FLOWSTEP_" + dynamicModel.ModuleCode + "_" + flowTypeStepModelTask.Result.StepCode + "_" + accraditationModelTask.Result.Id + ""; - Task baseGridRowColorsTask = cachesDic.AddTask(gridControlEx, "BaseGridRowColors", new Task(() => + Task baseGridRowColorsTask = cachesDic.AddTask(dataRow, "BaseGridRowColors", new Task(() => { return BaseModuleImpl.GetBaseGridRowColors(key); })); - Task flowstepGridRightMenusTask = cachesDic.AddTask(gridControlEx, "FlowstepGridRightMenus", new Task(() => + Task flowstepGridRightMenusTask = cachesDic.AddTask(dataRow, "FlowstepGridRightMenus", new Task(() => { return BaseModuleImpl.GetBaseGridRightMenus($"FLOWSTEP_{accraditationModelTask.Result.Formkey}"); })); @@ -317,10 +380,6 @@ namespace Lskj.Control //{ // return BaseImpl.GetDataTableResult(accraditationModelTask.Result.StepSql); //})); - string modelkeyName = isBill ? GridCustomColumnStruct.AuditMainGridView : GridCustomColumnStruct.BaseAuditMainGridView; - string customColumKey = modelkeyName + key; - cachesDic.AddTask(gridControlEx, "DynamicModel", dynamicModelTask); - gridControlEx.GetDataCaches(cachesDic, customColumKey); } return flowList; })); @@ -613,18 +672,19 @@ namespace Lskj.Control gridPanel.Padding = new System.Windows.Forms.Padding(2); gridPanel.Dock = DockStyle.Right; gridPanel.BorderStyle = BorderStyles.NoBorder; - if (!dataCaches.GetValue(dataRow, "GridControlEx", out GridControlEx grid)) + GridControlEx grid = new GridControlEx(); + if (dataCaches != null) { - grid = new GridControlEx(); + grid.GetDataCaches(dataCaches, modelkeyName + key); } grid.Parent = gridPanel; grid.Dock = DockStyle.Fill; grid.BorderStyle = BorderStyle.None; - if (!dataCaches.GetValue(grid, "BaseGridRowColors", out DataTable dtGridRowColors)) + if (!dataCaches.GetValue(dataRow, "BaseGridRowColors", out DataTable dtGridRowColors)) { dtGridRowColors = BaseModuleImpl.GetBaseGridRowColors(key); } - if (!dataCaches.GetValue(grid, "FlowstepGridRightMenus", out DataTable dtGridRightMenus)) + if (!dataCaches.GetValue(dataRow, "FlowstepGridRightMenus", out DataTable dtGridRightMenus)) { dtGridRightMenus = BaseModuleImpl.GetBaseGridRightMenus($"FLOWSTEP_{accrModel.Formkey}"); } @@ -1906,4 +1966,4 @@ namespace Lskj.Control } } -} \ No newline at end of file +} diff --git a/插件库/Lskj.Control/BrowserSetting/BsClient.cs b/插件库/Lskj.Control/BrowserSetting/BsClient.cs index ca3db4e..5753145 100644 --- a/插件库/Lskj.Control/BrowserSetting/BsClient.cs +++ b/插件库/Lskj.Control/BrowserSetting/BsClient.cs @@ -78,17 +78,17 @@ namespace Lskj.Control.BrowserSetting string arg2 = message.Arguments.GetString(2); string arg3 = message.Arguments.GetString(3); string arg4 = message.Arguments.GetString(4); - if (ERPInfo.Instance.MainControl.InvokeRequired) - { - ERPInfo.Instance.MainControl.Invoke(new Action(() => + PostToUiThread( + ERPInfo.Instance.MainControl, + delegate { - BsOpenModuleHandler.OpenModule(arg0, arg1, arg2, arg3, arg4); - })); - } - else - { - BrowserSetting.BsOpenModuleHandler.OpenModule(arg0, arg1, arg2, arg3, arg4); - } + BsOpenModuleHandler.OpenModule( + arg0, + arg1, + arg2, + arg3, + arg4); + }); } else if (message.Name.Equals("OpenOnRightClick")) { @@ -96,75 +96,103 @@ namespace Lskj.Control.BrowserSetting string arg1 = message.Arguments.GetString(1); string arg2 = message.Arguments.GetString(2); string arg3 = message.Arguments.GetString(3); - if (ERPInfo.Instance.MainControl.InvokeRequired) - { - ERPInfo.Instance.MainControl.Invoke(new Action(() => + PostToUiThread( + ERPInfo.Instance.MainControl, + delegate { - BsOpenModuleHandler.OpenRightModule(arg0, arg1, arg2, arg3); - })); - } - else - { - BsOpenModuleHandler.OpenRightModule(arg0, arg1, arg2, arg3); - } + BsOpenModuleHandler.OpenRightModule( + arg0, + arg1, + arg2, + arg3); + }); } else if (message.Name.Equals("CloseWin")) { - if (frmWebBrowser != null && frmWebBrowser.Parent != null && frmWebBrowser.Parent.Parent != null && frmWebBrowser.Parent.Parent is BaseForm baseForm) - { - if (baseForm.InvokeRequired) - { - baseForm.Invoke(new Action(() => - { - baseForm.DialogResult = DialogResult.OK; - baseForm.Close(); - })); - } - else + var browserControl = frmWebBrowser; + PostToUiThread( + browserControl, + delegate { + // Parent/Parent is a WinForms control-tree read and must + // not run on CEF's UI thread. + var parent = browserControl.Parent; + var baseForm = parent == null + ? null + : parent.Parent as BaseForm; + if (baseForm == null || baseForm.IsDisposed) + return; + baseForm.DialogResult = DialogResult.OK; baseForm.Close(); - } - } + }); } else if (message.Name.Equals("SelectPro")) { string arg0 = message.Arguments.GetString(0); string arg1 = message.Arguments.GetString(1); bool isclose = arg1!=null&&!string.IsNullOrWhiteSpace(arg1) && arg1.Equals("1") ? true : false; - if (frmWebBrowser != null && frmWebBrowser.Parent != null && frmWebBrowser.Parent.Parent != null && frmWebBrowser.Parent.Parent is BaseForm baseForm) - { - if (baseForm.InvokeRequired) - { - baseForm.Invoke(new Action(() => - { - BsOpenModuleHandler.AddReturnRow(arg0); - if (isclose) - { - baseForm.DialogResult = DialogResult.OK; - baseForm.Close(); - } - - })); - } - else + var browserControl = frmWebBrowser; + PostToUiThread( + browserControl, + delegate { + var parent = browserControl.Parent; + var baseForm = parent == null + ? null + : parent.Parent as BaseForm; + if (baseForm == null || baseForm.IsDisposed) + return; + BsOpenModuleHandler.AddReturnRow(arg0); - if (isclose) + if (isclose) { baseForm.DialogResult = DialogResult.OK; baseForm.Close(); - } - } - } - - - + } + }); } return base.OnProcessMessageReceived(browser, frame, sourceProcess, message); } + private static void PostToUiThread( + System.Windows.Forms.Control control, + Action action) + { + if (control == null || action == null || control.IsDisposed || + !control.IsHandleCreated) + return; + + Action guardedAction = delegate + { + if (control.IsDisposed) + return; + + try + { + action(); + } + catch (Exception exception) + { + ReportProcessMessageFailure(exception); + } + }; + + try + { + // CEF invokes OnProcessMessageReceived on its UI thread. A + // synchronous Control.Invoke can block that thread inside a + // modal dialog while the dialog waits for CEF to create its + // browser. Always queue the operation and return to CEF first. + control.BeginInvoke(guardedAction); + } + catch (Exception exception) + { + ReportProcessMessageFailure(exception); + } + } + private static void ReportProcessMessageFailure(Exception exception) { try @@ -173,10 +201,10 @@ namespace Lskj.Control.BrowserSetting } catch { - // Logging must never rethrow across the native CEF callback. + // Never propagate logging failures into a native CEF callback. } - Action showError = () => + Action showError = delegate { try { @@ -184,14 +212,14 @@ namespace Lskj.Control.BrowserSetting } catch { - // Error reporting must never terminate the browser callback. } }; try { System.Windows.Forms.Control mainControl = ERPInfo.Instance.MainControl; - if (mainControl == null || mainControl.IsDisposed || !mainControl.IsHandleCreated) + if (mainControl == null || mainControl.IsDisposed || + !mainControl.IsHandleCreated) return; if (mainControl.InvokeRequired) @@ -201,15 +229,36 @@ namespace Lskj.Control.BrowserSetting } catch { - // The main window may close while the CEF callback is reporting. + // The main window may close while the callback is reporting. } } + public void Created(CefBrowser cefBrowser) { - if (OnCreated != null) + if (frmWebBrowser != null && OnCreated != null) { OnCreated(cefBrowser, EventArgs.Empty); } } + + public bool Closing(CefBrowser browser) + { + var control = frmWebBrowser; + return control != null && control.BrowserClosing(browser); + } + + public void Closed(CefBrowser browser) + { + var control = frmWebBrowser; + if (control != null) + { + control.BrowserClosed(browser); + if (control.cefBrowserSettings == null) + { + frmWebBrowser = null; + OnCreated = null; + } + } + } } } diff --git a/插件库/Lskj.Control/BrowserSetting/BsDownloadHandler.cs b/插件库/Lskj.Control/BrowserSetting/BsDownloadHandler.cs index d6bf8de..f546ff0 100644 --- a/插件库/Lskj.Control/BrowserSetting/BsDownloadHandler.cs +++ b/插件库/Lskj.Control/BrowserSetting/BsDownloadHandler.cs @@ -42,7 +42,7 @@ namespace Lskj.Control.BrowserSetting } if (BsClient != null && BsClient.frmWebBrowser != null && BsClient.frmWebBrowser.Parent != null && BsClient.frmWebBrowser.Parent.Name.Equals("previewFrm")) { - browser.GetHost().CloseBrowser(); + BsClient.frmWebBrowser.CloseBrowser(); } else if (BsClient != null && BsClient.frmWebBrowser != null) { @@ -196,11 +196,24 @@ namespace Lskj.Control.BrowserSetting try { if (browser == null) return null; - IntPtr browserHandle = browser.GetHost().GetWindowHandle(); + IntPtr browserHandle; + using (var browserHost = browser.GetHost()) + { + browserHandle = browserHost.GetWindowHandle(); + } if (browserHandle == IntPtr.Zero) return null; IntPtr parentHandle = GetParent(browserHandle); - if (parentHandle == IntPtr.Zero) return null; - System.Windows.Forms.Control control = System.Windows.Forms.Control.FromHandle(parentHandle); + System.Windows.Forms.Control control = null; + while (parentHandle != IntPtr.Zero && control == null) + { + // The CEF child now sits inside an application-owned native + // host. Walk through that host to preserve the original + // FrmWebBrowser-based module lookup. + control = System.Windows.Forms.Control.FromHandle(parentHandle); + parentHandle = GetParent(parentHandle); + } + + if (control == null) return null; if (control != null && control.Parent != null && control.Parent.Parent != null) { diff --git a/插件库/Lskj.Control/BrowserSetting/BsLifeSpanHandler.cs b/插件库/Lskj.Control/BrowserSetting/BsLifeSpanHandler.cs index 1366afb..a421d3e 100644 --- a/插件库/Lskj.Control/BrowserSetting/BsLifeSpanHandler.cs +++ b/插件库/Lskj.Control/BrowserSetting/BsLifeSpanHandler.cs @@ -20,9 +20,20 @@ namespace Lskj.Control.BrowserSetting base.OnAfterCreated(browser); BsClient.Created(browser); } + + protected override bool DoClose(CefBrowser browser) + { + // CEF sends WM_CLOSE to the parked application-owned host after + // this callback. The host only allows that second close attempt. + return BsClient.Closing(browser); + } + protected override void OnBeforeClose(CefBrowser browser) { base.OnBeforeClose(browser); + + // Release owner references after CEF completes its async close. + BsClient.Closed(browser); } protected override bool OnBeforePopup( diff --git a/插件库/Lskj.Control/BrowserSetting/BsRequestHandler.cs b/插件库/Lskj.Control/BrowserSetting/BsRequestHandler.cs index a00c32f..2922124 100644 --- a/插件库/Lskj.Control/BrowserSetting/BsRequestHandler.cs +++ b/插件库/Lskj.Control/BrowserSetting/BsRequestHandler.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using System.Text; using Xilium.CefGlue; +using Lskj.Business; namespace Lskj.Control.BrowserSetting { @@ -46,7 +47,12 @@ namespace Lskj.Control.BrowserSetting protected override void OnRenderProcessTerminated(CefBrowser browser, CefTerminationStatus status) { - browser.Reload(); + // Reloading immediately from this callback can create a new + // renderer while the old one is tearing down and cause a native + // libcef crash loop. Record the termination and let the module + // lifecycle decide whether a later reopen is appropriate. + LogUtil.WriteError("CEF 渲染进程已终止,状态=" + status, + new InvalidOperationException("CEF renderer termination status=" + status)); } } diff --git a/插件库/Lskj.Control/BrowserSetting2/BsClient.cs b/插件库/Lskj.Control/BrowserSetting2/BsClient.cs index 04e139d..61adcf7 100644 --- a/插件库/Lskj.Control/BrowserSetting2/BsClient.cs +++ b/插件库/Lskj.Control/BrowserSetting2/BsClient.cs @@ -1,5 +1,6 @@ using Lskj.Business; using Lskj.Model; +using Lskj.Util; using System; using System.Collections.Generic; using System.Linq; @@ -54,6 +55,19 @@ namespace Lskj.Control.BrowserSetting2 return contextMenuHandler; } protected override bool OnProcessMessageReceived(CefBrowser browser, CefFrame frame, CefProcessId sourceProcess, CefProcessMessage message) + { + try + { + return OnProcessMessageReceivedCore(browser, frame, sourceProcess, message); + } + catch (Exception exception) + { + ReportProcessMessageFailure(exception); + return true; + } + } + + private bool OnProcessMessageReceivedCore(CefBrowser browser, CefFrame frame, CefProcessId sourceProcess, CefProcessMessage message) { if (message.Name.Equals("OpenModule")) { @@ -62,17 +76,17 @@ namespace Lskj.Control.BrowserSetting2 string arg2 = message.Arguments.GetString(2); string arg3 = message.Arguments.GetString(3); string arg4 = message.Arguments.GetString(4); - if (ERPInfo.Instance.MainControl.InvokeRequired) - { - ERPInfo.Instance.MainControl.Invoke(new Action(() => + PostToUiThread( + ERPInfo.Instance.MainControl, + delegate { - BrowserSetting.BsOpenModuleHandler.OpenModule(arg0, arg1, arg2, arg3, arg4); - })); - } - else - { - BrowserSetting.BsOpenModuleHandler.OpenModule(arg0, arg1, arg2, arg3, arg4); - } + BrowserSetting.BsOpenModuleHandler.OpenModule( + arg0, + arg1, + arg2, + arg3, + arg4); + }); } else if (message.Name.Equals("OpenOnRightClick")) { @@ -80,43 +94,140 @@ namespace Lskj.Control.BrowserSetting2 string arg1 = message.Arguments.GetString(1); string arg2 = message.Arguments.GetString(2); string arg3 = message.Arguments.GetString(3); - if (ERPInfo.Instance.MainControl.InvokeRequired) - { - ERPInfo.Instance.MainControl.Invoke(new Action(() => + PostToUiThread( + ERPInfo.Instance.MainControl, + delegate { - BrowserSetting.BsOpenModuleHandler.OpenRightModule(arg0, arg1, arg2, arg3); - })); - } - else - { - BrowserSetting.BsOpenModuleHandler.OpenRightModule(arg0, arg1, arg2, arg3); - } + BrowserSetting.BsOpenModuleHandler.OpenRightModule( + arg0, + arg1, + arg2, + arg3); + }); } else if (message.Name.Equals("CloseWin")) { - if (frmWebBrowser != null && frmWebBrowser.Parent != null && frmWebBrowser.Parent.Parent != null && frmWebBrowser.Parent.Parent is BaseForm baseForm) - { - if (baseForm.InvokeRequired) - { - baseForm.Invoke(new Action(() => - { - baseForm.Close(); - })); - } - else + var browserControl = frmWebBrowser; + PostToUiThread( + browserControl, + delegate { + // Resolve the owning form only after returning to the + // WinForms UI thread. + var parent = browserControl.Parent; + var baseForm = parent == null + ? null + : parent.Parent as BaseForm; + if (baseForm == null || baseForm.IsDisposed) + return; + baseForm.Close(); - } - } + }); } return base.OnProcessMessageReceived(browser, frame, sourceProcess, message); } + + private static void PostToUiThread( + System.Windows.Forms.Control control, + Action action) + { + if (control == null || action == null || control.IsDisposed || + !control.IsHandleCreated) + return; + + Action guardedAction = delegate + { + if (control.IsDisposed) + return; + + try + { + action(); + } + catch (Exception exception) + { + ReportProcessMessageFailure(exception); + } + }; + + try + { + // Never keep CEF's UI thread blocked while WinForms opens a + // modal module that may create another CEF browser. + control.BeginInvoke(guardedAction); + } + catch (Exception exception) + { + ReportProcessMessageFailure(exception); + } + } + + private static void ReportProcessMessageFailure(Exception exception) + { + try + { + LogHelper.Instance.WriteError(exception); + } + catch + { + // Never propagate logging failures into a native CEF callback. + } + + Action showError = delegate + { + try + { + MessageUtil.Show(exception); + } + catch + { + } + }; + + try + { + System.Windows.Forms.Control mainControl = ERPInfo.Instance.MainControl; + if (mainControl == null || mainControl.IsDisposed || + !mainControl.IsHandleCreated) + return; + + if (mainControl.InvokeRequired) + mainControl.BeginInvoke(showError); + else + showError(); + } + catch + { + // The main window may close while the callback is reporting. + } + } + public void Created(CefBrowser cefBrowser) { - if (OnCreated != null) + if (frmWebBrowser != null && OnCreated != null) { OnCreated(cefBrowser, EventArgs.Empty); } } + + public bool Closing(CefBrowser browser) + { + var control = frmWebBrowser; + return control != null && control.BrowserClosing(browser); + } + + public void Closed(CefBrowser browser) + { + var control = frmWebBrowser; + if (control != null) + { + control.BrowserClosed(browser); + if (control.cefBrowserSettings == null) + { + frmWebBrowser = null; + OnCreated = null; + } + } + } } } diff --git a/插件库/Lskj.Control/BrowserSetting2/BsLifeSpanHandler.cs b/插件库/Lskj.Control/BrowserSetting2/BsLifeSpanHandler.cs index e73cd34..e604c1f 100644 --- a/插件库/Lskj.Control/BrowserSetting2/BsLifeSpanHandler.cs +++ b/插件库/Lskj.Control/BrowserSetting2/BsLifeSpanHandler.cs @@ -20,9 +20,20 @@ namespace Lskj.Control.BrowserSetting2 base.OnAfterCreated(browser); BsClient.Created(browser); } + + protected override bool DoClose(CefBrowser browser) + { + // CEF sends WM_CLOSE to the parked application-owned host after + // this callback. The host only allows that second close attempt. + return BsClient.Closing(browser); + } + protected override void OnBeforeClose(CefBrowser browser) { base.OnBeforeClose(browser); + + // Release owner references after CEF completes its async close. + BsClient.Closed(browser); } protected override bool OnBeforePopup( diff --git a/插件库/Lskj.Control/BrowserSetting2/BsRequestHandler.cs b/插件库/Lskj.Control/BrowserSetting2/BsRequestHandler.cs index 59889f6..d96770d 100644 --- a/插件库/Lskj.Control/BrowserSetting2/BsRequestHandler.cs +++ b/插件库/Lskj.Control/BrowserSetting2/BsRequestHandler.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using System.Text; using Xilium.CefGlue; +using Lskj.Business; namespace Lskj.Control.BrowserSetting2 { @@ -46,7 +47,9 @@ namespace Lskj.Control.BrowserSetting2 protected override void OnRenderProcessTerminated(CefBrowser browser, CefTerminationStatus status) { - browser.Reload(); + // Do not start a new renderer during native teardown. + LogUtil.WriteError("CEF 渲染进程已终止,状态=" + status, + new InvalidOperationException("CEF renderer termination status=" + status)); } } diff --git a/插件库/Lskj.Control/CefBrowserLifetimeCoordinator.cs b/插件库/Lskj.Control/CefBrowserLifetimeCoordinator.cs new file mode 100644 index 0000000..519284d --- /dev/null +++ b/插件库/Lskj.Control/CefBrowserLifetimeCoordinator.cs @@ -0,0 +1,1209 @@ +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using System.Threading; +using System.Windows.Forms; +using Lskj.Util; +using Xilium.CefGlue; + +namespace Lskj.Control +{ + /// + /// Keeps an application-owned root HWND alive for the complete CEF + /// lifecycle. The root is created independently of recreatable WinForms + /// control handles, then embedded after CEF reports OnAfterCreated. + /// + internal sealed class CefBrowserLifetimeCoordinator + { + private static readonly CefBrowserSettings _sharedBrowserSettings = + new CefBrowserSettings(); + private readonly object _hostWindowSyncRoot = new object(); + private CefBrowser _browser; + private CefBrowserHostWindow _hostWindow; + private int _browserCreationPending; + private int _browserCloseRequested; + private int _browserCloseApproved; + private int _browserBeforeCloseReceived; + private int _disposeRequested; + private int _ownerHandleDestroying; + + public CefBrowser Browser + { + get { return Interlocked.CompareExchange(ref _browser, null, null); } + } + + public static CefBrowserSettings SharedBrowserSettings + { + get { return _sharedBrowserSettings; } + } + + public bool IsDisposing + { + get { return Thread.VolatileRead(ref _disposeRequested) != 0; } + } + + public bool IsUnavailable + { + get + { + return IsDisposing || + Thread.VolatileRead(ref _ownerHandleDestroying) != 0; + } + } + + public bool HasBrowserOrPendingCreation + { + get + { + return Browser != null || + Thread.VolatileRead(ref _browserCreationPending) != 0; + } + } + + public IntPtr GetCreationParentHandle( + IntPtr ownerHandle, + int width, + int height) + { + if (ownerHandle == IntPtr.Zero || + !CefBrowserNativeMethods.IsWindow(ownerHandle)) + throw new ArgumentException( + "A valid WinForms owner handle is required.", + "ownerHandle"); + + CefBrowserHostWindow hostWindow; + lock (_hostWindowSyncRoot) + { + if (_hostWindow == null || _hostWindow.IsDestroyed) + { + _hostWindow = new CefBrowserHostWindow( + ownerHandle, + width, + height, + HostWindowCloseRequested, + HostWindowDestroyed); + } + + hostWindow = _hostWindow; + } + + IntPtr hostHandle = hostWindow.Handle; + bool hostValid = hostHandle != IntPtr.Zero && + CefBrowserNativeMethods.IsWindow(hostHandle); + bool ownerValid = CefBrowserNativeMethods.IsWindow(ownerHandle); + TraceClose( + "creation-parent hostHwnd=" + hostHandle + + " hostValid=" + hostValid + + " hostParent=" + CefBrowserNativeMethods.GetParent(hostHandle) + + " ownerHwnd=" + ownerHandle + + " ownerValid=" + ownerValid + + " hostThread=" + hostWindow.OwnerThreadId + + " ownerThread=" + CefBrowserNativeMethods.GetWindowThread(ownerHandle), + null, + null); + + if (!hostValid || !ownerValid) + throw new InvalidOperationException( + "CEF browser creation requires live host and owner HWNDs."); + + return hostHandle; + } + + public bool TryBeginBrowserCreation() + { + if (IsUnavailable || Browser != null) + return false; + + if (Interlocked.CompareExchange(ref _browserCreationPending, 1, 0) != 0) + return false; + + if (!CefBrowserRuntimeLifetime.TryRegister(this)) + { + Interlocked.Exchange(ref _browserCreationPending, 0); + return false; + } + + Interlocked.Exchange(ref _browserCloseApproved, 0); + Interlocked.Exchange(ref _browserBeforeCloseReceived, 0); + if (!IsUnavailable) + return true; + + BrowserCreationFailed(); + return false; + } + + public void BrowserCreationFailed() + { + Interlocked.Exchange(ref _browserCreationPending, 0); + RequestHostWindowDestroy("creation-failed"); + TryReleaseHostWindow(); + CefBrowserRuntimeLifetime.Unregister(this); + } + + public bool BrowserCreated(CefBrowser browser) + { + if (browser == null) + throw new ArgumentNullException("browser"); + + Interlocked.Exchange(ref _browserCreationPending, 0); + var currentBrowser = Interlocked.CompareExchange( + ref _browser, + browser, + null); + TraceClose("created", browser, currentBrowser); + + if (currentBrowser != null && !IsSameBrowser(currentBrowser, browser)) + { + CloseBrowser(browser, true); + return false; + } + + if (IsUnavailable) + { + RequestClose(IsDisposing); + return false; + } + + return true; + } + + public bool BrowserClosed(CefBrowser browser, Action clearOwnerBrowser) + { + var currentBrowser = Browser; + bool sameBrowser = currentBrowser != null && + IsSameBrowser(currentBrowser, browser); + TraceClose( + "on-before-close same=" + sameBrowser, + browser, + currentBrowser); + if (!sameBrowser) + return false; + + if (clearOwnerBrowser != null) + clearOwnerBrowser(); + + if (currentBrowser != null) + Interlocked.CompareExchange(ref _browser, null, currentBrowser); + + Interlocked.Exchange(ref _browserBeforeCloseReceived, 1); + Interlocked.Exchange(ref _browserCloseRequested, 0); + RequestHostWindowDestroy("on-before-close"); + TryReleaseHostWindow(); + DisposeBrowserReference(currentBrowser); + CefBrowserRuntimeLifetime.Unregister(this); + return true; + } + + public bool BrowserClosing(CefBrowser browser) + { + var currentBrowser = Browser; + bool sameBrowser = currentBrowser != null && + IsSameBrowser(currentBrowser, browser); + TraceClose( + "do-close same=" + sameBrowser, + browser, + currentBrowser); + if (sameBrowser) + { + // DoClose runs after onbeforeunload has been accepted. Make the + // stable host top-level before returning so CEF's WM_CLOSE is + // never sent to the WPF root window. + bool parked = ParkBrowser(browser, "do-close"); + if (!parked) + { + // Suppress CEF's OS close event when the stable host could + // not be detached. Destroying the application-owned host + // is the non-standard-window equivalent of cefclient's + // DestroyRender path and avoids targeting the WPF root. + RequestHostWindowDestroy("do-close-park-failed"); + return true; + } + + Interlocked.Exchange(ref _browserCloseApproved, 1); + } + + // Match CEF's documented non-standard close flow. Returning false + // makes CEF send WM_CLOSE after this callback returns. The parked + // application-owned host handles that message and destroys itself. + return false; + } + + public void RequestClose() + { + RequestClose(false); + } + + private void RequestClose(bool forceClose) + { + var browser = Browser; + if (browser == null) + return; + + // Suppress only duplicate dispatches. The flag is reset as soon as + // CloseBrowser is delivered so a canceled onbeforeunload can be + // retried. A forced dispose is always allowed to upgrade a pending + // non-forced close. + if (!forceClose && + Interlocked.Exchange(ref _browserCloseRequested, 1) != 0) + return; + + Interlocked.Exchange(ref _browserCloseRequested, 1); + if (forceClose) + ParkBrowser(browser, "forced-close-request"); + + CloseBrowser(browser, forceClose); + } + + public void BeginDispose() + { + if (Interlocked.Exchange(ref _disposeRequested, 1) != 0) + return; + + Interlocked.Exchange(ref _ownerHandleDestroying, 1); + TraceOwnerAction("owner-dispose"); + RequestClose(true); + TryReleaseHostWindow(); + } + + public void BeginOwnerHandleDestruction() + { + if (IsDisposing || + Interlocked.Exchange(ref _ownerHandleDestroying, 1) != 0) + return; + + // WinForms can recreate a control HWND for DPI, parenting or style + // changes. Preserve the browser and only detach its stable host. + TraceOwnerAction("owner-handle-destroy"); + ParkBrowser(Browser, "owner-handle-destroy"); + } + + public void OwnerHandleCreated( + IntPtr ownerHandle, + int width, + int height) + { + if (IsDisposing || ownerHandle == IntPtr.Zero) + return; + + Interlocked.Exchange(ref _ownerHandleDestroying, 0); + var hostWindow = GetHostWindow(); + var browser = Browser; + if (hostWindow != null && !hostWindow.IsDestroyed && browser != null) + hostWindow.AttachTo(ownerHandle, width, height, true); + + TraceClose( + "owner-handle-created ownerHwnd=" + ownerHandle + + " ownerThread=" + CefBrowserNativeMethods.GetWindowThread(ownerHandle), + Browser, + null); + } + + public void AttachBrowserToOwner( + CefBrowser browser, + IntPtr ownerHandle, + int width, + int height) + { + if (browser == null || ownerHandle == IntPtr.Zero || + !CefBrowserNativeMethods.IsWindow(ownerHandle) || + IsUnavailable) + return; + + var currentBrowser = Browser; + if (currentBrowser == null || !IsSameBrowser(currentBrowser, browser)) + return; + + try + { + var hostWindow = GetHostWindow(); + if (hostWindow == null || hostWindow.IsDestroyed) + return; + + IntPtr browserHandle; + using (var browserHost = browser.GetHost()) + { + browserHandle = browserHost.GetWindowHandle(); + } + IntPtr hostHandle = hostWindow.Handle; + if (browserHandle == IntPtr.Zero || hostHandle == IntPtr.Zero) + return; + + IntPtr browserParent = CefBrowserNativeMethods.GetParent(browserHandle); + if (browserParent != hostHandle) + { + TraceClose( + "attach-invalid-parent browserHwnd=" + browserHandle + + " browserParent=" + browserParent + + " hostHwnd=" + hostHandle, + browser, + currentBrowser); + return; + } + + hostWindow.AttachTo(ownerHandle, width, height, true); + ResizeBrowserWindow(browserHandle, width, height, true); + TraceClose( + "attach browserHwnd=" + browserHandle + + " hostHwnd=" + hostHandle + + " hostParent=" + CefBrowserNativeMethods.GetParent(hostHandle) + + " hostThread=" + hostWindow.OwnerThreadId + + " ownerThread=" + CefBrowserNativeMethods.GetWindowThread(ownerHandle), + browser, + currentBrowser); + } + catch (Exception ex) + { + TraceClose( + "attach-failed " + ex.GetType().FullName, + browser, + currentBrowser); + } + } + + public void ResizeBrowser( + IntPtr browserHandle, + int width, + int height) + { + if (browserHandle == IntPtr.Zero || IsUnavailable) + return; + + try + { + var hostWindow = GetHostWindow(); + if (hostWindow == null || hostWindow.IsDestroyed) + return; + + hostWindow.Resize(width, height); + ResizeBrowserWindow(browserHandle, width, height, false); + } + catch (Exception) + { + } + } + + private bool ParkBrowser(CefBrowser browser, string reason) + { + var hostWindow = GetHostWindow(); + if (hostWindow == null || hostWindow.IsDestroyed) + return false; + + try + { + bool parked = hostWindow.Park(); + TraceClose( + "park reason=" + reason + + " parked=" + parked + + " hostHwnd=" + hostWindow.Handle + + " hostParent=" + + CefBrowserNativeMethods.GetParent(hostWindow.Handle), + browser, + Browser); + return parked; + } + catch (Exception ex) + { + TraceClose( + "park-failed " + ex.GetType().FullName, + browser, + Browser); + return false; + } + } + + private static void ResizeBrowserWindow( + IntPtr browserHandle, + int width, + int height, + bool show) + { + uint flags = CefBrowserNativeMethods.SwpNoZOrder | + CefBrowserNativeMethods.SwpNoActivate; + if (show) + flags |= CefBrowserNativeMethods.SwpShowWindow; + + CefBrowserNativeMethods.SetWindowPos( + browserHandle, + IntPtr.Zero, + 0, + 0, + Math.Max(1, width), + Math.Max(1, height), + flags); + } + + private CefBrowserHostWindow GetHostWindow() + { + lock (_hostWindowSyncRoot) + { + return _hostWindow; + } + } + + private bool HostWindowCloseRequested() + { + bool approved = + Thread.VolatileRead(ref _browserCloseApproved) != 0; + TraceClose( + "host-wm-close approved=" + approved, + Browser, + null); + if (!approved) + RequestClose(IsDisposing); + return approved; + } + + private void HostWindowDestroyed(CefBrowserHostWindow hostWindow) + { + TraceClose( + "host-destroyed hostHwnd=" + hostWindow.LastHandle, + Browser, + null); + TryReleaseHostWindow(); + } + + private void RequestHostWindowDestroy(string reason) + { + var hostWindow = GetHostWindow(); + if (hostWindow == null || hostWindow.IsDestroyed) + return; + + TraceClose( + "host-destroy-request reason=" + reason + + " hostHwnd=" + hostWindow.Handle + + " valid=" + CefBrowserNativeMethods.IsWindow(hostWindow.Handle), + Browser, + null); + hostWindow.RequestDestroy(); + } + + private void TryReleaseHostWindow() + { + var hostWindow = GetHostWindow(); + if (hostWindow == null) + return; + + bool browserFinished = Browser == null && + Thread.VolatileRead(ref _browserCreationPending) == 0; + if (!browserFinished) + return; + + bool closeCompleted = + Thread.VolatileRead(ref _browserBeforeCloseReceived) != 0; + if (!hostWindow.IsDestroyed) + { + if (IsDisposing || closeCompleted) + RequestHostWindowDestroy("release-ready"); + return; + } + + lock (_hostWindowSyncRoot) + { + if (ReferenceEquals(_hostWindow, hostWindow) && + Browser == null && + Thread.VolatileRead(ref _browserCreationPending) == 0) + { + _hostWindow = null; + } + } + + TraceClose( + "release-complete hostHwnd=" + hostWindow.LastHandle + + " browserClosed=" + closeCompleted, + null, + null); + } + + private static bool IsSameBrowser(CefBrowser left, CefBrowser right) + { + if (ReferenceEquals(left, right)) + return true; + if (left == null || right == null) + return false; + + try + { + return left.IsSame(right); + } + catch (Exception) + { + try + { + return left.Identifier == right.Identifier; + } + catch (Exception) + { + return false; + } + } + } + + private void CloseBrowser(CefBrowser browser, bool forceClose) + { + try + { + TraceClose( + "close-request force=" + forceClose, + browser, + null); + if (CefRuntime.CurrentlyOn(CefThreadId.UI)) + { + ExecuteCloseBrowser(browser, forceClose); + return; + } + + bool posted = CefRuntime.PostTask( + CefThreadId.UI, + new CefActionTask(delegate + { + ExecuteCloseBrowser(browser, forceClose); + })); + TraceClose("close-post-ui accepted=" + posted, browser, null); + if (!posted) + ExecuteCloseBrowser(browser, forceClose); + } + catch (Exception ex) + { + BrowserCloseFailed(browser, ex); + } + } + + private void ExecuteCloseBrowser(CefBrowser browser, bool forceClose) + { + try + { + TraceClose( + "close-execute-ui force=" + forceClose, + browser, + null); + using (var browserHost = browser.GetHost()) + { + browserHost.CloseBrowser(forceClose); + } + } + catch (Exception ex) + { + BrowserCloseFailed(browser, ex); + } + finally + { + Interlocked.Exchange(ref _browserCloseRequested, 0); + } + } + + private static void DisposeBrowserReference(CefBrowser browser) + { + if (browser == null) + return; + + try + { + browser.Dispose(); + } + catch (Exception ex) + { + TraceClose( + "browser-reference-dispose-failed " + ex.GetType().FullName, + null, + null); + } + } + + private void BrowserCloseFailed(CefBrowser browser, Exception exception) + { + TraceClose( + "close-failed " + exception.GetType().FullName, + browser, + Browser); + Interlocked.Exchange(ref _browserCloseRequested, 0); + if (IsDisposing) + RequestHostWindowDestroy("close-failed"); + TryReleaseHostWindow(); + } + + private void TraceOwnerAction(string action) + { + string stack = Environment.StackTrace + .Replace("\r", string.Empty) + .Replace("\n", " | "); + if (stack.Length > 1800) + stack = stack.Substring(0, 1800); + + TraceClose(action + " stack=" + stack, Browser, null); + } + + private static void TraceClose( + string action, + CefBrowser browser, + CefBrowser currentBrowser) + { + try + { + LogHelper.Instance.WriteLog( + "[CEF_LIFECYCLE] " + action + + "; browser=" + GetBrowserIdentifier(browser) + + "; current=" + GetBrowserIdentifier(currentBrowser) + + "; thread=" + Thread.CurrentThread.ManagedThreadId + + "; cefUi=" + CefRuntime.CurrentlyOn(CefThreadId.UI)); + } + catch (Exception) + { + } + } + + private static string GetBrowserIdentifier(CefBrowser browser) + { + if (browser == null) + return "null"; + + try + { + return browser.Identifier.ToString(); + } + catch (Exception) + { + return "unavailable"; + } + } + + private sealed class CefBrowserHostWindow : NativeWindow + { + private const int WmClose = 0x0010; + private const int WmReleaseHandle = 0x8001; + private const int WmPark = 0x8002; + private const int WmNcDestroy = 0x0082; + + private readonly uint _ownerThreadId; + private Func _onCloseRequested; + private Action _onDestroyed; + private IntPtr _lastHandle; + private int _destroyRequested; + private int _destroyed; + private int _parked; + private int _parking; + + public uint OwnerThreadId + { + get { return _ownerThreadId; } + } + + public bool IsDestroyed + { + get { return Thread.VolatileRead(ref _destroyed) != 0; } + } + + public bool IsParked + { + get { return Thread.VolatileRead(ref _parked) != 0; } + } + + public IntPtr LastHandle + { + get { return _lastHandle; } + } + + public CefBrowserHostWindow( + IntPtr ownerHandle, + int width, + int height, + Func onCloseRequested, + Action onDestroyed) + { + _onCloseRequested = onCloseRequested; + _onDestroyed = onDestroyed; + _ownerThreadId = CefBrowserNativeMethods.GetCurrentThreadId(); + + uint ownerThread = + CefBrowserNativeMethods.GetWindowThread(ownerHandle); + if (ownerThread != _ownerThreadId) + { + throw new InvalidOperationException( + "CEF host HWND must be created on the WinForms owner thread."); + } + + CreateHandle(new CreateParams + { + Caption = "Lskj.CefBrowserHostWindow", + Parent = IntPtr.Zero, + X = -32000, + Y = -32000, + Width = 1, + Height = 1, + Style = CefBrowserNativeMethods.WsPopup | + CefBrowserNativeMethods.WsClipChildren | + CefBrowserNativeMethods.WsClipSiblings + }); + _lastHandle = Handle; + Interlocked.Exchange(ref _parked, 1); + + if (_lastHandle == IntPtr.Zero || + !CefBrowserNativeMethods.IsWindow(_lastHandle)) + { + throw new InvalidOperationException( + "Failed to create the stable CEF root HWND."); + } + } + + public void AttachTo( + IntPtr ownerHandle, + int width, + int height, + bool show) + { + EnsureOwnerThread(ownerHandle); + if (IsDestroyed || Handle == IntPtr.Zero || + !CefBrowserNativeMethods.IsWindow(Handle)) + return; + + int style = CefBrowserNativeMethods.GetWindowLong( + Handle, + CefBrowserNativeMethods.GwlStyle); + CefBrowserNativeMethods.SetWindowLong( + Handle, + CefBrowserNativeMethods.GwlStyle, + (style & ~CefBrowserNativeMethods.WsPopup) | + CefBrowserNativeMethods.WsChild | + CefBrowserNativeMethods.WsClipChildren | + CefBrowserNativeMethods.WsClipSiblings); + CefBrowserNativeMethods.SetParent(Handle, ownerHandle); + if (!CefBrowserNativeMethods.IsWindow(Handle) || + CefBrowserNativeMethods.GetParent(Handle) != ownerHandle) + { + throw new InvalidOperationException( + "Failed to attach the CEF host HWND to its owner."); + } + + uint flags = CefBrowserNativeMethods.SwpNoZOrder | + CefBrowserNativeMethods.SwpNoActivate | + CefBrowserNativeMethods.SwpFrameChanged; + if (show) + flags |= CefBrowserNativeMethods.SwpShowWindow; + CefBrowserNativeMethods.SetWindowPos( + Handle, + IntPtr.Zero, + 0, + 0, + Math.Max(1, width), + Math.Max(1, height), + flags); + Interlocked.Exchange(ref _parked, 0); + } + + public void Resize(int width, int height) + { + if (IsDestroyed || Handle == IntPtr.Zero || + !CefBrowserNativeMethods.IsWindow(Handle)) + return; + + CefBrowserNativeMethods.SetWindowPos( + Handle, + IntPtr.Zero, + 0, + 0, + Math.Max(1, width), + Math.Max(1, height), + CefBrowserNativeMethods.SwpNoZOrder | + CefBrowserNativeMethods.SwpNoActivate); + } + + public bool Park() + { + if (IsDestroyed || Handle == IntPtr.Zero || + !CefBrowserNativeMethods.IsWindow(Handle)) + return false; + + if (IsParked) + return true; + + if (CefBrowserNativeMethods.GetCurrentThreadId() != + _ownerThreadId) + { + CefBrowserNativeMethods.SendMessage( + Handle, + WmPark, + IntPtr.Zero, + IntPtr.Zero); + return IsParked && + CefBrowserNativeMethods.IsWindow(Handle) && + CefBrowserNativeMethods.GetParent(Handle) == IntPtr.Zero; + } + + return ParkNow(); + } + + private bool ParkNow() + { + if (IsParked) + return true; + if (Interlocked.Exchange(ref _parking, 1) != 0) + return false; + + try + { + IntPtr handle = Handle; + if (IsDestroyed || handle == IntPtr.Zero || + !CefBrowserNativeMethods.IsWindow(handle)) + return false; + + CefBrowserNativeMethods.ShowWindow( + handle, + CefBrowserNativeMethods.SwHide); + int style = CefBrowserNativeMethods.GetWindowLong( + handle, + CefBrowserNativeMethods.GwlStyle); + // Win32 requires WS_CHILD to be cleared before setting a + // child window's parent to NULL. Reversing this order can + // leave the host attached to the WPF root while appearing + // parked in managed state. + CefBrowserNativeMethods.SetWindowLong( + handle, + CefBrowserNativeMethods.GwlStyle, + (style & ~CefBrowserNativeMethods.WsChild) | + CefBrowserNativeMethods.WsPopup | + CefBrowserNativeMethods.WsClipChildren | + CefBrowserNativeMethods.WsClipSiblings); + CefBrowserNativeMethods.SetParent(handle, IntPtr.Zero); + if (!CefBrowserNativeMethods.IsWindow(handle) || + CefBrowserNativeMethods.GetParent(handle) != IntPtr.Zero) + return false; + + CefBrowserNativeMethods.SetWindowPos( + handle, + IntPtr.Zero, + -32000, + -32000, + 1, + 1, + CefBrowserNativeMethods.SwpNoZOrder | + CefBrowserNativeMethods.SwpNoActivate | + CefBrowserNativeMethods.SwpFrameChanged); + Interlocked.Exchange(ref _parked, 1); + return true; + } + finally + { + Interlocked.Exchange(ref _parking, 0); + } + } + + public void RequestDestroy() + { + if (Interlocked.Exchange(ref _destroyRequested, 1) != 0) + return; + + IntPtr handle = Handle; + if (handle == IntPtr.Zero || + !CefBrowserNativeMethods.IsWindow(handle)) + { + NotifyDestroyed(); + return; + } + + if (CefBrowserNativeMethods.GetCurrentThreadId() == + _ownerThreadId) + { + DestroyNow(); + return; + } + + if (!CefBrowserNativeMethods.PostMessage( + handle, + WmReleaseHandle, + IntPtr.Zero, + IntPtr.Zero)) + { + Interlocked.Exchange(ref _destroyRequested, 0); + TraceClose( + "host-destroy-post-failed hostHwnd=" + handle, + null, + null); + } + } + + protected override void WndProc(ref Message message) + { + if (message.Msg == WmClose) + { + var onCloseRequested = _onCloseRequested; + bool allowClose = onCloseRequested == null || + onCloseRequested(); + if (allowClose) + base.WndProc(ref message); + return; + } + + if (message.Msg == WmReleaseHandle) + { + DestroyNow(); + return; + } + + if (message.Msg == WmPark) + { + ParkNow(); + return; + } + + if (message.Msg == WmNcDestroy) + { + _lastHandle = message.HWnd; + base.WndProc(ref message); + NotifyDestroyed(); + return; + } + + base.WndProc(ref message); + } + + private void DestroyNow() + { + IntPtr handle = Handle; + if (handle == IntPtr.Zero || + !CefBrowserNativeMethods.IsWindow(handle)) + { + NotifyDestroyed(); + return; + } + + TraceClose( + "host-destroying hostHwnd=" + handle, + null, + null); + DestroyHandle(); + } + + private void NotifyDestroyed() + { + if (Interlocked.Exchange(ref _destroyed, 1) != 0) + return; + + _onCloseRequested = null; + var onDestroyed = Interlocked.Exchange( + ref _onDestroyed, + null); + if (onDestroyed != null) + onDestroyed(this); + } + + private void EnsureOwnerThread(IntPtr ownerHandle) + { + EnsureCurrentThread(); + if (CefBrowserNativeMethods.GetWindowThread(ownerHandle) != + _ownerThreadId) + { + throw new InvalidOperationException( + "CEF host and owner HWND must belong to the same thread."); + } + } + + private void EnsureCurrentThread() + { + if (CefBrowserNativeMethods.GetCurrentThreadId() != + _ownerThreadId) + { + throw new InvalidOperationException( + "CEF host HWND may only be changed on its owner thread."); + } + } + } + } + + /// + /// Mirrors the browser list used by CEF's cefsimple application. CEF may + /// only be shut down after every asynchronous OnBeforeClose callback has + /// completed. + /// + public static class CefBrowserRuntimeLifetime + { + private static readonly object _syncRoot = new object(); + private static readonly HashSet + _activeBrowsers = new HashSet(); + private static readonly ManualResetEvent _allBrowsersClosed = + new ManualResetEvent(true); + private static bool _shutdownRequested; + + internal static bool TryRegister( + CefBrowserLifetimeCoordinator coordinator) + { + if (coordinator == null) + throw new ArgumentNullException("coordinator"); + + lock (_syncRoot) + { + if (_shutdownRequested) + return false; + + if (_activeBrowsers.Add(coordinator)) + _allBrowsersClosed.Reset(); + return true; + } + } + + internal static void Unregister( + CefBrowserLifetimeCoordinator coordinator) + { + if (coordinator == null) + return; + + lock (_syncRoot) + { + _activeBrowsers.Remove(coordinator); + if (_activeBrowsers.Count == 0) + _allBrowsersClosed.Set(); + } + } + + public static bool CloseAllBrowsersAndWait(int timeoutMilliseconds) + { + CefBrowserLifetimeCoordinator[] browsers; + lock (_syncRoot) + { + _shutdownRequested = true; + browsers = new CefBrowserLifetimeCoordinator[ + _activeBrowsers.Count]; + _activeBrowsers.CopyTo(browsers); + if (browsers.Length == 0) + return true; + } + + foreach (var browser in browsers) + browser.BeginDispose(); + + bool closed = _allBrowsersClosed.WaitOne( + Math.Max(0, timeoutMilliseconds)); + TraceShutdown(closed, timeoutMilliseconds); + return closed; + } + + private static void TraceShutdown( + bool closed, + int timeoutMilliseconds) + { + try + { + int remaining; + lock (_syncRoot) + { + remaining = _activeBrowsers.Count; + } + + LogHelper.Instance.WriteLog( + "[CEF_LIFECYCLE] shutdown-wait closed=" + closed + + "; remaining=" + remaining + + "; timeoutMs=" + timeoutMilliseconds); + } + catch (Exception) + { + } + } + } + + internal static class CefBrowserNativeMethods + { + internal const int GwlStyle = -16; + internal const int SwHide = 0; + internal const int WsChild = 0x40000000; + internal const int WsClipChildren = 0x02000000; + internal const int WsClipSiblings = 0x04000000; + internal const int WsPopup = unchecked((int)0x80000000); + internal const uint SwpNoZOrder = 0x0004; + internal const uint SwpNoActivate = 0x0010; + internal const uint SwpFrameChanged = 0x0020; + internal const uint SwpShowWindow = 0x0040; + + [DllImport("user32.dll", SetLastError = true)] + internal static extern IntPtr SetParent( + IntPtr childWindow, + IntPtr newParentWindow); + + [DllImport("user32.dll")] + internal static extern IntPtr GetParent(IntPtr window); + + [DllImport("user32.dll")] + [return: MarshalAs(UnmanagedType.Bool)] + internal static extern bool IsWindow(IntPtr window); + + [DllImport("user32.dll", SetLastError = true)] + internal static extern int GetWindowLong(IntPtr window, int index); + + [DllImport("user32.dll", SetLastError = true)] + internal static extern int SetWindowLong( + IntPtr window, + int index, + int newValue); + + [DllImport("user32.dll")] + [return: MarshalAs(UnmanagedType.Bool)] + internal static extern bool ShowWindow(IntPtr window, int command); + + [DllImport("user32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + internal static extern bool SetWindowPos( + IntPtr window, + IntPtr insertAfter, + int x, + int y, + int width, + int height, + uint flags); + + [DllImport("user32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + internal static extern bool PostMessage( + IntPtr window, + int message, + IntPtr wParam, + IntPtr lParam); + + [DllImport("user32.dll")] + internal static extern IntPtr SendMessage( + IntPtr window, + int message, + IntPtr wParam, + IntPtr lParam); + + [DllImport("user32.dll")] + internal static extern uint GetWindowThreadProcessId( + IntPtr window, + IntPtr processId); + + [DllImport("kernel32.dll")] + internal static extern uint GetCurrentThreadId(); + + internal static uint GetWindowThread(IntPtr window) + { + return window == IntPtr.Zero + ? 0 + : GetWindowThreadProcessId(window, IntPtr.Zero); + } + } + + internal sealed class CefActionTask : CefTask + { + private Action _action; + + public CefActionTask(Action action) + { + if (action == null) + throw new ArgumentNullException("action"); + + _action = action; + } + + protected override void Execute() + { + var action = Interlocked.Exchange(ref _action, null); + if (action == null) + return; + + try + { + action(); + } + catch (Exception) + { + // Never propagate managed exceptions into a native CEF task. + } + } + } +} diff --git a/插件库/Lskj.Control/FrmWebBrowser.Designer.cs b/插件库/Lskj.Control/FrmWebBrowser.Designer.cs index 99c48a1..92517a9 100644 --- a/插件库/Lskj.Control/FrmWebBrowser.Designer.cs +++ b/插件库/Lskj.Control/FrmWebBrowser.Designer.cs @@ -14,6 +14,8 @@ namespace Lskj.Control /// 如果应释放托管资源,为 true;否则为 false。 protected override void Dispose(bool disposing) { + if (disposing) + CloseBrowserForDispose(); if (disposing && (components != null)) { components.Dispose(); diff --git a/插件库/Lskj.Control/FrmWebBrowser.cs b/插件库/Lskj.Control/FrmWebBrowser.cs index be285b1..ca669dc 100644 --- a/插件库/Lskj.Control/FrmWebBrowser.cs +++ b/插件库/Lskj.Control/FrmWebBrowser.cs @@ -109,6 +109,45 @@ namespace Lskj.Control cefBrowserSettings.GetMainFrame().LoadUrl(webResponse.ResponseUri.AbsoluteUri); } } + + internal bool BrowserClosing(CefBrowser browser) + { + return false; + } + + internal void BrowserClosed(CefBrowser browser) + { + var currentBrowser = cefBrowserSettings; + if (currentBrowser == null || browser == null) + return; + + bool sameBrowser = ReferenceEquals(currentBrowser, browser); + if (!sameBrowser) + { + try + { + sameBrowser = currentBrowser.IsSame(browser); + } + catch (Exception) + { + } + } + + if (sameBrowser) + Interlocked.CompareExchange(ref cefBrowserSettings, null, currentBrowser); + } + + internal void CloseBrowser() + { + var browser = cefBrowserSettings; + if (browser != null) + browser.GetHost().CloseBrowser(); + } + + private void CloseBrowserForDispose() + { + } + public static string ToEnUrl(string url) { string[] urlParams = url.Split('?'); diff --git a/插件库/Lskj.Control/FrmWebBrowser2.Designer.cs b/插件库/Lskj.Control/FrmWebBrowser2.Designer.cs index 9ecc201..241f563 100644 --- a/插件库/Lskj.Control/FrmWebBrowser2.Designer.cs +++ b/插件库/Lskj.Control/FrmWebBrowser2.Designer.cs @@ -14,6 +14,8 @@ namespace Lskj.Control /// 如果应释放托管资源,为 true;否则为 false。 protected override void Dispose(bool disposing) { + if (disposing) + CloseBrowserForDispose(); if (disposing && (components != null)) { components.Dispose(); diff --git a/插件库/Lskj.Control/FrmWebBrowser2.cs b/插件库/Lskj.Control/FrmWebBrowser2.cs index 923fd9c..a296751 100644 --- a/插件库/Lskj.Control/FrmWebBrowser2.cs +++ b/插件库/Lskj.Control/FrmWebBrowser2.cs @@ -126,6 +126,45 @@ namespace Lskj.Control cefBrowserSettings.GetMainFrame().LoadUrl(webResponse.ResponseUri.AbsoluteUri); } } + + internal bool BrowserClosing(CefBrowser browser) + { + return false; + } + + internal void BrowserClosed(CefBrowser browser) + { + var currentBrowser = cefBrowserSettings; + if (currentBrowser == null || browser == null) + return; + + bool sameBrowser = ReferenceEquals(currentBrowser, browser); + if (!sameBrowser) + { + try + { + sameBrowser = currentBrowser.IsSame(browser); + } + catch (Exception) + { + } + } + + if (sameBrowser) + Interlocked.CompareExchange(ref cefBrowserSettings, null, currentBrowser); + } + + internal void CloseBrowser() + { + var browser = cefBrowserSettings; + if (browser != null) + browser.GetHost().CloseBrowser(); + } + + private void CloseBrowserForDispose() + { + } + public static string ToEnUrl(string url) { string[] urlParams = url.Split('?'); diff --git a/插件库/Lskj.Control/GridControlEx.ExtendedReturn.cs b/插件库/Lskj.Control/GridControlEx.ExtendedReturn.cs index a9d645d..e593149 100644 --- a/插件库/Lskj.Control/GridControlEx.ExtendedReturn.cs +++ b/插件库/Lskj.Control/GridControlEx.ExtendedReturn.cs @@ -47,6 +47,7 @@ namespace Lskj.Control private bool mExtendedReturnDisplayEventAttached; private bool mExtendedReturnEditEventAttached; private bool mExtendedReturnDisposeEventAttached; + private volatile bool mExtendedReturnDisposed; private int mExtendedReturnSchemaGeneration; private sealed class ExtendedLookupRequestState @@ -62,6 +63,9 @@ namespace Lskj.Control { public GridColumnModel Model; public ExtendedReturnSearchPopup Popup; + public PopupContainerControl PopupControl; + public RepositoryItemPopupContainerEdit SearchEdit; + public RepositoryItemTextEdit DisplayEdit; public PopupContainerEdit OwnerEdit; public DataRow BusinessRow; } @@ -124,7 +128,7 @@ namespace Lskj.Control private void GridControlEx_Disposed_ExtendedReturn(object sender, EventArgs e) { - DisposeExtendedReturnRequestTimers(); + DisposeExtendedReturnResources(); } private void InitExtendedSearchReturn(GridColumn column, GridColumnModel model) @@ -138,6 +142,8 @@ namespace Lskj.Control column.ColumnEdit = displayEdit; column.FilterMode = ColumnFilterMode.DisplayText; mExtendedReturnSearchEditors[model.FieldName] = searchEdit; + ExtendedReturnPopupContext context = GetExtendedReturnPopupContext(model.FieldName); + context.DisplayEdit = displayEdit; this.mControlList.Add(model); if (!mExtendedReturnEditEventAttached) @@ -150,6 +156,14 @@ namespace Lskj.Control private RepositoryItemPopupContainerEdit CreateExtendedReturnSearchEdit(GridColumnModel model) { + ExtendedReturnPopupContext previousContext; + if (mExtendedReturnPopupContexts.TryGetValue(model.FieldName, out previousContext)) + { + mExtendedReturnPopupContexts.Remove(model.FieldName); + mExtendedReturnSearchEditors.Remove(model.FieldName); + DisposeExtendedReturnPopupContext(previousContext); + } + Size popupSize = GetExtendedReturnPopupSize(model); ExtendedReturnSearchPopup popup = new ExtendedReturnSearchPopup(); popup.Dock = DockStyle.Fill; @@ -185,7 +199,9 @@ namespace Lskj.Control ExtendedReturnPopupContext context = new ExtendedReturnPopupContext { Model = model, - Popup = popup + Popup = popup, + PopupControl = popupControl, + SearchEdit = searchEdit }; popupControl.Tag = context; popup.Tag = context; @@ -662,6 +678,8 @@ namespace Lskj.Control string searchField, bool schemaOnly) { + if (mExtendedReturnDisposed) return; + GridColumnModel model = context.Model; ExtendedLookupRequest request; lock (mExtendedReturnSyncRoot) @@ -713,6 +731,8 @@ namespace Lskj.Control private void ExtendedReturnDebounceElapsed(object stateValue) { + if (mExtendedReturnDisposed) return; + string fieldName = stateValue as string; if (string.IsNullOrWhiteSpace(fieldName)) return; @@ -723,6 +743,8 @@ namespace Lskj.Control private void StartExtendedReturnWorker(string fieldName) { + if (mExtendedReturnDisposed) return; + ExtendedLookupRequest request; lock (mExtendedReturnSyncRoot) { @@ -750,6 +772,8 @@ namespace Lskj.Control { while (request != null) { + if (mExtendedReturnDisposed) return; + lock (mExtendedReturnSyncRoot) { ExtendedLookupRequestState currentState = GetExtendedLookupState(request.FieldName); @@ -761,6 +785,8 @@ namespace Lskj.Control } ExtendedLookupQueryResult result = QueryExtendedReturn(request); + if (mExtendedReturnDisposed) return; + bool deliverResult = false; ExtendedLookupRequest nextRequest = null; lock (mExtendedReturnSyncRoot) @@ -928,11 +954,11 @@ namespace Lskj.Control private void BeginInvokeExtendedReturnResult(ExtendedLookupRequest request, ExtendedLookupQueryResult result) { - if (this.IsDisposed || !this.IsHandleCreated) return; + if (mExtendedReturnDisposed || this.IsDisposed || !this.IsHandleCreated) return; MethodInvoker deliver = new MethodInvoker(delegate { - if (this.IsDisposed || request.Editor == null || request.Editor.IsDisposed) return; + if (mExtendedReturnDisposed || this.IsDisposed || request.Editor == null || request.Editor.IsDisposed) return; if (!object.ReferenceEquals(this.GridView.ActiveEditor, request.Editor)) return; if (this.GridView.FocusedColumn == null || !string.Equals(this.GridView.FocusedColumn.FieldName, request.FieldName, StringComparison.OrdinalIgnoreCase)) return; @@ -1286,6 +1312,8 @@ namespace Lskj.Control /// private void ResetExtendedReturnState() { + if (mExtendedReturnDisposed) return; + DisposeExtendedReturnRequestTimers(); lock (mExtendedReturnSyncRoot) { @@ -1309,6 +1337,7 @@ namespace Lskj.Control state.Version++; state.DeliveredVersion = 0; state.PendingRequest = null; + state.IsRunning = false; if (state.DebounceTimer != null) { state.DebounceTimer.Change(Timeout.Infinite, Timeout.Infinite); @@ -1318,5 +1347,163 @@ namespace Lskj.Control } } } + + /// + /// 释放扩展返回专用的弹窗控件树和事件订阅。 + /// PopupContainerControl 不在 GridControlEx.Controls 树中,不能依赖宿主控件的 + /// 默认 Dispose 递归释放;这里必须显式切断事件和 GridView 的本地化器订阅。 + /// + private void DisposeExtendedReturnResources() + { + lock (mExtendedReturnSyncRoot) + { + if (mExtendedReturnDisposed) return; + mExtendedReturnDisposed = true; + } + + DisposeExtendedReturnRequestTimers(); + + try + { + if (mExtendedReturnDisplayEventAttached) + { + this.GridView.CustomColumnDisplayText -= GridView_CustomColumnDisplayText_ExtendedReturn; + mExtendedReturnDisplayEventAttached = false; + } + if (mExtendedReturnEditEventAttached) + { + this.GridView.CustomRowCellEditForEditing -= GridView_CustomRowCellEditForEditing_ExtendedReturn; + this.GridView.RowCellClick -= GridView_RowCellClick_ExtendedReturn; + mExtendedReturnEditEventAttached = false; + } + } + catch + { + // 宿主 GridView 可能已经进入 DevExpress 的销毁流程,释放必须继续。 + } + + try + { + this.Disposed -= GridControlEx_Disposed_ExtendedReturn; + mExtendedReturnDisposeEventAttached = false; + } + catch + { + } + + List contexts = + new List(mExtendedReturnPopupContexts.Values); + List searchEditors = + new List(mExtendedReturnSearchEditors.Values); + + mExtendedReturnPopupContexts.Clear(); + mExtendedReturnSearchEditors.Clear(); + + foreach (ExtendedReturnPopupContext context in contexts) + { + DisposeExtendedReturnPopupContext(context); + } + + // 防止异常配置导致某个 editor 没有对应 context 时仍然遗留事件订阅。 + foreach (RepositoryItemPopupContainerEdit searchEdit in searchEditors) + { + DisposeExtendedReturnSearchEditor(searchEdit); + } + + lock (mExtendedReturnSyncRoot) + { + foreach (DataTable schema in mExtendedReturnSchemas.Values) + { + DisposeExtendedReturnDataTable(schema); + } + mExtendedReturnSchemas.Clear(); + mExtendedReturnSchemaSqls.Clear(); + mExtendedReturnQueryStates.Clear(); + mExtendedReturnSchemaGeneration++; + } + mExtendedReturnDisplayFields.Clear(); + + // 这些字段会在扩展返回弹窗操作中指向当前 GridView,销毁时不能继续保留引用。 + CurrentOperGridView = null; + CurrentOperModel = null; + CurrentOperColumnKey = string.Empty; + } + + private void DisposeExtendedReturnPopupContext(ExtendedReturnPopupContext context) + { + if (context == null) return; + + PopupContainerEdit ownerEdit = context.OwnerEdit; + if (ownerEdit != null) + { + try + { + if (!ownerEdit.IsDisposed && ownerEdit.IsPopupOpen) + { + ownerEdit.ClosePopup(); + } + } + catch + { + } + } + + ExtendedReturnSearchPopup popup = context.Popup; + if (popup != null) + { + try { popup.SearchRequested -= ExtendedReturnSearch_SearchRequested; } catch { } + try { popup.ClearRequested -= ExtendedReturnSearch_ClearRequested; } catch { } + try { popup.ResultSelected -= ExtendedReturnSearch_ResultSelected; } catch { } + try { popup.ResultGrid.DataSource = null; } catch { } + try { popup.ClearResultAndFilter(); } catch { } + try { popup.Tag = null; } catch { } + } + + PopupContainerControl popupControl = context.PopupControl; + if (popupControl != null) + { + try { popupControl.Tag = null; } catch { } + } + + DisposeExtendedReturnSearchEditor(context.SearchEdit); + DisposeExtendedReturnComponent(popup == null ? null : popup.ResultView); + DisposeExtendedReturnComponent(popup == null ? null : popup.ResultGrid); + DisposeExtendedReturnComponent(popup); + DisposeExtendedReturnComponent(popupControl); + DisposeExtendedReturnComponent(context.DisplayEdit); + + context.Model = null; + context.Popup = null; + context.PopupControl = null; + context.SearchEdit = null; + context.DisplayEdit = null; + context.OwnerEdit = null; + context.BusinessRow = null; + } + + private void DisposeExtendedReturnSearchEditor(RepositoryItemPopupContainerEdit searchEdit) + { + if (searchEdit == null) return; + + try { searchEdit.QueryPopUp -= ExtendedReturnSearch_QueryPopUp; } catch { } + try { searchEdit.Popup -= ExtendedReturnSearch_Popup; } catch { } + try { searchEdit.Closed -= ExtendedReturnSearch_Closed; } catch { } + try { searchEdit.QueryDisplayText -= ExtendedReturnSearch_QueryDisplayText; } catch { } + try { searchEdit.PopupControl = null; } catch { } + try { searchEdit.Tag = null; } catch { } + DisposeExtendedReturnComponent(searchEdit); + } + + private static void DisposeExtendedReturnDataTable(DataTable table) + { + if (table == null) return; + try { table.Dispose(); } catch { } + } + + private static void DisposeExtendedReturnComponent(IDisposable component) + { + if (component == null) return; + try { component.Dispose(); } catch { } + } } } diff --git a/插件库/Lskj.Control/GridControlEx.cs b/插件库/Lskj.Control/GridControlEx.cs index c00fc7a..1581567 100644 --- a/插件库/Lskj.Control/GridControlEx.cs +++ b/插件库/Lskj.Control/GridControlEx.cs @@ -560,6 +560,71 @@ namespace Lskj.Control this.gridView.MouseWheel += GridView_MouseWheel; } + private DbCommandBuilder _adapterCommandBuilder; + + protected static void DisposeDataAdapter(DbDataAdapter adapter) + { + if (adapter == null) + { + return; + } + + DbCommand[] commands = + { + adapter.SelectCommand, + adapter.InsertCommand, + adapter.UpdateCommand, + adapter.DeleteCommand + }; + + foreach (DbCommand command in commands) + { + try + { + command?.Dispose(); + } + catch + { + // Resource cleanup must not break control disposal or a new query. + } + } + + try + { + adapter.Dispose(); + } + catch + { + // Resource cleanup must not break control disposal or a new query. + } + } + + protected void SetAdapterCommandBuilder(DbDataAdapter adapter) + { + DisposeAdapterCommandBuilder(); + if (adapter == null) + { + return; + } + + _adapterCommandBuilder = SqlHelper.dbFactory.CreateCommandBuilder(); + _adapterCommandBuilder.DataAdapter = adapter; + } + + protected void DisposeAdapterCommandBuilder() + { + DbCommandBuilder commandBuilder = _adapterCommandBuilder; + _adapterCommandBuilder = null; + try + { + commandBuilder?.Dispose(); + } + catch + { + // Resource cleanup must not break control disposal or a new query. + } + } + private void DisposeGridResources() { if (_gridResourcesDisposed) @@ -574,9 +639,23 @@ namespace Lskj.Control mTimer.Dispose(); mTimer = null; } + try + { + if (GridControl != null) + { + GridControl.DataSource = null; + } + } + catch + { + // The inner DevExpress control may already be disposed. + } DisposeColumnImageCaches(ColumnList); DisposeRightMenuButtonImages(); ClearBarcodeImageCache(); + DisposeAdapterCommandBuilder(); + DisposeDataAdapter(AdapterObj); + AdapterObj = null; mControlSourceDic.Clear(); mControlList.Clear(); ColumnList.Clear(); @@ -585,6 +664,9 @@ namespace Lskj.Control ImageList.Clear(); RepeatedVerificationNames.Clear(); PromptMessage.Clear(); + ParentControl = null; + Model = null; + SysModel = null; if (_mainGridToolTipController != null) { _mainGridToolTipController.BeforeShow -= MainGvTool_BeforeShow; @@ -2032,7 +2114,7 @@ namespace Lskj.Control //})); Task customColumnByDatabaseTask = cachesDic.AddTask(this, "CustomColumnByDatabase", new Task(() => { - return this.GridView.GetCustomColumnByDatabase(customColumKey); + return GridExtend.GetCustomColumnByDatabase(customColumKey); })); } @@ -2167,12 +2249,14 @@ namespace Lskj.Control { if (adapter == null) return 0; + DbDataAdapter previousAdapter = this.AdapterObj; + bool adapterAssigned = false; + DataSet dataSet = null; try { - this.AdapterObj = adapter; - DataSet dataSet = new DataSet(); + dataSet = new DataSet(); // 重连配置 int maxRetryCount = 2; // 次数 @@ -2211,7 +2295,23 @@ namespace Lskj.Control } } + DataTable dt = dataSet.Tables.Count > 0 ? dataSet.Tables[0] : null; + int rowCount = dt == null ? 0 : dt.Rows.Count; + if (dt != null) + { + // Detach the result table before releasing the temporary DataSet. + dataSet.Tables.Remove(dt); + } + dataSet.Dispose(); + dataSet = null; + DisposeAdapterCommandBuilder(); + if (previousAdapter != null && !ReferenceEquals(previousAdapter, adapter)) + { + DisposeDataAdapter(previousAdapter); + } + this.AdapterObj = adapter; + adapterAssigned = true; if (this.ColumnList == null || this.ColumnList.Count == 0) { @@ -2226,12 +2326,10 @@ namespace Lskj.Control this.gridView.Columns.Clear(); } } - DbCommandBuilder cb = SqlHelper.dbFactory.CreateCommandBuilder(); - cb.DataAdapter = this.AdapterObj; + SetAdapterCommandBuilder(this.AdapterObj); //this.GridControl.DataSource = dataSet.Tables[0]; //解决大小写问题后在赋值 - DataTable dt = dataSet.Tables[0]; if (dt != null && this.gridView.Columns.Count > 0) { foreach (DataColumn column in dt.Columns) @@ -2272,12 +2370,20 @@ namespace Lskj.Control this.SetAutoColumns(); if (selectRowHandler) this.gridView.SelectRowHandler(this.gridView.FocusedRowHandle); - return dataSet.Tables[0] != null ? dataSet.Tables[0].Rows.Count : 0; + return rowCount; } catch (Exception ex) { + if (!adapterAssigned) + { + DisposeDataAdapter(adapter); + } MessageUtil.Show(ex); } + finally + { + dataSet?.Dispose(); + } return 0; } @@ -5532,6 +5638,7 @@ namespace Lskj.Control /// 事件参数。 private void OnGridControlExDisposed(object sender, EventArgs e) { + DisposeGridResources(); foreach (FrmModelLookUp2 modelLookUp in selectReturnModelLookUpCache.Values.Distinct()) { if (modelLookUp != null && !modelLookUp.IsDisposed) diff --git a/插件库/Lskj.Control/Lskj.Control.csproj b/插件库/Lskj.Control/Lskj.Control.csproj index 4086ad1..d949ae9 100644 --- a/插件库/Lskj.Control/Lskj.Control.csproj +++ b/插件库/Lskj.Control/Lskj.Control.csproj @@ -456,6 +456,7 @@ Component + diff --git a/插件库/Lskj.Control/Model/BandedGridDragGrid.cs b/插件库/Lskj.Control/Model/BandedGridDragGrid.cs index 197fcd6..1144e06 100644 --- a/插件库/Lskj.Control/Model/BandedGridDragGrid.cs +++ b/插件库/Lskj.Control/Model/BandedGridDragGrid.cs @@ -29,6 +29,65 @@ namespace Lskj.Control.Model public class BandedGridDragGrid : IDisposable { private bool _disposed; + + internal static void DisposeRegistrations( + Dictionary> sourceRegistrations, + Dictionary> targetRegistrations, + BandedGridView sourceGridView, + GridView targetGridView) + { + DisposeSourceRegistrations(sourceRegistrations, sourceGridView); + DisposeTargetRegistrations(targetRegistrations, targetGridView); + } + + private static void DisposeSourceRegistrations( + Dictionary> registrations, + BandedGridView gridView) + { + foreach (KeyValuePair> entry in registrations.ToList()) + { + bool removeKey = ReferenceEquals(entry.Key, gridView); + foreach (BandedGridDragGrid registration in entry.Value.ToList()) + { + if (removeKey || registration == null || registration._disposed || registration.References(gridView)) + { + if (registration != null) + registration.Dispose(); + entry.Value.Remove(registration); + } + } + if (removeKey || entry.Value.Count == 0) + registrations.Remove(entry.Key); + } + } + + private static void DisposeTargetRegistrations( + Dictionary> registrations, + GridView gridView) + { + foreach (KeyValuePair> entry in registrations.ToList()) + { + bool removeKey = ReferenceEquals(entry.Key, gridView); + foreach (BandedGridDragGrid registration in entry.Value.ToList()) + { + if (removeKey || registration == null || registration._disposed || registration.References(gridView)) + { + if (registration != null) + registration.Dispose(); + entry.Value.Remove(registration); + } + } + if (removeKey || entry.Value.Count == 0) + registrations.Remove(entry.Key); + } + } + + internal bool References(GridView gridView) + { + return gridView != null && + (ReferenceEquals(_sourceGridView, gridView) || + ReferenceEquals(_targetGridView, gridView)); + } /// /// 是否正在拖拽 /// diff --git a/插件库/Lskj.Control/Model/GridDragGrid.cs b/插件库/Lskj.Control/Model/GridDragGrid.cs index e517311..7ce3a9b 100644 --- a/插件库/Lskj.Control/Model/GridDragGrid.cs +++ b/插件库/Lskj.Control/Model/GridDragGrid.cs @@ -32,6 +32,43 @@ namespace Lskj.Control.Model public class GridDragGrid : IDisposable { private bool _disposed; + + internal static void DisposeRegistrations( + Dictionary> sourceRegistrations, + Dictionary> targetRegistrations, + GridView gridView) + { + DisposeRegistrations(sourceRegistrations, gridView); + DisposeRegistrations(targetRegistrations, gridView); + } + + private static void DisposeRegistrations( + Dictionary> registrations, + GridView gridView) + { + foreach (KeyValuePair> entry in registrations.ToList()) + { + bool removeKey = ReferenceEquals(entry.Key, gridView); + foreach (GridDragGrid registration in entry.Value.ToList()) + { + if (removeKey || registration == null || registration._disposed || registration.References(gridView)) + { + if (registration != null) + registration.Dispose(); + entry.Value.Remove(registration); + } + } + if (removeKey || entry.Value.Count == 0) + registrations.Remove(entry.Key); + } + } + + internal bool References(GridView gridView) + { + return gridView != null && + (ReferenceEquals(_sourceGridView, gridView) || + ReferenceEquals(_targetGridView, gridView)); + } /// /// 是否正在拖拽 /// diff --git a/插件库/Lskj.Control/Model/GridDragTree.cs b/插件库/Lskj.Control/Model/GridDragTree.cs index 2a016bb..4cee0b4 100644 --- a/插件库/Lskj.Control/Model/GridDragTree.cs +++ b/插件库/Lskj.Control/Model/GridDragTree.cs @@ -24,7 +24,7 @@ namespace Lskj.Control.Model /// /// 表格拖拽到树结构 /// - public class GridDragTree + public class GridDragTree : IDisposable { /// /// 拖动位置 @@ -38,6 +38,7 @@ namespace Lskj.Control.Model /// 拖动目标源 /// private TreeView _treeView; + private bool _disposed; public bool CanDragParentNode = false; /// /// 表格数据拖动完成 @@ -60,6 +61,31 @@ namespace Lskj.Control.Model this._treeView.DragOver += new DragEventHandler(treeView_DragOver); this._treeView.DragDrop += new DragEventHandler(treeView_DragDrop); } + + public void Dispose() + { + if (_disposed) + { + return; + } + + _disposed = true; + if (_gridView != null) + { + _gridView.MouseDown -= gridView_MouseDown; + _gridView.MouseMove -= gridView_MouseMove; + } + if (_treeView != null) + { + _treeView.DragOver -= treeView_DragOver; + _treeView.DragDrop -= treeView_DragDrop; + } + + OnDragComplete = null; + _hitInfo = null; + _gridView = null; + _treeView = null; + } /// diff --git a/插件库/Lskj.Control/Model/GridExtend.cs b/插件库/Lskj.Control/Model/GridExtend.cs index 7d94177..6624c5d 100644 --- a/插件库/Lskj.Control/Model/GridExtend.cs +++ b/插件库/Lskj.Control/Model/GridExtend.cs @@ -913,6 +913,14 @@ namespace Lskj.Control.Model /// The key. /// DataTable. public static DataTable GetCustomColumnByDatabase(this GridView gridView, string key) + { + return GetCustomColumnByDatabase(key); + } + + /// + /// 获取个性配置列数据,不依赖任何界面控件。 + /// + public static DataTable GetCustomColumnByDatabase(string key) { try { @@ -954,18 +962,7 @@ namespace Lskj.Control.Model { BaseImpl.ExecSqlValue("alter table " + ResourceKeys.SettingTableName + " add IfFixColumn bit"); } - // 读取普通用户读取个人配置,没有则读取管理员配置,管理未配置则读取系统默认.逐级上报读取方式. - string sqlValue = string.Format("select * from {0} where formKey='{1}' and operatorid='{2}' order by orderid ", ResourceKeys.SettingTableName, key, ERPInfo.Instance.UserId); - - DataTable table = BaseImpl.GetDataTableResult(sqlValue); - - //if (table == null || table.Rows.Count == 0) - //{ - // sqlValue = string.Format("select * from {0} where formKey='{1}' and operatorid='{2}'", ResourceKeys.SettingTableName, key, ERPInfo.Instance.UserManager); - // table = BaseImpl.GetDataTableResult(sqlValue); - //} - - return table; + return GetCustomColumnByDatabase(key); } catch (Exception) { diff --git a/插件库/Lskj.Control/Model/MyControl.cs b/插件库/Lskj.Control/Model/MyControl.cs index 8ddda13..9c7d5d3 100644 --- a/插件库/Lskj.Control/Model/MyControl.cs +++ b/插件库/Lskj.Control/Model/MyControl.cs @@ -32,8 +32,10 @@ namespace Lskj.Control.Model /// /// 查询控件、添加修改界面控件管理类 /// - public class MyControl + public class MyControl : IDisposable { + private const long UserSearchCooldownTicks = + TimeSpan.TicksPerMillisecond * 350; private string _searchSql; private DataTable _controls; private DataTable _schemes; @@ -46,8 +48,12 @@ namespace Lskj.Control.Model private GridControlEx _gridLeft; private TreeViewEx _tvLeft; private MyControl _leftSearch; - private DropDownButton _dropDown = new DropDownButton(); - private PopupMenu _popupMenu = new PopupMenu(); + private DropDownButton _dropDown; + private PopupMenu _popupMenu; + private bool mDisposed; + private bool mUserSearchInProgress; + private long mLastUserSearchCompletedTicks; + private int mAcceptedUserSearchCount; /// /// 计算字段 /// @@ -112,6 +118,10 @@ namespace Lskj.Control.Model /// /// The button object. public SimpleButton ButtonObj { get { return _button; } } + public int AcceptedUserSearchCount + { + get { return mAcceptedUserSearchCount; } + } /// /// 条件数据源刷新按钮 /// @@ -1024,6 +1034,10 @@ namespace Lskj.Control.Model // 创建模版搜索 if (_schemes != null && ModuleId > 0 && _schemes.Rows.Count > 0) { + if (_popupMenu == null) + { + _popupMenu = new PopupMenu(); + } _dropDown = new DropDownButton(); _dropDown.DropDownArrowStyle = DropDownArrowStyle.Show; _dropDown.DropDownControl = this._popupMenu; @@ -1204,6 +1218,10 @@ namespace Lskj.Control.Model // 创建模版搜索 if (_schemes != null && ModuleId > 0 && _schemes.Rows.Count > 0) { + if (_popupMenu == null) + { + _popupMenu = new PopupMenu(); + } _dropDown = new DropDownButton(); _dropDown.DropDownArrowStyle = DropDownArrowStyle.Show; _dropDown.DropDownControl = this._popupMenu; @@ -1736,8 +1754,14 @@ namespace Lskj.Control.Model WaitForm.HideForm(); mControlSourceDic.Clear(); if (this.OnDataSourceBindCallBack != null) this.OnDataSourceBindCallBack(this, null); - this.mTimer.Stop(); - this.mTimer.Dispose(); + Timer timer = this.mTimer; + this.mTimer = null; + if (timer != null) + { + timer.Tick -= mTimerTick; + timer.Stop(); + timer.Dispose(); + } } } } @@ -6091,6 +6115,61 @@ namespace Lskj.Control.Model } } + private bool TryBeginUserSearch() + { + if (mDisposed || mUserSearchInProgress) + { + return false; + } + + long now = DateTime.UtcNow.Ticks; + if (mLastUserSearchCompletedTicks > 0 && + now - mLastUserSearchCompletedTicks < UserSearchCooldownTicks) + { + return false; + } + + mUserSearchInProgress = true; + mAcceptedUserSearchCount++; + return true; + } + + private void EndUserSearch() + { + mUserSearchInProgress = false; + mLastUserSearchCompletedTicks = DateTime.UtcNow.Ticks; + } + + private bool ExecuteUserSearch(object sender, EventArgs e) + { + if (!TryBeginUserSearch()) + { + return false; + } + + try + { + SearchArgs args = new SearchArgs(); + if (OnSearchBeforeCallBack != null) + { + OnSearchBeforeCallBack(sender, args); + } + if (args.Continue) + { + this.SearchGrid(false); + } + if (OnSearchAfterCallBack != null) + { + OnSearchAfterCallBack(sender, e); + } + return true; + } + finally + { + EndUserSearch(); + } + } + /// /// 说明: /// 创建人:龚宇超 @@ -6115,18 +6194,8 @@ namespace Lskj.Control.Model { if (baseControl.Model.IsSearchControl) { - // 搜索控件回车直接搜索 - SearchArgs args = new SearchArgs(); - // 查询之前 - if (OnSearchBeforeCallBack != null) - OnSearchBeforeCallBack(sender, args); - - if (args.Continue) this.SearchGrid(false); - - // 查询之后 - if (OnSearchAfterCallBack != null) OnSearchAfterCallBack(sender, e); - - if (baseControl.Model.ScanAfterEmpty) + if (ExecuteUserSearch(sender, e) && + baseControl.Model.ScanAfterEmpty) { baseControl.EditText = string.Empty; } @@ -6225,14 +6294,7 @@ namespace Lskj.Control.Model { if (baseControl.Model.IsSearchControl) { - // 搜索控件回车直接搜索 - SearchArgs args = new SearchArgs(); - // 查询之前 - if (OnSearchBeforeCallBack != null) - OnSearchBeforeCallBack(sender, args); - if (args.Continue) this.SearchGrid(false); - // 查询之后 - if (OnSearchAfterCallBack != null) OnSearchAfterCallBack(sender, e); + ExecuteUserSearch(sender, e); } } } @@ -6249,45 +6311,57 @@ namespace Lskj.Control.Model /// The instance containing the event data. protected void OnSimpleButtonClick(object sender, EventArgs e) { + if (!TryBeginUserSearch()) + { + return; + } + try { - if (Model != null) + try { - bool allowBefore = ApiHelper.IsExecEventApi(Interface.Api.OperateEvent.BeforeSearchBtnClick, Model.ModuleCode, 0); - //查询前调用Api接口 - if (allowBefore) + if (Model != null) { - DataRow dataRow = this.GetAllControlValueRow(); - ApiHelper apiHelper = new ApiHelper(Model.ModuleCode, 0, dataRow); - apiHelper.OnEvent(Interface.Api.OperateEvent.BeforeSearchBtnClick, Interface.Api.ActionType.None); - if (apiHelper.apiHandlerModels.Count > 0) + bool allowBefore = ApiHelper.IsExecEventApi(Interface.Api.OperateEvent.BeforeSearchBtnClick, Model.ModuleCode, 0); + //查询前调用Api接口 + if (allowBefore) { - Hashtable hashtable = apiHelper.apiHandlerModels[apiHelper.apiHandlerModels.Count - 1].resultPmsHashtables[0]; - if (hashtable.ContainsKey("result")) + DataRow dataRow = this.GetAllControlValueRow(); + ApiHelper apiHelper = new ApiHelper(Model.ModuleCode, 0, dataRow); + apiHelper.OnEvent(Interface.Api.OperateEvent.BeforeSearchBtnClick, Interface.Api.ActionType.None); + if (apiHelper.apiHandlerModels.Count > 0) { - string jsonResult = hashtable["result"] + ""; - DataTable dataTable = JsonConvert.DeserializeObject(jsonResult); - this._mainGridEx.SetGridViewDataSource(dataTable); - return; + Hashtable hashtable = apiHelper.apiHandlerModels[apiHelper.apiHandlerModels.Count - 1].resultPmsHashtables[0]; + if (hashtable.ContainsKey("result")) + { + string jsonResult = hashtable["result"] + ""; + DataTable dataTable = JsonConvert.DeserializeObject(jsonResult); + this._mainGridEx.SetGridViewDataSource(dataTable); + return; + } } } } + } + catch (Exception ex) + { + MessageUtil.Show(ex.Message); + } + SearchArgs args = new SearchArgs(); + // 查询之前 + if (OnSearchBeforeCallBack != null) + OnSearchBeforeCallBack(sender, args); + if (args.Continue) this.SearchGrid(false); + + // 查询之后 + if (OnSearchAfterCallBack != null) OnSearchAfterCallBack(sender, e); } - catch (Exception ex) + finally { - MessageUtil.Show(ex.Message); + EndUserSearch(); } - SearchArgs args = new SearchArgs(); - // 查询之前 - if (OnSearchBeforeCallBack != null) - OnSearchBeforeCallBack(sender, args); - - if (args.Continue) this.SearchGrid(false); - - // 查询之后 - if (OnSearchAfterCallBack != null) OnSearchAfterCallBack(sender, e); } @@ -6322,15 +6396,7 @@ namespace Lskj.Control.Model /// The instance containing the event data. protected void OnFixButtonClick(object sender, EventArgs e) { - SearchArgs args = new SearchArgs(); - // 查询之前 - if (OnSearchBeforeCallBack != null) - OnSearchBeforeCallBack(sender, args); - - if (args.Continue) this.SearchGrid(false); - - // 查询之后 - if (OnSearchAfterCallBack != null) OnSearchAfterCallBack(sender, e); + ExecuteUserSearch(sender, e); } /// /// 说明:高级搜索按钮鼠标离开时 @@ -6716,6 +6782,154 @@ namespace Lskj.Control.Model } } + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + if (!disposing || mDisposed) + { + return; + } + + mDisposed = true; + mUserSearchInProgress = false; + + Timer timer = mTimer; + mTimer = null; + if (timer != null) + { + try + { + timer.Tick -= mTimerTick; + timer.Stop(); + timer.Dispose(); + } + catch + { + // A completed delayed-load timer may already be disposed. + } + } + + if (_button != null) + { + _button.Click -= OnSimpleButtonClick; + _button.Tag = null; + } + if (_fixButton != null) + { + _fixButton.Click -= OnFixButtonClick; + } + if (_refreshButton != null) + { + _refreshButton.Click -= OnRefreshButtonClick; + _refreshButton.Tag = null; + } + if (btnCondSearch != null) + { + btnCondSearch.Click -= OnBtnCondSearchClick; + btnCondSearch.Tag = null; + } + if (_dropDown != null) + { + _dropDown.Leave -= OnDropDownLeave; + _dropDown.DropDownControl = null; + } + foreach (SimpleButton button in mrpBtnDic.Values.Distinct()) + { + if (button != null) + { + button.Click -= OnModuleClick; + button.Tag = null; + } + } + + BarManager searchSchemeManager = + _popupMenu == null ? null : _popupMenu.Manager; + if (_popupMenu != null) + { + _popupMenu.Manager = null; + _popupMenu.ItemLinks.Clear(); + _popupMenu.Dispose(); + } + if (searchSchemeManager != null) + { + searchSchemeManager.Dispose(); + } + + if (_mainGridEx != null && + ReferenceEquals(_mainGridEx.ParentControl, this)) + { + _mainGridEx.ParentControl = null; + } + + try + { + if (Model != null && Model.DataCaches != null) + { + Model.DataCaches.Remove(this); + } + } + catch + { + // Cache cleanup must not interrupt module disposal. + } + + preSearchVerification = null; + OnSearchBeforeCallBack = null; + OnSearchAfterCallBack = null; + OnAddCallBack = null; + ClearDetails = null; + OnLastQueryTriggered = null; + OnSourceRefrshCallBack = null; + OnDataSourceBindCallBack = null; + OnDataFileRefrshCallBack = null; + OpenDocumentSource = null; + + _calcControls.Clear(); + _models.Clear(); + mControlList.Clear(); + mExtendedReturnSearchControls.Clear(); + mExtendedReturnPendingChanges.Clear(); + mControlSourceDic.Clear(); + mCacheDictionary.Clear(); + DetailSelection.Clear(); + changeLabelAutoGridLooks.Clear(); + mrpBtnDic.Clear(); + ModuleResultDic.Clear(); + ParaContrlsDic.Clear(); + + _controls = null; + _schemes = null; + _fixField = null; + _fixKey = null; + _button = null; + _fixButton = null; + _refreshButton = null; + _dropDown = null; + _popupMenu = null; + btnCondSearch = null; + _gridControl = null; + _mainGridEx = null; + _gridLeft = null; + _tvLeft = null; + _leftSearch = null; + _parentPanel = null; + CurrentData = null; + SystemModel = null; + SaveCondTab = null; + mrpDyncModel = null; + Model = null; + popUpDyncModel = null; + rightItemLinks = null; + SpecialLeftTable = null; + beforeData = null; + OtherParams = null; + } + } } diff --git a/插件库/Lskj.Control/Model/StaticBandedControl.cs b/插件库/Lskj.Control/Model/StaticBandedControl.cs index ab7b68a..e1de379 100644 --- a/插件库/Lskj.Control/Model/StaticBandedControl.cs +++ b/插件库/Lskj.Control/Model/StaticBandedControl.cs @@ -7,11 +7,94 @@ using System; using System.Collections.Generic; using System.Linq; using System.Text; +using System.Windows.Forms; +using WinFormsControl = System.Windows.Forms.Control; namespace Lskj.Control.Model { public static class StaticBandedControl { + public static void ReleaseModuleReferences(WinFormsControl moduleRoot, XtraTabPage modulePage, string moduleCode) + { + try + { + List bandedViews = CollectBandedGridViews(moduleRoot); + List targetViews = CollectTargetGridViews(moduleRoot); + foreach (BandedGridView view in bandedViews) + BandedGridDragGrid.DisposeRegistrations( + BandedGridViewDragGridDic, + BandedTargetViewDragGridDic, + view, + null); + foreach (GridView view in targetViews) + BandedGridDragGrid.DisposeRegistrations( + BandedGridViewDragGridDic, + BandedTargetViewDragGridDic, + null, + view); + if (SourceDragBandedGridView != null && + (bandedViews.Contains(SourceDragBandedGridView) || + (SourceDragBandedGridView.GridControl != null && SourceDragBandedGridView.GridControl.IsDisposed))) + SourceDragBandedGridView = null; + RemoveConditionPanels(moduleCode); + while (modulePage != null && DogVerifyModuleForms.Remove(modulePage)) + { + } + } + catch (Exception exception) + { + System.Diagnostics.Debug.WriteLine("释放旧 Banded 模块引用失败:" + exception); + } + } + + private static List CollectBandedGridViews(WinFormsControl root) + { + List result = new List(); + if (root == null) + return result; + foreach (WinFormsControl control in EnumerateControls(root)) + { + DevExpress.XtraGrid.GridControl grid = control as DevExpress.XtraGrid.GridControl; + BandedGridView view = grid == null ? null : grid.MainView as BandedGridView; + if (view != null && !result.Contains(view)) + result.Add(view); + } + return result; + } + + private static List CollectTargetGridViews(WinFormsControl root) + { + List result = new List(); + if (root == null) + return result; + foreach (WinFormsControl control in EnumerateControls(root)) + { + DevExpress.XtraGrid.GridControl grid = control as DevExpress.XtraGrid.GridControl; + GridView view = grid == null ? null : grid.MainView as GridView; + if (view != null && !result.Contains(view)) + result.Add(view); + } + return result; + } + + private static IEnumerable EnumerateControls(WinFormsControl root) + { + yield return root; + foreach (WinFormsControl child in root.Controls) + foreach (WinFormsControl nested in EnumerateControls(child)) + yield return nested; + } + + private static void RemoveConditionPanels(string moduleCode) + { + foreach (string key in ConditionsPanelDic.Keys.ToList()) + { + ModuleConditionsPanelEx panel; + if (string.Equals(key, moduleCode, StringComparison.OrdinalIgnoreCase) || + !ConditionsPanelDic.TryGetValue(key, out panel) || panel == null || panel.IsDisposed) + ConditionsPanelDic.Remove(key); + } + } /// /// 记录当前U盾权限窗口 /// @@ -44,4 +127,4 @@ namespace Lskj.Control.Model /// public static FrmProgressBar frmProgressBar; } -} \ No newline at end of file +} diff --git a/插件库/Lskj.Control/Model/StaticControl.cs b/插件库/Lskj.Control/Model/StaticControl.cs index ac7c004..45bb199 100644 --- a/插件库/Lskj.Control/Model/StaticControl.cs +++ b/插件库/Lskj.Control/Model/StaticControl.cs @@ -3,17 +3,215 @@ 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.Text; using System.Windows.Forms; +using Lskj.Model; +using System.Reflection; +using WinFormsControl = System.Windows.Forms.Control; namespace Lskj.Control.Model { public static class StaticControl { + /// + /// 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) + { + List gridViews = new List(); + try + { + gridViews = CollectGridViews(moduleRoot); + } + catch (Exception exception) + { + TraceCleanupFailure("收集表格引用", exception); + } + + try + { + foreach (GridView gridView in gridViews) + { + try + { + RemoveGridReferences(gridView); + } + catch (Exception exception) + { + TraceCleanupFailure("释放表格拖拽引用", exception); + } + } + + TryCleanup(() => RemoveConditionPanels(moduleCode), "释放条件面板引用"); + TryCleanup(() => RemoveProtectedReferences(moduleRoot, modulePage), "释放权限保护引用"); + TryCleanup(() => ClearRightMenuReferences(moduleRoot, gridViews), "释放右键菜单引用"); + TryCleanup( + () => StaticBandedControl.ReleaseModuleReferences(moduleRoot, modulePage, moduleCode), + "释放带状表格引用"); + } + finally + { + // DynamicModel caches retain completed tasks, result tables and + // their closures. Always release them after other references. + TryCleanup(() => ClearDataCaches(moduleRoot), "释放动态数据缓存"); + } + } + + 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 List CollectGridViews(WinFormsControl root) + { + List result = new List(); + if (root == null) + return result; + foreach (WinFormsControl control in EnumerateControls(root)) + { + DevExpress.XtraGrid.GridControl grid = control as DevExpress.XtraGrid.GridControl; + GridView view = grid == null ? null : grid.MainView as GridView; + if (view != null && !result.Contains(view)) + result.Add(view); + } + return result; + } + + private static IEnumerable EnumerateControls(WinFormsControl root) + { + yield return root; + foreach (WinFormsControl child in root.Controls) + foreach (WinFormsControl nested in EnumerateControls(child)) + yield return nested; + } + + private static void RemoveGridReferences(GridView gridView) + { + GridDragGrid.DisposeRegistrations(GridViewDragGridDic, TargetViewDragGridDic, gridView); + if (ReferenceEquals(SourceDragGridView, gridView) || + (SourceDragGridView != null && SourceDragGridView.GridControl != null && SourceDragGridView.GridControl.IsDisposed)) + SourceDragGridView = null; + } + + private static void RemoveConditionPanels(string moduleCode) + { + foreach (string key in ConditionsPanelDic.Keys.ToList()) + { + ModuleConditionsPanelEx panel; + if (string.Equals(key, moduleCode, StringComparison.OrdinalIgnoreCase) || + !ConditionsPanelDic.TryGetValue(key, out panel) || panel == null || panel.IsDisposed) + ConditionsPanelDic.Remove(key); + } + } + + private static void RemoveProtectedReferences(WinFormsControl root, XtraTabPage page) + { + while (page != null && DogVerifyModuleForms.Remove(page)) + { + } + foreach (XtraTabPage item in DogVerifyModuleForms.ToList()) + if (item == null || item.IsDisposed || IsContained(root, item)) + DogVerifyModuleForms.Remove(item); + foreach (IForm item in DogVerifyNoPageForms.ToList()) + if (item == null || item.SubForm == null || item.SubForm.IsDisposed || IsContained(root, item.SubForm)) + DogVerifyNoPageForms.Remove(item); + } + + private static bool IsContained(WinFormsControl root, WinFormsControl value) + { + if (root == null || value == null) + return false; + if (ReferenceEquals(root, value)) + return true; + foreach (WinFormsControl child in root.Controls) + if (IsContained(child, value)) + return true; + return false; + } + + private static void ClearRightMenuReferences(WinFormsControl root, List gridViews) + { + if (_RightMenuGridView != null && (gridViews.Contains(_RightMenuGridView) || + (_RightMenuGridView.GridControl != null && _RightMenuGridView.GridControl.IsDisposed))) + RightMenuGridView = null; + if (RightMenuMyControl != null && RightMenuMyControl.MainGridEx != null && + (RightMenuMyControl.MainGridEx.IsDisposed || IsContained(root, RightMenuMyControl.MainGridEx))) + RightMenuMyControl = null; + if (BomUnionPage != null && (BomUnionPage.IsDisposed || IsContained(root, BomUnionPage))) + BomUnionPage = null; + if (AddParentGrid.Value != null && (AddParentGrid.Value.IsDisposed || IsContained(root, AddParentGrid.Value))) + AddParentGrid = new KeyValuePair(); + } + + private static void ClearDataCaches(WinFormsControl root) + { + if (root == null) + return; + + // Clear the root first because child enumeration can fail while a + // DevExpress control is disposing its child collection. + ClearDynamicModels(root); + try + { + foreach (WinFormsControl control in EnumerateControls(root).Skip(1).ToList()) + ClearDynamicModels(control); + } + catch (Exception exception) + { + TraceCleanupFailure("遍历动态数据缓存", exception); + } + } + + private static void ClearDynamicModels(object instance) + { + if (instance == 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 + { + ClearDynamicModel(field.GetValue(instance)); + } + catch (Exception exception) + { + Debug.WriteLine("清理动态缓存字段失败:" + exception); + } + } + + private static void ClearDynamicModel(object value) + { + DynamicModel model = value as DynamicModel; + if (model == null || model.DataCaches == null) + return; + Dictionary caches = model.DataCaches; + foreach (Hashtable cache in caches.Values.OfType().ToList()) + { + foreach (IDisposable item in cache.Values.OfType().ToList()) + try { item.Dispose(); } catch { } + cache.Clear(); + } + caches.Clear(); + model.DataCaches = null; + } /// /// 记录当前U盾权限窗口 /// @@ -84,4 +282,4 @@ namespace Lskj.Control.Model } -} \ No newline at end of file +} diff --git a/插件库/Lskj.Control/ModuleEx.Designer.cs b/插件库/Lskj.Control/ModuleEx.Designer.cs index 66bd75a..9266dde 100644 --- a/插件库/Lskj.Control/ModuleEx.Designer.cs +++ b/插件库/Lskj.Control/ModuleEx.Designer.cs @@ -13,9 +13,13 @@ /// 如果应释放托管资源,为 true;否则为 false。 protected override void Dispose(bool disposing) { - if (disposing && (components != null)) + if (disposing) { - components.Dispose(); + ReleaseResourcesForDispose(); + if (components != null) + { + components.Dispose(); + } } base.Dispose(disposing); } diff --git a/插件库/Lskj.Control/ModuleEx.cs b/插件库/Lskj.Control/ModuleEx.cs index 4b273f8..f983a66 100644 --- a/插件库/Lskj.Control/ModuleEx.cs +++ b/插件库/Lskj.Control/ModuleEx.cs @@ -62,6 +62,8 @@ namespace Lskj.Control /// 左侧表格查询条件 /// private MyControl _leftGridSearchObj; + private GridDragTree _gridDragTree; + private bool mModuleResourcesReleased; public bool IsExcel { get { return Model is DynamicExcelModel; } } /// @@ -152,6 +154,134 @@ namespace Lskj.Control InitializeComponent(); } + public void ReleaseResourcesForDispose() + { + if (mModuleResourcesReleased) + { + return; + } + + mModuleResourcesReleased = true; + HashSet details = + new HashSet(); + HashSet grids = new HashSet(); + foreach (System.Windows.Forms.Control control in + EnumerateModuleControls(this)) + { + ModuleGridDetailEx detail = control as ModuleGridDetailEx; + if (detail != null) + { + details.Add(detail); + } + ModuleGridEx grid = control as ModuleGridEx; + if (grid != null) + { + grids.Add(grid); + } + } + + foreach (ModuleGridDetailEx detail in details) + { + detail.ReleaseResourcesForDispose(); + } + foreach (ModuleGridEx grid in grids) + { + grid.SaveEvent -= SetDetailGridView; + grid.OnExportCallBack -= SetExportGridView; + grid.ReleaseResourcesForDispose(); + } + + MyControl leftSearch = _leftGridSearchObj; + _leftGridSearchObj = null; + if (leftSearch != null) + { + leftSearch.OnDataSourceBindCallBack -= OnLeftControlObjDataSourceBindCallBack; + leftSearch.Dispose(); + } + + GridDragTree dragTree = _gridDragTree; + _gridDragTree = null; + if (dragTree != null) + { + dragTree.OnDragComplete -= OnDragTreeCompleted; + dragTree.Dispose(); + } + + try + { + if (treeLeft != null) + { + treeLeft.TreeNodeSelectAfter -= OnTreeLeftTreeNodeSelected; + treeLeft.OnTreeNodeMouseDoubleClick -= TreeLeft_OnTreeNodeMouseDoubleClick; + } + if (gridLeft != null && gridLeft.GridView != null) + { + gridLeft.GridView.RowClick -= OnGridViewRowClick; + } + if (treeGridLeft != null && treeGridLeft.TreeListObj != null) + { + treeGridLeft.TreeListObj.Click -= TreeListObj_Click; + treeGridLeft.TreeListObj.BeforeExpand -= TreeListObj_BeforeExpand; + treeGridLeft.TreeListObj.BeforeCollapse -= TreeListObj_BeforeCollapse; + } + if (MainBodyTab != null) + { + MainBodyTab.SelectedPageChanged -= MainBodyTab_SelectedPageChanged; + } + if (excelControlEx1 != null) + { + excelControlEx1.SaveCallBack -= OnExcelSaveClick; + } + } + catch + { + // A partially disposed child control must not stop cleanup. + } + + try + { + if (Model != null && Model.DataCaches != null) + { + Model.DataCaches.Remove(this); + } + } + catch + { + // Cache cleanup must not interrupt module disposal. + } + + OnLeftGridViewRowClickCallBack = null; + OnLeftTreeViewRowClickCallBack = null; + if (Details != null) + { + Details.Clear(); + } + LeftRowObj = null; + ColumnNameTable = null; + _currentRow = null; + ExcelControlObj = null; + Model = null; + SysModel = null; + } + + private static IEnumerable + EnumerateModuleControls(System.Windows.Forms.Control parent) + { + if (parent == null) + { + yield break; + } + foreach (System.Windows.Forms.Control child in parent.Controls) + { + yield return child; + foreach (System.Windows.Forms.Control nested in + EnumerateModuleControls(child)) + { + yield return nested; + } + } + } + /// /// Excel 控件包含 SpreadsheetControl、BarManager 和命令注册项, /// 普通表格模块不应在设计器初始化阶段创建这些重型资源。 @@ -249,9 +379,9 @@ namespace Lskj.Control if (Model.HasOperPrivilege() && model.IsBaseModule) { // 有权限则允许拖拽 - GridDragTree dragTree = new GridDragTree(this.ModuleGridObj.GridControlObj.GridView, this.treeLeft.TreeView); - dragTree.CanDragParentNode = true; - dragTree.OnDragComplete += new GridDragTreeCompleteEventHandler(OnDragTreeCompleted); + this._gridDragTree = new GridDragTree(this.ModuleGridObj.GridControlObj.GridView, this.treeLeft.TreeView); + this._gridDragTree.CanDragParentNode = true; + this._gridDragTree.OnDragComplete += new GridDragTreeCompleteEventHandler(OnDragTreeCompleted); } if (this.SysModel.TreeDoubleClick) { @@ -477,7 +607,6 @@ namespace Lskj.Control { case 1: case 4://左侧为树节点 - this.ModuleGridObj.LeftTreeViewEx = this.treeLeft; Task baseLeftTreeFieldTask = cachesDic.AddTask(this, "BaseLeftTreeField", new Task(() => { return BaseModuleImpl.GetBaseLeftTreeField(dynamicModel.ModuleCode);//获取表格左侧树字段 @@ -531,12 +660,7 @@ namespace Lskj.Control DataTable customQueryFieldsTab = customQueryFieldsTask.Result; if (customQueryFieldsTab != null && customQueryFieldsTab.Rows.Count > 0) { - Task leftGridSearchObjTask = cachesDic.AddTask(this, "LeftGridSearchObj", new Task(() => - { - string searchSql = baseLeftTreeFieldTask.Result["fieldsql"] + ""; - return new MyControl(searchSql, sysModel.IsTreeTable ? this.treeGridLeft as GridControlEx : this.gridLeft); - })); - ModuleGridDetailObj._leftGridSearchObj = leftGridSearchObjTask.Result; + // MyControl 必须由后续 UI 初始化阶段创建。 } else { @@ -558,7 +682,7 @@ namespace Lskj.Control Task customColumnByDatabaseTask = cachesDic.AddTask(gridLeft, "CustomColumnByDatabase", new Task(() => { string customColumKey = GridCustomColumnStruct.BaseLeftGridView + dynamicModel.ModuleCode; - return gridLeft.GridView.GetCustomColumnByDatabase(customColumKey); + return GridExtend.GetCustomColumnByDatabase(customColumKey); })); } else @@ -566,12 +690,11 @@ namespace Lskj.Control Task customColumnByDatabaseTask = cachesDic.AddTask(treeGridLeft, "CustomColumnByDatabase", new Task(() => { string customColumKey = GridCustomColumnStruct.BaseLeftGridView + dynamicModel.ModuleCode; - return treeGridLeft.GridView.GetCustomColumnByDatabase(customColumKey); + return GridExtend.GetCustomColumnByDatabase(customColumKey); })); } } } - ModuleGridObj.LeftGridEx = sysModel.IsTreeTable ? this.treeGridLeft : this.gridLeft; break; } if (dynamicModel is DynamicExcelModel)//isExcel diff --git a/插件库/Lskj.Control/ModuleGridDetailEx.Designer.cs b/插件库/Lskj.Control/ModuleGridDetailEx.Designer.cs index f254ab3..c9dd9ae 100644 --- a/插件库/Lskj.Control/ModuleGridDetailEx.Designer.cs +++ b/插件库/Lskj.Control/ModuleGridDetailEx.Designer.cs @@ -13,9 +13,13 @@ /// 如果应释放托管资源,为 true;否则为 false。 protected override void Dispose(bool disposing) { - if (disposing && (components != null)) + if (disposing) { - components.Dispose(); + ReleaseResourcesForDispose(); + if (components != null) + { + components.Dispose(); + } } base.Dispose(disposing); } diff --git a/插件库/Lskj.Control/ModuleGridDetailEx.cs b/插件库/Lskj.Control/ModuleGridDetailEx.cs index 9c9024e..3b16b31 100644 --- a/插件库/Lskj.Control/ModuleGridDetailEx.cs +++ b/插件库/Lskj.Control/ModuleGridDetailEx.cs @@ -39,6 +39,8 @@ namespace Lskj.Control /// public partial class ModuleGridDetailEx : UserControl { + private bool mModuleResourcesReleased; + private bool mDetailPageCachesPrepared; /// /// 系统模块实体对象 @@ -147,6 +149,68 @@ namespace Lskj.Control { InitializeComponent(); } + + internal void ReleaseResourcesForDispose() + { + if (mModuleResourcesReleased) + { + return; + } + + mModuleResourcesReleased = true; + MyControl searchObj = SearchObj; + if (searchObj != null) + { + searchObj.OnSearchAfterCallBack -= SearchObj_ClearDetails_DoubleMode; + searchObj.OnLastQueryTriggered -= SearchObj_ClearDetails_DoubleMode; + searchObj.OnSearchAfterCallBack -= SearchObj_ClearDetails; + searchObj.OnLastQueryTriggered -= SearchObj_ClearDetails; + } + + if (ModuleGridObj != null) + { + ModuleGridObj.AfterRefreshingCallBack -= ModuleGridObj_AfterRefreshingCallBack; + try + { + GridControlEx grid = ModuleGridObj.GridControlObj; + if (grid != null && grid.GridView != null) + { + grid.GridView.DoubleClick -= GridView_DoubleClick; + grid.GridView.DoubleClick -= OnGridViewDoubleClick; + grid.GridView.Click -= OnGridViewDoubleClick; + grid.GridView.MouseDown -= GridView_MouseDown; + grid.GridView.FocusedRowObjectChanged -= OnGridViewFocusedRowObjectChanged; + } + } + catch + { + // A partially disposed DevExpress view must not stop cleanup. + } + ModuleGridObj.ReleaseResourcesForDispose(); + } + + try + { + if (Model != null && Model.DataCaches != null) + { + Model.DataCaches.Remove(this); + } + } + catch + { + // Cache cleanup must not interrupt control disposal. + } + + OnGridViewRowClickCallBack = null; + if (GridDetailList != null) + { + GridDetailList.Clear(); + } + datamodel.Clear(); + _leftGridSearchObj = null; + Model = null; + SysModel = null; + } /// /// 说明:初始化控件 /// 创建人:龚宇超 @@ -189,6 +253,7 @@ namespace Lskj.Control } else { + PrepareDetailPageCaches(dataCaches); // 初始化底部多标签 this.InitializePages(); } @@ -220,7 +285,6 @@ namespace Lskj.Control //获取通用数据 Task sysModelTask = cachesDic.GetTask(this, "SysModel"); Task dynamicModelTask = cachesDic.GetTask(this, "DynamicModel"); - Task> detailsTask = cachesDic.GetTask>(this, "Details"); ModuleGridObj._leftGridSearchObj = _leftGridSearchObj; Task getDataCachesTask = cachesDic.AddTask(this, "DataCaches", new Task(() => { @@ -231,23 +295,29 @@ namespace Lskj.Control cachesDic.AddTask(ModuleGridObj, "DynamicModel", dynamicModelTask); cachesDic.AddTask(ModuleGridObj, "SysModel", sysModelTask); this.ModuleGridObj.GetDataCaches(cachesDic); - if (!string.IsNullOrEmpty(sysModel.MainModuleCodeField) && this.ModuleGridObj.GridControlObj.GridView.Columns.ColumnByFieldName(sysModel.MainModuleCodeField) != null) - { - //InitializeDynamicPages(); - } - else - { - //添加数据到mControl - cachesDic.AddTask(tcButtom, "DynamicModel", dynamicModelTask); - cachesDic.AddTask(tcButtom, "Details", detailsTask); - // 初始化底部多标签 - this.tcButtom.GetDataCaches(cachesDic); - } } return true; })); } + private void PrepareDetailPageCaches( + Dictionary cachesDic) + { + if (mDetailPageCachesPrepared || cachesDic == null) + { + return; + } + + mDetailPageCachesPrepared = true; + Task dynamicModelTask = + cachesDic.GetTask(this, "DynamicModel"); + Task> detailsTask = + cachesDic.GetTask>(this, "Details"); + cachesDic.AddTask(tcButtom, "DynamicModel", dynamicModelTask); + cachesDic.AddTask(tcButtom, "Details", detailsTask); + this.tcButtom.GetDataCaches(cachesDic); + } + /// /// 说明:初始化底部多标签数据 @@ -1515,4 +1585,4 @@ namespace Lskj.Control } -} \ No newline at end of file +} diff --git a/插件库/Lskj.Control/ModuleGridEx.Designer.cs b/插件库/Lskj.Control/ModuleGridEx.Designer.cs index 6a4000b..9410207 100644 --- a/插件库/Lskj.Control/ModuleGridEx.Designer.cs +++ b/插件库/Lskj.Control/ModuleGridEx.Designer.cs @@ -15,6 +15,7 @@ { if (disposing) { + ReleaseResourcesForDispose(); Lskj.Control.Model.PrintUtil.OnAfterPrint -= new System.EventHandler(OnReportPrintAfter); if (components != null) diff --git a/插件库/Lskj.Control/ModuleGridEx.cs b/插件库/Lskj.Control/ModuleGridEx.cs index fa7e96a..97fa756 100644 --- a/插件库/Lskj.Control/ModuleGridEx.cs +++ b/插件库/Lskj.Control/ModuleGridEx.cs @@ -424,6 +424,7 @@ namespace Lskj.Control /// 自定义查询条件 /// public MyControl SearchObj { get; private set; } + private bool mModuleResourcesReleased; /// /// 左查询条件 /// @@ -516,6 +517,17 @@ namespace Lskj.Control searchObj = new MyControl(ReplaceBmpField(this.SysModel.MenuSql), gcMain, LeftTreeViewEx, LeftGridEx, _leftGridSearchObj);//替换sql语句中的bmp字段 searchObj.SpecialLeftTable = this.SpecialLeftTable; searchObj.Model = Model; + if (!fixedQuery && Model.DataCaches != null) + { + searchObj.OtherParams = Model.OtherParams(); + DynamicModel searchModel = Model; + DataTable searchControls = _queryTable; + Model.DataCaches.AddTask(searchObj, "DynamicModel", + new Task(() => searchModel)); + Model.DataCaches.AddTask(searchObj, "ControlsTable", + new Task(() => searchControls)); + searchObj.GetSearchDataCaches(Model.DataCaches); + } } this.SearchObj = searchObj; @@ -2459,8 +2471,6 @@ namespace Lskj.Control form.SubForm.Dispose(); form = null; - GC.Collect(); - GC.WaitForPendingFinalizers(); } } catch (Exception ex) @@ -3680,6 +3690,102 @@ namespace Lskj.Control this.OperShortMode = true; } + internal void ReleaseResourcesForDispose() + { + if (mModuleResourcesReleased) + { + return; + } + + mModuleResourcesReleased = true; + PrintUtil.OnAfterPrint -= OnReportPrintAfter; + + MyControl searchObj = SearchObj; + SearchObj = null; + if (searchObj != null) + { + searchObj.OnSearchBeforeCallBack -= OnSearchBeforeCallBack; + searchObj.OnSearchAfterCallBack -= OnSearchAfterCallBack; + searchObj.OnDataSourceBindCallBack -= OnMainSearchDataSourceBindCallBack; + searchObj.Dispose(); + } + + try + { + if (gcMain != null) + { + if (gcMain.GridView != null) + { + gcMain.GridView.FocusedRowObjectChanged -= OnGridViewFocusedRowObjectChanged; + gcMain.GridView.ShowingEditor -= OnGridViewShowingEditor; + gcMain.GridView.DoubleClick -= OnGridViewDoubleClick; + gcMain.GridView.MouseDown -= OnGridViewMouseDown; + gcMain.GridView.MouseDown -= OnGridMouseDown; + gcMain.GridView.MouseMove -= OnGridMouseMove; + gcMain.GridView.MouseUp -= OnGridMouseUp; + } + if (gcMain.CustomGroupBandEx != null) + { + gcMain.CustomGroupBandEx.GridView.FocusedRowObjectChanged -= OnGridViewFocusedRowObjectChanged; + gcMain.CustomGroupBandEx.GridView.ShowingEditor -= OnGridViewShowingEditor; + } + if (gcMain.ParentControl != null && + ReferenceEquals(gcMain.ParentControl, searchObj)) + { + gcMain.ParentControl = null; + } + gcMain.Model = null; + gcMain.SysModel = null; + } + } + catch + { + // A partially disposed DevExpress view must not stop module cleanup. + } + + try + { + if (Model != null && Model.DataCaches != null) + { + Model.DataCaches.Remove(this); + } + } + catch + { + // Cache cleanup must not interrupt control disposal. + } + + OnSaveGridCallBack = null; + AfterDeleteGridCallBack = null; + RightGridCallBack = null; + DetailRightGridCallBack = null; + OnCloseCallback = null; + AfterRefreshingCallBack = null; + SaveEvent = null; + OnExportCallBack = null; + verifyDetailsTab = null; + + if (m_dragRowShadow != null) + { + m_dragRowShadow.Dispose(); + m_dragRowShadow = null; + } + ButtonModeRightMenus.Clear(); + RightButtons.Clear(); + itemCommonList.Clear(); + LineStatus.Clear(); + _leftGridSearchObj = null; + LeftTreeViewEx = null; + LeftGridEx = null; + SpecialLeftTable = null; + ParentGridEx = null; + ParentControlEx = null; + ParentControlObj = null; + TabObj = null; + Model = null; + SysModel = null; + } + /// /// 明细父表格点击后刷新明细按钮权限 /// @@ -3962,25 +4068,6 @@ namespace Lskj.Control { return BaseModuleImpl.GetSchemesList(dynamicModel.ModuleId);//获取高级查询条件模版 })); - //InitializeQueryCondition - Task searchObjTask = cachesDic.AddTask(this, "SearchObj", new Task(() => - { - DataTable queryTable = customQueryFieldsTask.Result; - bool fixedQuery = queryTable == null || queryTable.Rows.Count == 0; - MyControl myControl = new MyControl(sysModel.MenuSql, gcMain, LeftTreeViewEx, LeftGridEx, _leftGridSearchObj) - { - SpecialLeftTable = this.SpecialLeftTable, - Model = dynamicModel - }; - if (!fixedQuery) - { - myControl.OtherParams = dynamicModel.OtherParams(); - cachesDic.AddTask(myControl, "DynamicModel", dynamicModelTask); - cachesDic.AddTask(myControl, "ControlsTable", customQueryFieldsTask); - myControl.GetSearchDataCaches(cachesDic); - } - return myControl; - })); Task searchSysModelTask = cachesDic.AddTask(pl_top_search, "SysModel", new Task(() => { return new ModuleModel(MainImpl.GetSystemdllTab(dynamicModel.ModuleCode)); diff --git a/插件库/Lskj.Control/ModulePanelEx.Designer.cs b/插件库/Lskj.Control/ModulePanelEx.Designer.cs index 63066db..8c0c538 100644 --- a/插件库/Lskj.Control/ModulePanelEx.Designer.cs +++ b/插件库/Lskj.Control/ModulePanelEx.Designer.cs @@ -14,9 +14,14 @@ namespace Lskj.Control /// 如果应释放托管资源,为 true;否则为 false。 protected override void Dispose(bool disposing) { - if (disposing && (components != null)) + if (disposing) { - components.Dispose(); + Lskj.Control.Model.PrintUtil.OnAfterPrint -= + new EventHandler(OnReportPrintAfter); + if (components != null) + { + components.Dispose(); + } } base.Dispose(disposing); } diff --git a/插件库/Lskj.Control/ReplacementDetailEx.Designer.cs b/插件库/Lskj.Control/ReplacementDetailEx.Designer.cs index 4ff1ad5..342fa53 100644 --- a/插件库/Lskj.Control/ReplacementDetailEx.Designer.cs +++ b/插件库/Lskj.Control/ReplacementDetailEx.Designer.cs @@ -13,9 +13,13 @@ /// 如果应释放托管资源,为 true;否则为 false。 protected override void Dispose(bool disposing) { - if (disposing && (components != null)) + if (disposing) { - components.Dispose(); + ReleaseResourcesForDispose(); + if (components != null) + { + components.Dispose(); + } } base.Dispose(disposing); } diff --git a/插件库/Lskj.Control/ReplacementDetailEx.cs b/插件库/Lskj.Control/ReplacementDetailEx.cs index e054788..39f4126 100644 --- a/插件库/Lskj.Control/ReplacementDetailEx.cs +++ b/插件库/Lskj.Control/ReplacementDetailEx.cs @@ -37,6 +37,8 @@ namespace Lskj.Control /// public partial class ReplacementDetailEx : UserControl { + private bool mResourcesReleased; + private bool mDetailPageCachesPrepared; /// /// 系统模块实体对象 /// @@ -102,6 +104,39 @@ namespace Lskj.Control { InitializeComponent(); } + + internal void ReleaseResourcesForDispose() + { + if (mResourcesReleased) + { + return; + } + + mResourcesReleased = true; + if (ModuleGridObj != null) + { + ModuleGridObj.ReleaseResourcesForDispose(); + } + try + { + if (Model != null && Model.DataCaches != null) + { + Model.DataCaches.Remove(this); + } + } + catch + { + // Cache cleanup must not interrupt control disposal. + } + + if (GridDetailList != null) + { + GridDetailList.Clear(); + } + _leftGridSearchObj = null; + Model = null; + SysModel = null; + } /// /// 说明:初始化控件 /// 创建人:龚宇超 @@ -128,6 +163,7 @@ namespace Lskj.Control this.ModuleGridObj.InitializeControl(sysModel, dyncModel); RefreshDetail = this.SysModel.RefreshDetail == "0"; Console.WriteLine(DateTime.Now); + PrepareDetailPageCaches(dyncModel.DataCaches); // 初始化底部多标签 this.InitializePages(); Console.WriteLine(DateTime.Now); @@ -162,15 +198,27 @@ namespace Lskj.Control //获取通用数据 Task sysModelTask = cachesDic.GetTask(this, "SysModel"); Task dynamicModelTask = cachesDic.GetTask(this, "DynamicModel"); - Task> detailsTask = cachesDic.GetTask>(this, "Details"); cachesDic.AddTask(ModuleGridObj, "DynamicModel", dynamicModelTask); cachesDic.AddTask(ModuleGridObj, "SysModel", sysModelTask); - cachesDic.AddTask(ModuleGridObj, "Details", detailsTask); - cachesDic.AddTask(tcButtom, "DynamicModel", dynamicModelTask); - cachesDic.AddTask(tcButtom, "Details", detailsTask); //初始化顶部表格 ModuleGridObj.GetDataCaches(cachesDic); - // 初始化底部多标签 + } + + private void PrepareDetailPageCaches( + Dictionary cachesDic) + { + if (mDetailPageCachesPrepared || cachesDic == null) + { + return; + } + + mDetailPageCachesPrepared = true; + Task dynamicModelTask = + cachesDic.GetTask(this, "DynamicModel"); + Task> detailsTask = + cachesDic.GetTask>(this, "Details"); + cachesDic.AddTask(tcButtom, "DynamicModel", dynamicModelTask); + cachesDic.AddTask(tcButtom, "Details", detailsTask); tcButtom.GetDataCaches(cachesDic); } @@ -650,4 +698,4 @@ namespace Lskj.Control } } -} \ No newline at end of file +} diff --git a/插件库/Lskj.Control/TreeBandedGridControlEx.cs b/插件库/Lskj.Control/TreeBandedGridControlEx.cs index 714ebdc..7887784 100644 --- a/插件库/Lskj.Control/TreeBandedGridControlEx.cs +++ b/插件库/Lskj.Control/TreeBandedGridControlEx.cs @@ -2112,6 +2112,7 @@ namespace Lskj.Control public override int SetGridViewDataSource(DbDataAdapter adapter, bool selectRowHandler = true) { if (adapter == null) return 0; + DbDataAdapter previousAdapter = this.AdapterObj; if (this.ColumnList == null || this.ColumnList.Count == 0) { this.gcMain.Bands.Clear(); @@ -2122,53 +2123,74 @@ namespace Lskj.Control lastNodeIndex = 0; if (selectRowHandler) lastNodeIndex = this.TreeListObj.GetVisibleIndexByNode(this.TreeListObj.FocusedNode); - this.AdapterObj = adapter; DataSet dataSet = new DataSet(); //adapter.Fill(dataSet, "SetGridViewDataSource"); // 重连配置 int maxRetryCount = 2; // 次数 int retryDelay = 1000; // 等待时间 - for (int i = 0; i < maxRetryCount; i++) + try { - try + for (int i = 0; i < maxRetryCount; i++) { - adapter.Fill(dataSet, "SetGridViewDataSource"); - break;//连接成功 - } - catch (SqlException ex) - { - // 判断是否是连接相关错误 - if (IsConnectionError(ex.Number) || - ex.Message.Contains("物理连接不可用") || - ex.Message.Contains("传输级错误")) + try { - if (i < maxRetryCount - 1) + adapter.Fill(dataSet, "SetGridViewDataSource"); + break;//连接成功 + } + catch (SqlException ex) + { + // 判断是否是连接相关错误 + if (IsConnectionError(ex.Number) || + ex.Message.Contains("物理连接不可用") || + ex.Message.Contains("传输级错误")) { - SqlConnection.ClearAllPools(); - System.Threading.Thread.Sleep(retryDelay); - continue; // 重试 + if (i < maxRetryCount - 1) + { + SqlConnection.ClearAllPools(); + System.Threading.Thread.Sleep(retryDelay); + continue; // 重试 + } + else + { + // 最后一次重试失败,抛出错误 + throw new Exception($"经过 {maxRetryCount} 次重试后仍无法连接到数据库: {ex.Message}", ex); + } } else { - // 最后一次重试失败,抛出错误 - throw new Exception($"经过 {maxRetryCount} 次重试后仍无法连接到数据库: {ex.Message}", ex); + // 其他SQL错误直接抛出 + throw; } } - else - { - // 其他SQL错误直接抛出 - throw; - } } } + catch + { + dataSet.Dispose(); + DisposeDataAdapter(adapter); + throw; + } + DataTable resultTable = dataSet.Tables.Count > 0 ? dataSet.Tables[0] : null; + int rowCount = resultTable == null ? 0 : resultTable.Rows.Count; + if (resultTable != null) + { + dataSet.Tables.Remove(resultTable); + } + dataSet.Dispose(); - DbCommandBuilder cb = SqlHelper.dbFactory.CreateCommandBuilder(); - cb.DataAdapter = this.AdapterObj; + DisposeAdapterCommandBuilder(); + if (previousAdapter != null && !ReferenceEquals(previousAdapter, adapter)) + { + DisposeDataAdapter(previousAdapter); + } + this.AdapterObj = adapter; + + SetAdapterCommandBuilder(this.AdapterObj); //SqlCommandBuilder cb = new SqlCommandBuilder(this.AdapterObj); GetTreeExpanded(); - this.gcMain.DataSource = dataSet.Tables[0]; + this.gcMain.DataSource = resultTable; this.SetAutoColumns(); //根据记录展开对应节点 foreach (TreeListNode treeListNode in treeListNodes) @@ -2193,7 +2215,7 @@ namespace Lskj.Control { this.TreeListObj.FocusedNode = lastNode; } - return dataSet.Tables[0] != null ? dataSet.Tables[0].Rows.Count : 0; + return rowCount; } diff --git a/插件库/Lskj.Control/TreeGridControlEx.cs b/插件库/Lskj.Control/TreeGridControlEx.cs index f45eef8..1a82955 100644 --- a/插件库/Lskj.Control/TreeGridControlEx.cs +++ b/插件库/Lskj.Control/TreeGridControlEx.cs @@ -2540,60 +2540,83 @@ namespace Lskj.Control public override int SetGridViewDataSource(DbDataAdapter adapter, bool selectRowHandler = true) { if (adapter == null) return 0; + DbDataAdapter previousAdapter = this.AdapterObj; if (this.ColumnList == null || this.ColumnList.Count == 0) { this.TreeListObj.Columns.Clear(); } lastNodeIndex = 0; if (selectRowHandler) lastNodeIndex = this.TreeListObj.GetVisibleIndexByNode(this.TreeListObj.FocusedNode); - this.AdapterObj = adapter; DataSet dataSet = new DataSet(); //adapter.Fill(dataSet, "SetGridViewDataSource"); // 重连配置 int maxRetryCount = 2; // 次数 int retryDelay = 1000; // 等待时间 - for (int i = 0; i < maxRetryCount; i++) + try { - try + for (int i = 0; i < maxRetryCount; i++) { - adapter.Fill(dataSet, "SetGridViewDataSource"); - break;//连接成功 - } - catch (SqlException ex) - { - // 判断是否是连接相关错误 - if (IsConnectionError(ex.Number) || - ex.Message.Contains("物理连接不可用") || - ex.Message.Contains("传输级错误")) + try { - if (i < maxRetryCount - 1) + adapter.Fill(dataSet, "SetGridViewDataSource"); + break;//连接成功 + } + catch (SqlException ex) + { + // 判断是否是连接相关错误 + if (IsConnectionError(ex.Number) || + ex.Message.Contains("物理连接不可用") || + ex.Message.Contains("传输级错误")) { - SqlConnection.ClearAllPools(); - System.Threading.Thread.Sleep(retryDelay); - continue; // 重试 + if (i < maxRetryCount - 1) + { + SqlConnection.ClearAllPools(); + System.Threading.Thread.Sleep(retryDelay); + continue; // 重试 + } + else + { + // 最后一次重试失败,抛出错误 + throw new Exception($"经过 {maxRetryCount} 次重试后仍无法连接到数据库: {ex.Message}", ex); + } } else { - // 最后一次重试失败,抛出错误 - throw new Exception($"经过 {maxRetryCount} 次重试后仍无法连接到数据库: {ex.Message}", ex); + // 其他SQL错误直接抛出 + throw; } } - else - { - // 其他SQL错误直接抛出 - throw; - } } } - DbCommandBuilder cb = SqlHelper.dbFactory.CreateCommandBuilder(); - cb.DataAdapter = this.AdapterObj; + catch + { + dataSet.Dispose(); + DisposeDataAdapter(adapter); + throw; + } + DataTable resultTable = dataSet.Tables.Count > 0 ? dataSet.Tables[0] : null; + int rowCount = resultTable == null ? 0 : resultTable.Rows.Count; + if (resultTable != null) + { + dataSet.Tables.Remove(resultTable); + } + dataSet.Dispose(); + + DisposeAdapterCommandBuilder(); + if (previousAdapter != null && !ReferenceEquals(previousAdapter, adapter)) + { + DisposeDataAdapter(previousAdapter); + } + this.AdapterObj = adapter; + + SetAdapterCommandBuilder(this.AdapterObj); //SqlCommandBuilder cb = new SqlCommandBuilder(this.AdapterObj); GetTreeExpanded(); - this.TreeListObj.DataSource = dataSet.Tables[0]; + this.TreeListObj.DataSource = resultTable; this.AdjustIndicatorWidthSmart(); this.SetAutoColumns(); @@ -2609,7 +2632,7 @@ namespace Lskj.Control { this.TreeListObj.CollapseAll(); } - return dataSet.Tables[0] != null ? dataSet.Tables[0].Rows.Count : 0; + return rowCount; } diff --git a/插件库/Lskj.Main/Program.cs b/插件库/Lskj.Main/Program.cs index 2f1274f..e4914e7 100644 --- a/插件库/Lskj.Main/Program.cs +++ b/插件库/Lskj.Main/Program.cs @@ -185,7 +185,18 @@ namespace Lskj.Main { if (_cefInitialized) { - CefRuntime.Shutdown(); + bool browsersClosed = + CefBrowserRuntimeLifetime.CloseAllBrowsersAndWait(5000); + if (browsersClosed) + { + CefRuntime.Shutdown(); + } + else + { + LogHelper.Instance.WriteLog( + "[CEF_LIFECYCLE] CefRuntime.Shutdown skipped " + + "because browser close callbacks are incomplete."); + } _cefInitialized = false; } _mainProcessInitialized = false; @@ -234,14 +245,18 @@ namespace Lskj.Main commandLine.AppendSwitch("ignore-certificate-errors", "1"); commandLine.AppendSwitch("allow-running-insecure-content", "1"); - //如果注册表中CefMultithreading不为1,并且根目录中不存在BrowserMultithreading.txt"文件,就是单线程模式 - //只要有一个条件满足就是多线程模式 - if (!BrowserMultithreading() && !File.Exists(PubUtil.AbsolutelyPath + "BrowserMultithreading.txt")) - { - commandLine.AppendSwitch("single-process", "1");//单线程模式,默认开启。(开启单线程模式后会影响浏览器的性能) - } + // CEF's single-process switch is unsupported and is unrelated + // to MultiThreadedMessageLoop. Keep renderer and utility work + // in CEF's normal child processes, as in the official sample. commandLine.AppendSwitch("no-proxy-server"); + // CEF 87 can terminate the host with STATUS_WX86_BREAKPOINT + // while creating the first browser on machines whose GPU + // driver does not support the legacy compositor path. Keep + // software rendering enabled, but bypass GPU initialization. + commandLine.AppendSwitch("disable-gpu"); + commandLine.AppendSwitch("disable-gpu-compositing"); + commandLine.AppendSwitch("disable-features", "VizDisplayCompositor"); commandLine.AppendSwitch("ppapi-flash-version", "99.0.0.999"); commandLine.AppendSwitch("ppapi-flash-path", @"PepperFlash\pepflashplayer.dll"); //} diff --git a/插件库/Lskj.PubAccraditation/FrmAccraditation.Designer.cs b/插件库/Lskj.PubAccraditation/FrmAccraditation.Designer.cs index ffe33a0..c75c055 100644 --- a/插件库/Lskj.PubAccraditation/FrmAccraditation.Designer.cs +++ b/插件库/Lskj.PubAccraditation/FrmAccraditation.Designer.cs @@ -13,9 +13,13 @@ /// true if managed resources should be disposed; otherwise, false. protected override void Dispose(bool disposing) { - if (disposing && (components != null)) + if (disposing) { - components.Dispose(); + ReleaseResourcesForDispose(); + if (components != null) + { + components.Dispose(); + } } base.Dispose(disposing); } @@ -767,4 +771,4 @@ private DevExpress.XtraBars.PopupMenu pmFP; private DevExpress.XtraBars.BarManager barManager2; } -} \ No newline at end of file +} diff --git a/插件库/Lskj.PubAccraditation/FrmAccraditation.cs b/插件库/Lskj.PubAccraditation/FrmAccraditation.cs index f854069..803a5c1 100644 --- a/插件库/Lskj.PubAccraditation/FrmAccraditation.cs +++ b/插件库/Lskj.PubAccraditation/FrmAccraditation.cs @@ -48,6 +48,55 @@ namespace Lskj.PubAccraditation { InitializeComponent(); } + + private void ReleaseResourcesForDispose() + { + if (mResourcesReleased) + { + return; + } + + mResourcesReleased = true; + PrintUtil.OnAfterPrint -= OnReportPrintAfter; + + MyControl control = ControlObj; + ControlObj = null; + if (control != null) + { + control.OnDataSourceBindCallBack -= + OnControlObjOnDataSourceBindCallBack; + control.OnDataFileRefrshCallBack -= OnDataFileRefrshFile; + control.Dispose(); + } + + try + { + if (gridBillInfo != null) + { + TreeGridControlEx treeGrid = + gridBillInfo as TreeGridControlEx; + if (treeGrid != null) + { + treeGrid.OnDataSourceBindCallBack -= + OnGridBillInfoOnDataSourceBindCallBack; + } + else + { + gridBillInfo.OnDataSourceBindCallBack -= + OnGridBillInfoOnDataSourceBindCallBack; + } + gridBillInfo.ParentControl = null; + } + if (SysModel != null && SysModel.DataCaches != null) + { + SysModel.DataCaches.Remove(this); + } + } + catch + { + // A partially disposed child must not stop form cleanup. + } + } #region 公有变量 /// @@ -70,6 +119,7 @@ namespace Lskj.PubAccraditation /// 主表控件 /// public MyControl ControlObj; + private bool mResourcesReleased; /// /// 是否是Brp模块加载审核明细 /// @@ -385,16 +435,6 @@ namespace Lskj.PubAccraditation { return BaseModuleImpl.GetControlLocation(billAuditModelTask.Result.FormKey, dynamicModel.ModuleCode); })); - Task controlObjTask = cachesDic.AddTask(this, "ControlObj", new Task(() => - { - MyControl myControl = new MyControl(); - myControl.Model = SysModel; - myControl.PrimaryValue = dynamicModel.BillDocumentId; - cachesDic.AddTask(myControl, "DynamicModel", dynamicModelTask); - cachesDic.AddTask(myControl, "ControlsTable", controlLocationTask); - myControl.GetControlDataCaches(cachesDic); - return myControl; - })); Task detailColumnsTask = cachesDic.AddTask(gridBillInfo, "DetailColumns", new Task(() => { return BillImpl.GetDetailColumns(dynamicModel.ModuleCode); @@ -980,14 +1020,26 @@ namespace Lskj.PubAccraditation { Dictionary dataCaches = SysModel != null ? SysModel.DataCaches : null; // 初始化表头 + bool controlCreated = false; if (!dataCaches.GetValue(this, "ControlObj", out this.ControlObj)) { this.ControlObj = new MyControl(); + controlCreated = true; } if (!dataCaches.GetValue(this, "ControlLocation", out DataTable dtControlLocation)) { dtControlLocation = BaseModuleImpl.GetControlLocation(this.BillModelObj.FormKey, this.SysModel.ModuleCode); } + if (controlCreated && dataCaches != null) + { + DynamicModel controlModel = SysModel; + DataTable controlsTable = dtControlLocation; + dataCaches.AddTask(ControlObj, "DynamicModel", + new Task(() => controlModel)); + dataCaches.AddTask(ControlObj, "ControlsTable", + new Task(() => controlsTable)); + ControlObj.GetControlDataCaches(dataCaches); + } this.ControlObj.PrimaryValue = this.SysModel.BillDocumentId; this.ControlObj.isAuditMain = true; this.ControlObj.InitControl(dtControlLocation, this.pl_left_top); diff --git a/插件库/Lskj.PubBill/BillModule.Designer.cs b/插件库/Lskj.PubBill/BillModule.Designer.cs index 813f0e6..ab0c3d9 100644 --- a/插件库/Lskj.PubBill/BillModule.Designer.cs +++ b/插件库/Lskj.PubBill/BillModule.Designer.cs @@ -45,7 +45,6 @@ this.pl_main = new DevExpress.XtraEditors.PanelControl(); this.pl_main_center = new DevExpress.XtraEditors.PanelControl(); this.pl_main_center_detail = new DevExpress.XtraEditors.PanelControl(); - this.gcMain = new Lskj.Control.GridControlEx(); this.pl_main_center_master = new DevExpress.XtraEditors.XtraScrollableControl(); this.pl_treeview_detault_search = new DevExpress.XtraEditors.PanelControl(); this.panelControl4 = new DevExpress.XtraEditors.PanelControl(); @@ -102,7 +101,6 @@ ((System.ComponentModel.ISupportInitialize)(this.pl_main_center)).BeginInit(); this.pl_main_center.SuspendLayout(); ((System.ComponentModel.ISupportInitialize)(this.pl_main_center_detail)).BeginInit(); - this.pl_main_center_detail.SuspendLayout(); this.pl_main_center_master.SuspendLayout(); ((System.ComponentModel.ISupportInitialize)(this.pl_treeview_detault_search)).BeginInit(); this.pl_treeview_detault_search.SuspendLayout(); @@ -279,26 +277,12 @@ this.pl_main_center_detail.Appearance.BackColor = System.Drawing.Color.Transparent; this.pl_main_center_detail.Appearance.Options.UseBackColor = true; this.pl_main_center_detail.BorderStyle = DevExpress.XtraEditors.Controls.BorderStyles.NoBorder; - this.pl_main_center_detail.Controls.Add(this.gcMain); this.pl_main_center_detail.Dock = System.Windows.Forms.DockStyle.Fill; this.pl_main_center_detail.Location = new System.Drawing.Point(0, 164); this.pl_main_center_detail.Name = "pl_main_center_detail"; this.pl_main_center_detail.Size = new System.Drawing.Size(1211, 202); this.pl_main_center_detail.TabIndex = 5; // - // gcMain - // - this.gcMain.AdapterObj = null; - this.gcMain.BackColor = System.Drawing.Color.Transparent; - this.gcMain.Dock = System.Windows.Forms.DockStyle.Fill; - this.gcMain.Location = new System.Drawing.Point(0, 0); - this.gcMain.Margin = new System.Windows.Forms.Padding(5); - this.gcMain.Name = "gcMain"; - this.gcMain.SiftEnumObj = Lskj.Control.GridControlEx.HeaderSiftEnum.Sum; - this.gcMain.Size = new System.Drawing.Size(1211, 202); - this.gcMain.SysModel = null; - this.gcMain.TabIndex = 0; - // // pl_main_center_master // this.pl_main_center_master.AutoScroll = false; @@ -854,7 +838,6 @@ ((System.ComponentModel.ISupportInitialize)(this.pl_main_center)).EndInit(); this.pl_main_center.ResumeLayout(false); ((System.ComponentModel.ISupportInitialize)(this.pl_main_center_detail)).EndInit(); - this.pl_main_center_detail.ResumeLayout(false); this.pl_main_center_master.ResumeLayout(false); ((System.ComponentModel.ISupportInitialize)(this.pl_treeview_detault_search)).EndInit(); this.pl_treeview_detault_search.ResumeLayout(false); diff --git a/插件库/Lskj.PubBill/BillModule.cs b/插件库/Lskj.PubBill/BillModule.cs index fe87289..abd9bab 100644 --- a/插件库/Lskj.PubBill/BillModule.cs +++ b/插件库/Lskj.PubBill/BillModule.cs @@ -26,6 +26,7 @@ using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Data.SqlClient; +using System.Diagnostics; using System.Drawing; using System.IO; using System.Linq; @@ -89,6 +90,7 @@ namespace Lskj.PubBill /// private DataTable _detailColumns; private bool IsCopying = false; + private bool _moduleResourcesReleased; protected DataRow BillRowItem; @@ -260,6 +262,155 @@ namespace Lskj.PubBill InitializeComponent(); } + /// + /// Releases runtime objects created by the bill module. Cleanup is + /// idempotent and best-effort so it cannot change the close result. + /// + public void ReleaseModuleResources() + { + if (_moduleResourcesReleased) + return; + _moduleResourcesReleased = true; + + TryRelease(() => PrintUtil.OnAfterPrint -= new EventHandler(OnReportPrintAfter), "打印回调"); + TryRelease(ReleaseModuleEvents, "模块事件"); + TryRelease(ReleaseModuleDataSources, "模块数据源"); + TryRelease(ReleaseRuntimeCollections, "运行时集合"); + + BillRowItem = null; + ControlObj = null; + BillModel = null; + SysModel = null; + SumGridColumn = null; + ScanCodeTextEdit = null; + InitialSelectionTab = null; + _imageEdit = null; + _waterMark = null; + _itemImport = null; + _itemImportUpdate = null; + } + + private void ReleaseModuleEvents() + { + if (ScanCodeTextEdit != null) + ScanCodeTextEdit.KeyDown -= Text_KeyDown; + + if (gcMain != null) + { + gcMain.OnAfterLinkCilk -= OnAfterLinkCilkCall; + gcMain.OnAllAfterParseCallBack -= OnAllAfterParseCallBack; + if (gcMain.GridControl != null) + gcMain.GridControl.DataSourceChanged -= OnGcMainDataSourceChanged; + if (gcMain.GridView != null) + { + gcMain.GridView.KeyDown -= OnToWithdraw; + gcMain.GridView.ShowingEditor -= OnGridViewShowingEditor; + gcMain.GridView.MouseDown -= GridView_MouseDown; + gcMain.GridView.FocusedRowChanged -= GridView_FocusedRowChanged; + gcMain.GridView.InitNewRow -= OnGridViewInitNewRow; + gcMain.GridView.CellValueChanged -= OnGridViewCellValueChanged; + gcMain.GridView.RowCellClick -= OngcMainRowCellClick; + } + } + + if (tb_buttom != null) + { + tb_buttom.OnDetailSaveGridCallBack -= Tab_OnDetailSaveGridCallBack; + tb_buttom.BillDetailRightGridCallBack -= Tab_OnDetailSaveGridCallBack; + tb_buttom.OnRightCallback -= OnButtomRightCallBack; + if (tb_buttom.TabControlObj != null) + tb_buttom.TabControlObj.SelectedPageChanged -= TabControlObj_SelectedPageChanged; + } + } + + private void ReleaseModuleDataSources() + { + foreach (System.Windows.Forms.Control control in EnumerateChildControls(this).ToList()) + { + GridControlEx grid = control as GridControlEx; + if (grid != null) + { + TreeGridControlEx treeGrid = grid as TreeGridControlEx; + if (treeGrid != null && treeGrid.TreeListObj != null) + treeGrid.TreeListObj.DataSource = null; + if (grid.GridControl != null) + grid.GridControl.DataSource = null; + } + + XtraTabPage tabPage = control as XtraTabPage; + if (tabPage != null) + tabPage.Tag = null; + } + + if (_imageEdit != null && _imageEdit.TextEdit != null) + _imageEdit.TextEdit.Image = null; + if (cb_input != null) + cb_input.Properties.Items.Clear(); + if (cb_templete != null) + cb_templete.Properties.Items.Clear(); + } + + private static IEnumerable EnumerateChildControls( + System.Windows.Forms.Control root) + { + if (root == null) + yield break; + yield return root; + foreach (System.Windows.Forms.Control child in root.Controls) + foreach (System.Windows.Forms.Control nested in EnumerateChildControls(child)) + yield return nested; + } + + private void ReleaseRuntimeCollections() + { + foreach (BillModuleModel model in BillModuleModelDic.Values.ToList()) + { + if (model == null) + continue; + model.SourceControl = null; + model.SourceDetailControl = null; + model.SourceDetailDetailControl = null; + model.SourceModel = null; + } + BillModuleModelDic.Clear(); + pageNumKey.Clear(); + foreach (Dictionary rows in ToWithdrawRows) + if (rows != null) + rows.Clear(); + ToWithdrawRows.Clear(); + ChangeColorRow.Clear(); + DateColumnList.Clear(); + itemCommonList.Clear(); + ColorHandles.Clear(); + if (DetailMappingField != null) + DetailMappingField.Clear(); + if (_printSaveParams != null) + _printSaveParams.Clear(); + + if (_detailColumns != null) + { + _detailColumns.Dispose(); + _detailColumns = null; + } + if (SaveCondTab != null) + { + SaveCondTab.Dispose(); + SaveCondTab = null; + } + } + + private static void TryRelease(Action release, string operation) + { + try + { + release(); + } + catch (Exception exception) + { + Debug.WriteLine("释放单据模块资源失败[" + operation + "]:" + exception); + } + } + #region 初始化操作 /// /// 说明:窗口加载 diff --git a/插件库/Lskj.PubBill/FrmMain.cs b/插件库/Lskj.PubBill/FrmMain.cs index 4ae5aee..0abc7c2 100644 --- a/插件库/Lskj.PubBill/FrmMain.cs +++ b/插件库/Lskj.PubBill/FrmMain.cs @@ -209,5 +209,21 @@ namespace Lskj.PubBill } } + + protected override void OnFormClosed(FormClosedEventArgs e) + { + base.OnFormClosed(e); + this.FormClosing -= OnClose; + this.Activated -= FrmMain_Activated; + try + { + if (billModule1 != null) + billModule1.ReleaseModuleResources(); + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine("释放单据窗口资源失败:" + ex); + } + } } -} \ No newline at end of file +} diff --git a/插件库/Lskj.PubBrower/DllBaseClass.cs b/插件库/Lskj.PubBrower/DllBaseClass.cs index 579865d..e8884d6 100644 --- a/插件库/Lskj.PubBrower/DllBaseClass.cs +++ b/插件库/Lskj.PubBrower/DllBaseClass.cs @@ -33,7 +33,30 @@ namespace Lskj.PubBrower this.SubForm = main; this.SubForm.Text = moduleModel.FormText; main.PubBrowerModel = moduleModel; - main.OnLoad(); + + // CEF must be created after the caller has established the final + // WinForms owner/parent hierarchy and the form HWND exists. + if (moduleModel.downLoadFlag == 1) + { + main.OnLoad(); + } + else + { + EventHandler shown = null; + shown = delegate + { + main.Shown -= shown; + try + { + main.OnLoad(); + } + catch (Exception ex) + { + LogUtil.WriteError("Lskj.PubBrower.dll--初始化错误-->" + obj.ToString(), ex); + } + }; + main.Shown += shown; + } } catch (Exception ex) { diff --git a/插件库/Lskj.PubBrower/FrmMain.cs b/插件库/Lskj.PubBrower/FrmMain.cs index bec8a74..79a0b8b 100644 --- a/插件库/Lskj.PubBrower/FrmMain.cs +++ b/插件库/Lskj.PubBrower/FrmMain.cs @@ -100,8 +100,8 @@ namespace Lskj.PubBrower previewFrm previewFrm = new previewFrm(); previewFrm.moduleWebView.PubBrowerModel = this.PubBrowerModel; previewFrm.Dock = DockStyle.Fill; - previewFrm.OnLoad(url, RightKeyFlag); this.Controls.Add(previewFrm); + previewFrm.OnLoad(url, RightKeyFlag); } } catch (Exception ex) diff --git a/插件库/Lskj.PubBrower/previewFrm.cs b/插件库/Lskj.PubBrower/previewFrm.cs index af0b313..822e2d6 100644 --- a/插件库/Lskj.PubBrower/previewFrm.cs +++ b/插件库/Lskj.PubBrower/previewFrm.cs @@ -16,7 +16,6 @@ namespace Lskj.PubBrower public previewFrm() { InitializeComponent(); - this.SizeChanged += OnSizeChanged; } /// /// 说明:窗口加载 @@ -48,18 +47,5 @@ namespace Lskj.PubBrower MessageUtil.Show(Message, ex.Message); } } - /// - /// 窗体大小改变时 - /// - /// - /// - private void OnSizeChanged(object sender, EventArgs e) - { - if (this.moduleWebView.cefBrowserSettings != null) - { - var handle = this.moduleWebView.cefBrowserSettings.GetHost().GetWindowHandle(); - this.moduleWebView.ResizeWindow(handle, this.Width, this.Height); - } - } } } diff --git a/插件库/Lskj.PubBrower2/DllBaseClass.cs b/插件库/Lskj.PubBrower2/DllBaseClass.cs index 54f5caa..ea6b865 100644 --- a/插件库/Lskj.PubBrower2/DllBaseClass.cs +++ b/插件库/Lskj.PubBrower2/DllBaseClass.cs @@ -33,7 +33,30 @@ namespace Lskj.PubBrower2 this.SubForm = main; this.SubForm.Text = moduleModel.FormText; main.PubBrowerModel = moduleModel; - main.OnLoad(); + + // CEF must be created after the caller has established the final + // WinForms owner/parent hierarchy and the form HWND exists. + if (moduleModel.downLoadFlag == 1) + { + main.OnLoad(); + } + else + { + EventHandler shown = null; + shown = delegate + { + main.Shown -= shown; + try + { + main.OnLoad(); + } + catch (Exception ex) + { + LogUtil.WriteError("Lskj.PubBrower2.dll--初始化错误-->" + obj.ToString(), ex); + } + }; + main.Shown += shown; + } } catch (Exception ex) { diff --git a/插件库/Lskj.PubBrower2/FrmMain.cs b/插件库/Lskj.PubBrower2/FrmMain.cs index 53e665c..888ec25 100644 --- a/插件库/Lskj.PubBrower2/FrmMain.cs +++ b/插件库/Lskj.PubBrower2/FrmMain.cs @@ -95,8 +95,8 @@ namespace Lskj.PubBrower2 previewFrm previewFrm = new previewFrm(); previewFrm.moduleWebView.PubBrowerModel = this.PubBrowerModel; previewFrm.Dock = DockStyle.Fill; - previewFrm.OnLoad(url, RightKeyFlag); this.Controls.Add(previewFrm); + previewFrm.OnLoad(url, RightKeyFlag); } } diff --git a/插件库/Lskj.PubBrower2/previewFrm.cs b/插件库/Lskj.PubBrower2/previewFrm.cs index ffd2a19..dd27cd5 100644 --- a/插件库/Lskj.PubBrower2/previewFrm.cs +++ b/插件库/Lskj.PubBrower2/previewFrm.cs @@ -16,7 +16,6 @@ namespace Lskj.PubBrower2 public previewFrm() { InitializeComponent(); - this.SizeChanged += OnSizeChanged; } /// /// 说明:窗口加载 @@ -49,18 +48,5 @@ namespace Lskj.PubBrower2 } } - /// - /// 窗体大小改变时 - /// - /// - /// - private void OnSizeChanged(object sender, EventArgs e) - { - if (this.moduleWebView.cefBrowserSettings != null) - { - var handle = this.moduleWebView.cefBrowserSettings.GetHost().GetWindowHandle(); - this.moduleWebView.ResizeWindow(handle, this.Width, this.Height); - } - } } } diff --git a/插件库/Lskj.PubModelAdd/BaseModelAdd.Designer.cs b/插件库/Lskj.PubModelAdd/BaseModelAdd.Designer.cs index 7668a2b..f9c7f09 100644 --- a/插件库/Lskj.PubModelAdd/BaseModelAdd.Designer.cs +++ b/插件库/Lskj.PubModelAdd/BaseModelAdd.Designer.cs @@ -13,9 +13,13 @@ /// 如果应释放托管资源,为 true;否则为 false。 protected override void Dispose(bool disposing) { - if (disposing && (components != null)) + if (disposing) { - components.Dispose(); + ReleaseResourcesForDispose(); + if (components != null) + { + components.Dispose(); + } } base.Dispose(disposing); } diff --git a/插件库/Lskj.PubModelAdd/BaseModelAdd.cs b/插件库/Lskj.PubModelAdd/BaseModelAdd.cs index d81c54e..080f505 100644 --- a/插件库/Lskj.PubModelAdd/BaseModelAdd.cs +++ b/插件库/Lskj.PubModelAdd/BaseModelAdd.cs @@ -25,6 +25,24 @@ namespace Lskj.PubModelAdd { InitializeComponent(); } + + private void ReleaseResourcesForDispose() + { + if (mResourcesReleased) + { + return; + } + + mResourcesReleased = true; + MyControl search = _leftGridSearchObj; + _leftGridSearchObj = null; + if (search != null) + { + search.OnDataSourceBindCallBack -= + OnLeftControlObjDataSourceBindCallBack; + search.Dispose(); + } + } #region 私有变量 /// /// 模块初始化完成 @@ -42,6 +60,7 @@ namespace Lskj.PubModelAdd /// 左侧表格查询条件 /// private MyControl _leftGridSearchObj; + private bool mResourcesReleased; #endregion #region 公共变量 /// diff --git a/插件库/Lskj.PubModelAdd2/BaseModelAdd.Designer.cs b/插件库/Lskj.PubModelAdd2/BaseModelAdd.Designer.cs index 53c5806..46705da 100644 --- a/插件库/Lskj.PubModelAdd2/BaseModelAdd.Designer.cs +++ b/插件库/Lskj.PubModelAdd2/BaseModelAdd.Designer.cs @@ -13,9 +13,13 @@ /// 如果应释放托管资源,为 true;否则为 false。 protected override void Dispose(bool disposing) { - if (disposing && (components != null)) + if (disposing) { - components.Dispose(); + ReleaseResourcesForDispose(); + if (components != null) + { + components.Dispose(); + } } base.Dispose(disposing); } diff --git a/插件库/Lskj.PubModelAdd2/BaseModelAdd.cs b/插件库/Lskj.PubModelAdd2/BaseModelAdd.cs index c92db4b..5e232bc 100644 --- a/插件库/Lskj.PubModelAdd2/BaseModelAdd.cs +++ b/插件库/Lskj.PubModelAdd2/BaseModelAdd.cs @@ -28,6 +28,24 @@ namespace Lskj.PubModelAdd2 { InitializeComponent(); } + + private void ReleaseResourcesForDispose() + { + if (mResourcesReleased) + { + return; + } + + mResourcesReleased = true; + MyControl search = _leftGridSearchObj; + _leftGridSearchObj = null; + if (search != null) + { + search.OnDataSourceBindCallBack -= + OnLeftControlObjDataSourceBindCallBack; + search.Dispose(); + } + } #region 私有变量 /// /// 模块初始化完成 @@ -45,6 +63,7 @@ namespace Lskj.PubModelAdd2 /// 左侧表格查询条件 /// private MyControl _leftGridSearchObj; + private bool mResourcesReleased; #endregion #region 公共变量 /// diff --git a/插件库/Lskj.PubModelAdd3/BaseModelAdd.Designer.cs b/插件库/Lskj.PubModelAdd3/BaseModelAdd.Designer.cs index 4f668db..54cc42d 100644 --- a/插件库/Lskj.PubModelAdd3/BaseModelAdd.Designer.cs +++ b/插件库/Lskj.PubModelAdd3/BaseModelAdd.Designer.cs @@ -13,9 +13,13 @@ /// 如果应释放托管资源,为 true;否则为 false。 protected override void Dispose(bool disposing) { - if (disposing && (components != null)) + if (disposing) { - components.Dispose(); + ReleaseResourcesForDispose(); + if (components != null) + { + components.Dispose(); + } } base.Dispose(disposing); } diff --git a/插件库/Lskj.PubModelAdd3/BaseModelAdd.cs b/插件库/Lskj.PubModelAdd3/BaseModelAdd.cs index 0fa8a43..11aa1b6 100644 --- a/插件库/Lskj.PubModelAdd3/BaseModelAdd.cs +++ b/插件库/Lskj.PubModelAdd3/BaseModelAdd.cs @@ -28,6 +28,24 @@ namespace Lskj.PubModelAdd3 { InitializeComponent(); } + + private void ReleaseResourcesForDispose() + { + if (mResourcesReleased) + { + return; + } + + mResourcesReleased = true; + MyControl search = _leftGridSearchObj; + _leftGridSearchObj = null; + if (search != null) + { + search.OnDataSourceBindCallBack -= + OnLeftControlObjDataSourceBindCallBack; + search.Dispose(); + } + } #region 私有变量 /// /// 模块初始化完成 @@ -45,6 +63,7 @@ namespace Lskj.PubModelAdd3 /// 左侧表格查询条件 /// private MyControl _leftGridSearchObj; + private bool mResourcesReleased; #endregion #region 公共变量 /// diff --git a/插件库/Lskj.PubSpec2/FrmMain.Designer.cs b/插件库/Lskj.PubSpec2/FrmMain.Designer.cs index a1330d0..13b86d9 100644 --- a/插件库/Lskj.PubSpec2/FrmMain.Designer.cs +++ b/插件库/Lskj.PubSpec2/FrmMain.Designer.cs @@ -13,9 +13,13 @@ /// true if managed resources should be disposed; otherwise, false. protected override void Dispose(bool disposing) { - if (disposing && (components != null)) + if (disposing) { - components.Dispose(); + ReleaseResourcesForDispose(); + if (components != null) + { + components.Dispose(); + } } base.Dispose(disposing); } @@ -280,4 +284,4 @@ private DevExpress.XtraEditors.SimpleButton btnShowAll; private DevExpress.XtraEditors.SimpleButton btnRefresh; } -} \ No newline at end of file +} diff --git a/插件库/Lskj.PubSpec2/FrmMain.cs b/插件库/Lskj.PubSpec2/FrmMain.cs index 4724211..cbd2b00 100644 --- a/插件库/Lskj.PubSpec2/FrmMain.cs +++ b/插件库/Lskj.PubSpec2/FrmMain.cs @@ -41,6 +41,7 @@ namespace Lskj.PubSpec2 /// 表格列 /// private DataTable _gridColumns; + private GridDragTree _dragTree; /// /// 模块对象 @@ -56,6 +57,17 @@ namespace Lskj.PubSpec2 InitializeComponent(); } + private void ReleaseResourcesForDispose() + { + GridDragTree dragTree = _dragTree; + _dragTree = null; + if (dragTree != null) + { + dragTree.OnDragComplete -= OnDragCompleted; + dragTree.Dispose(); + } + } + /// /// 说明:窗口加载 /// 创建人:龚宇超 @@ -229,9 +241,9 @@ namespace Lskj.PubSpec2 this.gcMain.Model = this.Model; this._gridColumns = BaseModuleImpl.GetBaseGridColumns(this.Model.ModuleCode); - GridDragTree dragTree = new GridDragTree(this.gcMain.GridView, this.treeLeft.TreeView); - dragTree.CanDragParentNode = true; - dragTree.OnDragComplete += new GridDragTreeCompleteEventHandler(OnDragCompleted); + this._dragTree = new GridDragTree(this.gcMain.GridView, this.treeLeft.TreeView); + this._dragTree.CanDragParentNode = true; + this._dragTree.OnDragComplete += new GridDragTreeCompleteEventHandler(OnDragCompleted); this.gcMain.IsSpecModel = true; this.gcMain.SetEditColumns(this._gridColumns); diff --git a/插件库/Lskj.Report/FrmMain.Designer.cs b/插件库/Lskj.Report/FrmMain.Designer.cs index e26137c..b33862f 100644 --- a/插件库/Lskj.Report/FrmMain.Designer.cs +++ b/插件库/Lskj.Report/FrmMain.Designer.cs @@ -13,9 +13,13 @@ /// 如果应释放托管资源,为 true;否则为 false。 protected override void Dispose(bool disposing) { - if (disposing && (components != null)) + if (disposing) { - components.Dispose(); + DisposeReportGridViews(); + if (components != null) + { + components.Dispose(); + } } base.Dispose(disposing); } diff --git a/插件库/Lskj.Report/FrmMain.cs b/插件库/Lskj.Report/FrmMain.cs index 79442e3..2bc25d2 100644 --- a/插件库/Lskj.Report/FrmMain.cs +++ b/插件库/Lskj.Report/FrmMain.cs @@ -24,6 +24,8 @@ using Lskj.Business.Impl; using Lskj.Business; using System.Threading.Tasks; using System.Collections; +using System.IO; +using System.Reflection; namespace Lskj.Report { @@ -34,9 +36,19 @@ namespace Lskj.Report /// public DynamicReportModel Model; + private readonly HashSet + mPreexistingGridLocalizerViews = CaptureGridLocalizerViews(); + private readonly HashSet + mReportGridLocalizerViews = + new HashSet(); + private bool mReportGridResourcesReleased; + private int mDisposedReportGridViewCount; + private int mRemovedGridLocalizerHandlerCount; + public FrmMain() { InitializeComponent(); + this.Disposed += OnFrmMainDisposed; } /// @@ -66,6 +78,7 @@ namespace Lskj.Report this.mControl.Details = details; this.mControl.InitControls(Model); + CaptureReportGridLocalizerViews(); this.FormClosing += new FormClosingEventHandler(OnClose); } catch (Exception ex) @@ -86,7 +99,570 @@ namespace Lskj.Report private void OnClose(object sender, FormClosingEventArgs e) { + WriteReportLifecycleDiagnostics("closing-before-close-module"); this.mControl.CloseModule(); + WriteReportLifecycleDiagnostics("closing-after-close-module"); + } + + private void OnFrmMainDisposed(object sender, EventArgs e) + { + DisposeReportGridViews(); + WriteReportLifecycleDiagnostics( + "disposed-event;disposedGridViews=" + + mDisposedReportGridViewCount + + ";gridLocalizerRemoved=" + + mRemovedGridLocalizerHandlerCount); + mReportGridLocalizerViews.Clear(); + mPreexistingGridLocalizerViews.Clear(); + this.FormClosing -= OnClose; + this.Disposed -= OnFrmMainDisposed; + + DynamicReportModel model = this.Model; + this.Model = null; + if (model == null || model.DataCaches == null) + { + return; + } + + try + { + model.DataCaches.Clear(); + } + catch (Exception ex) + { + LogHelper.Instance.WriteError(ex, ResourceKeys.BaseReport); + } + } + + private void DisposeReportGridViews() + { + if (mReportGridResourcesReleased) + { + return; + } + + mReportGridResourcesReleased = true; + WriteReportLifecycleDiagnostics("dispose-before-gridviews"); + CaptureReportGridLocalizerViews(); + if (this.mControl != null) + { + this.mControl.ReleaseResourcesForDispose(); + } + + HashSet disposedViews = + new HashSet(); + int disposeErrorCount = 0; + int detachedDataSourceCount = 0; + foreach (System.Windows.Forms.Control control in + EnumerateControls(this.mControl)) + { + DevExpress.XtraGrid.GridControl gridControl = + control as DevExpress.XtraGrid.GridControl; + if (gridControl != null) + { + try + { + if (gridControl.DataSource != null) + { + gridControl.DataSource = null; + detachedDataSourceCount++; + } + } + catch (Exception ex) + { + disposeErrorCount++; + LogHelper.Instance.WriteError(ex, ResourceKeys.BaseReport); + } + + DevExpress.XtraGrid.Views.Base.BaseView[] views = + gridControl.ViewCollection + .Cast() + .ToArray(); + foreach (DevExpress.XtraGrid.Views.Base.BaseView view in views) + { + if (view == null || !disposedViews.Add(view)) + { + continue; + } + + try + { + view.Dispose(); + } + catch (Exception ex) + { + disposeErrorCount++; + LogHelper.Instance.WriteError(ex, ResourceKeys.BaseReport); + } + } + } + + Type controlType = control.GetType(); + if (controlType.FullName != null && + controlType.FullName.StartsWith( + "DevExpress.XtraTreeList.", + StringComparison.Ordinal)) + { + try + { + PropertyInfo dataSourceProperty = + controlType.GetProperty( + "DataSource", + BindingFlags.Instance | + BindingFlags.Public); + if (dataSourceProperty != null && + dataSourceProperty.CanRead && + dataSourceProperty.CanWrite && + dataSourceProperty.GetValue(control, null) != null) + { + dataSourceProperty.SetValue(control, null, null); + detachedDataSourceCount++; + } + } + catch (Exception ex) + { + disposeErrorCount++; + LogHelper.Instance.WriteError(ex, ResourceKeys.BaseReport); + } + } + } + + int trackedGridViews = mReportGridLocalizerViews.Count; + int removedGridLocalizerHandlers = + ReleaseTrackedGridLocalizerViews(disposedViews); + mDisposedReportGridViewCount = disposedViews.Count; + mRemovedGridLocalizerHandlerCount = removedGridLocalizerHandlers; + WriteReportLifecycleDiagnostics( + "dispose-after-gridviews;dispose-attempts=" + + disposedViews.Count + + ";dispose-errors=" + + disposeErrorCount + + ";detachedDataSources=" + + detachedDataSourceCount + + ";trackedGridViews=" + + trackedGridViews + + ";gridLocalizerRemoved=" + + removedGridLocalizerHandlers); + } + + private void CaptureReportGridLocalizerViews() + { + HashSet currentViews = + CaptureGridLocalizerViews(); + foreach (DevExpress.XtraGrid.Views.Base.BaseView view in currentViews) + { + if (!mPreexistingGridLocalizerViews.Contains(view)) + { + mReportGridLocalizerViews.Add(view); + } + } + } + + private int ReleaseTrackedGridLocalizerViews( + HashSet disposedViews) + { + foreach (DevExpress.XtraGrid.Views.Base.BaseView view in + mReportGridLocalizerViews) + { + if (view == null || + (disposedViews != null && !disposedViews.Add(view))) + { + continue; + } + + try + { + view.Dispose(); + } + catch (Exception exception) + { + LogHelper.Instance.WriteError(exception, ResourceKeys.BaseReport); + } + } + + return RemoveGridLocalizerHandlers(mReportGridLocalizerViews); + } + + private static HashSet + CaptureGridLocalizerViews() + { + HashSet result = + new HashSet(); + try + { + var activeLocalizer = + DevExpress.XtraGrid.Localization.GridLocalizer.Active; + if (activeLocalizer == null) + { + return result; + } + + Type currentType = activeLocalizer.GetType(); + while (currentType != null && currentType != typeof(object)) + { + FieldInfo[] fields = currentType.GetFields( + BindingFlags.Instance | + BindingFlags.Static | + BindingFlags.Public | + BindingFlags.NonPublic | + BindingFlags.DeclaredOnly); + foreach (FieldInfo field in fields) + { + if (!typeof(Delegate).IsAssignableFrom(field.FieldType)) + { + continue; + } + + object owner = field.IsStatic ? null : (object)activeLocalizer; + Delegate current; + try + { + current = field.GetValue(owner) as Delegate; + } + catch + { + continue; + } + if (current == null) + { + continue; + } + + foreach (Delegate handler in current.GetInvocationList()) + { + if (IsGridLocalizerHandler(handler)) + { + result.Add( + (DevExpress.XtraGrid.Views.Base.BaseView) + handler.Target); + } + } + } + + currentType = currentType.BaseType; + } + } + catch + { + // Diagnostics cleanup must never interfere with form construction. + } + + return result; + } + + private static int RemoveGridLocalizerHandlers( + HashSet targetViews) + { + if (targetViews == null || targetViews.Count == 0) + { + return 0; + } + + int removedCount = 0; + try + { + var activeLocalizer = + DevExpress.XtraGrid.Localization.GridLocalizer.Active; + if (activeLocalizer == null) + { + return 0; + } + + Type currentType = activeLocalizer.GetType(); + while (currentType != null && currentType != typeof(object)) + { + FieldInfo[] fields = currentType.GetFields( + BindingFlags.Instance | + BindingFlags.Static | + BindingFlags.Public | + BindingFlags.NonPublic | + BindingFlags.DeclaredOnly); + foreach (FieldInfo field in fields) + { + if (!typeof(Delegate).IsAssignableFrom(field.FieldType)) + { + continue; + } + + object owner = field.IsStatic ? null : (object)activeLocalizer; + Delegate current; + try + { + current = field.GetValue(owner) as Delegate; + } + catch + { + continue; + } + if (current == null) + { + continue; + } + + Delegate retained = null; + int fieldRemovedCount = 0; + foreach (Delegate handler in current.GetInvocationList()) + { + DevExpress.XtraGrid.Views.Base.BaseView view = + handler.Target as DevExpress.XtraGrid.Views.Base.BaseView; + if (IsGridLocalizerHandler(handler) && + targetViews.Contains(view)) + { + fieldRemovedCount++; + continue; + } + + retained = retained == null + ? handler + : Delegate.Combine(retained, handler); + } + + if (fieldRemovedCount == 0) + { + continue; + } + + try + { + field.SetValue(owner, retained); + removedCount += fieldRemovedCount; + } + catch + { + // Some DevExpress versions expose a read-only backing field. + } + } + + currentType = currentType.BaseType; + } + } + catch + { + // Diagnostics cleanup must never interfere with form disposal. + } + + return removedCount; + } + + private static bool IsGridLocalizerHandler(Delegate handler) + { + return handler != null && + string.Equals( + handler.Method.Name, + "OnLocalizer_Changed", + StringComparison.Ordinal) && + handler.Target is DevExpress.XtraGrid.Views.Base.BaseView; + } + + private void WriteReportLifecycleDiagnostics(string phase) + { + if (!IsReportDiagnosticsEnabled()) + { + return; + } + + try + { + LogHelper.Instance.WriteLog( + "REPORT_DIAGNOSTIC phase=" + + (phase ?? string.Empty) + + " " + + GetReportGridDiagnostics(), + "ResourceDiagnostics.Report"); + } + catch + { + // Diagnostics must never interfere with module close/dispose. + } + } + + private string GetReportGridDiagnostics() + { + int controlCount = 0; + int gridControlCount = 0; + int viewCount = 0; + HashSet views = + new HashSet(); + StringBuilder viewDetails = new StringBuilder(); + + try + { + if (this.mControl != null) + { + foreach (System.Windows.Forms.Control control in EnumerateControls(this.mControl)) + { + controlCount++; + DevExpress.XtraGrid.GridControl gridControl = + control as DevExpress.XtraGrid.GridControl; + if (gridControl == null) + { + continue; + } + + gridControlCount++; + foreach (DevExpress.XtraGrid.Views.Base.BaseView view in + gridControl.ViewCollection.Cast()) + { + if (view == null || !views.Add(view)) + { + continue; + } + + viewCount++; + if (viewDetails.Length < 1800) + { + if (viewDetails.Length > 0) + { + viewDetails.Append(","); + } + + viewDetails.Append(SafeViewDescription(view)); + } + } + } + } + } + catch (Exception exception) + { + viewDetails.Append(",enumeration-error="); + viewDetails.Append(exception.GetType().Name); + } + + return "formDisposed=" + + this.IsDisposed + + ";controlCount=" + + controlCount + + ";gridControlCount=" + + gridControlCount + + ";viewCount=" + + viewCount + + ";views=" + + viewDetails + + ";gridLocalizer=" + + DescribeGridLocalizer(); + } + + private static string SafeViewDescription( + DevExpress.XtraGrid.Views.Base.BaseView view) + { + try + { + return view.GetType().FullName + + ":" + + (view.Name ?? string.Empty).Replace(";", ","); + } + catch + { + return ""; + } + } + + private static string DescribeGridLocalizer() + { + try + { + var active = + DevExpress.XtraGrid.Localization.GridLocalizer.Active; + if (active == null) + { + return "active=null"; + } + + StringBuilder handlers = new StringBuilder(); + int handlerCount = 0; + Type currentType = active.GetType(); + while (currentType != null && currentType != typeof(object)) + { + FieldInfo[] fields = currentType.GetFields( + BindingFlags.Instance | + BindingFlags.Static | + BindingFlags.Public | + BindingFlags.NonPublic | + BindingFlags.DeclaredOnly); + foreach (FieldInfo field in fields) + { + if (!typeof(Delegate).IsAssignableFrom(field.FieldType)) + { + continue; + } + + object owner = field.IsStatic ? null : (object)active; + Delegate value = field.GetValue(owner) as Delegate; + if (value == null) + { + continue; + } + + foreach (Delegate handler in value.GetInvocationList()) + { + handlerCount++; + if (handlers.Length < 1800) + { + if (handlers.Length > 0) + { + handlers.Append(","); + } + + string targetType = handler.Target == null + ? "static" + : handler.Target.GetType().FullName; + handlers.Append(field.Name.Replace(";", ",")); + handlers.Append("->"); + handlers.Append(targetType.Replace(";", ",")); + handlers.Append("."); + handlers.Append(handler.Method.Name.Replace(";", ",")); + } + } + } + + currentType = currentType.BaseType; + } + + return "activeType=" + + active.GetType().FullName.Replace(";", ",") + + ";handlerCount=" + + handlerCount + + ";handlers=" + + handlers; + } + catch (Exception exception) + { + return "localizer-error=" + exception.GetType().Name; + } + } + + private static bool IsReportDiagnosticsEnabled() + { + try + { + return File.Exists( + Path.Combine( + AppDomain.CurrentDomain.BaseDirectory, + "Logs", + "ResourceDiagnostics", + "resource-diagnostics.enabled")); + } + catch + { + return false; + } + } + + private static IEnumerable EnumerateControls( + System.Windows.Forms.Control root) + { + if (root == null) + { + yield break; + } + yield return root; + foreach (System.Windows.Forms.Control child in root.Controls) + { + foreach (System.Windows.Forms.Control nested in EnumerateControls(child)) + { + yield return nested; + } + } } ///