fix(winforms): release module grid resources

This commit is contained in:
2026-08-26 01:10:27 +08:00
parent 6cde5df2f2
commit 1e06682a12
8 changed files with 491 additions and 85 deletions
@@ -31,6 +31,9 @@ using Lskj.Business;
public static class LabPicUrlPreview
{
private static readonly object AttachedViewsSync = new object();
private static readonly List<WeakReference> AttachedViews = new List<WeakReference>();
// ====== 对外:初始化图片列(创建 RepositoryItemPictureEdit、设为 Unbound ======
public static void InitPicColumn(GridControl gridControl, GridColumn gridColumn, GridColumnModel model)
{
@@ -61,6 +64,7 @@ public static class LabPicUrlPreview
if (view == null) return;
view.CustomUnboundColumnData -= OnCustomUnboundColumnData;
view.CustomUnboundColumnData += OnCustomUnboundColumnData;
TrackAttachedView(view);
}
// ====== 对外:绑定“双击大图预览”的事件(只需调用一次) ======
@@ -69,6 +73,54 @@ public static class LabPicUrlPreview
if (view == null) return;
view.DoubleClick -= OnViewDoubleClick;
view.DoubleClick += OnViewDoubleClick;
TrackAttachedView(view);
}
public static void Detach(ColumnView view)
{
if (view == null) return;
view.CustomUnboundColumnData -= OnCustomUnboundColumnData;
view.DoubleClick -= OnViewDoubleClick;
if (UntrackAttachedView(view))
{
ImageCache.Clear();
}
}
private static void TrackAttachedView(ColumnView view)
{
lock (AttachedViewsSync)
{
for (int i = AttachedViews.Count - 1; i >= 0; i--)
{
object target = AttachedViews[i].Target;
if (target == null)
{
AttachedViews.RemoveAt(i);
}
else if (ReferenceEquals(target, view))
{
return;
}
}
AttachedViews.Add(new WeakReference(view));
}
}
private static bool UntrackAttachedView(ColumnView view)
{
lock (AttachedViewsSync)
{
for (int i = AttachedViews.Count - 1; i >= 0; i--)
{
object target = AttachedViews[i].Target;
if (target == null || ReferenceEquals(target, view))
{
AttachedViews.RemoveAt(i);
}
}
return AttachedViews.Count == 0;
}
}
// ===================== 缩略图事件实现 =====================
@@ -292,6 +344,12 @@ public static class LabPicUrlPreview
// ====== 图片缓存(缩略图 & 原图)—— WebClient + Dictionary + SemaphoreC# 7.3 兼容 ======
private static class ImageCache
{
public static void Clear()
{
ThumbCache.Clear();
FullCache.Clear();
}
// —— 公共接口(缩略图) ——
public static bool TryGetThumb(string url, int targetHeight, out Image img)
{
@@ -326,6 +384,7 @@ public static class LabPicUrlPreview
new Dictionary<string, CacheEntry>(StringComparer.OrdinalIgnoreCase);
private static readonly Dictionary<string, Task<Image>> _tasks =
new Dictionary<string, Task<Image>>(StringComparer.OrdinalIgnoreCase);
private static int _generation;
private static readonly Semaphore _throttle = new Semaphore(4, 4); // 并发 4
private const int Capacity = 300;
@@ -377,6 +436,7 @@ public static class LabPicUrlPreview
return;
}
int generation = _generation;
Task<Image> task = Task.Factory.StartNew<Image>(delegate
{
_throttle.WaitOne();
@@ -394,15 +454,21 @@ public static class LabPicUrlPreview
}, CancellationToken.None, TaskCreationOptions.None, TaskScheduler.Default).ContinueWith<Image>(delegate (Task<Image> t)
{
Image result = t.Result;
bool keepResult = false;
lock (_lock)
{
_tasks.Remove(key);
if (result != null)
if (result != null && generation == _generation)
{
_cache[key] = new CacheEntry { Image = result, LastHitUtc = DateTime.UtcNow };
TrimIfNeededUnsafe();
keepResult = true;
}
}
if (!keepResult && result != null)
{
try { result.Dispose(); } catch { }
}
return result;
}, TaskScheduler.Default);
@@ -431,6 +497,27 @@ public static class LabPicUrlPreview
}
}
}
public static void Clear()
{
lock (_lock)
{
_generation++;
foreach (CacheEntry entry in _cache.Values)
{
try
{
if (entry != null && entry.Image != null)
entry.Image.Dispose();
}
catch { }
}
_cache.Clear();
// Drop task references immediately. In-flight continuations use
// the generation check and dispose stale images when they finish.
_tasks.Clear();
}
}
}
// ===== 原图缓存实现(容量更小) =====
@@ -447,6 +534,7 @@ public static class LabPicUrlPreview
new Dictionary<string, CacheEntry>(StringComparer.OrdinalIgnoreCase);
private static readonly Dictionary<string, Task<Image>> _tasks =
new Dictionary<string, Task<Image>>(StringComparer.OrdinalIgnoreCase);
private static int _generation;
private static readonly Semaphore _throttle = new Semaphore(2, 2); // 原图并发更小
private const int Capacity = 50;
@@ -491,6 +579,7 @@ public static class LabPicUrlPreview
return;
}
int generation = _generation;
Task<Image> task = Task.Factory.StartNew<Image>(delegate
{
_throttle.WaitOne();
@@ -509,15 +598,21 @@ public static class LabPicUrlPreview
}, CancellationToken.None, TaskCreationOptions.None, TaskScheduler.Default).ContinueWith<Image>(delegate (Task<Image> t)
{
Image result = t.Result;
bool keepResult = false;
lock (_lock)
{
_tasks.Remove(url);
if (result != null)
if (result != null && generation == _generation)
{
_cache[url] = new CacheEntry { Image = result, LastHitUtc = DateTime.UtcNow };
TrimIfNeededUnsafe();
keepResult = true;
}
}
if (!keepResult && result != null)
{
try { result.Dispose(); } catch { }
}
return result;
}, TaskScheduler.Default);
@@ -546,6 +641,27 @@ public static class LabPicUrlPreview
}
}
}
public static void Clear()
{
lock (_lock)
{
_generation++;
foreach (CacheEntry entry in _cache.Values)
{
try
{
if (entry != null && entry.Image != null)
entry.Image.Dispose();
}
catch { }
}
_cache.Clear();
// Drop task references immediately. In-flight continuations use
// the generation check and dispose stale images when they finish.
_tasks.Clear();
}
}
}
// ===== 共享底层工具 =====
@@ -209,6 +209,26 @@ namespace Lskj.Control.Model
this.OnRightCallback = handler;
}
internal virtual void ReleaseResourcesForDispose()
{
OnRightCallback = null;
OnBeforeHandleParamsCallback = null;
if (MenuStrip != null)
{
MenuStrip.Opening -= MenuStripOpening;
MenuStrip.Dispose();
MenuStrip = null;
}
RightMenuItems.Clear();
RightMenuBtnEdits.Clear();
RightMenuMrpBtnEdits.Clear();
BaseGridView = null;
ControlObj = null;
Model = null;
MenuTable = null;
AccountItem = null;
}
/// <summary>
/// <para>说明:右键菜单执行前调用</para>
/// <para>创建人:龚宇超</para>
@@ -38,6 +38,19 @@ namespace Lskj.Control.Model
}
}
internal override void ReleaseResourcesForDispose()
{
if (GridControlObj != null && GridControlObj.gridControl != null &&
ReferenceEquals(GridControlObj.gridControl.ContextMenuStrip, MenuStrip))
{
GridControlObj.gridControl.ContextMenuStrip = null;
}
GridControlObj = null;
_gridView = null;
_control = null;
base.ReleaseResourcesForDispose();
}
protected override void MenuStripOpening(object sender, System.ComponentModel.CancelEventArgs e)
{
int[] mSelectRows = this._gridView.GetSelectedRows() ?? new int[0];
@@ -40,7 +40,7 @@ namespace Lskj.Control.Model
{
try
{
RemoveGridReferences(gridView);
ReleaseGridViewReferences(gridView);
}
catch (Exception exception)
{
@@ -103,12 +103,18 @@ namespace Lskj.Control.Model
yield return nested;
}
private static void RemoveGridReferences(GridView gridView)
internal static void ReleaseGridViewReferences(GridView gridView)
{
if (gridView == null)
return;
GridDragGrid.DisposeRegistrations(GridViewDragGridDic, TargetViewDragGridDic, gridView);
if (ReferenceEquals(SourceDragGridView, gridView) ||
(SourceDragGridView != null && SourceDragGridView.GridControl != null && SourceDragGridView.GridControl.IsDisposed))
SourceDragGridView = null;
if (ReferenceEquals(_RightMenuGridView, gridView) ||
(_RightMenuGridView != null && _RightMenuGridView.GridControl != null && _RightMenuGridView.GridControl.IsDisposed))
RightMenuGridView = null;
}
private static void RemoveConditionPanels(string moduleCode)