今天在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[].Text = info.Length.ToString("N0");
item.SubItems[].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(-);
}
}
}

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

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

            public ListViewSubItem Add(ListViewSubItem item) {
EnsureSubItemSpace(, -);
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, -); foreach(ListViewSubItem item in items) {
if (item != null) {
owner.subItems[owner.SubItemCount++] = item;
}
} owner.UpdateSubItems(-);
}

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

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

        public string Text {
get {
if (SubItemCount == ) {
return string.Empty;
}
else {
return subItems[].Text;
}
}
set {
SubItems[].Text = value;
}
}

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

        public ListViewSubItemCollection SubItems {
get {
if (SubItemCount == ) {
subItems = new ListViewSubItem[];
subItems[] = new ListViewSubItem(this, string.Empty);
SubItemCount = ;
} 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 != -) {
listView.SetItemText(itemIndex, index, subItems[index].Text);
}
else {
for(int i=; 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个解决办法实现,在大数量时效果尤其明显。

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

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

    在NGUI里,UILabel控件可以支持一些简单功能的标签,使文本显示更丰富及实现类似超链接的功能.但是在使用的时候发现了NGUI3.5.9版本里存在着一个bug.不过还好修复这个bug也很简单. 在 ...

  2. jquery-multiselect在ie6里的一个bug

    在使用jquery-multiselect(一个把下拉框改造成带checkbox的可以多选的控件)时,正常时应该是下面这样:而它在ie6里是下面这样: 其中第一个bug参考‘ie6里png图片不透明’ ...

  3. VC2013的一个bug

    前段时间在尝试使用一个C++的GUI库nana.这个库最大的特点在于使用现代C++风格去编写GUI程序,而不需要使用大量的比较丑陋的代码(如MFC中的各种宏),或者其它的非C++元素.这是一个比较新的 ...

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

    开发背景: 公司目前使用Teambition里面的task作为bug管理系统,既没有bug的当前状态,也不能写上bug的详细复现步骤,被assign了任务(该修复bug或者验证bug是否被修复)也没有 ...

  5. 有人向我反馈了一个bug

    我是一个前端开发者,但我想这个故事对任何开发者都会引起共鸣的有人向你反馈了一个 bug. “26 楼会议室的灯亮着.它需要被熄灭.”bug 的备注里写道“你应该能在 5 分钟内搞定,只要按一下开关就好 ...

  6. 从修复 testerhome(rubychina)网站的一个 bug 学习 ruby&rails on ruby

    前言 testerhome: http://testerhome.com/topics/1480 对于一个差点脱离前沿技术人,想要学习ruby,就意味着要放弃熟悉的操作系统windows,熟悉的ide ...

  7. [置顶]VC2013的一个bug

    [置顶]VC2013的一个bug 前段时间在尝试使用一个C++的GUI库nana.这个库最大的特点在于使用现代C++风格去编写GUI程序,而不需要使用大量的比较丑陋的代码(如MFC中的各种宏),或者其 ...

  8. sqlite在Android上的一个bug:SQLiteCantOpenDatabaseException when nativeExecuteForCursorWindow

    更多内容在这里查看 https://ahangchen.gitbooks.io/windy-afternoon/content/ ::-/com.company.product W/System.er ...

  9. [android开发IDE]adt-bundle-windows-x86的一个bug:无法解析.rs文件--------rs_core.rsh file not found

    google的android自带的apps写的是相当牛逼的,将其导入到eclipse中方便我们学习扩展.可惜关于导入的资料太少了,尤其是4.1之后的gallery和camera合二为一了.之前导4.0 ...

随机推荐

  1. arcgis_engine_develop_error_42

    解决: 今天在VS2013打开程序时,手工添加了pageLayoutControl隔一会弹出错误窗口:Provide your license server administrator with th ...

  2. 【JAVA】【Eclipse】出现This element neither has attached source nor attached Javadoc...的解决方法

    This element neither has attached source nor attached Javadoc and hence no Javadoc could be found Ec ...

  3. python selenium 操作chrome

    1.下载chromedriver.exe 入在chrome 浏览器目录下 from selenium import webdriverimport os chromedriver = "C: ...

  4. java 平台 权限管理

    最近在做公司内部的物流业务平台的权限管理,感触颇多.记录一下 权限管理分两部分:数据权限和操作权限. 数据权限: 这个是和用户相关的. 因为平台是多机构的,所以再考虑数据权限的时候,是按照机构来管理的 ...

  5. Flex使用Blazeds与Java交互及自定义对象转换详解-DATAGRID读取ORACLE数据

    http://www.cnblogs.com/RocD-DuPeng/articles/1751040.html 一.建立Flex与Java交互的工程. 本文中讲到的交互是利用Blazeds的,因为这 ...

  6. jsp中两种include的区别【转】

    引用文章:http://www.ibm.com/developerworks/cn/java/j-jsp04293/ http://www.cnblogs.com/lazycoding/archive ...

  7. PHP集成百度Ueditor 1.4.3

    下载安装 1.首先到官网下载最新版的UE1.4.3UE官方下载地址:http://ueditor.baidu.com/website/download.html#ueditor 这里我下载的是1.4. ...

  8. Java程序员必须知道的10个调试技巧

    调试可以帮助识别和解决应用程序缺陷,在本文中,将使用大家常用的的开发工具Eclipse来调试Java应用程序. 但这里介绍的调试方法基本都是通用的,也适用于NetBeans IDE,我们会把重点放在运 ...

  9. 学习Linux入门50个基本命令

    Linux系统以一切皆文件的方式运行系统.虽然存在ubuntu版本的图形界面,但在企业的服务器里面还是以命令行系统运行为主. 以下是初学50个基本的Linux命令行的体会 1:pwd 显示当前你所在的 ...

  10. Jmeter使用入门

    修改时间 修改内容 修改人 2016.3.12 创建 刘永志 2016.6.18 完成 刘永志 Jmeter简介 Jmeter的基本概念 百度百科: Apache JMeter是Apache组织开发的 ...