为什么不可见的虚拟 ListView 的项目没有索引?
Why the Items of a Virtual ListView that are not visible don't have index?
我正在虚拟模式 (.NET 4.6) 中使用 ListView。
我试图在虚拟 ListView 中找到项目的索引:当我输入一个字母时,第一个包含以该字母开头的文本的项目应该是 selected。
这是 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;
}
如果在我滚动所有代码之前结果是可见项之一,我可以 select 结果项。但是,如果结果不可见并且我根本没有滚动任何内容,则方法 return null。
但是,如果我滚动到列表末尾一次,我就可以获得之前无法获得的项目索引。
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.
为什么我必须至少显示一次该项目才能获得索引,而没有 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索引值有关:当你创建一个ListViewItem时,你不能设置索引,所以这个方法来检索和return 一个匹配的 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()
查找项目。此方法 return 列表中的索引包含满足 Predicate<T>
参数定义的条件的对象。
这是 ListView.SearchForVirtualItem 处理程序期望的 e.Index
的值。
ListView 在变得难以管理或太慢之前可以容纳多少项目:没有任何进一步的规范,这是一个难以回答的问题。在列表模式下(如示例中所示),它可能与 100000
项目完美无缺,但设置 View = View.Details
可能会完全改变场景。它是否还必须处理图形对象?那么,有多大?在这种情况下,需要多少个句柄?在实践中,这是一个你自己测试不同场景的问题。
用户的观点也是要考虑的(或者它应该首先出现?:)。也许列表可以轻松滚动,但是找到特定项目也很容易吗?
如果您有很多项目要在 UI 中展示,您最有可能将它们组织在子目录中并提供简单、快速、可视化的方法来搜索和过滤它们,因此您的用户最终会使用不那么拥挤的子集,可能更接近他们实际需要使用或查找的内容。
这里有一个修复程序和一个代码示例,可以用来测试 ListView.FindItemWithText() 方法的功能(这个方法还需要稍作调整)。
ListView.VirtualMode
在设计器中设置
- 在示例中,ListViewItems 集合由
1,000
项列表表示,重复 100
次,因此 ListView VirtualListSize
设置为 100,000
项
→ btnLVSearch
: 用于搜索ListView项目的Button。
→ btnLVLoadData
:用于加载数据的Button,设置VirtualListSize
.
→ chkPrefixSearch
:选择 PrefixSearch
或 TextSearch
的 CheckBox。
→ chkCaseSensitiveSearch
: CheckBox 用于 set/reset 区分大小写搜索
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);
// [...]
}
我正在虚拟模式 (.NET 4.6) 中使用 ListView。
我试图在虚拟 ListView 中找到项目的索引:当我输入一个字母时,第一个包含以该字母开头的文本的项目应该是 selected。
这是 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;
}
如果在我滚动所有代码之前结果是可见项之一,我可以 select 结果项。但是,如果结果不可见并且我根本没有滚动任何内容,则方法 return null。
但是,如果我滚动到列表末尾一次,我就可以获得之前无法获得的项目索引。
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.
为什么我必须至少显示一次该项目才能获得索引,而没有 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索引值有关:当你创建一个ListViewItem时,你不能设置索引,所以这个方法来检索和return 一个匹配的 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()
查找项目。此方法 return 列表中的索引包含满足 Predicate<T>
参数定义的条件的对象。
这是 ListView.SearchForVirtualItem 处理程序期望的 e.Index
的值。
ListView 在变得难以管理或太慢之前可以容纳多少项目:没有任何进一步的规范,这是一个难以回答的问题。在列表模式下(如示例中所示),它可能与 100000
项目完美无缺,但设置 View = View.Details
可能会完全改变场景。它是否还必须处理图形对象?那么,有多大?在这种情况下,需要多少个句柄?在实践中,这是一个你自己测试不同场景的问题。
用户的观点也是要考虑的(或者它应该首先出现?:)。也许列表可以轻松滚动,但是找到特定项目也很容易吗?
如果您有很多项目要在 UI 中展示,您最有可能将它们组织在子目录中并提供简单、快速、可视化的方法来搜索和过滤它们,因此您的用户最终会使用不那么拥挤的子集,可能更接近他们实际需要使用或查找的内容。
这里有一个修复程序和一个代码示例,可以用来测试 ListView.FindItemWithText() 方法的功能(这个方法还需要稍作调整)。
ListView.VirtualMode
在设计器中设置- 在示例中,ListViewItems 集合由
1,000
项列表表示,重复100
次,因此 ListViewVirtualListSize
设置为100,000
项
→ btnLVSearch
: 用于搜索ListView项目的Button。
→ btnLVLoadData
:用于加载数据的Button,设置VirtualListSize
.
→ chkPrefixSearch
:选择 PrefixSearch
或 TextSearch
的 CheckBox。
→ chkCaseSensitiveSearch
: CheckBox 用于 set/reset 区分大小写搜索
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);
// [...]
}