TListBox の インデックスを知る方法について


HT優勝!  2006-04-10 21:23:19  No: 20951

TListBox ないにある項目をクリックしたときに、
以下の条件を調べて、それぞれに対する処理を行いたいのです。
1.何も選択されていない状態から、項目をクリックされた
2.既に選択されている項目と違う項目が選択された。
3.既に選択されている項目と同じ項目が選択された

int BackItem;
を作成し、Create 時に -1 を入れて、
今までなら、クリック時に BackItem を 変更していく方法を
使っていたのですが、

処理のいたるところ等で処理を行った場合に
BackItem を設定しなければならなくなりそうで、
どこかの処理で BackItem がずれてバグになりそうな予感がします。
デバッグで全て取りきれるかどうかもわからないぐらい
処理箇所が多くなるので。

OnClick時に何かのプロパティ等で調べることとか、
OnClick時にItemIndex が変更される前に発生する
BeforeClick イベントとかで処理するといった方法は
できないでしょうか?


deldel  2006-04-10 21:54:51  No: 20952

OnClick時に BackItem := ListBox1.ItemIndex; ではだめなのですか?


HT優勝!  2006-04-10 22:38:13  No: 20953

deldel さん、どうもです。
質問の記述で理解できなかったのかもしれませんね。
前回の質問でBackItem に値を入れる方法は既に試しています。
>int BackItem;
>iを作成し、Create 時に -1 を入れて、
>i今までなら、クリック時に BackItem を 変更していく方法を
>i使っていたのですが、

この方法を使った場合に、削除、や、追加、いくつかの処理
を行った場合に、BackItemを正常な数値に設定してやる必要があります。
つまり、OnClick 以外で、選択が変更される箇所でBackItemを
設定しなおす必要があります。
こうなった場合にメンテナンスやデバッグで悩まされる原因にもなるため
できればこの方法を避けたいのです。

そこで、なんですが、
例えば OnClick イベント前には OnMouseDown イベントが発生するので
OnMouseDown で ListBox1.ItemIndex が変更されていなければ
BackItem:=ListBox1.ItemIndex として、
直前の Index値 を取得できたのですが、実際には
OnMouseDown 時には既にIndex値が変更されていました。
このように他の処理や影響を受けないような方法がないものかと
質問させていただたしだいです。

すみません。よろしくお願いします。


igy  2006-04-10 23:22:22  No: 20954

>この方法を使った場合に、削除、や、追加、いくつかの処理
>を行った場合に、BackItemを正常な数値に設定してやる必要があります。

TListBoxには、itemsプロパティのObjectsに重複しない値(コードとか)
などを埋め込み、ItemIndexではなく、Objectsの中身をBackItemに格納すれば、
削除や追加などで位置が変わっても、BackItemを更新する必要がなくなりますが、
どうでしょう?


HT優勝!  2006-04-11 00:48:25  No: 20955

igyさんありがとうございます。

すみません。理解できませんでした。
まず、「itemsプロパティのObjects」の意味がわかりません。
Objectsがわかってないのかも。


igy  2006-04-11 01:13:55  No: 20956

リストボックスの中身を格納するとき、

    with ListBox1.Items do
    begin
        Add('Delphi1');
        Add('Delphi2');
        Add('Delphi3');
        Add('Delphi4');
        Add('Delphi5');
        Add('Delphi6');
        Add('Delphi7');
    end;

ではなく、

    with ListBox1.Items do
    begin
        AddObject('Delphi1', TObject(80));
        AddObject('Delphi2', TObject(90));
        AddObject('Delphi3', TObject(100));
        AddObject('Delphi4', TObject(120));
        AddObject('Delphi5', TObject(130));
        AddObject('Delphi6', TObject(140));
        AddObject('Delphi7', TObject(150));
    end;

のようにObjectも指定。

リストボックスのOnClickイベントで、選択された項目のObjectの内容を表示する場合、

procedure TForm1.ListBox1Click(Sender: TObject);
begin
    with ListBox1 do
        if ItemIndex >= 0 then
            Label7.Caption := IntToStr(Integer(Items.Objects[ItemIndex]));
end;


igy  2006-04-11 01:24:09  No: 20957

さらに、格納されている位置を探す場合、IndexOfObjectメソッドで。

procedure TForm1.Button7Click(Sender: TObject);
var
    idx: Integer;
begin
    with ListBox1 do
    begin
        idx := items.IndexOfObject(TObject(130));
        MessageDlg('130が格納されているのは、idx=' + IntToStr(idx), mtInformation, [mbOk], 0);
    end;
end;


