Delphi Programming / Object Pascal
サイトのトップページへ リンクのページへ ロゴマーク
[掲載 2022年10月22日] [更新 2022年10月22日] Delphi サンプルプログラム集
786_TListView のカラムヘッダのカスタム描画
動作確認等 Windows 10 ビルド 19043 Pro 64 bit + Delphi XE5(UP2) Pro VCL-32
786_TListView_ColumnHeader.zip [1.8 MB] 2022年10月22日版 (EXE 同梱)







TListView のカラムヘッダのカスタム描画


TListView のカラムヘッダのカスタム描画の参考サンプルです.
カラムヘッダのテキストの色とサイズと描画位置の設定と,背景色の設定のサンプルです.カラムヘッダは,コントロールとして外部から操作できません.本ページのサンプルでは,カラムヘッダコントロールをサブクラス化して処理しています.

本ページのサンプルでは,カラムヘッダの高さも変更しています.カラムヘッダ部の高さの設定方法については,以下のリンクの記事で確認してください.






01_TListView のカラムヘッダの文字列のフォントと描画位置を変更


TListView のカラムヘッダのテキストの色とサイズと,描画位置を設定します.実際には,デフォルトの描画に上書き描画します.これは,他の多くのコントロールにおけるカスタム描画と同じです.
背景は,元の TListView のカラムヘッダと同じテーマで塗り潰しします.



図1
カラムヘッダのフォントを変更
  • フォントサイズは 15
  • フォントの色は clBlue
  • 文字列の描画位置は,上下左右の中央

リスト1
TListView のカラムヘッダのテキストの色とサイズと描画位置を設定
unit Unit1;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes,
  Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.ComCtrls, Vcl.StdCtrls,
  Vcl.ExtCtrls, Vcl.Themes;

type
  TForm1 = class(TForm)
    ListView1: TListView;
    procedure FormCreate(Sender: TObject);
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;

  function LVHeaderWndProc(hWnd: HWND; Msg: UINT; wParam: WPARAM;
    lParam: LPARAM): LRESULT; stdcall;
  procedure DrawHeaderText(AHDC: HDC; const AText: string; ARect: TRect;
    ATextFlags: Cardinal; const AFontSize: Integer; const ATextColor: TColor;
    ADetail: TThemedElementDetails; AElement: TThemedHeader);

var
  Form1: TForm1;

implementation

uses
  Winapi.CommCtrl,
  System.Types,
  Winapi.Dwmapi,
  Winapi.UxTheme;

{$R *.dfm}

var
  FLVHOriginProc : Pointer;
  FhLVHeader     : THandle;

//=============================================================================
//  フォーム生成時の処理
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
var
  LCol   : TListColumn;
  LItem  : TListItem;
  LColNo : Integer;
  LRowNo : Integer;
begin
  ListView1.ViewStyle := vsReport;

  ListView1.Items.BeginUpdate;

  for LColNo := 0 to 2 do begin
    LCol := ListView1.Columns.Add;
    LCol.Caption := 'Col ' + IntToStr(LColNo + 1);
    LCol.Width   := 100;
  end;

  // テスト表示用のデータ
  for LColNo := 0 to 1 do begin
    for LRowNo := 0 to 49 do begin
      LItem :=  ListView1.Items.Add;
      LItem.Caption := Format('    %.6d',[LRowNo + 1]);
      ListView1.Items[LRowNo].SubItems.Add(Format('    %.5d',[LRowNo + 1]));
    end;
  end;

  ListView1.Items.EndUpdate;


  // カラムヘッダのコントロールのウィンドウハンドルを取得
  FhLVHeader := ListView_GetHeader(ListView1.Handle);
  // カラムヘッダコントロールの WndProc を退避し,新しく WndProc を設定
  FLVHOriginProc := Pointer(SetWindowLong(FhLVHeader,
                           GWL_WNDPROC,
                           NativeInt(@LVHeaderWndProc)));
