Merge pull request #3502 from RussKie/fix_3494_dispose_ListView_imagelists

This commit is contained in:
Igor Velikorossov 2020-06-25 14:39:53 +10:00 коммит произвёл GitHub
Родитель c8f38b524d 60b5a1ef19
Коммит f9f39fecb7
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
2 изменённых файлов: 205 добавлений и 161 удалений

Просмотреть файл

@ -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)