1

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

这是FindItemWithTextlistView1_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;
    }
}

这是我的 SearchForVirtualItem 方法:

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;
}

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

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

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

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

附带问题,正常模式下的 ListView 什么时候变慢?在100,000物品之后,还是1,000,000物品?

Edit1:
这是我的listView1_RetrieveVirtualItem代码:

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

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

RunWorkerCompleted方法 :

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是虚拟 ListView 从中获取数据的来源。

4

1 回答 1

1

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

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

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

一个简单的解决方案是使用List.FindIndex()方法,而不是使用FirstOrDefault(). 此方法返回 List 中包含满足Predicate<T>参数定义的条件的对象的索引。
这是e.IndexListView.SearchForVirtualItem处理程序所期望的值。


ListView 在变得难以管理或太慢之前可以容纳多少项目:没有任何进一步的规范,这是一个难以回答的问题。100000它在列表模式下的项目可能表现得非常好(如示例中所示),但设置View = View.Details可能会完全改变场景。它是否还必须处理图形对象?嗯,有多大?在这种情况下,需要多少个句柄?在实践中,这是您在测试不同场景时自己回答的问题。
用户的观点也是要考虑的(还是应该先考虑?:)。也许列表可以轻松滚动,但查找特定项目是否也很容易?
如果你有很多项目要在 UI 中展示,你很可能应该将它们组织在子类别中并提供简单的、快速、直观的方法来搜索和过滤它们,因此您的用户最终可以使用不那么拥挤的子集,可能更接近他们实际需要使用或查找的内容。


这是一个修复和一个代码示例,应该允许测试ListView.FindItemWithText()方法的功能(这个也需要一个小的调整)。

  • ListView.VirtualMode在设计器中 设置
  • 在示例中,ListViewItems 集合由1,000项目列表表示,重复100次数,因此将 ListViewVirtualListSize设置为100,000项目

ListView VirtualMode 搜索项

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

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;
    }
}

处理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);
    // [...]
}
于 2020-05-10T23:12:57.650 回答