.net类库里ListView的一个BUG

今天在CSDN论坛里看一个帖子,说是在ListView中添加了条目后第一行内容不显示,为了还原他的问题我写了以下代码。

复制代码

private void LoadFiles(DirectoryInfo dir)

{

FileInfo[] files = dir.GetFiles();

foreach (FileInfo file in files)

{

ListViewItem item = new ListViewItem();

item.Tag = file;

item.SubItems.AddRange(SubItems.ToArray());

listView1.Items.Add(item);

UpdateItem(item);

}

}

ListViewItem.ListViewSubItem[] SubItems

{

get

{

return new ListViewItem.ListViewSubItem[] { new ListViewItem.ListViewSubItem(), new ListViewItem.ListViewSubItem() };

}

}

private void UpdateItem(ListViewItem item)

{

FileInfo info = (FileInfo)item.Tag;

item.Text = info.Name;

item.SubItems[1].Text = info.Length.ToString("N0");

item.SubItems[2].Text = info.LastWriteTime.ToString();

}

复制代码

ListView共有3列,分别显示文件名、大小和最后修改时间,运行以后我发现,文件名可以显示,但是后面2列不能显示。经过各种调试和偶遇,终于让我发现,只要改变ListViewItem.Text的值,后面两列的内容就能够显示了,于是初步解决方案是改变ListViewItem.Text的赋值顺序,把它放在所有SubItem.Text赋值以后再赋值。

为了找到根本原因,我翻查了.net类库的源代码,最后终于发现问题所在,先来看看ListViewSubItem.Text的源代码。

复制代码

public string Text {

get {

return text == null ? "" : text;

}

set {

text = value;

if (owner != null) {

owner.UpdateSubItems(-1);

}

}

}

复制代码

在对此属性赋值时,首先检查owner字段的值是否为空,如果不为空才调用owner.UpateSubItems方法对ListView进行更新。很明显,出现上面的问题时,owner值一定为空,通过在VS里调试证实了这点。

现在的问题是,为什么这owner会为空,owner的类型是ListViewItem,从字面理解它应该是SubItem所属的那个行项目,正常情况下在添加到ListViewItem.SubItems以后就应该不会为空,于是我猜是在添加的时候这个owner没有被赋值。后来通过查看源代码以后证实了我的想法,来看看ListViewSubItemCollection关于添加子项的源码

复制代码

public ListViewSubItem Add(ListViewSubItem item) {

EnsureSubItemSpace(1, -1);

item.owner = this.owner;

owner.subItems[owner.SubItemCount] = item;

owner.UpdateSubItems(owner.SubItemCount++);

return item;

}

public void AddRange(ListViewSubItem[] items) {

if (items == null) {

throw new ArgumentNullException("items");

}

EnsureSubItemSpace(items.Length, -1);

foreach(ListViewSubItem item in items) {

if (item != null) {

owner.subItems[owner.SubItemCount++] = item;

}

}

owner.UpdateSubItems(-1);

}

复制代码

很明显,Add方法对owner进行了赋值,但AddRange方法没有,而在帖子里所用的是AddRange方法,所以造成了这个问题。

那为什么对Text赋值以后,子项里的内容又能够显示了呢?好吧,再来看看ListViewItem.Text的源码

复制代码

public string Text {

get {

if (SubItemCount == 0) {

return string.Empty;

}

else {

return subItems[0].Text;

}

}

set {

SubItems[0].Text = value;

}

}

复制代码

对ListViewItem.Text的赋值实际上就是对它第0个子项的Text赋值,那为什么这个子项可以工作呢,好吧,再来看看第0个子项的来历,以下是ListViewItem.SubItems的源码。

复制代码

public ListViewSubItemCollection SubItems {

get {

if (SubItemCount == 0) {

subItems = new ListViewSubItem[1];

subItems[0] = new ListViewSubItem(this, string.Empty);

SubItemCount = 1;

}

if (listViewSubItemCollection == null) {

listViewSubItemCollection = new ListViewSubItemCollection(this);

}

return listViewSubItemCollection;

}

}

