为啥不可见的虚拟 ListView 的项目没有索引?
Posted
技术标签:
【中文标题】为啥不可见的虚拟 ListView 的项目没有索引?【英文标题】:Why the Items of a Virtual ListView that are not visible don't have index?为什么不可见的虚拟 ListView 的项目没有索引? 【发布时间】:2020-08-26 03:53:44 【问题描述】:我正在使用虚拟模式下的 ListView (.NET 4.6)。 我试图在虚拟 ListView 中找到 Items 的索引:当我输入一个字母时,应该选择第一个带有以该字母开头的文本的项目。
这是listView1_KeyDown
中的FindItemWithText
:
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 的正常行为还是有什么问题?
附带问题,normal 模式下的 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 从中获取数据的来源。
【问题讨论】:
【参考方案1】:看起来这是一个简单的误解,与存储的ListViewItem Index值有关:创建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 时从未设置过。
这就是为什么 ListView 会找到已经显示的项目(这些项目有一个索引,虚拟列表不需要调用SearchForVirtualItem()
来检索它们,它只会调用FindItem()
)。
一个简单的解决方案是使用List.FindIndex() 方法,而不是使用FirstOrDefault()
查找项目。此方法返回 List 中包含满足 Predicate<T>
参数定义的条件的对象的索引。
这是 ListView.SearchForVirtualItem 处理程序所期望的 e.Index
的值。
ListView 在变得难以管理或太慢之前可以容纳多少项目:没有任何进一步的规范,这是一个难以回答的问题。在 List 模式下,100000
项目可能表现得非常好(如示例中所示),但设置 View = View.Details
可能会完全改变场景。它是否还必须处理图形对象?嗯,有多大?在这种情况下,需要多少个句柄?在实践中,这是您在测试不同场景时自己回答的问题。
用户的观点也是要考虑的(还是应该先考虑?:)。也许列表可以轻松滚动,但查找特定项目是否也很容易?
如果您在 UI 中有很多项目要呈现,您很可能应该将它们组织在子类别中,并提供简单、快速的可视化方法来搜索和过滤它们,以便您的用户最终使用不那么拥挤的子集,可能更接近他们实际需要使用或找到的东西。
这里有一个修复程序和一个代码示例,可以用来测试ListView.FindItemWithText() 方法的功能(这也需要稍加调整)。
ListView.VirtualMode
在设计器中设置
在示例中,ListViewItems 集合由1,000
项的列表表示,重复100
次,因此ListView VirtualListSize
设置为100,000
项
→ btnLVSearch
:用于搜索 ListView 项目的 Button。
→ btnLVLoadData
:用于加载数据并设置VirtualListSize
的按钮。
→ chkPrefixSearch
:选择PrefixSearch
或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
禁止按键,否则在分配e.Index = itemIndex
后立即触发第二个SearchForVirtualItem
事件(这次,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);
// [...]
【讨论】:
以上是关于为啥不可见的虚拟 ListView 的项目没有索引?的主要内容,如果未能解决你的问题,请参考以下文章