DOBON.NET プログラミング道: .NET Framework, VB.NET, C#, Visual Basic, Visual Studio, インストーラ, ...

TreeViewのノードをDrag&Dropにより移動、コピーできるようにする

エクスプローラのフォルダがドラッグ&ドロップによりコピーや移動ができるのと同じように、ツリービューのノードをドラッグ&ドロップでコピーや移動できるようにしてみましょう。なお、ドラッグ&ドロップの方法に関する説明はこちらでしていますので、ここでは一切いたしません。

この方法について、「マイクロソフト サポート技術情報」の「[HOW TO] Visual Basic .NET アプリケーションにツリー ビューのドラッグ アンド ドロップ機能を追加する方法」「[HOW TO] Visual C# .NET アプリケーションにツリー ビューのドラッグ アンド ドロップ機能を追加する方法」でも紹介されているようですが、ここで紹介するコードはこれをさらに拡張したような感じになっています(このサンプルコードを書き終えてからこのようなページがあることを知りました)。具体的には、以下のような点が拡張されています。

  • Ctrlキーを押しながらドラッグ&ドロップすることで、コピーすることができます。
  • ドラッグ中のマウスの下にあるノードを選択状態にし、どこにドロップしようとしているか分かるようにしています。
  • ドロップされた後は、移動(またはコピー)されたノードが選択されます。
  • ノード上以外の場所へドラッグされると、ドロップ効果はNoneになります。
  • ドラッグしたノードを子ノード(または自分自身)にドロップすることを禁止しています。
  • 「マイクロソフトサポート技術情報」ではDragDropイベントハンドラでドロップしたノードを削除していますが、ここではItemDragイベントハンドラで削除するようにしています。
  • 「マイクロソフトサポート技術情報」では同じTreeView内のドラッグ&ドロップを許可していませんが、ここでは同一TreeView内のドラッグ&ドロップにも対応しています。

以下のサンプルコードでは、フォームにツリービューコントロールTreeView1が追加されており、TreeView1内のノードをドラッグ&ドロップにより移動、コピーできるようにしています。

VB.NET
コードを隠すコードを選択
'フォームのLoadイベントハンドラ
Private Sub Form1_Load(ByVal sender As Object, _
        ByVal e As EventArgs) Handles MyBase.Load
    'TreeView1へのドラッグを受け入れる
    TreeView1.AllowDrop = True

    'ノードを追加する
    TreeView1.Nodes.Add("いち")
    TreeView1.Nodes(0).Nodes.Add("よん")
    TreeView1.Nodes(0).Nodes(0).Nodes.Add("ご")
    TreeView1.Nodes.Add("に")
    TreeView1.Nodes.Add("さん")

    'イベントハンドラを追加する
    AddHandler TreeView1.ItemDrag, AddressOf TreeView1_ItemDrag
    AddHandler TreeView1.DragOver, AddressOf TreeView1_DragOver
    AddHandler TreeView1.DragDrop, AddressOf TreeView1_DragDrop
End Sub

'ノードがドラッグされた時
Private Sub TreeView1_ItemDrag(ByVal sender As Object, _
        ByVal e As ItemDragEventArgs)
    Dim tv As TreeView = CType(sender, TreeView)
    tv.SelectedNode = CType(e.Item, TreeNode)
    tv.Focus()

    'ノードのドラッグを開始する
    Dim dde As DragDropEffects = _
        tv.DoDragDrop(e.Item, DragDropEffects.All)

    '移動した時は、ドラッグしたノードを削除する
    If (dde And DragDropEffects.Move) = DragDropEffects.Move Then
        tv.Nodes.Remove(CType(e.Item, TreeNode))
    End If
End Sub