复制代码

由于帖子里使用了ListViewItem的无参数构造函数,因此在第一次调用SubItems属性时,SubItemCount的值为0,这时就会自动插入一个子项,而这里使用的构造函数直接把当前ListViewItem传进去了,子项的owner就有了值,因此可以正常显示文字。回想前面的对子项Text赋值的源码,在赋值以后会调用owner.UpdateSubItems(-1)来更新显示,这个方法并不是仅仅更新一个子项,而是会更新所有子项,因此所有的内容又都可以看到了。

最后还有一个问题,为什么调用ListView.Refresh或Invalidate方法没用呢?我没有做深入研究,只是做一个猜想。因为.net的ListView控件只是对原生Windows的ListView控件的封装,在OwnerDraw为false时,所有的绘图都由原生的ListView控件完成。从以上代码可以看出,子项的文本在托管代码里保存了一份,而我敢肯定在原生的控件里也保存了一份,当owner存在时,这两个值是相同的,而在owner不存在时,由于没有更新导致原生控件里没有更新而失去了同步,这样无论怎么Refresh都是没有用的。

Bug就分析到此,原因找到了,解决办也自然有了。但我想说的不是解决办法,而是怎么利用这个BUG,再来看看ListViewItem.UpdateSubItems方法。

复制代码

internal void UpdateSubItems(int index){

UpdateSubItems(index, SubItemCount);

}

internal void UpdateSubItems(int index, int oldCount){

if (listView != null && listView.IsHandleCreated) {

int subItemCount = SubItemCount;

int itemIndex = Index;

if (index != -1) {

listView.SetItemText(itemIndex, index, subItems[index].Text);

}

else {

for(int i=0; i < subItemCount; i++) {

listView.SetItemText(itemIndex, i, subItems[i].Text);

}

}

for (int i = subItemCount; i < oldCount; i++) {

listView.SetItemText(itemIndex, i, string.Empty);

}

}

}

复制代码

ListViewSubItem.set_Text在调用此方法时,专入的参数是-1,可以看出这将会导致所有的子项重绘,这点前面说过了。按此计算,如果ListView有10列,那每行需要重绘100次,其中有90次是在做无用功,不但增加了CPU的负担,还会可能会导致界面闪烁,但如果合理地利用这个BUG,可以有效改善这个情况。

==补充======================================================

做了一个实地测试,30列200行,做一次所有行和列的刷新,常规方法700ms,而利用这个BUG可以降到25ms。

最后做个总结

在为ListView添加行项目时,各项目的SubItem如果采用AddRange方法添加,会导致在后续更新SubItem的Text时,界面上不会更改,解决办法有两种:

1、不要使用ListViewItem.SubItems.AddRange方法,而改用Add。

2、仍旧使用AddRange方法,但在更新内容时,第0列(也就是ListViewItem.Text)最后更新。

但是这个BUG歪打正着地为提升ListView性能提供了可能,可使用上面的第2个解决办法实现,在大数量时效果尤其明显。

时间: 2024-10-09 22:01:45

.net类库里ListView的一个BUG的相关文章

NGUI中UILabel使用url标签的一个bug

