Delphi Programming / Object Pascal
サイトのトップページへ リンクのページへ ロゴマーク
[掲載 2022年10月15日] [更新 2022年10月21日] Delphi サンプルプログラム集
   
784_TListView の行の高さを設定
動作確認等 Windows 10 ビルド 19043 Pro 64 bit + Delphi XE5(UP2) Pro VCL-32
784_TListView_ItemHeight.zip [2.7 MB] 2022年10月21日版 (EXE 同梱)







TListView の行の高さを設定


TListView の行の高さを設定,あるいは変更するサンプルです.
本ページのサンプルは,TListView.ViewStyle が vsReport の時の結果です.このプロパティが他の値の時の挙動は異なります.CN_MEASUREITEM メッセージによる方法は,vsReport 以外の時は機能しません.





01_TImageList を使用して TListView の行の高さを設定


TImageList を使用する方法です.
フォームに TImageList を配置して,TListView の StateImages プロパティをその TImageList にします.行の高さは TImageList の Height プロパティで指定します.TListView の行の高さは,( TImageList.Height + 1 ) になります.ただし,TImageList.Height の値が 16 以下の場合は,常に 17 になりました.16 は,TImageList の Widht, Height プロパティのデフォルト値です.
実際の行の高さは,TListView.Font.Size の値によつても異なります.

このサンプルは,Button1 をクリックすると,TImageList.Height の値を 2 ピクセル単位で増加させます.これで実行時に動作を確認できるようにしています.

通常,TListView.SmallImages は,各行,つまり,各項目 (アイテム) に関連したアイコン画像に使用します.そこで,本サンプルでは,TListView.StateImages を使用しています.



図1
EXE 起動時の画面
  • TImageList.Height の値は 4
    TImageList.Widht の値は 1
  • 行の高さは 17 となる
  • TListView.Font.size の値によっても異なる
図2
TImageList.Height の値を変更
  • TImageList.Height が 16 以下の場合,
    行の高さは 17 になる
  • TImageList.Height が 17 以上の場合,
    行の高さは TImageList.Height + 1 になる

リスト1
TImageList を使用して 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.ImgList, Vcl.ExtCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    ListView1: TListView;
    ImageList1: TImageList;
    Panel1: TPanel;
    procedure FormCreate(Sender: TObject);
    procedure Button1Click(Sender: TObject);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

uses
  Winapi.CommCtrl;

{$R *.dfm}

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

  // 行の高さを設定
  ImageList1.Height := 4;
  ImageList1.Width  := 1;
  ListView1.StateImages := ImageList1;


  ListView1.Items.BeginUpdate;
  LCol := ListView1.Columns.Add;
  LCol.Caption := 'Test Items';
  LCol.Width   := 200;

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

  Button1Click(nil);
end;

//=============================================================================
//  ListView1 の行の高さを変更
//
//  ImageList を使用した場合,最小値があるので,それ以下の値は設定できない
//  本サンプルでは 17 なので,実用的には問題ないと思われる
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LRect : TRect;
begin
  if Sender <> nil then begin
    ImageList1.Height := ImageList1.Height + 2;
  end;

  // ListView の 1 番目 ( インデックスが 0 ) の行の高さを取得して表示
  // uses に Winapi.CommCtrl が必要
  LRect := ListView1.Items[0].DisplayRect(drBounds);
  Panel1.Caption := 'ItemHeight : ' + IntToStr(LRect.Bottom - LRect.Top);
end;

end.


上のサンプルの実行結果ては,TListView の各行の文字列の描画開始位置の左端にマージンがあります.このマージンは TImagaeList を使用することで起こります.これを回避するには,TListView.OwnerDraw プロパティを True にして,オーナ描画します.



図3
任意の位置に描画

procedure TForm1.ListView1DrawItem(Sender: TCustomListView; Item: TListItem;
  Rect: TRect; State: TOwnerDrawState);
var
  LRect : TRect;
  LText : string;
begin
  LRect := Rect;
  LText := Item.Caption;
  ListView1.Canvas.TextRect(LRect, LText, [tfVerticalCenter, tfSingleLine]);
end;




02_CN_MEASUREITEM を使用して TListView の行の高さを設定


CN_MEASUREITEM メッセージを使用する方法です.この方法は,TImageList を使用する必要がない場合に有効な方法です.

TListView は,Windows のコモンコントロールである ListView のラッパークラスです.Windows のコモンコントロールは,コントロールを生成する時に,自分自身に通知メッセージを送信します.Delphi ではこれを CN_MEASUREITEM メッセージで捕捉できます.
このメッセージの TWMMeasureItem レコード化に ItemHeight というメンバがあります.この値を設定します.