'ドラッグしている時
Private Sub TreeView1_DragOver(ByVal sender As Object, _
        ByVal e As DragEventArgs)
    'ドラッグされているデータがTreeNodeか調べる
    If e.Data.GetDataPresent(GetType(TreeNode)) Then
        If (e.KeyState And 8) = 8 And _
            (e.AllowedEffect And DragDropEffects.Copy) = _
                DragDropEffects.Copy Then
            'Ctrlキーが押されていればCopy
            '"8"はCtrlキーを表す
            e.Effect = DragDropEffects.Copy
        ElseIf (e.AllowedEffect And DragDropEffects.Move) = _
            DragDropEffects.Move Then
            '何も押されていなければMove
            e.Effect = DragDropEffects.Move
        Else
            e.Effect = DragDropEffects.None
        End If
    Else
        'TreeNodeでなければ受け入れない
        e.Effect = DragDropEffects.None
    End If

    'マウス下のNodeを選択する
    If e.Effect <> DragDropEffects.None Then
        Dim tv As TreeView = CType(sender, TreeView)
        'マウスのあるNodeを取得する
        Dim target As TreeNode = _
            tv.GetNodeAt(tv.PointToClient(New Point(e.X, e.Y)))
        'ドラッグされているNodeを取得する
        Dim [source] As TreeNode = _
            CType(e.Data.GetData(GetType(TreeNode)), TreeNode)
        'マウス下のNodeがドロップ先として適切か調べる
        If Not target Is Nothing AndAlso _
            Not target Is [source] AndAlso _
            Not IsChildNode([source], target) Then
            'Nodeを選択する
            If target.IsSelected = False Then
                tv.SelectedNode = target
            End If
        Else
            e.Effect = DragDropEffects.None
        End If
    End If
End Sub

'ドロップされたとき
Private Sub TreeView1_DragDrop(ByVal sender As Object, _
        ByVal e As DragEventArgs)
    'ドロップされたデータがTreeNodeか調べる
    If e.Data.GetDataPresent(GetType(TreeNode)) Then
        Dim tv As TreeView = CType(sender, TreeView)
        'ドロップされたデータ(TreeNode)を取得
        Dim [source] As TreeNode = _
            CType(e.Data.GetData(GetType(TreeNode)), TreeNode)
        'ドロップ先のTreeNodeを取得する
        Dim target As TreeNode = _
            tv.GetNodeAt(tv.PointToClient(New Point(e.X, e.Y)))
        'マウス下のNodeがドロップ先として適切か調べる
        If Not target Is Nothing AndAlso _
            Not target Is [source] AndAlso _
            Not IsChildNode([source], target) Then
            'ドロップされたNodeのコピーを作成
            Dim cln As TreeNode = CType([source].Clone(), TreeNode)
            'Nodeを追加
            target.Nodes.Add(cln)
            'ドロップ先のNodeを展開
            target.Expand()
            '追加されたNodeを選択
            tv.SelectedNode = cln
        Else
            e.Effect = DragDropEffects.None
        End If
    Else
        e.Effect = DragDropEffects.None
    End If
End Sub

''' <summary>
''' あるTreeNodeが別のTreeNodeの子ノードか調べる
''' </summary>
''' <param name="parentNode">親ノードか調べるTreeNode</param>
''' <param name="childNode">子ノードか調べるTreeNode</param>
''' <returns>子ノードの時はTrue</returns>
Private Shared Function IsChildNode( _
        ByVal parentNode As TreeNode, _
        ByVal childNode As TreeNode) As Boolean
    If childNode.Parent Is parentNode Then
        Return True
    ElseIf Not childNode.Parent Is Nothing Then
        Return IsChildNode(parentNode, childNode.Parent)
    Else
        Return False
    End If
End Function
C#
コードを隠すコードを選択
//フォームのLoadイベントハンドラ
private void Form1_Load(object sender, System.EventArgs e)
{
    //TreeView1へのドラッグを受け入れる
    TreeView1.AllowDrop = true;

    //ノードを追加する
    TreeView1.Nodes.Add("いち");
    TreeView1.Nodes[0].Nodes.Add("よん");
    TreeView1.Nodes[0].Nodes[0].Nodes.Add("ご");
    TreeView1.Nodes.Add("に");
    TreeView1.Nodes.Add("さん");

    //イベントハンドラを追加する
    TreeView1.ItemDrag +=
        new ItemDragEventHandler(TreeView1_ItemDrag);
    TreeView1.DragOver +=
        new DragEventHandler(TreeView1_DragOver);
    TreeView1.DragDrop +=
        new DragEventHandler(TreeView1_DragDrop);
}

//ノードがドラッグされた時
private void TreeView1_ItemDrag(object sender, ItemDragEventArgs e)
{
    TreeView tv = (TreeView) sender;
    tv.SelectedNode = (TreeNode) e.Item;
    tv.Focus();
    //ノードのドラッグを開始する
    DragDropEffects dde =
        tv.DoDragDrop(e.Item, DragDropEffects.All);
    //移動した時は、ドラッグしたノードを削除する
    if ((dde & DragDropEffects.Move) == DragDropEffects.Move)
        tv.Nodes.Remove((TreeNode) e.Item);
}