在NGUI里,UILabel控件可以支持一些简单功能的标签,使文本显示更丰富及实现类似超链接的功能.但是在使用的时候发现了NGUI3.5.9版本里存在着一个bug.不过还好修复这个bug也很简单. 在UILabel中支持[url=link]text[/url]的方式来定义类超链接的文本.bug就出现在同一个UILabel里使用两个及以上这种标签时,最终显示的label内容就会全错掉.   text内容:[url=a]a[/url][url= 当再输入任一字符后,label的内容就全消失了. bu

亚马逊AWS学习——多网络接口下配置EC2实例连接公网的一个“bug”

之前在<亚马逊AWS学习--EC2的自定义VPC配置>这篇文章中讲述了如何设置自定义VPC并使自己的EC2实例能够连接公网.本篇说一下连接公网时会出现的一个小问题. 如题所示,在一个EC2实例具有多个网络接口的环境下,如果为其配置公网连接会有一个"bug".其实也不能说是"bug",而是AWS网络环境的限制. 1. 主网络接口 我们知道,很多时候我们的一台主机需要有多个网络接口,以使其同时架设在不同的网络中.EC2实例创建时会有一个主网络接口,默认描述为

有人向我反馈了一个bug

//我是一个前端开发者,但我想这个故事对任何开发者都会引起共鸣的 有人向你反馈了一个 bug. “26 楼会议室的灯亮着.它需要被熄灭.”bug 的备注里写道“你应该能在 5 分钟内搞定,只要按一下开关就好了.“ 你去了 26 楼的会议室.灯的确亮着,但房间里没有灯的开关. 所以,你准备安装一个.但设计师说,它会破坏房间的美感.另外,墙壁是混凝土.你需要合适的工具才能安装开关.但是,没有人会批准购买这些工具.如果没有合适的工具,安装开关将需要两天.他们希望你现在就能把灯关上,因为他们害怕 CEO

解决JSONCPP 0.10.2的一个Bug

最近在使用jsoncpp 0.10.2的过程中碰到一个bug,创建的数组,无法超过5个元素,测试代码如下: int j = 0; int count = 20; Json::Value root; Json::Value item; for (int i = 0; i < count; i++) { root[i] = i; j = root.size(); } 在我的实际项目中,如果数组只有1个是元素(该元素稍微有点大的JSON对象),也有可能出现这个元素的值错误的故障,超过5个肯定出错. 在

F#编译器的一个Bug

[<Struct>] typeTestStruct = val mutable _x:int new(x) = {_x = x} member this.Set(x) = this._x <- x member this.X with get() = this._x; let a = TestStruct(2) let c() = a.Set 10 a.X c();; 这里编译器不报错,但是执行c()的结果是2. 如果把[<Struct>]去掉,执行结果是10,这就正确了.

挺有意思的一个bug

偶遇一奇怪的bug,型如$("tt").html("<div>"+0||Math.floor(Math.random()*100))+"</div>") 这个东西出现一个截取问题,看起来挺长的,实际上结果只有$("tt").html("<div>"+0)的结果,后面被截取了,想了半天有点云里雾里的,其实这就是一个细节的问题,或运算有个特点,是只要前部分为true结果就为t

【总结】使用jdbc+servlet开发一个bug管理系统的经验总结

开发背景: 公司目前使用Teambition里面的task作为bug管理系统,既没有bug的当前状态,也不能写上bug的详细复现步骤,被assign了任务(该修复bug或者验证bug是否被修复)也没有邮件通知 也不能查看自己名下当前有多少bug待修复,分别属于哪些项目. 收获: 1. 前台jsp: 1.1. <a href="mailto:邮箱地址"> 安装了outlook后点击此链接会打开一个新建邮件界面 1.2. <meta http-equiv="re

CSS IE6、7下关于Position的一个bug问题分享

又好久没来了,小码哥甚是想念 想念我的人.由于近期工作中跟CSS打交道较多,所以偶尔会碰到有关它的一些问题,CSS很强大,尤其是后来的CSS3.鄙人正在学习中,如果就所遇到的问题,分析有偏差,望大家海涵哈!!下面就说说我刚刚遇到的一个问题,也许某些前辈大拿们会在心里BS我,不过对我来说,都是收获!嘎嘎,, 总所周知,我们都知道CSS中的定位position属性是一个相当重要和特殊的属性.它分别有一下几个属性值: position:relative; position:absolute; posi

AIX6.1/11.2.0.3数据库上关于SWAP的一个BUG

昨天去南京某客户那里调优新上线的业务数据库,在查看alert.log日志时发现在过去的一段时间里,每过几个小时或间隔一段时间,就会报类似以下的内容: Thu Aug 21 09:01:26 2014 WARNING: Heavy swapping observed on system in last 5 mins. pct of memory swapped in [8.42%] pct of memory swapped out [2.16%]. Please make sure there