윈폼(WinForm)을 가지고 프로그래밍을 하다가 보면.. 리스트뷰(ListView) 컨트롤을 사용할 때가 있다.

이 리스트뷰 컨트롤은 사용하기도 쉽고, 열(Column)이나 그룹(Group) 을 가지고 자료들을 그룹화할 수 있기 때문에 꽤 자주 쓰이는 컨트롤 중 하나이다.


그런데, 기본으로 제공되는 리스트뷰 컨트롤(System.Windows.Forms.ListView)의 성능은? 좋은 편은 아니다.

테마도 적용이 안되있어서 API를 사용하여 강제적으로 테마를 적용해야 그럴듯하게 표시된다.


(사진 1. 테마가 적용되지 않은 ListView 컨트롤의 모습)

사진 1에서 알 수 있듯 테마가 적용되지 않은 컨트롤은 윈도우 7임에도 불구하고 XP에서 볼 수 있듯한 테마로 리스트뷰를 그리고 있다.


(사진 2. 같은 환경 내에서의 탐색기(explorer)의 리스트뷰 모습)

사진 2에서는 사진 1의 리스트뷰보다 훨씬 멋지고 고급스러운 테마로 리스트뷰를 그리고 있다.


그리고 계속 선택하고, 스크롤 등의 작업을 해보면 알겠지만 기본으로 제공되는 리스트뷰 컨트롤은 심하게 깜빡인다.

깜빡거림과 테마 쯤이야 대수롭지 않게 생각하시는 분들께선 상관없겠지만, 거슬리고!! 테마가 너무 거지같아서!!! 변화를 주고자 하시는 분들께선 이 글이 도와줄거라 생각됩니다.



이 깜빡거림과 테마 문제를 해결하는 것은 정말로 어려운 일이 아닙니다.

  • API 선언
  • ListView 클래스를 상속하는 클래스 생성


API?? 상속?? 클래스??

이게 무슨 말인지 이해가 안되시는 분들, 맨 밑으로 스크롤 쭉 내리셔서 제가 요약해드리는 것만 읽으셔도 됩니다.

이해가 안되시더라도 글을 읽고싶다 하시는 분들은 읽어주시면 됩니다 :)




그럼 API는 어떤걸 사용할까요?

SetWindowTheme API를 사용합니다.


ListView 클래스를 상속하는 클래스 생성은 어떻게?

정말 간단합니다.

public class ListViewEx : ListView {
}

Public Class ListViewEx
    Inherits ListView
End Class


이렇게 클래스를 하나 생성하면, ListView 와 똑같은 컨트롤을 이름만 다르게 한 ListViewEx 컨트롤이 생성됩니다.

이제 정말 다인가요?

아쉽게도 이렇게 간단하면 좋겠지만, 이렇게 간단한 것은 아닙니다.

새로 생성한 클래스의 생성자에 딱 한줄만 추가하면 됩니다.

public class ListViewEx : ListView {
    public ListViewEx() {
        SetStyle(ControlStyles.OptimizedDoubleBuffer |
                 ControlStyles.AllPaintingInWmPaint |
                 ControlStyles.ResizeRedraw, true);
    }
}

Public Class ListViewEx
    Inherits ListView
    
    Public Sub New()
        SetStyle(ControlStyles.OptimizedDoubleBuffer Or _
                 ControlStyles.AllPaintingInWmPaint Or _
                 ControlStyles.ResizeRedraw, True)
    End Sub
End Class


이걸 더 간단히 바꾸면 이렇게 바꿀 수 있습니다.

OptimizedDoubleBuffer 에 해당하는 값인 0x20000,

AllPaintingInWmPaint 에 해당하는 값인 0x2000,

그리고 마지막으로 ResizeRedraw 에 해당하는 값인 0x10을 이용하여 세 가지 옵션에 해당하는 값인 0x22010 을 ControlStyles 형식으로 캐스팅하면 됩니다.

public class ListViewEx : ListView {
    public ListViewEx() {
        SetStyle((ControlStyles) 0x22010, true);
    }
}

Public Class ListViewEx
    Inherits ListView
    
    Public Sub New()
        SetStyle(&H22010, True)
    End Sub
End Class


이런 식으로 말이죠.

자 여기까지 하면, 얼추 클래스의 골격을 잡는 일은 마무리되었습니다.

그럼 이제 SetWindowTheme API를 이용하여 테마를 적용하기만 하면 끝입니다.

SetWindowTheme API는 어디에 위치해야 할까요?

OnHandleCreated 메서드를 오버라이드한 후에 base.OnHandleCreated(e); 를 호출한 다음 부분에 위치시키면 됩니다.

protected override void OnHandleCreated(EventArgs e) {
    base.OnHandleCreated(e);
    
    // 핸들이 생성된 후에 테마를 적용한다.
    SetWindowTheme(Handle, "explorer", null);
}

