.net類庫裡ListView的一個BUG

來源:互聯網
上載者:User

標籤:http   io   ar   os   使用   sp   for   檔案   div   

今天在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控制項完成。從以上代碼可以看出,子項的文本在Managed 程式碼裡儲存了一份,而我敢肯定在原生的控制項裡也儲存了一份,當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個解決辦法實現,在大數量時效果尤其明顯。

.net類庫裡ListView的一個BUG

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.