end;

//=============================================================================
//  フォームを閉じる時の処理
//=============================================================================
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
  SetWindowLong(FhLVHeader, GWL_WNDPROC, NativeInt(FLVHOriginProc));
end;

//-----------------------------------------------------------------------------^
//  SetWindowLong 関数のコールバック関数
//  TListView のカラムヘッダコントロールのサブクラス化処理
//-----------------------------------------------------------------------------^
function LVHeaderwndProc(hWnd: HWND; Msg: UINT; wParam: WPARAM;
  lParam: LPARAM): LRESULT; stdcall;
  {$WRITEABLECONST ON}
const
  FIsPessed : Boolean = False;
{$WRITEABLECONST OFF}
var
  LListView : TListView;
  LHDLyaout : PHDLayout;
  LHeaderDC : HDC;
  LCurPos   : TPoint;
  LRect     : TRect;
  LDetails  : TThemedElementDetails;
  LElement  : TThemedHeader;
  LTextFlag : Cardinal;
  LCaption  : string;
  LIndex    : Integer;
begin
  // 操作対象の TListview
  LListView := Form1.ListView1;

  Result := CallWindowProc(FLVHOriginProc, hWnd, Msg, wParam, lParam);

  // カラムヘッダの高さを設定
  if Msg = HDM_LAYOUT then begin
    LHDLyaout := PHDLayout(lParam);
    LHDLyaout.WindowPos.cy := 40;
    LHDLyaout.Rect.Top := LHDLyaout.WindowPos.cy + 2;
  end;

  case Msg of
    WM_LBUTTONDOWN : FIsPessed := True;
    WM_LBUTTONUP   : FIsPessed := False;
    WM_MOUSEMOVE   :
    begin
      // 左のカラムに移動した時に WM_PAINT が発生しない現象の回避策
      LVHeaderwndProc(FhLVHeader, WM_PAINT, 0, 0);
    end;
  end;

  if Msg = WM_PAINT then begin
    LHeaderDC := GetDC(FhLVHeader); 

    for LIndex := 0 to LListView.Columns.Count -1 do begin
      Header_GetItemRect(FhLVHeader, LIndex, @LRect);

      GetCursorPos(LCurPos);
      ScreenToClient(FhLVHeader, LCurPos);

      // マウスカーソル下のカラムの判定
      if PtInRect(LRect, LCurPos) then begin
        if FIsPessed then begin
          LElement := thHeaderItemPressed;
        end else begin
          LElement := thHeaderItemHot;
        end;
      end else begin
        LElement := thHeaderItemNormal;
      end;

    LCaption  := LListView.Column[LIndex].Caption;
      LTextFlag := DT_SINGLELINE or DT_CENTER or DT_VCENTER;

      LDetails := TStyleManager.SystemStyle.GetElementDetails(LElement);
      DrawHeaderText(LHeaderDC, LCaption, LRect, LTextFlag, 15, clBlue,
                     LDetails, LElement);
    end;
  end;
end;

//-----------------------------------------------------------------------------^
//  実際のカラムヘッダの描画ルーチン
//
//  フォントのサイズと文字列の描画位置を変更するので背景を塗り潰す
//  背景を塗り潰さないと元の文字列が描画される
//-----------------------------------------------------------------------------^
procedure DrawHeaderText(AHDC: HDC; const AText: string; ARect: TRect;
  ATextFlags: Cardinal; const AFontSize: Integer; const ATextColor: TColor;
  ADetail: TThemedElementDetails; AElement: TThemedHeader);
var
  LdttOpts  : TDTTOpts;
  LFont     : TFont;
  LhOldFont : HFONT;
  LCanvas   : TCanvas;
