†Sibylのお部屋†
作成開始日 2021.12.27
最終更新日 2023.02.06
●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.ItemIndex | 0始まり、-1で無選択 |
全項目数 | n :=ListBox.Items.Count | 最終項目のIndexはCount-1 |
項目番号から項目名を取得 | st:=LinsBox.Items.Names[i] | ReadOnly |
項目名から項目番号を取得 | i :=ListBox.Items.IndexOf('st') | IndexOfNameではない |
項目名を変更する | LinsBox.Items.Strings[i]:=st | R/W |
表示リストの最上段の項目番号 | i: =ListBox.TopIndex; | 表示制御に使用 |
表示更新の一時中断 | ListBox.BeginUpdate;/EndUpdate; | 書換中のちらつき対策 |
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]; //「秋=紅葉」
たとえば、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:=...」みたいな書き方もありうるかと思ったが、両方とも無理だった。コンパイル段階で型の不一致でハネられる。後者はコンパイル段階でプロパティが確定していないので理解可能だが、前者はちょっとわからん…
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の順でイベントが発生する。しかし、先に選択項目に関する処理を行ってからコンポーネントをアクティブにする場合もある。その場合は、二つのイベントハンドラの実行順序は逆になる。実行順序によって、処理に矛盾や重複・相殺が発生する可能性を頭に置いておくこと。