TListViewで行の高さを低く設定したときの実際の高さを取得するには

解決


take  2022-10-13 07:53:04  No: 150578  IP: 192.*.*.*

TListViewでViewStyleが vsReportの設定の時に行の高さを変えるために
TImageListを作ってHeigthに行の高さを指定してSmallImagesに割り当てると思惑通り高さが変わりました。

ところが高さの値を少なくしていくと
行として表示可能な限界値があるようで、ある一定の値から反映されなくなります。
表示が潰れない親切な設計ですが、行の高さを計算しているため実測値と異なってしまいます。

環境にもよりますが高さ 10をTImageListのHeigthに設定すると、実際の高さは 20ほどになりました。

実測値を取得する方法はあるのでしょうか?

OnDrawItemイベントでRectが渡されるのでこれが実際の値ですが

OnDrawItemイベント前に値が必要な場合もあるのでこの方法以外で取得できればいいのですが

編集 削除
Mr.XRAY  2022-10-13 22:21:52  No: 150579  IP: 192.*.*.*

うーむ,趣旨が理解し難いというか,何と言ったらいいのか.
以下のコードを試してみてください.
  
  
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;

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;
    procedure FormCreate(Sender: TObject);
    procedure Button1Click(Sender: TObject);
    procedure ListView1DrawItem(Sender: TCustomListView; Item: TListItem;
      Rect: TRect; State: TOwnerDrawState);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

//=============================================================================
//  フォーム生成時の処理
//
//  本サンプルの動作確認は以下
//   Windows 10 ビルド 19043 Pro 64 bit + Delphi XE5(UP2) Pro VCL-32
//=============================================================================
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;
end;

//=============================================================================
//  ListView1 の行の高さを変更
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
begin
  ListView1.ItemHeight := ListView1.ItemHeight + 2;
end;

//=============================================================================
//  ListView1 の OnDrawItem イベント
//=============================================================================
procedure TForm1.ListView1DrawItem(Sender: TCustomListView; Item: TListItem;
  Rect: TRect; State: TOwnerDrawState);
begin
  ListView1.Canvas.TextOut(Rect.Left, Rect.Top, Item.Caption);
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.

編集 削除
take  2022-10-14 00:06:58  No: 150580  IP: 192.*.*.*

質問がわかりづらかったのでわかりやすく書き直します。

【やりたいこと】
  TListViewでセルに編集機能を持たせるためTEditを置いて編集するクラスを制作しています

【困っていること】
  クリックしたときにそのセルのサイズに合わせてTEditの位置を変えています。
  しかし Topの座標指定がずれることがあります。
  
  調べていくと、TListViewにImageListを関連づけして、ImageListの高さを変えて TListViewの行の高さを変更する方法だと
  表示可能な行よりも低く設定された場合、TListView は ImageListの Heightの値では無く表示可能な高さで表示されます。
  そのためズレるようです
  
  ImageListを使わずに 設定値通りの高さで表示する方法
  または
  実際に表示されている行の高さが取得出来れば助かります。
  
  クリックしたときにTEditをそのセルの位置へ移動させるサンプルを作りました。

【環境】
OS :Windows10
コンパイラ DelphiXE5、Delphi10.4

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


type
  TListViewEx = class(TListView)
  private
    { Private 宣言 }
    FImages : TImageList;
    FEdit   : TEdit;
    FItemheight : Integer;

    function TopIndex() : Integer;

    function ColumnLeft(const aCol : Integer) : Integer;
    function GetScrollBarLeft() : Integer;
    function GetColumAt(const X,Y : Integer) : Integer;
    function GetColumnHeight(): Integer;

    //function GetItemHeight: Integer;
    procedure SetItemHeight(const Value: Integer);

    procedure OnSelfClick(Sender: TObject);
    procedure OnSelfDrawItem(Sender: TCustomListView; Item: TListItem;
      Rect: TRect; State: TOwnerDrawState);
    function GetColumn: Integer;
  protected
    procedure CNMeasureitem(var Message: TWMMeasureItem); message CN_MEASUREITEM;
  public
    { Public 宣言 }
    constructor Create(AOwner: TComponent); override;
    destructor Destroy;override;

    procedure Clear();override;
    property ItemHeight : Integer read FItemHeight write SetItemHeight;
    property Column : Integer read GetColumn;
  end;

type
  TForm8 = class(TForm)
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure FormShow(Sender: TObject);
  private
    { Private 宣言 }
    FListView : TListViewEx;
  public
    { Public 宣言 }
  end;

