It looks like it's a simple misunderstanding related to the stored ListViewItem Index value: when you create a ListViewItem, you cannot set the Index, so this method to retrieve and return a matching ListViewItem:
private void listView1_RetrieveVirtualItem(object sender, RetrieveVirtualItemEventArgs e)
{
var item = lvis.OfType<ListViewItem>().FirstOrDefault([...]);
e.Index = item.Index;
}
will fail: item.Index
is always -1
, since was never set when the ListViewItem was created.
That's why the ListView will find Items that have already been shown (these have an Index, the virtual list doesn't need to retrieve them calling SearchForVirtualItem()
, it will just call FindItem()
).
A simple solution is to use the List.FindIndex() method, instead of finding an Item using FirstOrDefault()
. This method returns the index in the List that contains the object that meets the criteria defined by the Predicate<T>
argument.
This is the value of e.Index
that the ListView.SearchForVirtualItem handler is expecting.
How many items a ListView can hold before it becomes difficult to manage or too slow: without any further specifications, this is a question difficult to answer. It may behave perfectly fine with 100000
items in List mode (as in the example), but setting the View = View.Details
may change the scenario completely. Does it have to also handle graphics object? Well, how large? How many handles are needed, in this case? In practice, it's a question you answer yourself testing different scenarios.
The User perspective is also to consider (or should it come first? :). Maybe the list is scrollable with ease, but is it also easy to locate a specific Item?
If you have a lot of Items to present in the UI, you should most probably organize them in sub cathegories and provide easy, quick, visual methods to search and filter them, so your Users end up working with much less crowded subsets, probably closer to what they actually need to use or find.
Here's a fix and a code sample that should allow to test the functionality of the ListView.FindItemWithText() method (this one also needs a small tweak).
- The
ListView.VirtualMode
is set in the Designer
- In the example, the ListViewItems collection is represented by a list of
1,000
items, repeated 100
times, so the ListView VirtualListSize
is set to 100,000
items
→ btnLVSearch
: the Button used to search the ListView items.
→ btnLVLoadData
: the Button used to load the data and sets the VirtualListSize
.
→ chkPrefixSearch
: the CheckBox that selects a PrefixSearch
or a TextSearch
.
→ chkCaseSensitiveSearch
: the CheckBox used to set/reset the case sensitive search
int currentStartIndex = 0;
List<ListViewItem> listItems = null;
private void btnLVLoadData_Click(object sender, EventArgs e)
{
listItems = new List<ListViewItem>();
// [...]
// Fill the listItems collection
listView1.VirtualListSize = listItems.Count;
}
private void listView1_RetrieveVirtualItem(object sender, RetrieveVirtualItemEventArgs e)
{
if (e.ItemIndex >= 0) {
e.Item = listItems[e.ItemIndex];
}
}
private void listView1_SearchForVirtualItem(object sender, SearchForVirtualItemEventArgs e)
{
StringComparison comparison = chkCaseSensitiveSearch.Checked
? StringComparison.CurrentCulture
: StringComparison.CurrentCultureIgnoreCase;
int itemIndex = -1;
if (e.IsPrefixSearch) {
itemIndex = listItems.FindIndex(e.StartIndex,
itm => itm.Text.StartsWith(e.Text, comparison));
}
else if (e.IsTextSearch) {
itemIndex = listItems.FindIndex(e.StartIndex,
itm => itm.Text.IndexOf(e.Text, comparison) >= 0);
}
e.Index = itemIndex;
}
private void btnLVSearch_Click(object sender, EventArgs e)
{
var item = listView1.FindItemWithText(
txtLVSearch.Text, false, currentStartIndex, chkPrefixSearch.Checked);
if (item != null) {
currentStartIndex = item.Index + 1;
listView1.SelectedIndices.Add(item.Index);
item.Selected = true;
listView1.EnsureVisible(item.Index);
listView1.Focus();
}
else {
currentStartIndex = 0;
}
}
When handling the ListView.KeyPress
event, set e.Handled = true
to suppress the key press, otherwise a second SearchForVirtualItem
event is triggered immediately after e.Index = itemIndex
is assigned (this time, with e.IsPrefixSearch
set to false
):
private void listView1_KeyPress(object sender, KeyPressEventArgs e)
{
e.Handled = true;
var item = listView1.FindItemWithText(
e.KeyChar.ToString(), false, currentStartIndex, chkPrefixSearch.Checked);
// [...]
}