.net 라이브러리에 있는 ListView의 BUG

13263 단어 ListView
오늘 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를 업데이트합니다.위의 문제가 발생했을 때 위너 값이 반드시 비어 있음을 VS에서 디버깅을 통해 증명하였다.
문제는 왜 이 owner가 비어 있느냐는 것이다. owner의 유형은ListViewItem이다. 글씨체로 보면 SubItem이 속한 줄 항목이어야 하며 정상적인 상황에서ListViewItem에 추가된다.SubItems는 앞으로 비어 있지 않을 것입니다. 그래서 추가할 때 이 위너가 부여되지 않았을 것입니다.나중에 원본 코드를 확인한 후에 제 생각을 확인했습니다.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의 성능을 향상시키기 위해 가능성을 제공했고 위의 두 번째 해결 방법을 사용하여 실현할 수 있으며 대량의 경우 효과가 특히 뚜렷하다.

좋은 웹페이지 즐겨찾기