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
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
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
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;
+ }
+ }
}
///