Protected Overrides Sub OnHandleCreated(e As EventArgs)
    MyBase.OnHandleCreated(e)
    
    ' 핸들이 생성된 후에 테마를 적용한다.
    SetWindowTheme(Handle, "explorer", Nothing)
End Sub


ListViewEx 클래스의 전체 소스 코드는 다음과 같습니다.

public class ListViewEx : ListView {
    
    [DllImport("uxtheme", CharSet = CharSet.Auto)]
    static extern Boolean SetWindowTheme(IntPtr hWindow, String subAppName, String subIDList);
    
    public ListViewEx() {
        SetStyle((ControlStyles) 0x22010, true);
    }
    
    protected override void OnHandleCreated(EventArgs e) {
        base.OnHandleCreated(e);
        
        // 핸들이 생성된 후에 테마를 적용한다.
        SetWindowTheme(Handle, "explorer", null);
    }
}

Public Class ListViewEx
    Inherits ListView

    <DllImport("uxtheme", CharSet := CharSet.Auto)> _
    Private Shared Function SetWindowTheme(hWindow As IntPtr, subAppName As [String], subIDList As [String]) As [Boolean]
    End Function

    Public Sub New()
        SetStyle(&H22010, True)
    End Sub

    Protected Overrides Sub OnHandleCreated(e As EventArgs)
        MyBase.OnHandleCreated(e)

        ' 핸들이 생성된 후에 테마를 적용한다.
        SetWindowTheme(Handle, "explorer", Nothing)
    End Sub
End Class


그리고 이 코드의 실행 결과는 다음과 같습니다.


(사진 3. 테마가 적용된 리스트뷰 컨트롤)

이렇게 만들어진 클래스를 사용하면 깜빡임도 없고 테마까지 적용된 상태로 표시되게 됩니다.


결론

OptimizedDoubleBuffer, AllPaintingInWmPaint 옵션을 주어 깜빡거림을 최소화하였고,

ResizeRedraw 옵션으로 크기가 조정될 때 항목이 그려지지 않는 것을 방지하였습니다.


그리고 핸들이 생성될 때 테마를 적용시켜 기존 테마의 밋밋함을 제거했습니다.


다음은 결과 코드입니다. 결과 코드가 폼 또는 독립적인 파일에 정의되어야 합니다.

(결과 코드를 폼에 추가하고 빌드를 하면 사용자 정의 구성요소에 ListViewEx 항목이 추가됩니다. 추가된 항목을 ListView 컨트롤 대신 사용하시면 됩니다)

폼에 어떻게 정의하냐구요?

: 아래 코드를 참고하여 따라하시면 됩니다.


독립적인 파일에 어떻게 정의하냐구요?

: 프로젝트에 소스 파일(*.cs 또는 *.vb) 을 추가하고 그 파일에 ListViewEx 클래스의 전체 코드를 추가하시면 됩니다.

using System;
using System.Windows.Forms;

// 이 네임스페이스가 반드시 포함되어야 합니다!
using System.Runtime.InteropServices;

namespace WindowsApplication1 {
    public partial class MainForm : Form {
        // 폼 클래스 내부에 이렇게~
        public class ListViewEx : ListView {
            [DllImport("uxtheme", CharSet = CharSet.Auto)]
            static extern Boolean SetWindowTheme(IntPtr hWindow, String subAppName, String subIDList);
            
            public ListViewEx() {
                SetStyle((ControlStyles) 0x22010, true);
            }
            
            protected override void OnHandleCreated(EventArgs e) {
                base.OnHandleCreated(e);
                
                // 핸들이 생성된 후에 테마를 적용한다.
                SetWindowTheme(Handle, "explorer", null);
            }
        }
    }
}

Imports System
Imports System.Windows.Forms

' 이 네임스페이스가 반드시 포함되어야 합니다!
Imports System.Runtime.InteropServices

Namespace WindowsApplication1
    Public Partial Class MainForm
        Inherits Form
        ' 폼 클래스 내부에 이렇게~
        Public Class ListViewEx
            Inherits ListView
            <DllImport("uxtheme", CharSet := CharSet.Auto)> _
            Private Shared Function SetWindowTheme(hWindow As IntPtr, subAppName As [String], subIDList As [String]) As [Boolean]
            End Function

            Public Sub New()
                SetStyle(&H22010, True)
            End Sub

            Protected Overrides Sub OnHandleCreated(e As EventArgs)
                MyBase.OnHandleCreated(e)

                ' 핸들이 생성된 후에 테마를 적용한다.
                SetWindowTheme(Handle, "explorer", Nothing)
            End Sub
        End Class
    End Class
End Namespace


이런 식으로 Form 클래스 내에 넣어주시면 됩니다!


긴 글 읽어주셔서 감사합니다 !!

+ Recent posts