//ドラッグしている時
private void TreeView1_DragOver(object sender, DragEventArgs e)
{
    //ドラッグされているデータがTreeNodeか調べる
    if (e.Data.GetDataPresent(typeof(TreeNode)))
    {
        if ((e.KeyState & 8) == 8 &&
            (e.AllowedEffect & DragDropEffects.Copy) == 
            DragDropEffects.Copy)
            //Ctrlキーが押されていればCopy
            //"8"はCtrlキーを表す
            e.Effect = DragDropEffects.Copy;
        else if ((e.AllowedEffect & DragDropEffects.Move) ==
            DragDropEffects.Move)
            //何も押されていなければMove
            e.Effect = DragDropEffects.Move;
        else
            e.Effect = DragDropEffects.None;
    }
    else
        //TreeNodeでなければ受け入れない
        e.Effect = DragDropEffects.None;

    //マウス下のNodeを選択する
    if (e.Effect != DragDropEffects.None)
    {
        TreeView tv = (TreeView) sender;
        //マウスのあるNodeを取得する
        TreeNode target =
            tv.GetNodeAt(tv.PointToClient(new Point(e.X, e.Y)));
        //ドラッグされているNodeを取得する
        TreeNode source =
            (TreeNode) e.Data.GetData(typeof(TreeNode));
        //マウス下のNodeがドロップ先として適切か調べる
        if (target != null && target != source &&
                !IsChildNode(source, target))
        {
            //Nodeを選択する
            if (target.IsSelected == false)
                tv.SelectedNode = target;
        }
        else
            e.Effect = DragDropEffects.None;
    }
}

//ドロップされたとき
private void TreeView1_DragDrop(object sender, DragEventArgs e)
{
    //ドロップされたデータがTreeNodeか調べる
    if (e.Data.GetDataPresent(typeof(TreeNode)))
    {
        TreeView tv = (TreeView) sender;
        //ドロップされたデータ(TreeNode)を取得
        TreeNode source = 
            (TreeNode) e.Data.GetData(typeof(TreeNode));
        //ドロップ先のTreeNodeを取得する
        TreeNode target = 
            tv.GetNodeAt(tv.PointToClient(new Point(e.X, e.Y)));
        //マウス下のNodeがドロップ先として適切か調べる
        if (target != null && target != source && 
            !IsChildNode(source, target))
        {
            //ドロップされたNodeのコピーを作成
            TreeNode cln = (TreeNode) source.Clone();
            //Nodeを追加
            target.Nodes.Add(cln);
            //ドロップ先のNodeを展開
            target.Expand();
            //追加されたNodeを選択
            tv.SelectedNode = cln;

        }
        else
            e.Effect = DragDropEffects.None;
    }
    else
        e.Effect = DragDropEffects.None;
}

/// <summary>
/// あるTreeNodeが別のTreeNodeの子ノードか調べる
/// </summary>
/// <param name="parentNode">親ノードか調べるTreeNode</param>
/// <param name="childNode">子ノードか調べるTreeNode</param>
/// <returns>子ノードの時はTrue</returns>
private static bool IsChildNode(TreeNode parentNode, TreeNode childNode)
{
    if (childNode.Parent == parentNode)
        return true;
    else if (childNode.Parent != null)
        return IsChildNode(parentNode, childNode.Parent);
    else
        return false;
}
  • 履歴:
  • 2004/12/20 C#のTreeView1_DragOverを修正しました。
  • 2012/2/7 VB.NETのIsChildNodeメソッドのドキュメントコメントが間違えていたのを修正。『「マイクロソフトサポート技術情報」では同じTreeView内のドラッグ&ドロップしか許可していません』を『同じTreeView内のドラッグ&ドロップを許可していません』に修正。

注意:この記事では、基本的な事柄の説明が省略されているかもしれません。初心者の方は、特に以下の点にご注意ください。

  • イベントハンドラの意味が分からない、C#のコードをそのまま書いても動かないという方は、こちらをご覧ください。
  • .NET Tipsをご利用いただく際は、注意事項をお守りください。