VB.NET-ツリービュー上でのドラッグANDドロップでDropHighlightしたい。

解決


yuu  2003-10-31 20:19:03  No: 109629

VB.NETでツリービュー上でドラッグアンドドロップできる処理を作成しているのですが。

1)ドラッグ中にエクスプローラのように、ツリーをスクロールをしたい。
2)ドラッグ中にエクスプローラのように、もし、ツリーにサブツリーがある場合には、展開をしたい。
3)ドラッグ中にエクスプローラのように、どこにドラッグされるか、
ハイライト表示(VB6.0のころは、DropHighlightメソッドでできた?)又は、
ドラッグツリー名の表示をしたい。

Win2000
VB.NET2003


red-fish  2003-11-01 23:30:38  No: 109630

VB.NETでツリービュー上でドラッグアンドドロップできる処理を作成しているのですが。

1)ドラッグ中にエクスプローラのように、ツリーをスクロールをしたい。
2)ドラッグ中にエクスプローラのように、もし、ツリーにサブツリーがある場合には、展開をしたい。
3)ドラッグ中にエクスプローラのように、どこにドラッグされるか、
ハイライト表示(VB6.0のころは、DropHighlightメソッドでできた?)又は、
ドラッグツリー名の表示をしたい。

> 1)...ツリーをスクロールをしたい。

TreeNodeのPrevVisibleNode, NextVisibleNodeに対してEnsureVisible

> 2)...ツリーにサブツリーがある場合には、展開をしたい。

TreeNodeのExpand

> 3)...どこにドラッグされるかハイライト表示
>   ...又は、ドラッグツリー名の表示をしたい。

ドラッグ先をTreeView1.GetNodeAt()で取得してBackColor等を設定
ドラッグツリー名はTreeNodeのFullPathで取得