var
  Form8: TForm8;

implementation

{$R *.dfm}

{ TListViewEx }

procedure TListViewEx.CNMeasureitem(var Message: TWMMeasureItem);
begin
  inherited;
  Message.MeasureItemStruct.itemHeight := FItemHeight;
end;

function TListViewEx.ColumnLeft(const aCol: Integer): Integer;
var
  i,x: Integer;
begin
  result := 0;
  if aCol = 0 then exit;
  x := 0;
  for i := 0 to aCol-1 do begin
    x := x + Columns[i].Width;
  end;
  result := x;
end;

constructor TListViewEx.Create(AOwner: TComponent);
begin
  inherited;
  FImages := TImageList.Create(Self);
  OnClick := OnSelfClick;
  OnDrawItem := OnSelfDrawItem;
  OwnerDraw := True;
  ItemHeight := 11;                   // 行の高さを指定

  FEdit := TEdit.Create(Self);
  FEdit.Parent := Self;
  FEdit.Visible := False;
end;

destructor TListViewEx.Destroy;
begin
  FEdit.Free;
  FImages.Free;
  inherited;
end;

procedure TListViewEx.Clear;
begin
  Items.Clear;
  FImages.Clear;
end;

function TListViewEx.GetColumAt(const X, Y: Integer): Integer;
var
  i,xx : Integer;
begin
  result := -1;
  xx := -GetScrollBarLeft();                // 基準値をスクロールバーの現在位置から取得
  for i := 0 to Columns.Count-1 do begin    // 列数ループ
    xx := xx + Columns[i].Width;            // 基準値に列幅を加算
    if X < xx then begin                    // カーソルが列内にある場合
      result := i;                          // カーソルの列位置として返す
      exit;                                 // 処理終了
    end;
  end;
end;

function TListViewEx.GetColumn: Integer;
var
  p : TPoint;
begin
  p := ScreenToClient(Mouse.CursorPos);        // マウス位置取得
  result := GetColumAt(p.X,p.Y);               // マウス位置から列を取得
end;

function TListViewEx.GetColumnHeight: Integer;
var
  Header_Handle: HWND;
  WindowPlacement: TWindowPlacement;
begin
  Header_Handle := ListView_GetHeader(Handle);
  FillChar(WindowPlacement, SizeOf(WindowPlacement), 0);
  WindowPlacement.Length := SizeOf(WindowPlacement);
  GetWindowPlacement(Header_Handle, @WindowPlacement);
  Result  := WindowPlacement.rcNormalPosition.Bottom -
    WindowPlacement.rcNormalPosition.Top+1;
end;


{
function TListViewEx.GetItemHeight: Integer;
begin
  if SmallImages<>nil then begin
    result := FImages.Height;              // ここで実際の行の高さが取得出来るのが理想
  end
  else begin
    result := 16;
  end;
end;
}

function TListViewEx.GetScrollBarLeft: Integer;
var
  SInfo: TScrollInfo;
begin
  SInfo.cbSize := SizeOf(SInfo);
  SInfo.fMask := SIF_ALL;
  GetScrollInfo(Handle, SB_HORZ, SInfo);
  result := SInfo.nPos;
end;

procedure TListViewEx.OnSelfClick(Sender: TObject);
var
  aCol : Integer;
begin
  aCol := GetColumn;
  if aCol=-1 then exit;
  
  FEdit.Left := (ColumnLeft(aCol) - GetScrollBarLeft());
  FEdit.Top :=  (ItemIndex -  TopIndex) * (ItemHeight+1) +GetColumnHeight();         // 問題の部分
  FEdit.Width := Columns[aCol].Width;
  FEdit.Height := ItemHeight;
  FEdit.Visible := True;
end;

procedure TListViewEx.OnSelfDrawItem(Sender: TCustomListView; Item: TListItem;
  Rect: TRect; State: TOwnerDrawState);
var
  i: Integer;
  r : Trect;
  cv : TCanvas;
