†Sibylのお部屋†

【Sibyl】リストボックス:ListBox

作成開始日 2021.12.27
最終更新日 2023.02.06

基本事項

ListBoxの機能のほとんどはitems:TStringsに依存している。したがって、ListBoxの機能を調べたければ、TStringsクラスを調べること。基本的に、以下のように区別すると良い。

●ItemIndex:現在選択されている項目の番号

現在選択されている項目はListBox.ItemIndexで参照する(これはTStringsではくTListBoxのプロパティ)。ItemIndexは0始まりなので、n番目の項目(item)のIndexは(n-1)となる。一方、Itemの総数(Items.Count)はそのまま総数なので、ループを回す時などは、0〜(Items.Count-1)で回す。なお、何も選択されていない状態では「ListBox.ItemIndex=-1」となる。

【注意】ここで「ListBox」は「TListBox」型のインスタンスを意味する。実際には「ListBox1」や「ListBox2」などのシステムが自動的に付ける名前、もしくは「MyListBox」などユーザーが明示的に付ける名前が使用される。

●項目番号と項目名の相互変換

i番目の項目(Item)の名前はItems.Names[i]で取得できる。逆に、項目名から項目番号(Index)を取得するにはItems.IndexOf()を使用する。ただし、異なった項目が同じ項目名を持っていた場合は…どうなるんだろ?なお、Items.Names[]は参照のみで変更はできない。項目名を変更するにはItems.Stringsを使う(参照にもStringsが使えるとは思うが)。

機 能プロパティ/メソッド備 考
リストの初期化ListBox.Items.Clear;リストを空にする
項目の追加ListBox.Items.Add('st');項目を末尾に追加
項目の挿入ListBox.Items.Insert(n,'st');項目をn番の位置に挿入
現在選択されている項目の番号i :=ListBox.ItemIndex0始まり、-1で無選択
全項目数n :=ListBox.Items.Count最終項目のIndexはCount-1
項目番号から項目名を取得st:=LinsBox.Items.Names[i]ReadOnly
項目名から項目番号を取得i :=ListBox.Items.IndexOf('st')IndexOfNameではない
項目名を変更するLinsBox.Items.Strings[i]:=stR/W
表示リストの最上段の項目番号i: =ListBox.TopIndex;表示制御に使用
表示更新の一時中断ListBox.BeginUpdate;/EndUpdate;書換中のちらつき対策

項目名から項目番号を取得する

TStrings型(items)の「IndexOf()」関数を使用する。
n:=ListBox.Items.IndexOf('xyz');	//「xyz」は項目名、nは項目番号
					// 該当項目がない場合は -1
なお、「IndexOf()」関数とは別に「IndexOfName()」という関数も存在するが、こちらは連想配列用の関数。関数名的に混同しやすいので注意。
ListBox.Items.Values['春']:='桜';	// 0
ListBox.Items.Values['夏']:='朝顔';	// 1
ListBox.Items.Values['秋']:='紅葉';	// 2
ListBox.Items.Values['冬']:='万両';	// 3

n :=ListBox.Items.IndexOfName('秋');	// 2
st:=ListBox.Items[n];			//「秋=紅葉」

ListBoxの代入と切替

オブジェクトは代入が可能であり、代入によって操作対象を切り替えることができる。

たとえば、2ウィンドウ型のファイラーを作成する場合、左右のウィンドウ(ListBox1とListBox2)内の操作はほぼ同じ物なので、それぞれにイベントハンドラを作成して、ほぼ同じコードを書くのは面倒であるし、間違いや不整合も起きやすい。両方のListBoxが同じイベントハンドラを使用して、操作対象の方を切り替える、という方法の方がスマート。

次の例ではListBox1OnScanイベントハンドラをListBox1/ListBox2の両方に割り当てている(つまり、ListBox2OnScanはない)。

Procedure TForm1.ListBox1OnScan (Sender: TObject; Var KeyCode: TKeyCode);
Var
  ListTmp: TListBox;
  ....
Begin
  If Sender=ListBox1 then ListTmp:=ListBox1;
  If Sender=ListBox2 then ListTmp:=ListBox2;
  ....
  sf:=ListTmp.Items.Names[5];	// 代入したオブジェクトが参照できるだけでなく、
  ListTmp.ItemIndex:=10;	// 値の設定なども元オブジェクトに反映される
ここで、Senderはイベントハンドラの第1引数で、呼び出したオブジェクトそのものを差す。このSenderで元オブジェクトを判別している(ここではListBox1/ListBox2)。

ポイントは「ListTmp:=ListBox1;」「ListTmp:=ListBox2;」と言ったオブジェクトの代入操作。これはListBoxの中身のコピーではなく、アドレスのコピーである(と思う)。そのため、単に値を参照するだけでなく、代入操作なども元オブジェクトに反映される。このように、類似オブジェクトが複数ある場合、オブジェクトを一般化して操作対象を切り替える、という方法が便利。

SenderがListBox1/2を意味するのだから、Senderを直に代入する「ListTmp:=Sender;」や、Senderを直に弄る「Sender.ItemIndex:=...」みたいな書き方もありうるかと思ったが、両方とも無理だった。コンパイル段階で型の不一致でハネられる。後者はコンパイル段階でプロパティが確定していないので理解可能だが、前者はちょっとわからん…

TStringsとTStringList

ListBoxのリストを操作するために、TStringsクラスの一時変数を作成したいことがある。しかし、TStringsクラスのインスタンスを直に作成することはできないらしい。代りに、TStringListクラスを使うらしい。しかも、使用前にCreateが必要らしい。たぶん、items全体を一時変数にコピーすると言う使い方も可能だと思う。
Var
  myList: TStringList;

Begin
  myList.Create;			// これが必要みたい
  myList.Add('ahoaho');			// 項目追加
  myList.Add('bakabaka');		// もう一項目追加
  Caption:=IntToStr(myList.Count);	// この例ではタイトルバーに「2」と表示
End;

選択項目の移動時のイベントハンドラ

リストボックス内の選択項目が変化(移動)したときに、特定の処理を行いたいことがある。たとえば、名前のリストからある名前(項目)を選択すると、別のコンポーネントにその人の住所や連絡先が表示される、と言ったような処理がこれにあたる。

このような場合には、OnItemFocusイベントハンドラを使用する。この際、気を付けなければならないのは、日本語の所謂「選択」は、TListBoxでは「Focus」であって「Select」ではないこと。TListBoxにはOnItemSelectというイベントハンドラも存在するが、こちらは当該項目上で[Enter]を押したときに発生するイベントハンドラである。非常に紛らわしい。

もう一つ留意すべきは、イベントハンドラの実行順序である。ListBoxをアクティブにして選択項目を移動する場合は、ListBoxOnEnter→OnItemFocusの順でイベントが発生する。しかし、先に選択項目に関する処理を行ってからコンポーネントをアクティブにする場合もある。その場合は、二つのイベントハンドラの実行順序は逆になる。実行順序によって、処理に矛盾や重複・相殺が発生する可能性を頭に置いておくこと。


【Sibylのお部屋目次】 【ホーム】