« ■Delphi起動直後に表示される「ホームページ」 | トップページ | ☆TreeViewのお手軽なUndo処理 »

☆TreeViewのDrag&Drop

TreeView Drag drop delphi・・・このキーワードでここに来られる方がおられます。以前に .NET(Windowsフォームアプリケーション)での処理を書いていますので、それが該当するんでしょうね。
でもおそらくWIN32での処理をお探しだと思いますので、その処理を書きたいと思います。

簡単に説明すると「TreeView1.OnDragDropで Node の Selected、DropTarget、MoveTo を使って処理するだけです。」と書いてしまえばいいんでしょうが、せっかくなので縮小ノードの展開及び非表示部分のスクロール処理、並びにターゲットの選択部分によるノードの移動処理を含めたサンプルにしたいと思います。

でもTreeViewは奥深いので、Node.DataやOnChange等が原因で様々な問題が起こります。又、DragModeをdmAutomaticで使うとクリックしたかっただけなのに、ノード移動させてしまったという問題も比較的よく起こります。くれぐれもご注意を(笑)

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, ComCtrls, ImgList, ExtCtrls;

type
  TForm1 = class(TForm)
    TreeView1: TTreeView;
    ImageList1: TImageList;
    Timer1: TTimer;
    Timer2: TTimer;
    procedure FormCreate(Sender: TObject);
    procedure TreeView1DragDrop(Sender, Source: TObject; X, Y: Integer);
    procedure TreeView1DragOver(Sender, Source: TObject; X, Y: Integer;
      State: TDragState; var Accept: Boolean);
    procedure TreeView1EndDrag(Sender, Target: TObject; X, Y: Integer);
    procedure Timer1Timer(Sender: TObject);
    procedure Timer2Timer(Sender: TObject);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}


//テスト用アイコン作成
procedure MakeTestIcon(ImageList: TImageList);
var
  Bitmap: TBitmap;
begin
  Bitmap := TBitmap.Create;
  try
    Bitmap.Height := 16;
    Bitmap.Width := 16;
    Bitmap.Canvas.Brush.Color := clRed;
    Bitmap.Canvas.FillRect(Rect(2,2,14,14));
    ImageList.Add(Bitmap,nil);
    Bitmap.Canvas.Brush.Color := clBlue;
    Bitmap.Canvas.FillRect(Rect(2,2,14,14));
    ImageList.Add(Bitmap,nil);
  finally
    Bitmap.Free;
  end;
end;

// 初期設定いろいろ
procedure TForm1.FormCreate(Sender: TObject);
const
  FileName = 'data.txt';
var
  Node: TTreeNode;
  Path: String;
begin
  // 展開用タイマー設定
  Timer1.Enabled := False;
  Timer1.Interval := 1500;

  // スクロール用タイマー設定
  Timer2.Enabled := False;
  Timer2.Interval := 50;

  // アイコンの作成
  MakeTestIcon(ImageList1);

  // TreeView1の設定
  TreeView1.Images := ImageList1;
  TreeView1.ReadOnly := True;
  TreeView1.DragMode := dmAutomatic;
  TreeView1.Height := 150;

  // データの読み込み
  Path := ExtractFilePath(Application.ExeName);
  TreeView1.LoadFromFile(Path + FileName);

  Node := TreeView1.Items.GetFirstNode;
  repeat
    Node.ImageIndex := 1;
    Node.StateIndex := 2;
    Node := Node.GetNext;
  until Node = nil;

  // スクロールの際、ごみが出るから(^^;
  TreeView1.ControlStyle := TreeView1.ControlStyle - [csDisplayDragImage];
end;

var
  ScrollBarCommand: Integer;

procedure TForm1.TreeView1DragOver(Sender, Source: TObject; X, Y: Integer;
  State: TDragState; var Accept: Boolean);

  function IsSelfOrMyChild(dtNode, slNode: TTreeNode): Boolean;
  begin
    Result := False;
    while (dtNode <> nil) do
    begin
      if (slNode = dtNode) then
      begin
        Result := True;
        Exit;
      end;
      dtNode := dtNode.Parent;
    end;
  end;

const
  Offset = 20;
var
  slNode, dtNode: TTreeNode;
begin
  slNode := TreeView1.Selected;
  dtNode := TreeView1.DropTarget;

  Accept := ((Sender as TTreeView) = (Source as TTreeView)) and
            (not IsSelfOrMyChild(dtNode, slNode));

  if not Accept then Exit;
  
  // ノードを展開させます。
  if (dtNode <> nil) then
  begin
    if Timer1.Enabled and (Timer1.Tag <> dtNode.AbsoluteIndex) then
      Timer1.Enabled := False;
    if (not dtNode.Expanded) and dtNode.HasChildren then
    begin
      Timer1.Tag := dtNode.AbsoluteIndex;
      Timer1.Enabled := True;
    end;
  end;

  // 表示されていない部分をスクロールさせます。
  if (Y < Offset) or (Y >= TreeView1.ClientHeight - Offset) then
    begin
      if (Y < Offset) then
        ScrollBarCommand := SB_LINEUP
      else
        ScrollBarCommand := SB_LINEDOWN;
      Timer2.Enabled := True;
    end
  else
    begin
      ScrollBarCommand := -1;
      Timer2.Enabled := False;
    end;
end;

procedure TForm1.TreeView1DragDrop(Sender, Source: TObject; X, Y: Integer);
var
  srcNode, dstNode: TTreeNode;
  HT: THitTests;
begin
  // Explorer等は、ファイル名でソートされていますが、
  // ソートではなく意図したノードの順番に移動させたい場合があります。
  // そのような時に私のアプリでは、
  // アイコンで挿入移動、文字列で子追加移動という仕様にしています。
  // これって結構便利だと思うんですけど、あまり採用されてないみたい。

  srcNode :=TreeView1.Selected;           // 選択ノード
  dstNode := TreeView1.DropTarget;        // ドロップターゲットノード

  HT := TreeView1.GetHitTestInfoAt(X, Y);
  if (htOnIcon in HT) then
    srcNode.MoveTo(dstNode, naInsert)     // アイコンを選択している場合
  else
    srcNode.MoveTo(dstNode, naAddChild);  // 項目を選択している場合
end;

procedure TForm1.TreeView1EndDrag(Sender, Target: TObject; X, Y: Integer);
begin
  ScrollBarCommand := -1;
  Timer1.Enabled := False;
  Timer2.Enabled := False;
end;

// 閉じているノードを展開させます。
procedure TForm1.Timer1Timer(Sender: TObject);
begin
  TreeView1.Items[Timer1.Tag].Expanded := True;
  Timer1.Enabled := False;
end;

// スクロールさせます。
procedure TForm1.Timer2Timer(Sender: TObject);
begin
  if (ScrollBarCommand > -1) then
    SendMessage(TreeView1.Handle, WM_VSCROLL, ScrollBarCommand, 0);
end;

end.

プログラムと同じフォルダに、下記の内容を書き込んだテキストファイルを作成して下さい。
ファイル名は、data.txtです。
node-01
	node-02
	node-03
		node-04
		node-05
node-06
	node-07
		node-08
node-09
	node-10
node-11
node-12
node-13
node-14
	node-15
node-16
node-17
node-18
node-19
node-20
node-21
	node-22
node-23

|

« ■Delphi起動直後に表示される「ホームページ」 | トップページ | ☆TreeViewのお手軽なUndo処理 »