A bug in the. net class library ListView, listview in the. net library
I read a post in the CSDN forum today, saying that the first line of content after an entry is added to ListView is not displayed. I wrote the following code to restore the problem.
Copy the code 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 () ;}copy code ListView has three columns: file name, size, and last modification time. After running, I found that the file name can be displayed, but the last two columns cannot. After various debugging and encounters, I finally found that I only need to change the ListViewItem. text value, the content of the next two columns can be displayed, so the initial solution is to change the ListViewItem. text Value Order, put it in all subitems. text is assigned and then assigned. To find the root cause, I checked the source code of the. net class library and finally found the problem. Let's take a look at the source code of ListViewSubItem. Text. Copy the public string Text {get {return text = null? "": Text;} set {text = value; if (owner! = Null) {owner. updateSubItems (-1) ;}} when the copy Code assigns a value to this attribute, first check whether the value of the owner field is null. If not empty, the owner is called. the UpateSubItems method updates the ListView. Obviously, the owner value must be blank when the above problem occurs. This is confirmed by debugging in. The question is, why is the owner empty? The owner type is ListViewItem, which is literally the row project to which the SubItem belongs and is added to ListViewItem normally. subItems will not be empty in the future, so I guess this owner is not assigned a value when it is added. Later, after checking the source code, I confirmed my idea and checked the source code of the ListViewSubItemCollection item. Copy the 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);} the copied code is obvious. The Add method assigns a value to the owner, but the AddRange method does not. In the post, the AddRange method is used, this problem is caused. Why can the content in the subitem be displayed after the Text value is assigned? Okay, let's take a look at ListViewItem. copy the public string Text {get {if (SubItemCount = 0) {return string. empty;} else {return subItems [0]. text ;}} set {SubItems [0]. text = value ;}} copy the code to ListViewItem. the value assignment of Text is actually to assign a value to the Text of its 0th sub-items. Why can this sub-item work? Well, let's take a look at the origins of its 0th sub-items. below is the ListViewItem. source code of SubItems. Copy the 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 ;}} because the ListViewItem non-parameter constructor is used in the post, when the SubItems attribute is called for the first time, SubItemCount When the value is 0, a subitem is automatically inserted. The constructor used here directly uploads the current ListViewItem, And the subitem owner has a value, so the text can be displayed normally. Recall the source code of the subitem Text assignment. The owner is called after the assignment. updateSubItems (-1) to update the display. This method does not update only one subitem, but updates all subitems, so all the content can be seen again. The last question is, why is it useless to call the ListView. Refresh or Invalidate method? I did not do any in-depth research, but just made a conjecture. Because the. net ListView control only encapsulates the ListView control of the Native Windows, when OwnerDraw is false, all the drawings are completed by the native ListView control. From the code above, we can see that the text of the subitem is saved in the managed code, and I am sure a copy is saved in the native control. When the owner exists, these two values are the same, and when the owner does not exist, synchronization is lost because the native control is not updated because there is no update, so Refresh is useless no matter how. The Bug is analyzed here. The cause is found, and the solution is also available. But what I want to talk about is not a solution, but how to use this BUG. Let's take a look at the ListViewItem. UpdateSubItems method. Copy the code 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) ;}} copy the code ListViewSubItem. when set_Text calls this method, the specific parameter is-1. It can be seen that this will lead to re-painting of all sub-items. Based on this calculation, if the ListView has 10 columns, each row needs to be re-painted 100 times, 90 of which are useless, not only increasing the CPU burden, it may also cause the interface to flash, but if the BUG is used properly, this situation can be effectively improved. ===================================================== ======================== I did a field test, refresh all rows and columns for 30 columns in 200 ms. The general method is 700 ms, and the BUG can be reduced to 25 ms. Finally, when adding a row project to the ListView, if the SubItem of each item is added using the AddRange method, the page will not be changed when the SubItem Text is updated in the future, there are two solutions: 1. Do not use ListViewItem. subItems. instead, use the Add method. 2. The AddRange method is still used, but when the content is updated, The 0th columns (that is, ListViewItem. Text) are last updated. However, this BUG provides the possibility to improve the ListView performance. You can use the above 2nd solutions to achieve this, especially in the case of a large number of timeliness results.