Delphiメモ

No021:TListViewについてあれこれ

−前提−

基本的にViewStyleがvsReportであることが前提。

−リスト表示向けのプロパティセッティング−

・Checkboxes:用途に応じて
・ColumnClick:ソートを実装しないならFalse
・Columns:ヘッダーの項目はここで設定する
・GridLines:True
・HideSelection:基本的にFalse
・MultiSelect:概ねTrue。用途に応じて
・ReadOnly:True
・RowSelect:True
・ShowColumnHeader:True(デフォルトだが)
・ViewStyle:vsReport

これであとはColumnsさえ設定すれば見てくれは完成。

−アイテムの追加−

フォームをForm1、リストビューをListView1、
オープンダイアログをOPD、ボタンをButton1とし、
Button1のOnClickで選択されたファイル名、ファイルパスの順で表示する例。

procedure TForm1.Button1Click(Sender: TObject);
var
  NewItem: TListItem;
  FileName, FullPath: String;
begin
  if OPD.Execute then
  begin
    //AllowMultiSelectはFalseとする
    FileName:= ExtractFileName(OPD.FileName);
    FullPath:= ExtractFilePath(OPD.FileName);
    NewItem:= ListView1.Items.Add;
    NewItem.Caption:= FileName;
    NewItem.SubItems.Add(FullPath);
  end;
end;

これで左からファイル名、ファイルパスの順で表示されるはず。
項目がまだあるならNewItem.SubItems.Addをさらに書けばいいだけ。
ちなみに項目は左からCaption、SubItems[0]、SubItems[1]...となる。

−選択されたアイテムの削除−

今度はButton1のOnClickで選択されたアイテムを削除

procedure TForm1.Button1Click(Sender: TObject);
var
  i: Integer;
begin
  //SelCountが1以上で実行
  if ListView1.SelCount > 0 then
  begin
    //MultiSelectがTrueの時でも削除できる
    for i := 0 to ListView1.SelCount - 1 do
    begin
      ListView1.Selected.Delete;  
    end;
  end;
end;

複数のアイテムが選択されていても、
これで選択されてる部分全てが削除される。

−ドラッグアンドドロップでアイテムを入れ替える−

あらかじめDragModeをdmAutomaticに設定しておき、
グローバル変数で

DragIndex: Integer;

と宣言しておく。

procedure TForm1.ListView1DragOver(Sender, Source: TObject; X, Y:
 Integer; State: TDragState; var Accept: Boolean);
begin
  //アイテム選択数が1の時のみ有効にする
  if ListView1.SelCount = 1 then
  begin
    DragIndex:= ListView1.Selected.Index;
    Accept:= Source is TListView;    
  end
  else
  begin
    Accept:= False;
  end;
end;

まずはDragOverイベントでAcceptを設定する。
DragIndexには選択しているアイテムのIndexを代入している。
ここではSourceがTListViewだった場合のみAcceptがTrueになる。
もっと入念にやるならif (Source as TListView).Name = 'ListView1' thenを
さらに付け加えてからAccept:= Trueとすればいい。

次にDragDropイベントでドロップ時の入れ替えを実装する。
コードは以下の通り。

procedure TForm1.ListView1DragDrop(Sender,Source: TObject; X, Y: Integer);
var
  Pos: Integer;
  NewItem: TListItem;
begin
  if ListView1.GetItemAt(X,Y) <> nil then//@
  begin
    Pos:= ListView1.GetItemAt(X,Y).Index;//A
    if Pos <> DragIndex then//B
    begin
      if Pos > DragIndex then
      begin
        //C
        NewItem:= ListView1.Items.Insert(Pos+1);
      end
      else
      begin
        //D
        NewItem:= ListView1.Items.Insert(Pos);
      end;
      //E
      NewItem.Caption:= PlayList.Selected.Caption;
      NewItem.SubItems.Add(PlayList.Selected.SubItems[0]);
      NewItem.SubItems.Add(PlayList.Selected.SubItems[1]);

      ListView1.Selected.Delete;
      ListView1.Items[Pos].Selected:= True;
      ListView1.Items[Pos].Focused:= True;
    end;      
  end;
end;

コードが長いのでコメントに振った番号順に解説する。

まず@でドロップ地点のアイテムがあるかどうかを判定する。
GetItemAtメソッドは指定した座標のアイテムを返す。
この場合引数のX,YにはそのままイベントハンドラのX,Yを指定してやればいい。
これでドロップ地点にアイテムがあれば処理を続行する。
無い場合は入れ替えが出来ないので処理せず終わる。

次にAでGetItemAtメソッドで得たアイテムのIndexをPosに代入しておく。
この数値を元にBでドラッグを開始したアイテムと
ドロップ地点のアイテムが同一かどうかを判定する。
無論同一ならば処理の必要がない。

B以降はドラッグ元とドロップ地点のアイテムの位置関係で処理が変わる。
まずCはドロップ地点がドラッグ元よりも下にある場合。
注意点はPos+1にInsertするということ。
もしPosのままだとDragIndex=0でPosが1の場合、
1にInsertしても位置関係が変わってくれない。
Dは逆にドロップ地点がドラッグ元よりも上にある場合。
そのままPosにInsertすればいい。
あとはEで選択されたアイテムの内容をそれぞれNewItemに移し、
選択されたアイテムを削除して新しいアイテムを選択状態にすればいいだけ。

−チェックの付いたアイテムを削除する−

この方法に限らずいろいろと応用可能なテクニック。

procedure TForm1.Button1Click(Sender: TObject);
var
  i: Integer;
begin
  i:= 0;
  while i < ListView1.Items.Count do
  begin
    if ListView1.Items[i].Checked = True then
    begin
      ListView1.Items[i].Delete;
    end
    else
    begin
      Inc(i);
    end;
  end;
end;

For文でやろうとすると削除するたびにItems.Countが減少するため、
そのうち範囲外エラーが出てしまう。
この場合だと削除した場合はもう一度同じインデックスのアイテムを調べ、
削除しなければインデックスが増加するという仕組み。
他にも特定のサブアイテムに特定の文字列を含むアイテムを削除したりと
かなり応用範囲が広い。

とまぁ色々書いてみた。
間違いがあったらツッコミよろしく!

トップに戻る

関連ページ