begin
  DrawThemeBackground(TStyleManager.SystemStyle.Theme[ADetail.Element],
                          AHDC, ADetail.Part, ADetail.State, ARect, nil);


  // フォント関係の設定
  if AElement = thHeaderItemPressed then begin
    ARect.Left := ARect.Left + 2;
    ARect.Top  := ARect.Top + 2;
  end;

  LFont := TFont.Create;
  try
    LFont.Size := AFontSize;
    LFont.Style := [fsBold];
    LhOldFont   := SelectObject(AHDC, LFont.Handle);

    FillChar(LdttOpts, SizeOf(TDTTOpts), $0 );
    LdttOpts.dwSize   := SizeOf(TDTTOpts);
    LdttOpts.crText   := ColorToRGB(ATextColor);
    LdttOpts.dwFlags  := DTT_TEXTCOLOR;
    LdttOpts.iStateId := ADetail.State;

    DrawThemeTextEx(TStyleManager.SystemStyle.Theme[ADetail.Element],
                    AHDC,
                    ADetail.Part,
                    ADetail.State,
                    AText,
                    Length(AText),
                    ATextFlags,
                    ARect,
                    LdttOpts);

    // 元のフォントに戻しておく
    SelectObject(AHDC, LhOldFont);
  finally
    FreeAndNil(LFont);
  end;
end;

end.




02_TListView のカラムヘッダの背景色を変更


ListView の,マウスカーソル下にないカラムの背景色を変更します.
TCanvas を生成して,その TCanvas に描画します.



図2
カラムヘッダの背景色を変更

リスト2
TCanvas を生成して,TListView のカラムヘッダの背景色を変更
//-----------------------------------------------------------------------------^
//  実際のカラムヘッダの描画ルーチン
//
//  フォントのサイズと文字列の描画位置を変更するので背景を塗り潰す
//  背景を塗り潰さないと元の文字列が描画される
//-----------------------------------------------------------------------------^
procedure DrawHeaderText(AHDC: HDC; const AText: string; ARect: TRect;
  ATextFlags: Cardinal; const AFontSize: Integer; const ATextColor: TColor;
  ADetail: TThemedElementDetails; AElement: TThemedHeader);
var
  LdttOpts  : TDTTOpts;
  LFont     : TFont;
  LhOldFont : HFONT;
  LCanvas   : TCanvas;
begin
  // マウスカーソル下にないカラムの背景色を変更
  if AElement = thHeaderItemNormal then begin
    LCanvas := TCanvas.Create;
    try
      LCanvas.Handle := AHDC;
      LCanvas.Brush.Color := $00C8DAEA;
      LCanvas.FillRect(ARect);
    finally
      FreeAndNil(LCanvas);
    end;
  end else begin
    DrawThemeBackground(TStyleManager.SystemStyle.Theme[ADetail.Element],
                          AHDC, ADetail.Part, ADetail.State, ARect, nil);
  end;


  // フォント関係の設定
  if AElement = thHeaderItemPressed then begin
    ARect.Left := ARect.Left + 2;
    ARect.Top  := ARect.Top + 2;
  end;

  LFont := TFont.Create;
  try
    LFont.Size := AFontSize;
    LFont.Style := [fsBold];
    LhOldFont   := SelectObject(AHDC, LFont.Handle);

    FillChar(LdttOpts, SizeOf(TDTTOpts), $0 );
    LdttOpts.dwSize   := SizeOf(TDTTOpts);
    LdttOpts.crText   := ColorToRGB(ATextColor);
    LdttOpts.dwFlags  := DTT_TEXTCOLOR;
    LdttOpts.iStateId := ADetail.State;

    DrawThemeTextEx(TStyleManager.SystemStyle.Theme[ADetail.Element],
                    AHDC,
                    ADetail.Part,
                    ADetail.State,
                    AText,
                    Length(AText),
                    ATextFlags,
                    ARect,
                    LdttOpts);

    // 元のフォントに戻しておく
    SelectObject(AHDC, LhOldFont);
  finally
    FreeAndNil(LFont);
  end;
end;