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」の部分に該当します。
構造ビューで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);
以上、かなり実用的なサンプルだと思いました。