RE最下位(;´д`)  2006-04-11 01:32:19  No: 20958

procedure TForm1.ListBox1MouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
var
  i, Index, Data: Integer;
  s: string;
begin
  with TListBox(Sender) do begin
   Index := ItemAtPos(Point(X, Y), True);
   if Index >= 0 then begin
    Data := Integer(Items.Objects[Index]);
    case Data of
     0: s := '初めてクリック';
     1: s := '違う項目を選択';
     2: s := '同じ項目を選択';
    end;
    Memo1.Lines.Add(Format('%d  %d  %s', [Index, Data, s]));
    for i:=0 to Pred(Items.Count) do Items.Objects[i] := TObject(1);
    Items.Objects[Index] := TObject(2);
   end;
  end;
end;

未選択時の追加…AddObject('追加項目', TObject(0));
既選択時の追加…AddObject('追加項目', TObject(1));


HT優勝!  2006-04-11 01:50:53  No: 20959

igyさん、RE最下位(;´д`) さん
どうもすみません。
私の説明がとても不十分のようなので、
求めている回答と違うような気がします。

もともとの処理イメージですが以下のようになります。

var
  BackItem:  Integer = -1;

procedure TForm1.ListBox1Click(Sender: TObject);
begin
  if          ( BackItem = -1) then begin
    //  クリック直前にどの項目も選択されていない
  end else if ( ListBox1.ItemIndex = BackItem ) then begin
    //  クリック直前と今回クリックされた項目が同じ
  end else begin
    //  クリック直前に選択されていた項目と違う項目をクリック
  end;
  BackItem = ListBox1.ItemIndex;
end;

この状態ですと、リストボックスのクリックだけの処理であれば
問題ないのですが、例えば、[削除][追加][挿入]などのボタンを
フォームに貼り付けて、リストの内容が変更された場合に
次に、このクリックイベントが発生されたときに
BackItem の中身と実際にクリック直前に選択されていたItemIndexの値とが
違うものになってしまいます。

それぞれのボタン処理イベント等の最終で
  BackItem = ListBox1.ItemIndex;
を実行すればよいのですが、これ以外にも
処理ボタン等があり、それらの場合に  BackItem を管理しながらプログラムを作ることになります。
このとき何らかのミスにより  BackItem の値が違うものになる可能性が
多々発生し、デバッグに時間がかかるため
クリック直前のItemIndexとクリック後のItemIndexを比較できる方法を
探しています

すみません、よろしくお願いします。


ミエナイ  2006-04-11 02:03:59  No: 20960

いったいナニをどうしたいのか見えてこないね。
クリック直前のItemIndexを保存/使用する必要がなければ、
Object値操作だけで、項目追加、挿入、削除も問題無し。


igy  2006-04-11 02:19:07  No: 20961

ItemIndexのような“位置”を覚えるのではなく、(追加・削除で位置が変わるので)

>TListBoxには、itemsプロパティのObjectsに重複しない値(コードとか)
>などを埋め込み、ItemIndexではなく、Objectsの中身をBackItemに格納すれば、
>削除や追加などで位置が変わっても、BackItemを更新する必要がなくなりますが、
>どうでしょう?

と書いたようにObjectsに記憶している“値(コードとか)”を覚えるのでは
だめなのですか?


HT優勝!  2006-04-11 02:52:54  No: 20962

基本的に以下のようなプログラムです。
リストボックスに[1111][2222]の2つの項目が入っているとします。
プログラムを起動して、2222を選択したとします。
このとき、ItemIndex=1 です。
次に追加ボタンを押します。
「3333」という項目が追加され、追加した項目が選択されるように
ListBox1.ItemIndex := ListBox1.Count - 1;
としています。
以下のプログラムではこの後、「3333」をクリックすると、
「アイテム変更処理」が行われません。
BackItemが1のままで、ItemIndexは2になっているからです。

目的は
クリック時に「アイテム変更処理」が行われるようにしたいのです。
ただ、BackItem をプログラムのいたるところで設定するといった
方法はとりたくないという条件をつけてわけですが
私の説明はまだ、理解するには不十分なのでしょうか?

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls;

type
  TForm1 = class(TForm)
    ListBox1: TListBox;
    Button1: TButton;
    procedure ListBox1Click(Sender: TObject);
    procedure Button1Click(Sender: TObject);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;
  BackItem:  Integer;

implementation

{$R *.dfm}

//  リストボックスのクリック
procedure TForm1.ListBox1Click(Sender: TObject);
begin
 if BackItem = ListBox1.ItemIndex then begin
  //アイテム変更処理
 end;
 BackItem := ListBox1.ItemIndex;
end;

//  挿入ボタンのクリック
procedure TForm1.Button1Click(Sender: TObject);
begin
 ListBox1.Items.Add('3333');
 ListBox1.ItemIndex := ListBox1.Count - 1;
end;

end.


igy  2006-04-11 03:49:57  No: 20963

コード上でItemIndexに直接、値を格納するかわりに、次のようなメソッドを使うのはどうですか?

procedure TForm1.SetItemIndex(AIdx: Integer);
begin
    ListBox1.ItemIndex := AIdx;
    BackItem := ListBox1.ItemIndex;
end;

ListBox1.ItemIndex := ListBox1.Count - 1;
  ↓
SetItemIndex(ListBox1.Count - 1);

>BackItem をプログラムのいたるところで設定するといった方法はとりたくない

は満たせます?


delphi forever  2006-04-12 01:11:58  No: 20964

>こうなった場合にメンテナンスやデバッグで悩まされる原因にもなるため
>できればこの方法を避けたいのです。