CN_MEASUREITEM は,コントロールか生成された時の通知メッセージですから,実行時に行の高さを変更するには,コントロールの再生成が必要です.本サンプルでは RecreateWnd 手続きで再生成しています.
行の高さは,TListView に ItemHeight というプロパティを追加して使用しています.

このサンプルは,Button1 をクリックすると,ItemHeight プロパティの値を2 ピクセル単位で増加させます.これで実行時に動作を確認できるようにしています.



図4
EXE 起動時の画面
  • TListView.ItemHeight の値は 4
  • この値が TListView の行の高さ
図5
TLisview.ItemHeight の値を変更
  • 20 の場合
  • この値が TListView の行の高さ

TListView の StateImages ゃ SmallImages プロパティに TImageList を指定すると,行の高さは TImageList の Height プロパティの値によって決まります.
CN_MEASUREITEM メッセージによる行の高さの設定を有効にしたままで TListView にアイコン画像などを描画したい場合は,Draw 関係のイベント内で画像を描画します.



リスト2
CN_MEASUREITEM メッセージを使用して 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;

type
  TListView = class(Vcl.ComCtrls.TListView)
  private
    FItemHeight : UInt;
    procedure SetItemHeight(const Value: UInt);
  protected
    procedure CNMeasureitem(var Message: TWMMeasureItem); message CN_MEASUREITEM;
  public
    property ItemHeight : UInt read FItemHeight write SetItemHeight;
  end;

  TForm1 = class(TForm)
    Button1: TButton;
    ListView1: TListView;
    Panel1: TPanel;
    procedure FormCreate(Sender: TObject);
    procedure Button1Click(Sender: TObject);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

uses
  Winapi.CommCtrl;

{$R *.dfm}

//=============================================================================
//  フォーム生成時の処理
//
//  ここで表示している ItemHeight の値は ListView.ItemHeight の値と同じ
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
var
  LCol  : TListColumn;
  LItem : TListItem;
  LIndex: Integer;
begin
  ListView1.ViewStyle := vsReport;
  ListView1.OwnerDraw := True;
  ListView1.ItemHeight := 4;


  ListView1.Items.BeginUpdate;
  LCol := ListView1.Columns.Add;
  LCol.Caption := 'Test Items';
  LCol.Width   := 200;

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

  Button1Click(nil);
end;

//=============================================================================
//  ListView1 の行の高さを変更
//
//  ここで表示している ItemHeight の値は ListView.ItemHeight の値と同じ
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LRect : TRect;
begin
  if Sender <> nil then begin
    ListView1.ItemHeight := ListView1.ItemHeight + 2;
  end;

  // ListView の 1 番目 ( インデックスが 0 ) の行の高さを取得して表示
  // uses に Winapi.CommCtrl が必要
  LRect := ListView1.Items[0].DisplayRect(drBounds);
  Panel1.Caption := 'ItemHeight : ' + IntToStr(LRect.Bottom - LRect.Top);
end;

{ TListView }

//-----------------------------------------------     -------------------------
//  TListView の CN_MEASUREITEM メッセージ処理
//  CN_MEASUREITEM は,コントロール生成時に自身に送信されてくるメッセージ
//  ItemHeight の値を設定
//  OwnerDraw の値が True でないと機能しない 
//-----------------------------------------------     -------------------------
procedure TListView.CNMeasureitem(var Message: TWMMeasureItem);
begin
  inherited;
  Message.MeasureItemStruct.itemHeight := FItemHeight;
end;

//-----------------------------------------------     -------------------------
//  ItemHeight プロパティの設定用メソッド
//-----------------------------------------------     -------------------------
procedure TListView.SetItemHeight(const Value: UInt);
var
  LTopIndex : Integer;
begin
  if Value <> FItemHeight then begin
    FItemHeight := Value;

    if Items.Count > 1 then begin
      LTopIndex := TopItem.Index;
      Selected := nil;
      RecreateWnd;
      Scroll(0, LTopIndex * FItemHeight);
    end;
  end;
end;

end.


このサンプルでは,以下のクラス宣言を使用しています.これは,ユニット内の TListView に対してだけ有効です.元のクラスとは機能が異なるわけですから,通常は,元のクラス名とは異なるクラス名の継承クラスを宣言し,実行時に生成して使用します.
今回は,行の高さ設定の動作確認だけが目的なので,この宣言を使用しています.


type
  TListView = class(Vcl.ComCtrls.TListView)




03_TListView の行の高さを取得


