为什么不可见的 Virtual ListView 的 Items 没有索引? [英] Why the Items of a Virtual ListView that are not visible don't have index?

查看:33
本文介绍了为什么不可见的 Virtual ListView 的 Items 没有索引?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在使用虚拟模式 (.NET 4.6) 的 ListView.
我试图在虚拟 ListView 中找到 Items 的索引:当我输入一个字母时,应该选择带有以该字母开头的文本的第一个项目.

这是listView1_KeyDown中的FindItemWithText:

if (char.IsLetterOrDigit(e.KeyChar)){var str = e.KeyChar.ToString();如果(tempStr != str){指数 = 0;tempStr = str;}var item = listView1.FindItemWithText(str, false, Index, true);如果(项目!= null){item.Selected = true;item.Focused = true;item.EnsureVisible();索引 = item.Index + 1;}}

这是我的 SearchForVirtualItem 方法:

var item = lvis.OfType().FirstOrDefault(我 =>i.Text.ToLower().StartsWith(e.Text.ToLower()) &&i.Index >= e.StartIndex);如果(项目==空){}别的{e.Index = item.Index;}

如果在我滚动所有代码工作之前结果是可见项目之一,我可以选择结果项目.但是,如果结果不可见并且我根本没有滚动任何内容,则该方法返回 null.

但是如果我滚动到列表的末尾,即使一次我也可以获得之前无法获得的项目的索引.

<块引用>

示例:如果我在一个虚拟列表中有 200 个项目(从列表填充200 ListViewItem) 并且只有前 50 个可见,如果我按下 c字母和以 c 字母开头的项目在前 50 个中,它们将被选中.
但是如果我按下 x 并且虚拟 ListView 中的项目是在最后 50 处,该方法将返回 null.如果我改为将列表滚动到最后,然后我按 x,项目以 x 开头的将被选中.

为什么我必须至少显示一次项目才能有索引,而没有 index = -1?
这是虚拟 ListView 的正常行为还是有什么问题?

附带问题,普通模式下的ListView什么时候变慢?在 100,000 个项目之后,还是在 1,000,000 个项目之后?


这是我的 listView1_RetrieveVirtualItem 代码:

private void listView1_RetrieveVirtualItem(object sender, RetrieveVirtualItemEventArgs e){如果 (lvis.Count > 0){e.Item = lvis[e.ItemIndex];}}

我不使用缓存.
我使用 BackGroundWorker 从 SQLite 数据库中获取数据;我创建 ListViewitems 并将它们添加到列表中(var lvis = new List).

RunWorkerCompleted 方法:

private void Pl_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e){var obj = e.Result;如果(对象!= null){删除选择();lvis = (List)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 是虚拟 ListView 获取数据的来源.

解决方案

看起来这是一个简单的误解,与存储的 ListViewItem Index 值有关:创建 ListViewItem 时,不能设置 Index,所以这个方法检索并返回一个匹配的ListViewItem:

private void listView1_RetrieveVirtualItem(object sender, RetrieveVirtualItemEventArgs e){var item = lvis.OfType().FirstOrDefault([...]);e.Index = item.Index;}

失败:item.Index 始终是 -1,因为在 ListViewItem 时从未设置已创建.
这就是为什么 ListView 会找到已经显示的项目(这些项目有一个索引,虚拟列表不需要调用 SearchForVirtualItem() 来检索它们,它只会调用 FindItem()).

一个简单的解决方案是使用

btnLVSearch:用于搜索 ListView 项目的 Button.
btnLVLoadData:用于加载数据并设置VirtualListSize的Button.
chkPrefixSearch:选择 PrefixSearchTextSearch 的 CheckBox.
chkCaseSensitiveSearch:CheckBox 用于设置/重置区分大小写的搜索

int currentStartIndex = 0;列表列表项 = 空;private void btnLVLoadData_Click(object sender, EventArgs e){listItems = new List();//[...]//填充listItems集合listView1.VirtualListSize = listItems.Count;}private void listView1_RetrieveVirtualItem(对象发送者,RetrieveVirtualItemEventArgs e){如果(e.ItemIndex >= 0){e.Item = listItems[e.ItemIndex];}}private void listView1_SearchForVirtualItem(对象发送者,SearchForVirtualItemEventArgs e){StringComparison 比较 = chkCaseSensitiveSearch.Checked?StringComparison.CurrentCulture: StringComparison.CurrentCultureIgnoreCase;int itemIndex = -1;如果(e.IsPrefixSearch){itemIndex = listItems.FindIndex(e.StartIndex,它=>itm.Text.StartsWith(e.Text, 比较));}否则如果(e.IsTextSearch){itemIndex = listItems.FindIndex(e.StartIndex,它=>itm.Text.IndexOf(e.Text, compare) >= 0);}e.Index = itemIndex;}private void btnLVSearch_Click(object sender, EventArgs e){var item = listView1.FindItemWithText(txtLVSearch.Text, false, currentStartIndex, chkPrefixSearch.Checked);如果(项目!= null){currentStartIndex = item.Index + 1;listView1.SelectedIndices.Add(item.Index);item.Selected = true;listView1.EnsureVisible(item.Index);listView1.Focus();}别的 {currentStartIndex = 0;}}

在处理 ListView.KeyPress 事件时,设置 e.Handled = true 以抑制按键按下,否则第二个 SearchForVirtualItem 事件是在分配 e.Index = itemIndex 后立即触发(这次,e.IsPrefixSearch 设置为 false):

private void listView1_KeyPress(object sender, KeyPressEventArgs e){e.handled = true;var item = listView1.FindItemWithText(e.KeyChar.ToString(), false, currentStartIndex, chkPrefixSearch.Checked);//[...]}

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 with c letter are among the first 50, they will be selected.
But if I press x and the items in the virtual ListView are at the last 50, the method will return null. If I instead scroll the list to the end and then I press x, the items that start with x 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.

解决方案

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);
    // [...]
}

这篇关于为什么不可见的 Virtual ListView 的 Items 没有索引?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