begin
  r := Rect;
  cv := Canvas;
  if (odFocused in State) or (odSelected in State)  then begin
    cv.Brush.Color := clActiveCaption;
    cv.Font.Color := clWhite;
  end
  else begin
    cv.Brush.Color := clWhite;
    cv.Font.Color := clBlack;
  end;
  cv.Brush.Style := bsSolid;
  cv.FillRect(Rect);
  cv.Brush.Style := bsClear;


  r.Right := Columns[0].Width;
  Canvas.TextOut(r.Left, r.Top, Item.Caption);
  for i := 0 to Item.SubItems.Count-1 do begin
    r.Left := r.Left +Columns[i].Width;
    r.Right :=r.Left + Columns[i+1].Width;
    Canvas.TextOut(r.Left, r.Top, Item.SubItems[i]);
  end;
end;

procedure TListViewEx.SetItemHeight(const Value: Integer);
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;

  // ImageListで行の高さを変更する処理
  SmallImages := nil;
  FImages.Height := Value;
  FImages.Width := Value;
  SmallImages := FImages;

end;

function TListViewEx.TopIndex: Integer;
var
  SInfo: TScrollInfo;
begin
  SInfo.cbSize := SizeOf(SInfo);
  SInfo.fMask := SIF_ALL;
  GetScrollInfo(Handle, SB_VERT, SInfo);
  result := SInfo.nPos;
end;

procedure TForm8.FormCreate(Sender: TObject);
var
  dc : TListColumn;
begin
  FListView := TListViewEx.Create(Self);
  FListView.Parent := Self;
  FListView.Align := alClient;
  FListView.ViewStyle := vsReport;
  FListView.RowSelect := True;
  dc := FListView.Columns.Add;
  dc.Caption := '列1';
  dc.Width := 100;
  dc := FListView.Columns.Add;
  dc.Caption := '列2';
  dc.Width := 200;
end;

procedure TForm8.FormDestroy(Sender: TObject);
begin
  FListView.Free;
end;

procedure TForm8.FormShow(Sender: TObject);
var
  dl : TListItem;
  i: Integer;
begin
  for i := 0 to 30 do begin

  dl := FListView.Items.Add;
  dl.Caption := IntToStr(i+1)+'行目';
  dl.SubItems.Add('データ1');
  end;
end;

編集 削除
調剤薬局マン  2022-10-14 01:29:10  No: 150581  IP: 192.*.*.*

試していませんが
高さを取得したいListItemが分かるなら、TListItem.DisplayRect メソッドとか?

ListView_GetItemRectを使っているので、直接呼んでみる(可視範囲内のListItemのIndexが必要かな?)
https://learn.microsoft.com/ja-jp/windows/win32/api/commctrl/nf-commctrl-listview_getitemrect

編集 削除
Mr.XRAY  2022-10-14 01:45:09  No: 150582  IP: 192.*.*.*

>  ImageListを使わずに 設定値通りの高さで表示する方法 

私が提示したコードは TImageLIst ( ImageList ? ) を使用していませんが・・・
私の提示したコードは「高さの表示」ではなく,「高さの設定」でした.
お役には立たなかったようですね.

編集 削除
take  2022-10-14 02:32:24  No: 150583  IP: 192.*.*.*

調剤薬局マンさん、ありがとうございました ListView_GetItemRectを直接呼ぶ事で解決しました。
Mr.XRAY さん、単なる説明不足で誤解させてしまいました、
ListView_GetItemRectで検索したら Mr.XRAY さんのWebサイトが出てきて、サンプルを参考にさせていただきました。

【072_TListView のスクロールバーの表示制御】
http://mrxray.on.coocan.jp/Delphi/plSamples/072_TListView_ScrollBar.htm

さきほどのサンプルを修正したものはこちらです。

この修正では高さに制限が無く指定した高さに設定されるようになりました。
挙動は変わりましたが、これで問題が無いので解決とさせて頂きます。

ありがとうございました。

procedure TListViewEx.OnSelfClick(Sender: TObject);
var
  aCol : Integer;
  r : TRect;
begin
  aCol := GetColumn;
  if aCol=-1 then exit;

  ListView_GetItemRect(Handle,ItemIndex,r,LVIR_BOUNDS);                          // 追加

  FEdit.Left := (ColumnLeft(aCol) - GetScrollBarLeft());
  //FEdit.Top :=  (ItemIndex -  TopIndex) * (ItemHeight+1) +GetColumnHeight();   // 修正前
  FEdit.Top :=  (ItemIndex -  TopIndex) * (r.Height) +GetColumnHeight();         // 修正後
  FEdit.Width := Columns[aCol].Width;
  //FEdit.Height := ItemHeight;                                                  // 修正前
  FEdit.Height := r.Height;                                                      // 修正後
  FEdit.Visible := True;
end;

編集 削除