Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Do not crash when an invalid VirtualMode==true ListView is destroyed #11679

Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/System.Windows.Forms/src/Resources/SR.resx
Original file line number Diff line number Diff line change
Expand Up @@ -4018,7 +4018,7 @@ Stack trace where the illegal operation occurred was:
<value>Occurs whenever the state of a virtual item is changed.</value>
</data>
<data name="ListViewVirtualListSizeDescr" xml:space="preserve">
<value>Sets the count of the item collection when the ListView is in virtual mode.</value>
<value>Sets the count of the item collection when the ListView is in virtual mode. If VirtualMode is set to true, and the VirtualListSize property is greater than 0, you must handle the RetrieveVirtualItem event providing valid items.</value>
</data>
<data name="ListViewVirtualListSizeInvalidArgument" xml:space="preserve">
<value>Value of '{1}' is not valid for '{0}'. {0} should be greater than or equal to 0.</value>
Expand Down
4 changes: 2 additions & 2 deletions src/System.Windows.Forms/src/Resources/xlf/SR.cs.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions src/System.Windows.Forms/src/Resources/xlf/SR.de.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions src/System.Windows.Forms/src/Resources/xlf/SR.es.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions src/System.Windows.Forms/src/Resources/xlf/SR.fr.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions src/System.Windows.Forms/src/Resources/xlf/SR.it.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions src/System.Windows.Forms/src/Resources/xlf/SR.ja.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions src/System.Windows.Forms/src/Resources/xlf/SR.ko.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions src/System.Windows.Forms/src/Resources/xlf/SR.pl.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions src/System.Windows.Forms/src/Resources/xlf/SR.pt-BR.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions src/System.Windows.Forms/src/Resources/xlf/SR.ru.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions src/System.Windows.Forms/src/Resources/xlf/SR.tr.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions src/System.Windows.Forms/src/Resources/xlf/SR.zh-Hans.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions src/System.Windows.Forms/src/Resources/xlf/SR.zh-Hant.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,20 @@ internal interface IInnerList
ListViewItem Insert(int index, ListViewItem item);
void Remove(ListViewItem item);
void RemoveAt(int index);
ListViewItem? GetItemByIndex(int index)
{
ArgumentOutOfRangeException.ThrowIfNegative(index);
ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(index, Count);

try
{
return this[index];
}
catch
{
return null;
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,14 @@ public virtual ListViewItem this[int index]
}
}

internal ListViewItem? GetItemByIndex(int index)
{
ArgumentOutOfRangeException.ThrowIfNegative(index);
ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(index, InnerList.Count);

return InnerList.GetItemByIndex(index);
}

object? IList.this[int index]
{
get
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,35 +34,7 @@ public int Count

public ListViewItem this[int displayIndex]
{
get
{
_owner.ApplyUpdateCachedItems();

if (_owner.VirtualMode)
{
// if we are showing virtual items, we need to get the item from the user
RetrieveVirtualItemEventArgs rVI = new(displayIndex);
_owner.OnRetrieveVirtualItem(rVI);
rVI.Item!.SetItemIndex(_owner, displayIndex);
return rVI.Item;
}
else
{
ArgumentOutOfRangeException.ThrowIfNegative(displayIndex);
ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(displayIndex, _owner._itemCount);

if (_owner.IsHandleCreated && !_owner.ListViewHandleDestroyed)
{
_owner._listItemsTable.TryGetValue(DisplayIndexToID(displayIndex), out ListViewItem? item);
return item!;
}
else
{
Debug.Assert(_owner._listViewItems is not null, "listItemsArray is null, but the handle isn't created");
return _owner._listViewItems[displayIndex];
}
}
}
get => GetItemByIndexInternal(displayIndex, throwInVirtualMode: true)!;
set
{
_owner.ApplyUpdateCachedItems();
Expand All @@ -84,6 +56,44 @@ public ListViewItem this[int displayIndex]
}
}

public ListViewItem? GetItemByIndex(int index) =>
GetItemByIndexInternal(index, throwInVirtualMode: false);

private ListViewItem? GetItemByIndexInternal(int index, [NotNullWhen(true)] bool throwInVirtualMode)
{
_owner.ApplyUpdateCachedItems();

if (_owner.VirtualMode)
{
// If we are showing virtual items, we need to get the item from the user.
RetrieveVirtualItemEventArgs rVI = new(index);
_owner.OnRetrieveVirtualItem(rVI);
if (rVI.Item is null)
{
return !throwInVirtualMode ? null : throw new InvalidOperationException(SR.ListViewVirtualItemRequired);
}

rVI.Item.SetItemIndex(_owner, index);
return rVI.Item;
}
else
{
ArgumentOutOfRangeException.ThrowIfNegative(index);
ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(index, _owner._itemCount);

if (_owner.IsHandleCreated && !_owner.ListViewHandleDestroyed)
{
_owner._listItemsTable.TryGetValue(DisplayIndexToID(index), out ListViewItem? item);
return item!;
}
else
{
Debug.Assert(_owner._listViewItems is not null, "listItemsArray is null, but the handle isn't created");
return _owner._listViewItems[index];
}
}
}

public ListViewItem Add(ListViewItem value)
{
if (_owner.VirtualMode)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5117,7 +5117,7 @@ internal override void ReleaseUiaProvider(HWND handle)

for (int i = 0; i < Items.Count; i++)
{
Items[i].ReleaseUiaProvider();
Items.GetItemByIndex(i)?.ReleaseUiaProvider();
}

if (_defaultGroup is not null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5203,6 +5203,66 @@ public void ListView_AnnounceColumnHeader_WorksCorrectly(int x, int y, string ex
Assert.True(listView.IsHandleCreated);
}

public static TheoryData<ListViewItem> GetListViewItemTheoryData() => new()
{
{ new("Item 1") },
{ null }
};

[WinFormsTheory]
[MemberData(nameof(GetListViewItemTheoryData))]
// Regression test for https://github.com/dotnet/winforms/issues/11663.
public void ListView_VirtualMode_ReleaseUiaProvider_Success(ListViewItem listItem)
{
using ListView listView = new()
{
VirtualMode = true,
VirtualListSize = 1
};

listView.RetrieveVirtualItem += (s, e) =>
{
e.Item = e.ItemIndex switch
{
0 => listItem,
_ => throw new NotImplementedException()
};
};

listView.AccessibilityObject.Should().NotBeNull();

Action action = () => listView.ReleaseUiaProvider(listView.InternalHandle);
action.Should().NotThrow();
listView.IsAccessibilityObjectCreated.Should().BeFalse();
listView.IsHandleCreated.Should().BeFalse();
}

[WinFormsFact]
public void ListView_VirtualMode_GetListViewItemAsExpected()
{
using ListView listView = new()
{
VirtualMode = true,
VirtualListSize = 2
};

ListViewItem listItem1 = new("Item 1");
ListViewItem listItem2 = null;
listView.RetrieveVirtualItem += (s, e) =>
{
e.Item = e.ItemIndex switch
{
0 => listItem1,
1 => listItem2,
_ => throw new NotImplementedException()
};
};
listView.Items.GetItemByIndex(0).Should().Be(listView.Items[0]);
listView.Items.GetItemByIndex(1).Should().BeNull();
Action action = () => listView.Items[1].ToString();
action.Should().Throw<InvalidOperationException>(SR.ListViewVirtualItemRequired);
}

private class SubListViewAccessibleObject : ListView.ListViewAccessibleObject
{
internal string AnnouncedColumn { get; private set; }
Expand Down