.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[].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的更多相关文章
- NGUI中UILabel使用url标签的一个bug
在NGUI里,UILabel控件可以支持一些简单功能的标签,使文本显示更丰富及实现类似超链接的功能.但是在使用的时候发现了NGUI3.5.9版本里存在着一个bug.不过还好修复这个bug也很简单. 在 ...
- jquery-multiselect在ie6里的一个bug
在使用jquery-multiselect(一个把下拉框改造成带checkbox的可以多选的控件)时,正常时应该是下面这样:而它在ie6里是下面这样: 其中第一个bug参考‘ie6里png图片不透明’ ...
- VC2013的一个bug
前段时间在尝试使用一个C++的GUI库nana.这个库最大的特点在于使用现代C++风格去编写GUI程序,而不需要使用大量的比较丑陋的代码(如MFC中的各种宏),或者其它的非C++元素.这是一个比较新的 ...
- 【总结】使用jdbc+servlet开发一个bug管理系统的经验总结
开发背景: 公司目前使用Teambition里面的task作为bug管理系统,既没有bug的当前状态,也不能写上bug的详细复现步骤,被assign了任务(该修复bug或者验证bug是否被修复)也没有 ...
- 有人向我反馈了一个bug
我是一个前端开发者,但我想这个故事对任何开发者都会引起共鸣的有人向你反馈了一个 bug. “26 楼会议室的灯亮着.它需要被熄灭.”bug 的备注里写道“你应该能在 5 分钟内搞定,只要按一下开关就好 ...
- 从修复 testerhome(rubychina)网站的一个 bug 学习 ruby&rails on ruby
前言 testerhome: http://testerhome.com/topics/1480 对于一个差点脱离前沿技术人,想要学习ruby,就意味着要放弃熟悉的操作系统windows,熟悉的ide ...
- [置顶]VC2013的一个bug
[置顶]VC2013的一个bug 前段时间在尝试使用一个C++的GUI库nana.这个库最大的特点在于使用现代C++风格去编写GUI程序,而不需要使用大量的比较丑陋的代码(如MFC中的各种宏),或者其 ...
- sqlite在Android上的一个bug:SQLiteCantOpenDatabaseException when nativeExecuteForCursorWindow
更多内容在这里查看 https://ahangchen.gitbooks.io/windy-afternoon/content/ ::-/com.company.product W/System.er ...
- [android开发IDE]adt-bundle-windows-x86的一个bug:无法解析.rs文件--------rs_core.rsh file not found
google的android自带的apps写的是相当牛逼的,将其导入到eclipse中方便我们学习扩展.可惜关于导入的资料太少了,尤其是4.1之后的gallery和camera合二为一了.之前导4.0 ...
随机推荐
- js事件(一)之事件流
1.事件流定义 事件发生时会在元素节点与根节点之间按照特定的顺序传播,路径所经过的所有节点都会收到该事件,这个传播过程即DOM事件流. 事件传播的顺序对应浏览器的两种事件流模型:捕获型事件流和冒泡型事 ...
- 用systemd脚本自动启动node js程序
vi /usr/lib/systemd/system/pano.service [Unit] Description=pano - main site of site_name.com Documen ...
- 4、Python:strip(),split()
1.strip()函数 strip()是删除'()'里面的字符,当()为空时,默认删除空白符(包括'\n','\r','\t','') (1)s.strip(rm) 删除s字符串中开头. ...
- 前端---DOM
一.介绍: http://www.cnblogs.com/wupeiqi/articles/5643298.html 什么是DOM? DOM字面意思是文档对象模型,DOM将网页内的元素与内容呈现为一个 ...
- IndexedDB参考资料网址
IndexedDB:浏览器里内置的数据库, Web骇客 http://www.webhek.com/indexeddb/ 前端的数据库:IndexedDB入门(很全面) http://web.jobb ...
- Struts2和Spring的整合
1.Struts和Spring之间的整合,还是配置问题. 2.最重要的是spring是个容器,原来所有的框架的使用是要注入到spring中的啊.... 怪不得,说它是个容器那,原来还真是个容器啊! 3 ...
- HTTPS 和 HTTP
https://www.zhihu.com/question/52790301
- FreeMaker实现变量求和
今天在项目上遇到统计分页页面的某个字段的总和,前台页面是使用FreeMaker实现的,记录一下: <#assign tprice = 0 > <#list orderlist ...
- UE3 ExampleGame Android版无法运行解决方案
首先将 UE3\UnrealEngine3\UDKGame\Build\Android 文件夹 拷贝到 ExampleGame\Build下面.里面有相应的android 配置文件.xml 若果此时 ...
- 21.2 Partitioning Types
分区类型: range:根据列值得一个给定的范围 list:和range相似,除了分区被选择基于的列被匹配在一个被设定为分离的值 hash 基于列组成的表达式返回的非负值 key 相似hash ,除 ...