为什么虚拟ListView的不可见项没有索引?

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了为什么虚拟ListView的不可见项没有索引?相关的知识,希望对你有一定的参考价值。

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

这里是FindItemWithText中的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;
    }
}

这是我的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个项目(从列表中填充如果我按c,则只有200个ListViewItem),只有前50个可见字母和以c字母开头的项目在前50个字符中,被选中。但是,如果按x,则虚拟ListView中的项目是最后一个50,该方法将返回null。如果我改为将列表滚动到最后,然后按x将以x开头。

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

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

编辑1:这是我的listView1_RetrieveVirtualItem代码:

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

我不使用缓存。我使用BackGroundWorker从SQLite数据库获取数据。我创建ListViewitems并将其添加到列表(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从中获取其数据的来源。

答案

似乎是与存储的ListViewItem Index值有关的简单misunderstanding:创建ListViewItem时,无法设置Index,因此此方法检索并返回匹配的ListViewItem:

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

失败item.Index始终为-1,因为在创建ListViewItem时从未设置过。

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


ListView在变得难以管理之前已经可以容纳多少个项目或太慢:如果没有任何进一步的规范,这是一个很难回答的问题。在列表模式下,对于100000项目,它可能表现得很好(如示例中所示),但是设置View = View.Details可能会完全改变场景。是否还必须处理图形对象?好吧,多大?在这种情况下,需要多少把柄?在实践中,这是您在测试不同方案时回答自己的问题。还应考虑“用户”角度(还是应该首先考虑?:)。列表可以轻松滚动,但是找到特定项目也容易吗?如果UI中要显示很多项目,则很可能应该将它们组织在子类中,并提供简单的quick可视化方法来搜索和过滤它们,因此您的用户最终可以减少拥挤的工作子集,可能更接近他们实际需要使用或找到的子集。


这里是一个修复程序和一个代码示例,应该可以测试ListView.FindItemWithText()方法的功能,该方法也需要进行一些小的调整。

  • [ListView.VirtualMode在设计器中设置
  • 在此示例中,ListViewItems集合由1,000个项的列表表示,重复了100次,因此ListView VirtualListSize设置为100,000个项

ListView VirtualMode Search Items

btnLVSearch:用于搜索ListView项目的按钮。→[btnLVLoadData:用于加载数据并设置VirtualListSize的按钮。→chkPrefixSearch:选择PrefixSearchTextSearch的复选框。→[chkCaseSensitiveSearch:用于设置/重置区分大小写的搜索的复选框

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的不可见项没有索引?的主要内容,如果未能解决你的问题,请参考以下文章

Android ListView:获取可见项的数据索引

在 WPF ListView C# 中获取第一个可见项

ListView 中的不同行布局

如何为 ListView 的 ContextActions 的 MenuItem 添加可见性绑定

获取 ListView 可见项

ORACLE不可见索引(Invisible Indexes)