でいけると思いますが「エクスプローラのように」となると
delayタイムを設けないと快適な操作感は得られないので、
ちょっとゴチャゴチャしそうですね。この辺りの実装方法は
いろいろあるかと思いますが、一例としてこんな感じ。
(あまり検証していないので不具合ある可能性大(^^;)

  Private m_nodeDrag As TreeNode = Nothing
  Private m_nodeHighlight As TreeNode = Nothing

  Private Sub Form1_Load(ByVal sender As System.Object, _
                         ByVal e As System.EventArgs) Handles MyBase.Load
    TreeView1.AllowDrop = True
    'キリよくサイズ調整
    TreeView1.Height = TreeView1.VisibleCount() * TreeView1.ItemHeight() + 4
  End Sub

  'Dragの開始
  Private Sub TreeView1_ItemDrag(ByVal sender As Object, _
                                 ByVal e As System.Windows.Forms.ItemDragEventArgs _
                                 ) Handles TreeView1.ItemDrag
    m_nodeHighlight = Nothing
    m_nodeDrag = CType(e.Item, TreeNode)
    m_nodeDrag.BackColor = Color.LightGray
    TreeView1.SelectedNode = Nothing
    TreeView1.DoDragDrop(m_nodeDrag, DragDropEffects.Move)
  End Sub

  'Dragさまよい中
  Private Sub TreeView1_DragOver(ByVal sender As Object, _
                                 ByVal e As System.Windows.Forms.DragEventArgs _
                                 ) Handles TreeView1.DragOver
    Static s_hoverStartTime As DateTime = DateTime.Now

    If e.Data.GetDataPresent("System.Windows.Forms.TreeNode") Then
      Dim nodeHover As TreeNode = _
          TreeView1.GetNodeAt(TreeView1.PointToClient(New Point(e.X, e.Y)))

      Dim timeHover As Integer = _
          CInt(DateTime.Now.Subtract(s_hoverStartTime).TotalMilliseconds)
      If timeHover >= 800 Then
        'delay800msで、自動展開
        nodeHover.Expand()
        s_hoverStartTime = DateTime.Now
      ElseIf timeHover >= 300 Then
        'delay300msで、自動Scroll
        If Not nodeHover.PrevVisibleNode Is Nothing AndAlso Not nodeHover.PrevVisibleNode.IsVisible Then
          nodeHover.PrevVisibleNode.EnsureVisible()
          s_hoverStartTime = DateTime.Now
        End If
        If Not nodeHover.NextVisibleNode Is Nothing AndAlso Not nodeHover.NextVisibleNode.IsVisible Then
          nodeHover.NextVisibleNode.EnsureVisible()
          s_hoverStartTime = DateTime.Now
        End If
      End If

      'Dropターゲットの着色とカーソル状態設定
      If nodeHover Is m_nodeHighlight Then
        e.Effect = DragDropEffects.Move
        Me.Text = nodeHover.FullPath
      Else
        '(!)Drag元の配下へDropできないようにするとかは、このSampleでは考慮していない
        If Not m_nodeHighlight Is Nothing Then
          m_nodeHighlight.BackColor = TreeView1.BackColor
          m_nodeHighlight.ForeColor = TreeView1.ForeColor
        End If
        If nodeHover Is m_nodeDrag Then
          e.Effect = DragDropEffects.None
        Else
          nodeHover.BackColor = SystemColors.Highlight
          nodeHover.ForeColor = SystemColors.HighlightText
          m_nodeHighlight = nodeHover
          s_hoverStartTime = DateTime.Now
          e.Effect = DragDropEffects.Move
        End If
      End If
    End If
  End Sub

  'Drop発生
  Private Sub TreeView1_DragDrop(ByVal sender As Object, _
                                 ByVal e As System.Windows.Forms.DragEventArgs _
                                 ) Handles TreeView1.DragDrop
    'TODO:Dropの処理を記述
  End Sub

  'D&Dの完了or中断
  Private Sub TreeView1_MouseUp(ByVal sender As Object, _
                                ByVal e As System.Windows.Forms.MouseEventArgs _
                                ) Handles TreeView1.MouseUp
    If Not m_nodeHighlight Is Nothing Then
      m_nodeHighlight.BackColor = TreeView1.BackColor
      m_nodeHighlight.ForeColor = TreeView1.ForeColor
      m_nodeHighlight = Nothing
    End If
    If Not m_nodeDrag Is Nothing Then
      m_nodeDrag.BackColor = TreeView1.BackColor
      m_nodeDrag = Nothing
    End If
  End Sub

<参考URL>
▼Windows フォームにおけるドラッグ アンド ドロップ操作の実行
http://www.microsoft.com/japan/msdn/library/default.asp?url=/japan/msdn/library/ja/vbcon/html/vbtskstartingdragoperations.asp
▼宇宙仮面のC# プログラミングへようこそ! ドラッグアンドドロップ
http://ukamen.hp.infoseek.co.jp/Programming1/DragAndDrop/


yuu  2003-11-02 20:05:22  No: 109631

ていねいな説明たいへんありがとうございました。
とてもたすかりました。

ここに、フォルダを移動する処理を書いているのですが、
フォルダの移動処理に失敗した場合、(Drag元と同じ場所へDropしたようなときに)、表示ツリービューの先頭が選択されてしまいます。
なぜでしょうか?

  'Drop発生
  Private Sub TreeView1_DragDrop(ByVal sender As Object, _
                                 ByVal e As System.Windows.Forms.DragEventArgs _
                                 ) Handles TreeView1.DragDrop
    'TODO:Dropの処理を記述
  End Sub

さて、教えていただいたコードに、
リストビューから、ツリービューへドラッグする処理を追加したいと思い、
(エクスプローラのように)
以下のように追加してみました。

    Private Sub ListView1_ItemDrag(ByVal sender As Object, ByVal e As System.Windows.Forms.ItemDragEventArgs) Handles ListView1.ItemDrag
        If e.Button = MouseButtons.Left Then
            'invoke the drag and drop operation
            m_nodeHighlight = Nothing
            DoDragDrop(e.Item, DragDropEffects.Move Or DragDropEffects.Copy)
        End If
    End Sub

1回目にリストービューからツリービューへドラッグすると、
マウスカーソルが丸に×の表示で、少しツリービュー上でドラッグしていると、ドロップできるようになります。なぜでしょうか?
その後は、すぐドロップできます。
プログラム起動直後に、
すぐにドロップできるようにするには、どうしたらよいのでしょうか?


red-fish  2003-11-02 22:40:56  No: 109632

> ここに、フォルダを移動する処理を書いているのですが、

まず最初に断っておきますと、あのSampleはご質問の
(1),(2),(3)を満たすだけの例であり、TreeViewから
TreeViewへのDragAndDropしか想定していません。
よって、DragOverが走る前に、まずTreeView1_ItemDrag
が走っているはずだという前提があります。
ListViewからも持ってくるとなると、
.GetDataPresent("System.Windows.Forms.ListViewItem")
とするだけではなく、他にも色々と考えるべき点が
出てくることになります。
継ぎ足しで組んでいると、結果的にトータルで生産性の
悪い事態になるでしょうから、SampleはあくまでSample
として、ご自身で各Methodを理解しながら0から組んでみ
ることをおすすめします。

> ...ツリービューの先頭が選択されてしまいます。
> なぜでしょうか?

TreeView1_ItemDrag イベント内の
    TreeView1.SelectedNode = Nothing
は不要でした。Drag元はまずSelectedになると私は勘違い
していたので(=HighlightColorと区別がつかない)、この
1行を入れましたが、そうとは限らないので意味のない1行
です。選択がリセットされるなら、これが怪しいでしょう。

> 1回目にリストービューからツリービューへドラッグすると、
> マウスカーソルが丸に×の表示で、少しツリービュー上でド
> ラッグしていると、ドロップできるようになります。なぜでしょうか?

これは再現しなかったのですが??? (on VS2002(fw1.0-SP2))

「マウスカーソルが丸に×」になるということは、
  e.Effect = DragDropEffects.None
に入っているか、あるいはこの辺りのCodeに全く到達でき
ていないか、TreeView1_DragOverイベント自体呼ばれてないか
でしょうから、その辺りでConsole.WriteLine()入れるなり
して調べてみてください。


yuu  2003-11-03 10:44:21  No: 109633

デバッグしてみたのですが、いまいちわからないので、
もう少し教えてください。すみません。

>は不要でした。Drag元はまずSelectedになると私は勘違い
>していたので(=HighlightColorと区別がつかない)、この
>1行を入れましたが、そうとは限らないので意味のない1行
>です。選択がリセットされるなら、これが怪しいでしょう。

そのとおりでした。

> 1回目にリストービューからツリービューへドラッグすると、
> マウスカーソルが丸に×の表示で、少しツリービュー上でド
> ラッグしていると、ドロップできるようになります。なぜでしょうか?

> これは再現しなかったのですが??? (on VS2002(fw1.0-SP2))

「マウスカーソルが丸に×」になるということは、
丸に斜め線でした。
これについては、以下のコードを追加することにより、一応解消されました。

   Private Sub ListView1_DragEnter(ByVal sender As Object, ByVal e As System.Windows.Forms.DragEventArgs) Handles ListView1.DragEnter
        ' Check to be sure that the drag content is the correct type for this 
        ' control. If not, reject the drop.
        'If (e.Data.GetDataPresent("System.Windows.Forms.listviewitem")) Then
        ' If the Ctrl key was pressed during the drag operation then perform
        ' a Copy. If not, perform a Move.
        If (e.KeyState And CtrlMask) = CtrlMask Then
            e.Effect = DragDropEffects.Copy
        Else
            e.Effect = DragDropEffects.Move
        End If
        'Else
        '    e.Effect = DragDropEffects.None
        'End If
    End Sub

>よって、DragOverが走る前に、まずTreeView1_ItemDrag
>が走っているはずだという前提があります。
>ListViewからも持ってくるとなると、
>.GetDataPresent("System.Windows.Forms.ListViewItem")
>とするだけではなく、他にも色々と考えるべき点が
>出てくることになります。

このへんの説明がだいぶひっかかるのですが、

TreeView上でのドラッグは、正常にできるということは、
ListviewからTreeviewへドラッグすると、
TreeView1_DragOverイベントは、起きるみたいなのですが、数秒後のタイムラグのあと動き始めるみたいです。当初は、nodeHover=nothingなのですが、数秒後たった後、いきなり表示され始める。
 
一度でも動き出すと次からは、正常に動いているみたいです。
どうしてよいか今のままですとわからない状態です。
やはり、TreeView1_ItemDragを走らせる何かをしないとだめなのでしょうか?


yuu  2003-11-03 10:48:55  No: 109634

ここからの文章がへんです。

このへんの説明がだいぶひっかかるのですが、

ListviewからTreeviewへドラッグすると、
TreeView1_DragOverイベントは、起きるみたいなのですが、数秒後のタイムラグのあと動き始めるみたいです。(ツリーのハイライト表示が開始されます。)
当初は、nodeHover=nothingなのですが、数秒後たった後、いきなり表示され始める。
一度でも動き出すと次からは、ListviewからTreeviewへドラッグすると、すぐにハイライト表示されます。

どうしてよいか今のままですとわからない状態です。
やはり、TreeView1_ItemDragを走らせる何かをしないとだめなのでしょうか?


yuu  2003-11-04 01:20:14  No: 109635

Private Sub TreeView1_DragOver(ByVal sender As Object, ByVal e As System.Windows.Forms.DragEventArgs) Handles TreeView1.DragOver
      Dim nodeHover As TreeNode = _
          TreeView1.GetNodeAt(TreeView1.PointToClient(New Point(e.X, e.Y)))
のあとに、
                If nodeHover Is Nothing Then
                    nodeHover = TreeView1.SelectedNode
                End If
を追加したら、うまくいきました。
ありがとうございました。


red-fish  2003-11-04 03:11:49  No: 109636

> 一度でも動き出すと次からは

という辺りは、おそらく TreeView1_MouseUp に原因があります。
_MouseUpが走るのは、
「マウスボタンがこのControlの上でUpされたよ」ではなくて、
「以前このControlでDownされたマウスボタンが(今やっと)Upされたよ」
です。
DragDropを行った場合、その結果は完了させたか、中断したかの
2つに1つです。Sampleではメンバー変数でNodeを保持している
ので、これを開放する後始末をしたいがため、TreeView1_MouseUp
を入れましたが、本来
成功の場合=DragDropで行う。
中断の場合=MouseUpか或いは他に適切なところがあればそこで。
とすべきですが、SampleゆえMouseUpでまとめて行っています。
ListViewからDragAndDropするなら、TreeView1_MouseUpイベント
は何の働きもしていません。これがメンバー変数の状態に影響を
与えているのです。

すでに解決とのことですが、
基本的に、メンバー変数を使わずにイベント引数から取得するよ
うにすることは望ましい方向だと思います。
ただ、どちらのControlでどのようなイベントが起こるのかには
十分な調査をすることを忘れずに!


※返信する前に利用規約をご確認ください。

※Google reCAPTCHA認証からCloudflare Turnstile認証へ変更しました。






  このエントリーをはてなブックマークに追加