зеркало из https://github.com/dotnet/winforms.git
Merge pull request #3502 from RussKie/fix_3494_dispose_ListView_imagelists
This commit is contained in:
Коммит
f9f39fecb7
|
@ -1172,42 +1172,27 @@ namespace System.Windows.Forms
|
|||
[SRDescription(nameof(SR.ListViewLargeImageListDescr))]
|
||||
public ImageList LargeImageList
|
||||
{
|
||||
get
|
||||
{
|
||||
return imageListLarge;
|
||||
}
|
||||
get => imageListLarge;
|
||||
set
|
||||
{
|
||||
if (value != imageListLarge)
|
||||
if (value == imageListLarge)
|
||||
{
|
||||
EventHandler recreateHandler = new EventHandler(LargeImageListRecreateHandle);
|
||||
EventHandler disposedHandler = new EventHandler(DetachImageList);
|
||||
EventHandler changeHandler = new EventHandler(LargeImageListChangedHandle);
|
||||
return;
|
||||
}
|
||||
|
||||
if (imageListLarge != null)
|
||||
{
|
||||
imageListLarge.RecreateHandle -= recreateHandler;
|
||||
imageListLarge.Disposed -= disposedHandler;
|
||||
imageListLarge.ChangeHandle -= changeHandler;
|
||||
}
|
||||
DetachLargeImageListHandlers();
|
||||
imageListLarge = value;
|
||||
AttachLargeImageListHandlers();
|
||||
|
||||
imageListLarge = value;
|
||||
if (!IsHandleCreated)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (value != null)
|
||||
{
|
||||
value.RecreateHandle += recreateHandler;
|
||||
value.Disposed += disposedHandler;
|
||||
value.ChangeHandle += changeHandler;
|
||||
}
|
||||
|
||||
if (IsHandleCreated)
|
||||
{
|
||||
User32.SendMessageW(this, (User32.WM)LVM.SETIMAGELIST, (IntPtr)LVSIL.NORMAL, value == null ? IntPtr.Zero : value.Handle);
|
||||
if (AutoArrange && !listViewState1[LISTVIEWSTATE1_disposingImageLists])
|
||||
{
|
||||
UpdateListViewItemsLocations();
|
||||
}
|
||||
}
|
||||
User32.SendMessageW(this, (User32.WM)LVM.SETIMAGELIST, (IntPtr)LVSIL.NORMAL, value is null ? IntPtr.Zero : value.Handle);
|
||||
if (AutoArrange && !listViewState1[LISTVIEWSTATE1_disposingImageLists])
|
||||
{
|
||||
UpdateListViewItemsLocations();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1426,48 +1411,38 @@ namespace System.Windows.Forms
|
|||
[SRDescription(nameof(SR.ListViewSmallImageListDescr))]
|
||||
public ImageList SmallImageList
|
||||
{
|
||||
get
|
||||
{
|
||||
return imageListSmall;
|
||||
}
|
||||
get => imageListSmall;
|
||||
set
|
||||
{
|
||||
if (imageListSmall != value)
|
||||
if (imageListSmall == value)
|
||||
{
|
||||
EventHandler recreateHandler = new EventHandler(SmallImageListRecreateHandle);
|
||||
EventHandler disposedHandler = new EventHandler(DetachImageList);
|
||||
return;
|
||||
}
|
||||
|
||||
if (imageListSmall != null)
|
||||
{
|
||||
imageListSmall.RecreateHandle -= recreateHandler;
|
||||
imageListSmall.Disposed -= disposedHandler;
|
||||
}
|
||||
imageListSmall = value;
|
||||
if (value != null)
|
||||
{
|
||||
value.RecreateHandle += recreateHandler;
|
||||
value.Disposed += disposedHandler;
|
||||
}
|
||||
DetachSmallImageListListHandlers();
|
||||
imageListSmall = value;
|
||||
AttachSmallImageListListHandlers();
|
||||
|
||||
if (IsHandleCreated)
|
||||
{
|
||||
User32.SendMessageW(this, (User32.WM)LVM.SETIMAGELIST, (IntPtr)LVSIL.SMALL, value == null ? IntPtr.Zero : value.Handle);
|
||||
if (!IsHandleCreated)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (View == View.SmallIcon)
|
||||
{
|
||||
View = View.LargeIcon;
|
||||
View = View.SmallIcon;
|
||||
}
|
||||
else if (!listViewState1[LISTVIEWSTATE1_disposingImageLists])
|
||||
{
|
||||
UpdateListViewItemsLocations();
|
||||
}
|
||||
User32.SendMessageW(this, (User32.WM)LVM.SETIMAGELIST, (IntPtr)LVSIL.SMALL, value is null ? IntPtr.Zero : value.Handle);
|
||||
|
||||
if (View == View.Details)
|
||||
{
|
||||
Invalidate(true /*invalidateChildren*/);
|
||||
}
|
||||
}
|
||||
if (View == View.SmallIcon)
|
||||
{
|
||||
View = View.LargeIcon;
|
||||
View = View.SmallIcon;
|
||||
}
|
||||
else if (!listViewState1[LISTVIEWSTATE1_disposingImageLists])
|
||||
{
|
||||
UpdateListViewItemsLocations();
|
||||
}
|
||||
|
||||
if (View == View.Details)
|
||||
{
|
||||
Invalidate(invalidateChildren: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1545,87 +1520,62 @@ namespace System.Windows.Forms
|
|||
[SRDescription(nameof(SR.ListViewStateImageListDescr))]
|
||||
public ImageList StateImageList
|
||||
{
|
||||
get
|
||||
{
|
||||
return imageListState;
|
||||
}
|
||||
get => imageListState;
|
||||
set
|
||||
{
|
||||
if (imageListState == value)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (UseCompatibleStateImageBehavior)
|
||||
{
|
||||
if (imageListState != value)
|
||||
DetachStateImageListHandlers();
|
||||
imageListState = value;
|
||||
AttachStateImageListHandlers();
|
||||
|
||||
if (IsHandleCreated)
|
||||
{
|
||||
EventHandler recreateHandler = new EventHandler(StateImageListRecreateHandle);
|
||||
EventHandler disposedHandler = new EventHandler(DetachImageList);
|
||||
|
||||
if (imageListState != null)
|
||||
{
|
||||
imageListState.RecreateHandle -= recreateHandler;
|
||||
imageListState.Disposed -= disposedHandler;
|
||||
}
|
||||
imageListState = value;
|
||||
if (value != null)
|
||||
{
|
||||
value.RecreateHandle += recreateHandler;
|
||||
value.Disposed += disposedHandler;
|
||||
}
|
||||
|
||||
if (IsHandleCreated)
|
||||
{
|
||||
User32.SendMessageW(this, (User32.WM)LVM.SETIMAGELIST, (IntPtr)LVSIL.STATE, value == null ? IntPtr.Zero : value.Handle);
|
||||
}
|
||||
User32.SendMessageW(this, (User32.WM)LVM.SETIMAGELIST, (IntPtr)LVSIL.STATE, value is null ? IntPtr.Zero : value.Handle);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (imageListState != value)
|
||||
DetachStateImageListHandlers();
|
||||
|
||||
if (IsHandleCreated && imageListState != null && CheckBoxes)
|
||||
{
|
||||
EventHandler recreateHandler = new EventHandler(StateImageListRecreateHandle);
|
||||
EventHandler disposedHandler = new EventHandler(DetachImageList);
|
||||
// If CheckBoxes are set to true, then we will have to recreate the handle.
|
||||
// For some reason, if CheckBoxes are set to true and the list view has a state imageList, then the native listView destroys
|
||||
// the state imageList.
|
||||
// (Yes, it does exactly that even though our wrapper sets LVS_SHAREIMAGELISTS on the native listView.)
|
||||
// So we make the native listView forget about its StateImageList just before we recreate the handle.
|
||||
// Likely related to https://devblogs.microsoft.com/oldnewthing/20171128-00/?p=97475
|
||||
User32.SendMessageW(this, (User32.WM)LVM.SETIMAGELIST, (IntPtr)LVSIL.STATE, IntPtr.Zero);
|
||||
}
|
||||
|
||||
if (imageListState != null)
|
||||
{
|
||||
imageListState.RecreateHandle -= recreateHandler;
|
||||
imageListState.Disposed -= disposedHandler;
|
||||
}
|
||||
imageListState = value;
|
||||
AttachStateImageListHandlers();
|
||||
|
||||
if (IsHandleCreated && imageListState != null && CheckBoxes)
|
||||
{
|
||||
//
|
||||
// If CheckBoxes are set to true, then we will have to recreate the handle.
|
||||
// For some reason, if CheckBoxes are set to true and the list view has a state imageList, then the native listView destroys
|
||||
// the state imageList.
|
||||
// (Yes, it does exactly that even though our wrapper sets LVS_SHAREIMAGELISTS on the native listView.)
|
||||
// So we make the native listView forget about its StateImageList just before we recreate the handle.
|
||||
User32.SendMessageW(this, (User32.WM)LVM.SETIMAGELIST, (IntPtr)LVSIL.STATE, IntPtr.Zero);
|
||||
}
|
||||
if (!IsHandleCreated)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
imageListState = value;
|
||||
if (CheckBoxes)
|
||||
{
|
||||
// need to recreate to get the new images pushed in.
|
||||
RecreateHandleInternal();
|
||||
}
|
||||
else
|
||||
{
|
||||
User32.SendMessageW(this, (User32.WM)LVM.SETIMAGELIST, (IntPtr)LVSIL.STATE, (imageListState is null || imageListState.Images.Count == 0) ? IntPtr.Zero : imageListState.Handle);
|
||||
}
|
||||
|
||||
if (value != null)
|
||||
{
|
||||
value.RecreateHandle += recreateHandler;
|
||||
value.Disposed += disposedHandler;
|
||||
}
|
||||
|
||||
if (IsHandleCreated)
|
||||
{
|
||||
if (CheckBoxes)
|
||||
{
|
||||
// need to recreate to get the new images pushed in.
|
||||
RecreateHandleInternal();
|
||||
}
|
||||
else
|
||||
{
|
||||
User32.SendMessageW(this, (User32.WM)LVM.SETIMAGELIST, (IntPtr)LVSIL.STATE, (imageListState == null || imageListState.Images.Count == 0) ? IntPtr.Zero : imageListState.Handle);
|
||||
}
|
||||
|
||||
// Comctl should handle auto-arrange for us, but doesn't
|
||||
if (!listViewState1[LISTVIEWSTATE1_disposingImageLists])
|
||||
{
|
||||
UpdateListViewItemsLocations();
|
||||
}
|
||||
}
|
||||
// Comctl should handle auto-arrange for us, but doesn't
|
||||
if (!listViewState1[LISTVIEWSTATE1_disposingImageLists])
|
||||
{
|
||||
UpdateListViewItemsLocations();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2254,6 +2204,43 @@ namespace System.Windows.Forms
|
|||
/// </summary>
|
||||
public void ArrangeIcons() => ArrangeIcons((ListViewAlignment)LVA.DEFAULT);
|
||||
|
||||
private void AttachLargeImageListHandlers()
|
||||
{
|
||||
if (imageListLarge is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// NOTE: any handlers added here should be removed in DetachLargeImageListHandlers
|
||||
imageListLarge.RecreateHandle += new EventHandler(LargeImageListRecreateHandle);
|
||||
imageListLarge.Disposed += new EventHandler(DetachImageList);
|
||||
imageListLarge.ChangeHandle += new EventHandler(LargeImageListChangedHandle);
|
||||
}
|
||||
|
||||
private void AttachSmallImageListListHandlers()
|
||||
{
|
||||
if (imageListSmall is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// NOTE: any handlers added here should be removed in DetachSmallImageListListHandlers
|
||||
imageListSmall.RecreateHandle += new EventHandler(SmallImageListRecreateHandle);
|
||||
imageListSmall.Disposed += new EventHandler(DetachImageList);
|
||||
}
|
||||
|
||||
private void AttachStateImageListHandlers()
|
||||
{
|
||||
if (imageListState is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// NOTE: any handlers added here should be removed in DetachStateImageListHandlers
|
||||
imageListState.RecreateHandle += new EventHandler(StateImageListRecreateHandle);
|
||||
imageListState.Disposed += new EventHandler(DetachImageList);
|
||||
}
|
||||
|
||||
public void AutoResizeColumns(ColumnHeaderAutoResizeStyle headerAutoResize)
|
||||
{
|
||||
if (!IsHandleCreated)
|
||||
|
@ -2485,8 +2472,6 @@ namespace System.Windows.Forms
|
|||
}
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
/// <summary>
|
||||
/// Handles custom drawing of list items - for individual item font/color changes.
|
||||
///
|
||||
|
@ -2931,6 +2916,40 @@ namespace System.Windows.Forms
|
|||
UpdateListViewItemsLocations();
|
||||
}
|
||||
|
||||
private void DetachLargeImageListHandlers()
|
||||
{
|
||||
if (imageListLarge is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
imageListLarge.RecreateHandle -= new EventHandler(LargeImageListRecreateHandle);
|
||||
imageListLarge.Disposed -= new EventHandler(DetachImageList);
|
||||
imageListLarge.ChangeHandle -= new EventHandler(LargeImageListChangedHandle);
|
||||
}
|
||||
|
||||
private void DetachSmallImageListListHandlers()
|
||||
{
|
||||
if (imageListSmall is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
imageListSmall.RecreateHandle -= new EventHandler(SmallImageListRecreateHandle);
|
||||
imageListSmall.Disposed -= new EventHandler(DetachImageList);
|
||||
}
|
||||
|
||||
private void DetachStateImageListHandlers()
|
||||
{
|
||||
if (imageListState is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
imageListState.RecreateHandle -= new EventHandler(StateImageListRecreateHandle);
|
||||
imageListState.Disposed -= new EventHandler(DetachImageList);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disposes of the component. Call dispose when the component is no longer needed.
|
||||
/// This method removes the component from its container (if the component has a site)
|
||||
|
@ -2941,21 +2960,12 @@ namespace System.Windows.Forms
|
|||
if (disposing)
|
||||
{
|
||||
// Remove any event sinks we have hooked up to imageLists
|
||||
if (imageListSmall != null)
|
||||
{
|
||||
imageListSmall.Disposed -= new EventHandler(DetachImageList);
|
||||
imageListSmall = null;
|
||||
}
|
||||
if (imageListLarge != null)
|
||||
{
|
||||
imageListLarge.Disposed -= new EventHandler(DetachImageList);
|
||||
imageListLarge = null;
|
||||
}
|
||||
if (imageListState != null)
|
||||
{
|
||||
imageListState.Disposed -= new EventHandler(DetachImageList);
|
||||
imageListState = null;
|
||||
}
|
||||
DetachSmallImageListListHandlers();
|
||||
imageListSmall = null;
|
||||
DetachLargeImageListHandlers();
|
||||
imageListLarge = null;
|
||||
DetachStateImageListHandlers();
|
||||
imageListState = null;
|
||||
|
||||
// Remove any ColumnHeaders contained in this control
|
||||
if (columnHeaders != null)
|
||||
|
@ -4145,18 +4155,20 @@ namespace System.Windows.Forms
|
|||
|
||||
private void LargeImageListChangedHandle(object sender, EventArgs e)
|
||||
{
|
||||
if (!VirtualMode && (null != sender) && (sender == imageListLarge) && IsHandleCreated)
|
||||
if (VirtualMode || sender is null || sender != imageListLarge || !IsHandleCreated)
|
||||
{
|
||||
foreach (ListViewItem item in Items)
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (ListViewItem item in Items)
|
||||
{
|
||||
if (item.ImageIndexer.ActualIndex != ImageList.Indexer.DefaultIndex && item.ImageIndexer.ActualIndex >= imageListLarge.Images.Count)
|
||||
{
|
||||
if (item.ImageIndexer.ActualIndex != -1 && item.ImageIndexer.ActualIndex >= imageListLarge.Images.Count)
|
||||
{
|
||||
SetItemImage(item.Index, imageListLarge.Images.Count - 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
SetItemImage(item.Index, item.ImageIndexer.ActualIndex);
|
||||
}
|
||||
SetItemImage(item.Index, imageListLarge.Images.Count - 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
SetItemImage(item.Index, item.ImageIndexer.ActualIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5283,7 +5295,7 @@ namespace System.Windows.Forms
|
|||
if (IsHandleCreated)
|
||||
{
|
||||
IntPtr handle = IntPtr.Zero;
|
||||
if (StateImageList != null)
|
||||
if (imageListState != null)
|
||||
{
|
||||
handle = imageListState.Handle;
|
||||
}
|
||||
|
@ -5992,7 +6004,7 @@ namespace System.Windows.Forms
|
|||
return unchecked((int)(long)User32.SendMessageW(this, (User32.WM)LVM.HITTEST, IntPtr.Zero, ref lvhi));
|
||||
}
|
||||
|
||||
private LVHITTESTINFO SetupHitTestInfo ()
|
||||
private LVHITTESTINFO SetupHitTestInfo()
|
||||
{
|
||||
Point pos = Cursor.Position;
|
||||
pos = PointToClient(pos);
|
||||
|
|
|
@ -1092,6 +1092,38 @@ namespace System.Windows.Forms.Tests
|
|||
Assert.False(listView.CheckBoxes);
|
||||
}
|
||||
|
||||
[WinFormsFact]
|
||||
public void ListView_DisposeWithReferencedImageListDoesNotLeak()
|
||||
{
|
||||
// must be separate function because GC of local variables is not precise
|
||||
static WeakReference CreateAndDisposeListViewWithImageListReference(ImageList imageList)
|
||||
{
|
||||
// short lived test code, whatever you need to trigger the leak
|
||||
using var listView = new ListView();
|
||||
listView.LargeImageList = imageList;
|
||||
|
||||
// return a weak reference to whatever you want to track GC of
|
||||
// creating a long weak reference to make sure finalizer does not resurrect the ListView
|
||||
return new WeakReference(listView, true);
|
||||
}
|
||||
|
||||
// simulate a long-living ImageList by keeping it alive for the test
|
||||
using var imageList = new ImageList();
|
||||
|
||||
// simulate a short-living ListView by disposing it (returning a WeakReference to track finalization)
|
||||
var listViewRef = CreateAndDisposeListViewWithImageListReference(imageList);
|
||||
|
||||
GC.Collect(); // mark for finalization (also would clear normal weak references)
|
||||
GC.WaitForPendingFinalizers(); // wait until finalizer is executed
|
||||
GC.Collect(); // wait for long weak reference to be cleared
|
||||
|
||||
// at this point the WeakReference is cleared if -and only if- the finalizer was called and did not resurrect the object
|
||||
// (if the test ever fails you can set a breakpoint here, debug the test, and make heap snapshot in VS;
|
||||
// then search for the ListView in the heap snapshot UI and look who is referencing it, usually you
|
||||
// can derive from types referencing the ListView who is to blame)
|
||||
Assert.False(listViewRef.IsAlive);
|
||||
}
|
||||
|
||||
[WinFormsTheory]
|
||||
[CommonMemberData(nameof(CommonTestHelper.GetBoolTheoryData))]
|
||||
public void ListView_DoubleBuffered_Get_ReturnsExpected(bool value)
|
||||
|
|
Загрузка…
Ссылка в новой задаче