이번 포스트에서는, 윈도우7의 Aero UI에 맞게 THeaderControl의 헤더섹션을 제대로 OwnerDraw 하는 방법에 대해 살펴봅니다.
THeaderControl은 글자 그대로 헤더 섹션들을 정의해놓은 헤더 컨트롤입니다. ListView에도 포함되어 있구요. 이 HeaderControl의 각 섹션에는 Text 속성을 통해 나타날 텍스트를 지정해줄 수 있습니다. 그런데 이 Text에는 멀티라인, 즉 여러 줄의 텍스트를 지정할 수는 없습니다. 그래서 헤더컨트롤의 섹션에 여러 줄의 텍스트를 표시할 필요가 있을 때는 OwnerDraw를 이용하는데요, 델파이에서라면 대략 다음과 비슷한 식입니다.
1 2 3 4 5 6 7 |
procedure TForm1.HeaderControl1DrawSection(HeaderControl: THeaderControl; Section: THeaderSection; const Rect: TRect; Pressed: Boolean); begin FillRect(HeaderControl.Canvas.Handle,Rect, HeaderControl.Canvas.Brush.Handle); HeaderControl.Canvas.TextOut(Rect.Left+4, Rect.Top+4, `첫번째 라인`); HeaderControl.Canvas.TextOut(Rect.Left+4, Rect.Top+17, `두번째 라인`); end; |
이런 코드는, 윈도우XP까지는 다음과 같이 의도한 대로 나타납니다.
그런데, 동일한 코드가, 윈도우 비스타 이상, 윈도우7에서는 다음과 같이 부자연스럽게 나타납니다.
보다시피, 윈도우7에서는 헤더컨트롤의 형태가 XP까지의 버튼 형태와는 달리 그라데이션으로 그려집니다. 그런데 섹션을 OwnerDraw로 지정한 경우(Style=hsOwnerDraw), 그라데이션 배경을 그리지 않고 그냥 비워두고는 그리기 동작 전체를 개발자에게 맡겨버립니다. (이전에는 OwnerDraw로 지정된 섹션에도 일반 섹션처럼 버튼 모양은 그려줬었으니, 에어로 환경에서도 그라데이션 배경은 그려줘야 맞는 건데요. 이건 윈도우가 버전들 사이에서 일관성이 없는 조치인 거죠)
이 그라데이션은 윈도우7의 Aero 테마의 효과입니다. 따라서 그냥 그라데이션을 그려서 따라하는 것이 아니라 테마의 기능을 빌려서 그려줘야 다른 일반 섹션들과 동일하게 나타납니다. 윈도우 테마의 기능을 따라서 UI를 그려주려면, Themes(Vcl.Themes) 유닛의 ThemeServices를 이용하면 됩니다. (델파이/C++빌더 XE2 버전부터는 StyleServices로 이름이 바뀌었습니다만 여전히 ThemeServices도 사용 가능합니다)
따라서, OwnerDraw를 위한 OnDrawSection 이벤트 핸들러에서 윈도우7의 에어로 UI 배경을 그려주는 코드가 추가로 필요하게 됩니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
// Delphi 코드 uses Themes; procedure TForm1.HeaderControl1DrawSection(HeaderControl: THeaderControl; Section: THeaderSection; const Rect: TRect; Pressed: Boolean); var LDetails: TThemedElementDetails; begin SetBkMode(HeaderControl.Canvas.Handle, TRANSPARENT); LDetails := ThemeServices.GetElementDetails(thHeaderItemNormal); ThemeServices.DrawElement(HeaderControl.Canvas.Handle, LDetails, Rect, Rect); HeaderControl.Canvas.TextOut(Rect.Left+4, Rect.Top+4, `첫번째 라인`); HeaderControl.Canvas.TextOut(Rect.Left+4, Rect.Top+17, `두번째 라인`); end; |
1 2 3 4 5 6 7 8 9 10 |
// C++Builder 코드 void __fastcall TForm1::HeaderControl1DrawSection(THeaderControl *HeaderControl, THeaderSection *Section, const TRect &Rect, bool Pressed) { SetBkMode(HeaderControl->Canvas->Handle, TRANSPARENT); TThemedElementDetails LDetails = ThemeServices()->GetElementDetails(thHeaderItemNormal); ThemeServices()->DrawElement(HeaderControl->Canvas->Handle, LDetails, Rect, Rect); HeaderControl->Canvas->TextOut(Rect.Left+4, Rect.Top+4, "첫번째 라인"); HeaderControl->Canvas->TextOut(Rect.Left+4, Rect.Top+17, "두번째 라인"); } |
여기서 ThemeServices 객체의 메소드 GetElementDetails 함수는 지정한 UI 요소의 정보를 가져옵니다. 그리고 DrawElement 함수는 지정한 DC에 지정한 UI 객체 정보대로 그려줍니다. (SetBkMode 함수는 Win32 API 함수로서, 지정된 DC의 배경색을 투명하게 혹은 불투명하게 지정하기 위해 사용합니다. 여기서는 SetBkMode 함수를 사용하지 않으면 글자가 그려지는 영역에 불투명한 배경이 그려지면서 그라데이션을 어색하게 가려버리게 됩니다)
그렇다면, 에어로 UI가 없어진 윈도우8에서는 어떨까요. 실제로 테스트를 해봤는데, 아래와 같이 잘 나옵니다. 이것은 ThemeServices가 현재 OS의 테마 설정을 제대로 읽어오고, 또 헤더컨트롤 등의 델파이/C++빌더의 컴포넌트들도 해당 윈도우 버전의 UI를 잘 따르기 때문입니다.
이 기능은 델파이/C++빌더에서 처음으로 에어로를 지원하게 된 2007 버전부터 제대로 사용할 수 있습니다.
노트1: 이 기능을 사용하려면 Themes(Vcl.Themes) 유닛을 uses 해야 하는데요. 이 유닛은 델파이가 윈도우XP에서 처음으로 테마를 지원했던 델파이7 버전에서 추가되었습니다. 따라서 델파이/C++빌더6 이하에서는 전혀 사용이 불가능하고요. 하지만 그 이상의 버전이라고 다 사용해도 되는 것은 아닙니다. 델파이/C++빌더 2006까지의 버전은 에어로가 처음 도입된 비스타를 지원하지 않는 버전이기 때문에 , 7~2006까지의 ThemeServices가 테마 자체는 그려주지만 역으로 THeaderControl 등의 VCL 컨트롤 쪽에서 에어로 테마를 제대로 반영하지 못한 XP 스타일로 나타나게 됩니다. 즉 컨트롤은 에어로가 반영되지 않았는데 OwnerDraw한 영역은 에어로로 그려지는 역효과가 납니다. 따라서, 윈도우7에서 델파이 7~2006 사이의 버전으로 개발된 애플리케이션에서는 차라리 ThemeServices를 사용하지 않는 것이 더 나을 것 같습니다. 물론 가장 좋은 선택은 적어도 2007 이상의 버전을 사용하는 것입니다.
노트2: XE2 버전부터는 ThemeServices 대신 StyleServices를 사용하도록 권장됩니다. XE2 버전에서 기존의 윈도우 테마 기능 외에 델파이 자체 스타일 기능이 도입되면서 기존의 ThemeServices가 StyleServices로 바뀐 것인데요. 하지만 ThemeServices 함수는 결과 타입만 바뀌었을 뿐 그전과 동일하게 존재하기 때문에, ThemeServices 그대로 사용하더라도 컴파일과 실행에 아무런 문제가 없습니다(현재 최신 버전인 XE5까지 정상 동작). 다만 StyleServices가 더 권장되기 때문에, ThemeServices를 사용하면 컴파일시에 deprecated 경고가 나타나게 됩니다. deprecated 처리된 이상 향후 버전의 언젠가에는 ThemeServices가 사라질 가능성이 있기 때문에, 델파이 버전들에서 향후 호환성을 우선한다면 StyleServices를 쓰는 게 좋겠습니다. 반대로 하위 호환성을 우선한다면 ThemeServices를 쓰는 게 낫고요.