こうなると、TListBoxから派生したクラスを書かないと
いけなさそうな気もしますが・・・。
挑戦してみました。

>クリック時に「アイテム変更処理」が行われるようにしたいのです。

TListBoxのアイテムと、それの付加的な情報が常に存在するわけですよね。
その付加的な情報を TObjectから派生したクラスにして、どなたかが
書かれた様にObjectsに追加すればよいです。
この場合、BackItemみたいなインデックスを保存しておく変数は
いりません。

ただ、オブジェクトを作成するコードと破棄するコードを
ListBoxのItemsの状態と同期を取って
書かねばら無いので、やはりソースが見にくくなったり、
バグがでたりといろいろ問題があるかと。

TListData= class( TObject)
  Data1: string;
  Data2: string;
end;

//挿入ボタンのクリック
procedure TForm1.InsertButtonClick(Sender: TObject);
var
  ListData: TListData;
begin
 ListData:= TListData.Create;
 ListBox1.Items.AddObject('3333',ListData);
end;

procedure TForm1.ListBoxClick(Sender: TObject);
begin
  // これは TListBox1.OnClickのハンドラ
  
  EditButtonClick(nil);
end;

//編集
procedure TForm1.EditButtonClick;
var
  ListData: TListData;
begin
  if ListBox1.ItemIndex<0 then Exit;
  if ListBox1.ItemIndex<ListBox1.Items.Count-1 do
  begin
    ListData:= ListBox1.Items.Objects( ListBox1.ItemIndex) as TListData;
    Edit1.Text:= ListData.Data1;
    Edit2.Text:= ListData.Data2;
  end;
end;

//削除
procedure TForm1.DeleteButtonClick;
begin
  if ListBox1.ItemIndex<0 then Exit;
  if ListBox1.ItemIndex<ListBox1.Items.Count-1 do
  begin
    ListData:= ListBox1.Items.Objects( ListBox1.ItemIndex) as TListData;
    ListData.Free;
    ListBox1.Items.Delete( ListBox1.ItemIndex);
  end;  
end;

//クリア
procedure TForm1.ClearListBoxItems;
begin
  if ListBox1.Items.Count>0 then
  for i:=ListBox1.Items.Count-1 downto 0 do
  begin
    (ListBox1.Objects[i] as TListData).Free;
    
  end;

  ListBox1.Items.Clear;
end;

こんな感じで常に、選択項目と付加的な情報の表示内容が
一致すると思います。

>この状態ですと、リストボックスのクリックだけの処理であれば
>問題ないのですが、例えば、[削除][追加][挿入]などのボタンを
>フォームに貼り付けて、リストの内容が変更された場合に
>次に、このクリックイベントが発生されたときに
>BackItem の中身と実際にクリック直前に選択されていたItemIndexの値とが
>違うものになってしまいます。

は、これで回避できてると思います。(確認はしてませんが)

まあ、結局のところ、igyさんが最後の1つ前に言ったことと同じかもしれない。
気もする。

やっぱり、派生 を避けては通れないと思います。
これを機に、学んでみてはどうでしょうか。
これまでの内容を TListBoxExで再実装(涙)

TListBoxEx = class(TListBox)
  //自分で考えてください(汗)
end;


  2006-04-12 17:42:54  No: 20965

OnClickで変更を検知するのをやめるとか・・・
ItemIndexと同期をとるように変更するBackItemではなく、
BackItemと同期をとるようにItemIndexを変更する。

type
  TForm1 = class(TForm)
  private
    FBackItem  :Integer;

    procedure SetBackItem(Value:Integer);
  public
    property BackItem: Integer read FBackItem write SetBackItem;
  end;

procedure TForm1.SetBackItem(Value:Integer);
begin
  if FBackItem <> Value then begin
    FBackItem := Value;
    {//ここでアイテム変更イベント//}
    OnChangeItemIndexHogeHoge...
  end;
  if ListBox1.ItemIndex <> FBackItem then begin
    ListBox1.ItemIndex := FBackItem;
  end;
end;

//  リストボックスのクリック
procedure TForm1.ListBox1Click(Sender: TObject);
begin
  BackItem := ListBox1.ItemIndex;
end;

//  挿入ボタンのクリック
procedure TForm1.Button1Click(Sender: TObject);
begin
  ListBox1.Items.Add('3333');
  BackItem := ListBox1.Count - 1;
end;


deldel  2006-04-12 18:41:18  No: 20966

ざっと読んだのですが、以下ではだめですか?

//  リストボックスのクリック
procedure TForm1.ListBox1Click(Sender: TObject);
begin
  //アイテム変更処理
end;

若しくは、

//  挿入ボタンのクリック
procedure TForm1.Button1Click(Sender: TObject);
begin
 ListBox1.Items.Add('3333');
 ListBox1.ItemIndex := ListBox1.Count - 1;
 //アイテム変更処理
end;


※返信する前に利用規約をご確認ください。

※Google reCAPTCHA認証からCloudflare Turnstile認証へ変更しました。






  このエントリーをはてなブックマークに追加