Delphi Sample VariableHeightItems をいじる

Delphi活用

Sampleの概要

TListViewの個々のアイテムの高さをTextのフォントサイズや行数に合わせて調整するサンプルです。

このサンプルから何が学べるか

TListViewの個々のアイテムの取り扱い。

TListViewの個々のアイテムのBitmap描画。

テキスト描画に必要な領域のサイズの取得。

リソースに格納されたデータの取り出し。

キーワード

TListView.OnUpdateObjects イベント

ItemAppearance DynamicAppearance

リソース

TStreamReader

TResourceStream

TArray<String>

サンプルのカスタマイズ

表示内容の差し替え(リソースの差し替え)

このサンプルでリスト内に表示されている文字データはリソース内にあります。

IDEのプロジェクトメニューの「リソースと画像」からリソースの管理画面が表示されます。

サンプルでリストに表示されるリソースは「Blabla」という名前のリソースです。

ここでは、「myblabla」というリソースを追加しました。

サンプルではリストにリソースを表示するために、ボタンクリックからReadTextというprocedureが呼び出されています。

このReadTextをリソース名を指定できるprocedureに書き換えました。

ReadText(‘myblabla’);などで以下の書き換えたprocedureを呼び出します。

procedure TVariableHeight.ReadText(const ResName: string);
const
  Delimiters: array of char = [#10, #13];
var
  Reader: TStreamReader;
  Stream: TResourceStream;
begin
  Finalize(FText);
  Stream := TResourceStream.Create(HInstance, ResName, RT_RCDATA);
  Reader := TStreamReader.Create(Stream);
  try
    FText := Reader.ReadToEnd.Split(Delimiters, TStringSplitOptions.ExcludeEmpty)
  finally
    Reader.Close;
    Reader.Free;
    Stream.Free;
  end;
end;

ここでは、TResourceStreamとTStreamReaderを使ってStreamを介してStringの配列であるFTextにリソースの内容を移しています。ここでは、リソースからTResourceStream.Createを使ってテキストデータを取り出していますが、アニメgifなどのリソースを取り出すときも、TResourceStream.Createの3番目はRT_RCDATAでいいようです。また、TResourceStream.Create実行後は後述のMemoコンポーネントのSaveToStreamの実行後のようにStreamを巻き戻す必要はありません。

話をサンプルに戻すと、リソースの文章は改行コードで区切られてFTextの各配列要素となります。Stringをリストに表示する際は、ボタンクリックの中で、FText[Random(Length(FText))];のように配列の要素(index)をランダムに指定しています。なお、元のサンプルでは、FTextに文字が入っているときは、リソースのStreamへの読み込み以降のコードは実行されないのですが、ここでは始めに、Finalize(FText)を実行して、配列を初期化しています。

Memoコンポーネントの内容を表示する

次にリソースの代わりにMemoコンポーネントの内容を表示するために、MemoコンポーネントのSaveToStreamを使ってサンプルと同じようにStreamを介してメモの内容をFTextに移すprocedureを作成しました。

procedure TVariableHeight.UsingMemoExecute(Sender: TObject);
const
  Delimiters: array of char = [#10, #13];
var
  Reader: TStreamReader;
  Stream: TMemoryStream;
begin
  Finalize(FText);
  Stream := TMemoryStream.Create;
  Memo1.Lines.SaveToStream(Stream);
  Stream.Position:= 0;
  Reader := TStreamReader.Create(Stream, TEncoding.Default);
  try
    FText := Reader.ReadToEnd.Split(Delimiters, TStringSplitOptions.ExcludeEmpty)
  finally
    Reader.Close;
    Reader.Free;
    Stream.Free;
  end;
  ListView1.Items.Add.Data['txtMain'] := FText[Random(Length(FText))];

end;

ここでは、TResourceStreamの代わりにTMemoryStreamを使っています。

TStreamReaderで読み込む前にStream.Position:= 0;でStreamの巻き戻しを行います。
TStreamReader.Createの第2パラメータでTEncoding.Defaultを指定しています。

次に、Streamを使わずにMemoコンポーネントのLinesの内容をインデックスごとにStringの配列に移すために次のprocedureを作成しました。

procedure TVariableHeight.MemoToFTextExecute(Sender: TObject);
var
  i: Integer;
begin
  Finalize(FText);
  SetLength(Ftext, Memo1.Lines.Count);
  For i := 0 to Memo1.Lines.Count -1 do
  begin
    Ftext[i] := Memo1.lines[i];
  end;

  ListView1.Items.Add.Data['txtMain'] := FText[Random(Length(FText))];

end;

このprocedureの最後の1行で、リストにFTextのランダムな要素を表示しています。

FTextを介さずに、直接リストにMemoコンポーネントの要素をランダムに表示する場合は以下のように書けます。

ListView1.Items.Add.Data[‘txtMain’] := Memo1.Lines[Random(Memo1.Lines.Count-1)];

Memoコンポーネントの内容を1つのリストアイテムに表示する場合は以下でOKです。

ListView1.Items.Add.Data[‘txtMain’] := Memo1.Text;

最後はシンプルな形に落ち着きましたが、Streamや配列の勉強になりました。

リストアイテムのテキスト表示領域

IDEのデザインモードでListViewコンポーネントの上で右クリックしてデザインモードの切り替えを選択するとListViewコンポーネントの見た目が以下のように変わります。

ListView1.Items.Add.Data[‘txtMain’] :=
の中の’txtMain’は上の図の「項目 txtMain」の部分に該当します。

画像に alt 属性が指定されていません。ファイル名: image-10.png

構造ビューでItemAppearanceをクリックし、オブジェクトインスペクタでItemAppearanceを例えば、ImageListItemBottomDetailに変更すると

のようになり、「項目 txtMain」の部分が「項目 Text」になります。

リストアイテムの追加と同時にTextへ書き込むときは、

With ListView1.Items.Add do Text:= Memo1.Text;

などとしますが、このサンプルの肝であるリスト項目のテキスト描画領域の調整は、ItemAppearanceがDynamicAppearanceでないと、そのままでは機能しませんので、ここではItemAppearanceをDynamicAppearanceに戻します。

DynamicAppearanceの初期状態から「Text1」を「txtMain」に戻し、TImageObjectAppearanceを追加して、imgSizeという名前に変えると元のプログラムが機能するようになります(表示位置の調整も必要ですが)。変更したItemAppearanceを元に戻すことで、DynamicAppearanceのカスタマイズが体験できました。(始めDynamicAppearanceに戻してもプログラムが元通り動かなくて焦ったことは内緒だ)

選択アイテムの表示内容の変更

サンプルではリストアイテムを追加しているだけなので、選択されているリストアイテムの内容を差し替えるコードを書きました。

procedure TVariableHeight.ItemChangeExecute(Sender: TObject);
var
  ListItem: TListViewItem;
begin
  ListItem:= TListViewItem(Listview1.Selected);
  if ListItem = nil then
  begin
    ListItem:= ListView1.Items.Add;
    ListItem.Data['txtMain']:= Memo1.Text;
  end else
  begin
    ListItem.Data['txtMain']:= Memo1.Text;
    ListView1UpdateObjects(Listview1,ListItem);
  end;

end;

選択されているリストアイテムがないときは新規追加とし、選択されているリストアイテムの内容を変更したときは、UpdateObjectsイベントハンドラを明示的に呼び出しています。(自動では呼び出されないようです)

Data[‘txtMain’]をString型にキャストする

Data[‘txtMain’]はTValueなので、そのままではString型の変数に代入できません。

String型の変数に代入するときは、AsTypeを使います。

var
  i: integer;
  St: String;
begin
  St:= ListView1.Items[i].Data['txtMain'].AsType<String>;

フォントサイズの変更とスケール表示の無効化

これらはサンプルとしては勉強になるのですが、メモの内容を表示するだけなら不要なので無効化しました。

  // Randomize the font when updating for the first time
  if Drawable.TagFloat = 0 then
  begin
  {
    Drawable.Font.Size := 1; // Ensure that default font sizes do not play against us
    Drawable.Font.Size := 10 + Random(4) * 4;
  }
    Drawable.Font.Size := 18;

    Drawable.TagFloat := Drawable.Font.Size;
  {
    if Text.Length < 100 then
      Drawable.Font.Style := [TFontStyle.fsBold];
   }
  end;

  // Calculate item height based on text in the drawable
  AItem.Height := GetTextHeight(Drawable, AvailableWidth, Text);
  Drawable.Height := AItem.Height;
  Drawable.Width := AvailableWidth;

  SizeImg.OwnsBitmap := False;
  //SizeImg.Bitmap := GetDimensionBitmap(SizeImg.Width, AItem.Height);

以上、かなり実用的なサンプルだと思いました。

タイトルとURLをコピーしました