ドラッグアンドドロップでTreeView内のTreeNodeを移動する

注意:
この文書は以前「.NETでいきまっしょい!」で公開していたものですが、公開以降メンテナンスされていません。 今や古い情報となった内容が記載されている場合があるのでご注意ください。

TreeViewにドラッグアンドドロップ関連の処理を書き加えることで、TreeView内に表示されているTreeNodeをドラッグアンドドロップで移動できるように拡張したTreeViewクラスを紹介する。 このコードでは、ドロップ先のノードとドラッグされているノードの親子関係をチェックし、ドロップできるか否かを判定している。

ソースコード
001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
Public Class DragDropTreeView

    Inherits TreeView

    ''' <summary>
    ''' ドラッグアンドドロップされるノードのデータ
    ''' </summary>
    Private Structure DragDropNodeData

        Public Sub New(ByVal node As TreeNode)

            m_Node = node

        End Sub

        Private m_Node As TreeNode

        Public ReadOnly Property Node() As TreeNode
            Get
                Return m_Node
            End Get
        End Property

    End Structure

    ''' <summary>
    ''' 
    ''' </summary>
    Public Sub New()

        MyBase.New()

        Me.AllowDrop = True

    End Sub

    ' ドラッグアンドドロップの開始点
    Private mouseDownPoint As Point

    ' ドラッグするノード
    Private dragDropNode As TreeNode

    ''' <summary>
    ''' MouseDown
    ''' </summary>
    Protected Overrides Sub OnMouseDown(ByVal e As MouseEventArgs)

        If (e.Button And MouseButtons.Left) = MouseButtons.Left Then

            ' ドラッグアンドドロップの開始点
            mouseDownPoint = New Point(e.X, e.Y)

            dragDropNode = Me.GetNodeAt(mouseDownPoint)

        Else

            mouseDownPoint = Point.Empty

            dragDropNode = Nothing

        End If

        MyBase.OnMouseDown(e)

    End Sub

    ''' <summary>
    ''' MouseUp
    ''' </summary>
    Protected Overrides Sub OnMouseUp(ByVal e As MouseEventArgs)

        mouseDownPoint = Point.Empty

        MyBase.OnMouseUp(e)

    End Sub

    ''' <summary>
    ''' MouseMove
    ''' </summary>
    Protected Overrides Sub OnMouseMove(ByVal e As MouseEventArgs)

        If (e.Button And MouseButtons.Left) = MouseButtons.Left Then

            ' ドラッグアンドドロップの範囲にあるか否かをチェックする
            Dim dragBound As Rectangle = New Rectangle(e.X - SystemInformation.DragSize.Width \ 2, e.Y - SystemInformation.DragSize.Height \ 2, SystemInformation.DragSize.Width, SystemInformation.DragSize.Height)

            If Not dragBound.Contains(mouseDownPoint) AndAlso Not dragDropNode Is Nothing Then

                ' ドラッグアンドドロップ用のデータを作成
                Dim nodeData As New DragDropNodeData(dragDropNode)

                ' ノードをドラッグアンドドロップする
                Me.DoDragDrop(nodeData, DragDropEffects.Move)

            End If

        End If

        MyBase.OnMouseMove(e)

    End Sub

    ''' <summary>
    ''' DragEnter
    ''' </summary>
    Protected Overrides Sub OnDragEnter(ByVal drgevent As DragEventArgs)

        If drgevent.Data.GetDataPresent(GetType(DragDropNodeData)) Then

            drgevent.Effect = DragDropEffects.Move

        Else

            drgevent.Effect = DragDropEffects.None

        End If

        MyBase.OnDragEnter(drgevent)

    End Sub

    ''' <summary>
    ''' DragDrop
    ''' </summary>
    Protected Overrides Sub OnDragDrop(ByVal drgevent As DragEventArgs)

        If drgevent.Data.GetDataPresent(GetType(DragDropNodeData)) Then

            ' 入れ替え先のインデックス
            Dim targetNode As TreeNode = Me.GetNodeAt(Me.PointToClient(New Point(drgevent.X, drgevent.Y)))

            If Not targetNode Is Nothing Then

                ' ドラッグアンドドロップするアイテムのデータを取得
                Dim nodeData As DragDropNodeData = DirectCast(drgevent.Data.GetData(GetType(DragDropNodeData)), DragDropNodeData)

                ' 移動対象を含むノード一覧を取得
                Dim parentNodes As TreeNodeCollection

                If targetNode.Parent Is Nothing Then

                    parentNodes = Me.Nodes

                Else

                    parentNodes = targetNode.Parent.Nodes

                End If

                ' ノードのインデックスを取得
                Dim targetNodeIndex As Integer = parentNodes.IndexOf(targetNode)

                ' 一度削除する
                Me.Nodes.Remove(nodeData.Node)

                ' 目的の場所に挿入
                parentNodes.Insert(targetNodeIndex, nodeData.Node)

                ' 選択されているノードを設定
                Me.SelectedNode = nodeData.Node

            End If

        End If

        MyBase.OnDragDrop(drgevent)

    End Sub

End Class