コントロールをテーマでオーナードロー

 Grid系のコンポーネントの固定セルがのっぺりとしていてXP等で表示すると浮いて感じる事があります。こんな時はテーマ描画APIを使ってオーナードローするしかありません。これはDelphi7以降でThemes,UxThemeをusesする事で実現できます(それ以前のバージョンについてはユーザが独自に実装したユニットファイルが必要です。海外に幾つか存在するようです)。

 テーマ描画APIの概要については"Windows XP ビジュアルスタイルの使用(microsoft)"を参照してもらうとして、以下に具体例を載せておきます。

uses
  ...Themes,UxTheme;


var
  FTHEME_BUTTON: hTheme;
  FTHEME_HEADER: hTheme;
  FTHEME_COMBOBOX: hTheme;


procedure OpenTheme;
var
  Version:TOSVERSIONINFO;
  VersionNumber:Currency;
begin
  FTHEME_BUTTON   := 0;
  FTHEME_HEADER   := 0;
  FTHEME_COMBOBOX := 0;
  Version.dwOSVersionInfoSize := SizeOf(Version);
  if GetVersionEx(Version) then
    begin
      VersionNumber := StrToCurrDef(Format('%d.%d',[Version.dwMajorVersion,Version.dwMinorVersion]),0);
      if VersionNumber >= 5.1 then
        if GetComCtlVersion >= ComCtlVersionIE6 then
          begin
           if IsThemeActive then
            begin
             FTHEME_BUTTON   := OpenThemeData(Application.Handle,'BUTTON'  );
                FTHEME_HEADER   := OpenThemeData(Application.Handle,'HEADER'  );
                FTHEME_COMBOBOX := OpenThemeData(Application.Handle,'COMBOBOX');
            end;
 end;
    end;
end;

procedure CloseTheme;
begin
  if FTHEME_BUTTON <> 0 then
    CloseThemeData(FTHEME_BUTTON);
  if FTHEME_HEADER <> 0 then
    CloseThemeData(FTHEME_HEADER);
  if FTHEME_COMBOBOX <> 0 then
    CloseThemeData(FTHEME_COMBOBOX);
end;

 こんな感じでテーマハンドルを取得/解放する関数を用意しておきます。例ではボタン/ヘッダ(ListView)/チェックボックスのテーマハンドルを取得しています。OSがXP以降でテーマが有効の場合には FTHEME_??? 変数はテーマハンドルを保持します。テーマ無効の場合にはテーマAPIで描画してはいけません。GetComCtlVersion や OpenThemeData 等のテーマ API は古い OS ではサポートされていないので、事前に GetVersionEx() でOSのバージョンを得ています。

if FTHEME_BUTTON <> 0 then
    DrawThemeBackground(FTHEME_BUTTON,Canvas.Handle,BP_PUSHBUTTON,PBS_NORMAL,ARect,nil)
  else
    DrawFrameControl(Canvas.Handle,ARect,DFC_BUTTON, DFCS_BUTTONPUSH);

 実際に描画する場合にはこんな感じです。例ではボタンを描画しています。ボタンの文字列を描画するにはDrawThemeText()を併用して下さい。
 
 このままだと、テーマがアプリケーション実行時に変更されるとマズい事になるので、WM_THEMECHANGEDメッセージを捕捉して、テーマの変更時の処理を加えます。

procedure WMTHEMECHANGED(var message: TMsg); message WM_THEMECHANGED;

procedure WMTHEMECHANGED(var message: TMsg);
begin
  CloseTheme;
  OpenTheme;
end;

こんな感じでテーマハンドルを閉じてから再度テーマハンドルを取得します。

テーマの描画の具体例は以上ですが、ThemesをusesしているのであればTThemeServicesを使って処理するのもテです(Helpには載っていませんが...使い方はテーマ描画APIが理解できればすぐに解るでしょう)。

See Also:

 BACK