鳥の巣箱

ネトゲしたり、機械いじったり、ソフト書いたり、山篭ったり、ギャンブルしたりする人

TListViewをResizeしたときのItem Objectの操作

TListViewを使っていてハマった問題。

TListViewのサイズが変わると、リスト内のオブジェクト(ButtonやImageなど)のサイズが勝手に変わってしまっていた。

ことの発端

FireMonkey リスト ビューの外観のカスタマイズ - RAD Studio

ListViewのアイテムオブジェクトとして使えるTTextButtonObjectAppearanceやTImageObjectAppearanceにはHeightプロパティが存在しない。

厳密に言えば存在はしているんだけど、スコープがpublishedではなくてpublicになっているため、オブジェクトインスペクタで変更ができないんですよ。
しかもAlignプロパティがTrailing、Center、Leadingしかない。
なんで、フォームデザイナ上で一つのリストアイテム内にボタンを縦に2つ並べたい場合や、TAlignLayoutでいうとこのVertCenterみたいなことができないんですね。

この問題を解決するためには、TListViewにアイテムを追加する際に一工夫する必要があります。

procedure AddListView;
var
  I : Integer;
  LItem : TListViewItem;
begin
  LItem         := ListView.Items.Add;
  for I := 0 to LItem.Objects.Count -1 do
    begin
      name  := LItem.Objects[I].Name;

      if name = 'UpBtn' then
        begin
          litem.Objects[I].Height         := 35;
          LItem.Objects[I].PlaceOffset.Y  := 5;
          LItem.Objects[I].Align          := TListItemAlign.Trailing;
        end
      else if name = 'DownBtn' then
        begin
          LItem.Objects[I].Height         := 35;
          LItem.Objects[I].PlaceOffset.Y  := 45;
          LItem.Objects[I].Align          := TListItemAlign.Trailing;
        end;

必要な部分だけざっくり抜き出すとこんな感じ。
TListViewItem.Objectsの中には、アイテムオブジェクトが配列になって格納されてます。
これを一つずつ確認していって、目的のオブジェクトを見つけたところで、コード上で直接Heightなどをいじってやると実現できます。

うまくいったかと思いきや、今度はListViewのサイズが変わったときにせっかく指定したHeightやらなにやらが全部なかったことにされて、アイテム内いっぱいのHeightに勝手に変わってしまうんですよ。
これは困った。

OnResizeじゃ解決しない

ってことで、リサイズされたときにもう一度直接指定してやればいいだろと。
ListViewのOnResizeイベントが発生したときに、もう一度TListViewItem.Objectの中を直接変更するようにしてみたところ。

変わらない。

なぜか。

それはこういう処理手順を踏んでいたからだった。
継承ルート
TControl→TStyledControl→TAdapterListView→TListViewBase→
 TPresentedListView→TAppearanceListView→TCustomListView→TListView

  1. TListViewのサイズが変更される
  2. TListViewの継承元にあたるTListViewBaseのResizeメソッドが実行される。
  3. TListViewBase.ResizeがinheritedしてTControl.Resizeが実行される
  4. TControl.Resize内でOnResizeイベントが呼び出される
  5. TListViewBase.Resizeに戻り、Adapter.ResetViewsを呼び出す
  6. TListViewのViewがすべてリセットされる

という動作をしていた。
つまり、TListViewBase.Resizeが真っ先にinheritedしているのでResetViewsの前にOnResizeイベントが発生したいたわけだ。
なのでOnResizeイベント内でいくら外観を調整しようとすべて無に帰されるわけ。

結局どうすんのかい。って話ですが、そのためにTListViewにはOnUpdateObjectsがあります。
これはアイテムオブジェクトが更新されたあとに発生するイベントなので、上記のResetViewsの後です。*1

OnUpdateObjectイベントにOnResizeの処理を移行して無事解決と。

*1:※似た名前のOnUpdatingObjectsイベントがありますが、これは更新される前(つまりResetViewsの前)に発生するので間違えると同じことに