问题
I'm working with a ListView in Virtual Mode (.NET 4.6).
I tried to find the index of Items in the virtual ListView: when I enter a letter, the first item with text that start with that letter should be selected.
Here is the FindItemWithText
in listView1_KeyDown
:
if (char.IsLetterOrDigit(e.KeyChar))
{
var str = e.KeyChar.ToString();
if (tempStr != str)
{
Index = 0;
tempStr = str;
}
var item = listView1.FindItemWithText(str, false, Index, true);
if (item != null)
{
item.Selected = true;
item.Focused = true;
item.EnsureVisible();
Index = item.Index + 1;
}
}
Here is my SearchForVirtualItem method:
var item = lvis.OfType<ListViewItem>().FirstOrDefault(
i => i.Text.ToLower().StartsWith(e.Text.ToLower()) &&
i.Index >= e.StartIndex);
if (item == null)
{
}
else
{
e.Index = item.Index;
}
If the result is one of the visible items before I scroll at all the code works and I can select the result item. But if the result is not visible and I didn't scroll anything at all the method return null.
But if I scroll to the end of the list even once I can get the index of the item that before I couldn't.
Example: If I have 200 items in a virtual list (populated from a list of 200 ListViewItem) and only the first 50 are visible, if I press the
c
letter and items that start withc
letter are among the first 50, they will be selected.
But if I pressx
and the items in the virtual ListView are at the last50
, the method will returnnull
. If I instead scroll the list to the end and then I pressx
, the items that start withx
will be selected.
Why I have to show the item at least once to have an index and not having index = -1?
Is this the normal behavior of a virtual ListView or is there something wrong?
Side question, when does a ListView in normal mode become slow? After 100,000
items, or 1,000,000
items?
Edit1:
Here is my listView1_RetrieveVirtualItem
code:
private void listView1_RetrieveVirtualItem(object sender, RetrieveVirtualItemEventArgs e)
{
if (lvis.Count > 0)
{
e.Item = lvis[e.ItemIndex];
}
}
I don't use a cache.
I use BackGroundWorker to get the data from a SQLite database; I create ListViewitems and add them to a List (var lvis = new List<ListViewItem>
).
The RunWorkerCompleted
method:
private void Pl_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
var obj = e.Result;
if (obj != null)
{
RemoveSelection();
lvis = (List<ListViewItem>)obj;
listView1.Items.Clear();
listView1.VirtualListSize = lvis.Count;
listView1.Invalidate();
var No_of_items = listView1.Items.Count + " pin(s)";
count.Text = No_of_items;
tabLabel.Text = GetButton().Text + " | " + No_of_items;
}
}
lvis
is the source where the virtual ListView get its data from.
回答1:
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, repeated100
times, so the ListViewVirtualListSize
is set to100,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);
// [...]
}
来源:https://stackoverflow.com/questions/61715611/why-the-items-of-a-virtual-listview-that-are-not-visible-dont-have-index