TListView の行の高さは,TListItem.DiaplayRect メソッド,あるいは ListView_GetItemRect関数で取得できます.実際に取得するのは Item の TRect 構造体の値ですが,この値から ( (LRect.Bottom - LRect.Top ) で計算できます.LIndex は,Item のインデックス ( 0 ペース ) の値です.TListView では,全ての行が同じ高さですから,この値は 0 で構いません.
これらのメソッド,関数は,TListView に LVM_GETITEMRECT メッセージを送信して値を取得しています.このコードをコンパイルするには,uses に Winapi.CommCtrl が必要です.



procedure TForm1.Button1Click(Sender: TObject);
var
  LRect  : TRect;
  LIndex : Integer;
begin
  LIndex := 0;
  LRect := ListView1.Items[LIndex].DisplayRect(drBounds);
  // 行の高さ = LRect.Bottom - LRect.Top
end;

procedure TForm1.Button1Click(Sender: TObject);
var
  LRect  : TRect;
  LIndex : Integer;
begin
  LIndex := 0;
  ListView_GetItemRect(ListView1.Handle, LIndex, LRect, LVIR_BOUNDS);
  // 行の高さ = LRect.Bottom - LRect.Top
end;




04_TListView のカラムヘッダの高さの取得と設定


TListView のカラムヘッダのの高さは,次のようなコードで取得できます.
このコードをコンパイルするには.uses に Winapi.CommCtrl が必要です.


procedure TForm1.Button1Click(Sender: TObject);
var
  LhHeader : THandle;
  LRect    : TRect;
begin
  LhHeader := ListView_GetHeader(ListView1.Handle);
  if (ListView1.Columns.Count > 0) then begin
    Header_GetItemRect(LhHeader, 0, @LRect);
    // カラムヘッダの高さ = LRect.Bottom - LRect.Top
  end;
end;


以下は,TListView のカラムヘッダの高さを設定するサンプルです.
TListView のカラムヘッダ部の描画は,TCustomListView.HeaderWndProc メソッドで行われています.このメソッドは,private 部にあるので,外部からは利用できません.このサンプルでは,カラムヘッダのコントロールをサブクラス化して処理しています.
カラムヘッダの生成時に,カラムヘッがダが送信する HDM_LAYOUTメッセージで処理します.実行中に変更したい場合は,CN_MEASUREITEM のサンプルのように,ウィンドウの再生成が必要です.



図6
カラムヘッダ設定コードがない場合
  • ヘッダ部にマウスカーソルがある状態
  • カラムヘッダ部の高さは 24
図7
カラムヘッダの高さを設定した場合
  • ヘッダ部にマウスカーソルがある状態
  • カラムヘッダ部の高さは 70

リスト3
HDM_LAYOUT メッセージを処理して,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;

type
  TForm1 = class(TForm)
    ListView1: TListView;
    Panel1: TPanel;
    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;
                                         var
  Form1: TForm1;

implementation

uses
  Winapi.CommCtrl,
  System.Types;

{$R *.dfm}

var
  FLVHOriginProc : Pointer;
  FhLVHeader     : THandle;

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

  ListView1.Items.BeginUpdate;
  LCol := ListView1.Columns.Add;
  LCol.Caption := 'Test Items';
  LCol.Width   := 200;

  // テスト表示用のデータ
  for LIndex := 0 to 49 do begin
    LItem :=  ListView1.Items.Add;
    LItem.Caption := Format('    %.6d',[LIndex + 1]);
  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 のカラムヘッダコントロールのサブクラス化処理
//
//  HDM_LAYOUT メッセージを捕捉して処理
//  PHDLayout 構造体のPHDLayout.WindowPos.cy がヘッダ部の高さ
//-----------------------------------------------------------------------------^
function LVHeaderwndProc(hWnd: HWND; Msg: UINT; wParam: WPARAM;
  lParam: LPARAM): LRESULT; stdcall;
var
  LHDLyaout : PHDLayout;
  LRect     : TRect;
begin
  Result := CallWindowProc(FLVHOriginProc, hWnd, Msg, wParam, lParam);

  if Msg = HDM_LAYOUT then begin
    LHDLyaout := PHDLayout(lParam);
    LHDLyaout.WindowPos.cy := 70;
    LHDLyaout.Rect.Top := LHDLyaout.WindowPos.cy + 2;

    // ヘッダ部の高さを取得して表示
   Header_GetItemRect(FhLVHeader, 0, @LRect);
    Form1.Panel1.Caption := 'Header Height : ' + IntToStr(LRect.Bottom - LRect.Top);
  end;
end;

end.