« ☆TreeViewのDrag&Drop | トップページ | ■Delphi2007 PDF版ヘルプの公開 »

☆TreeViewのお手軽なUndo処理

前回のTreeViewのDrag&Dropのコードを使って、お手軽なUndo処理(手抜き?)を紹介します。
一番お手軽なのは、SaveToFile、LoadFromFileを使って一時ファイルでやり取りする方法です。しかしこれでは、展開状態を別に保存する必要があり面倒です。そこでもう一つTreeViewを用意してクローンを作成することにします。

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

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

  // Undo用TreeViewに登録させます。       //←追加
  UndoRegist;                             //←追加

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

次にUndoの処理です。
フォームにTreeViewを1つ、SpeedButtonを2つ追加します。
そして、次のコードを最後に追加します。
var
  idx1: Integer;
  CanUndo: Boolean = False;
  // TopIndex: Integer; // (A)

// Undo用TreeViewに登録させます。
procedure TForm1.UndoRegist;
var
  I: Integer;
  F: Boolean;
begin
  TreeView1.Items.BeginUpdate;
  try
    // TopIndex := TreeView1.TopItem.AbsoluteIndex; // (A)

    if TreeView1.Selected = nil then
      idx1 := -1
    else
      idx1 := TreeView1.Selected.AbsoluteIndex;

    TreeView2.Items.Assign(TreeView1.Items);

    // 展開状態の保存
    for I := TreeView1.Items.Count -1 downto 0 do
    begin
      F := TreeView1.Items[I].Expanded;
      if TreeView2.Items[I].Expanded <> F then
        TreeView2.Items[I].Expanded := True;
    end;
    CanUndo := True;
  finally
    TreeView1.Items.EndUpdate;
  end;
end;

// Undo
procedure TForm1.SpeedButton1Click(Sender: TObject);
var
  I: Integer;
  F: Boolean;
begin
  // Undoできない場合の処理
  if not CanUndo then Exit;

  TreeView1.Items.BeginUpdate;
  try
    TreeView1.Items.Assign(TreeView2.Items);

    // 展開状態の再現
    for I := TreeView2.Items.Count -1 downto 0 do
    begin
      F := TreeView2.Items[I].Expanded;
      if TreeView1.Items[I].Expanded <> F then
        TreeView1.Items[I].Expanded := True;
    end;

    if idx1 >= 0 then
      TreeView1.Items[idx1].Selected := True;
    TreeView1.Selected.MakeVisible; // (B)

    CanUndo := False;
  finally
    TreeView1.Items.EndUpdate;
    // TreeView1.TopItem := TreeView1.Items[TopIndex]; // (A)
  end;
end;

// Delete
procedure TForm1.SpeedButton2Click(Sender: TObject);
begin
  if TreeView1.Selected <> nil then
  begin
    // Undo用TreeViewに登録させます。
    UndoRegist;
    TreeView1.Selected.Delete;
  end;
end;


あとは実行するだけです。移動や削除をしてからUndoボタンを押すと、Undoできます。処理前にUndoRegistを呼び出せば、追加、挿入時もこのUndo機能が使えます。 Undo後は、MakeVisibleで、Undo前の選択ノード表示していますが、TopItemにこだわるのであれば、(B)をコメントアウトして、(A)を有効にします。 TopItemへの設定は、TreeView1.Items.BeginUpdate~EndUpdate内では無効みたいなので、ちょっとちらつきます。

TreeViewのアイテム数にもよりますが、最近のマシンでは、これぐらいのメモリの無駄は問題ないと思います。しかし、これはあくまで一つのサンプルなので、きちんとしたアプリでは、クラスとしてUndoList、RedoListを作成する 、ファイルを利用する等で、リソースが「MOTTAINAI」と言われないような処理を心掛けたいですね(笑)

[2007/08/04 am10:54 全面的に訂正しました]
展開状態を設定し忘れていたからです(^^;

|

« ☆TreeViewのDrag&Drop | トップページ | ■Delphi2007 PDF版ヘルプの公開 »