DragDrop-Sample

    • VB.NET

    Es gibt 13 Antworten in diesem Thema. Der letzte Beitrag () ist von ErfinderDesRades.

      DragDrop-Sample

      primitiv-Sample, wie anwendungsübergreifendes DragnDrop umsetzbar ist:

      VB.NET-Quellcode

      1. Imports System.Collections.Specialized
      2. Public Class frmDragDropSample
      3. Private Sub Control_DragDrop(ByVal sender As Object, ByVal e As DragEventArgs) _
      4. Handles TextBox1.DragDrop, ListBox1.DragDrop
      5. Dim data = DirectCast(e.Data, DataObject)
      6. Select Case True
      7. Case sender Is TextBox1
      8. 'zum Testen kann Text aussm VS-Editor auf die Textbox gezogen werden
      9. Dim txt As String = data.GetText
      10. TextBox1.AppendText(txt & Environment.NewLine)
      11. Case sender Is ListBox1
      12. 'zum Testen können Dateien/Ordner aussm DateiBrowser auf die ListBox gezogen werden
      13. Dim files As StringCollection = data.GetFileDropList
      14. For Each file As String In files
      15. ListBox1.Items.Add(file)
      16. Next
      17. End Select
      18. End Sub
      19. Private Sub Control_DragEnter(ByVal sender As Object, ByVal e As DragEventArgs) _
      20. Handles ListBox1.DragEnter, TextBox1.DragEnter
      21. 'erlaubte DropEffects festlegen
      22. Dim data = DirectCast(e.Data, DataObject)
      23. Select Case True
      24. Case sender Is TextBox1
      25. If data.ContainsText Then e.Effect = DragDropEffects.All
      26. Case sender Is ListBox1
      27. If data.ContainsFileDropList Then e.Effect = DragDropEffects.All
      28. End Select
      29. End Sub
      30. End Class

      Vorraussetzung, dass ühaupt auf ein Control gedropt werden kann, ist, dass seine
      Control.AllowDrop - Property auf True eingestellt ist (ist hier im FormDesigner erledigt, geht aber auch per Code)

      Wichtig ist v.a. das Verständnis des DataObjects, das in den EventArgs angeliefert wird.
      Ein DataObject enthält die Daten, zuzüglich Informationen über den Datentyp.
      Es kann "dieselben" Daten in verschiedenen Formaten enthalten (zB sowohl als "Text", als auch als "Richtext") - hier wird aber jeweils nur ein Format abgefragt.
      Ist (im Drag_Enter) das gewünschte Datenformat nicht enthalten, so wird kein DragDropEffect gesetzt, und dann kann auch nicht gedropt werden (was dem User mittels des entsprechenden Cursor-Icon angezeigt wird)

      VB.NET-Quellcode

      1. Dim data = DirectCast(e.Data, DataObject)
      e.Data wird etwas unspezifisch als IDataObject angeliefert - der Cast auf System.Windows.Forms.DataObject erleichtert die Handhabung.

      data.GetFileDropList wird als System.Collections.Specialized.StringCollection angeliefert - das ist eiglich ein veralteter AuflistungsTyp für Strings - heutzutage nähme man natürlich List(Of String). Aber DragnDrop ist halt älter als das Framework2, und wg. Abwärtskompatibilität habenses wohl so belassen.

      weitere Möglichkeiten von DragnDrop
      Im DragEnter kann mit

      VB.NET-Quellcode

      1. Dim modifiers As Keys = Control.ModifierKeys
      der Status der Modifier-Tasten (Shift, Strg, Alt, Alt+Strg) abgefragt werden, und dementsprechend der gemeinte DropEffect festgelegt werden - was dem User per Cursor-Icon signalisiert wird.
      Dieser Effect wird dann auch ins DragDrop-Event transportiert, sodass dort spezifisch reagiert werden kann, etwa "Kopieren" im Unterschied zu "Verschieben", und derlei Dinge.

      Auch kann statt des _DragEnter-Events das _DragOver-Event verarbeitet werden, welches in schneller Folge während des gesamten DragOvers gefeuert wird.
      Dadurch erhält der User Gelegenheit, mittels der Modifier-Tasten, die gemeinte Aktion noch während des Draggens zu ändern.



      Sample-Code-Zips:
      DragDropSample ist das Sample hier zu diesem Post, also einfaches anwendungsübergreifendes Dragging.

      DragjobTester ist ein Sample für Draggen innerhalb der Anwendung. Das ist im Grunde ein wesentlich verschiedenes Thema, auf das ich erst in post#9 eingehe.
      ich häng nun noch DragjobTester an, ein klein Projekt für Draggen innerhalb der Anwendung.
      Besonderer Trick dabei ist, dass von einem DatagridView mit Multiselection gedraggt wird. Die Crux speziell dabei ist, dass das DGV seine Selection bereits beim MouseDown löscht.
      Daher habe ich eine Speicherklasse für diese Selection mir ausgedacht, mit der man sie eben wieder herstellen kann.
      Dateien

      Dieser Beitrag wurde bereits 9 mal editiert, zuletzt von „ErfinderDesRades“ ()

      Hi
      halte ich immernoch für unsauber. Nicht jedes IDataObject ist gleichzeitig ein DataObject. Die Abfrage mit Select Case ist mMn ebenfalls etwas unschön anzusehen. Da wäre es fast besser, die Methoden in Funktionen auszulagern und jeweils einen Wert zurückzugeben, ob der Drag-Drop-Vorgang erfolgreich wäre bzw. erfolgreich war und bei letzterem noch in einem mit ByRef übergebenen Parameter den letztendlich gespeicherten Wert zurückzugeben. Das Auslagern kommt halt aber nur in Frage, wenn man mehrere Controls hat.
      Das wäre z.B. eine Lösung, wie ich sie in etwa programmiert hätte. Der von e.Data.GetData kann meines Wissens nicht Nothing sein (korrigiert mich bzw. den Code, wenn ich falsch liege). Ich habe das deshalb im Code nicht extra behandelt. Außerdem wäre es schön, wenn es ein Ereignis gäbe, das bei Änderung der Modifizierer geworfen wird, das habe ich aber bisher noch nicht entdeckt. Damit könnte man das DragOver umgehen.

      VB.NET-Quellcode

      1. Private Sub ControlInstance_DragDrop(ByVal sender As Object, ByVal e As System.Windows.Forms.DragEventArgs) Handles ControlInstance.DragDrop
      2. 'Effekt bestimmen, der verwendet wird (hier ist nur Link erlaubt)
      3. Dim effect As DragDropEffects = e.AllowedEffect And (DragDropEffects.Link)
      4. If effect <> DragDropEffects.None Then
      5. 'Erlaubte Daten ueberpruefen
      6. If e.Data.GetDataPresent(DataFormats.FileDrop) Then 'Dateiliste wird gedroppt
      7. 'gedroppte Daten ermitteln
      8. Dim data As Object = e.Data.GetData(DataFormats.FileDrop)
      9. 'gedrueckte Mousebuttons
      10. Dim mouseButtons As Keys = CType(e.KeyState And (Keys.LButton Or Keys.RButton Or Keys.XButton1 Or Keys.XButton2 Or Keys.MButton), Keys)
      11. 'gedrueckte Modifizierer
      12. Dim modifiers As Keys = CType(e.KeyState And (Keys.Control Or Keys.Alt Or Keys.Shift), Keys)
      13. 'Typen behandeln
      14. If TypeOf data Is IEnumerable(Of String) Then
      15. 'Iteration durch die in der Auflistung gespeicherten Strings moeglich
      16. 'Spezifische Verarbeitung (hier Ausgabe in der Text-Eigenschaft des senders).
      17. Dim sb As New System.Text.StringBuilder(2048)
      18. For Each file As String In DirectCast(data, IEnumerable(Of String))
      19. sb.AppendLine(file)
      20. Next
      21. DirectCast(sender, Control).Text = sb.ToString()
      22. 'Hier wird dann der Effekt ermittelt, der verwendet werden soll
      23. 'z.B. hier: wird mit der linken Maustaste gedroppt, wird der guenstigste Fall genommen (hier Link)
      24. If mouseButtons = Keys.LButton Then
      25. 'kann gar nichts anderes, als Link sein, wird aber fuer andere Verwendungs-
      26. 'zwecke trotzdem wie nachfolgend behandelt
      27. If (effect And DragDropEffects.Link) = DragDropEffects.Link Then
      28. e.Effect = DragDropEffects.Link
      29. Else
      30. e.Effect = DragDropEffects.None
      31. End If
      32. Else
      33. 'hier koennte man z.B. ein Kontextmenu anzeigen lassen
      34. e.Effect = DragDropEffects.None
      35. End If
      36. ElseIf TypeOf data Is IEnumerable Then
      37. 'gibt an, ob data Strings enthaelt
      38. Dim valid As Boolean = False
      39. 'Iteration durch die in der Auflistung gespeicherten Strings moeglich
      40. 'Spezifische Verarbeitung (hier Ausgabe in der Text-Eigenschaft des senders).
      41. Dim sb As New System.Text.StringBuilder(2048)
      42. For Each entry As Object In DirectCast(data, IEnumerable)
      43. If TypeOf entry Is String Then
      44. sb.AppendLine(DirectCast(entry, String))
      45. valid = True
      46. End If
      47. Next
      48. 'ist die IEnumerable gueltig, die Text-Eigenschaft setzen, ansonsten ist der DragDrop-Vorgang
      49. 'ungueltig
      50. If valid Then
      51. DirectCast(sender, Control).Text = sb.ToString()
      52. 'Hier wird dann der Effekt ermittelt, der verwendet werden soll
      53. 'z.B. hier: wird mit der linken Maustaste gedroppt, wird der guenstigste Fall genommen (hier Link)
      54. If mouseButtons = Keys.LButton Then
      55. 'kann gar nichts anderes, als Link sein, wird aber fuer andere Verwendungs-
      56. 'zwecke trotzdem wie nachfolgend behandelt
      57. If (effect And DragDropEffects.Link) = DragDropEffects.Link Then
      58. e.Effect = DragDropEffects.Link
      59. Else
      60. e.Effect = DragDropEffects.None
      61. End If
      62. Else
      63. 'hier koennte man z.B. ein Kontextmenu anzeigen lassen
      64. e.Effect = DragDropEffects.None
      65. End If
      66. Else
      67. e.Effect = DragDropEffects.None
      68. End If
      69. Else
      70. e.Effect = DragDropEffects.None
      71. 'Typen nicht unterstuetzt, der Else-Block tritt normalerweise nicht ein (Behandlung bereits im
      72. 'DragOver/DragEnter-Event) Ansonsten hier andere Faelle behandeln
      73. End If
      74. Else
      75. 'Alles Andere wird nicht unterstuetzt
      76. e.Effect = DragDropEffects.None
      77. End If
      78. Else
      79. 'Keiner der erlaubten Effekte wird vom DragDrop erfasst
      80. e.Effect = DragDropEffects.None
      81. End If
      82. End Sub
      83. Private Sub ControlInstance_DragEnter(ByVal sender As Object, ByVal e As System.Windows.Forms.DragEventArgs) Handles ControlInstance.DragEnter
      84. 'Effekt bestimmen, der verwendet wird (hier ist nur Link erlaubt)
      85. Dim effect As DragDropEffects = e.AllowedEffect And (DragDropEffects.Link)
      86. If effect <> DragDropEffects.None Then
      87. 'Auf zugelassene Modifizierer ueberpruefen (hier ist nur LButton erlaubt)
      88. 'Die Mausbuttons LButton, RButton, MButton, XButton1, XButton2 und die Modifizierer Shift, Alt, Control
      89. 'lassen sich mit Or kombinieren
      90. If e.KeyState = Keys.LButton Then 'e.KeyState auf alle erlaubten Kombinationen ueberpruefen
      91. 'Erlaubte Daten ueberpruefen
      92. If e.Data.GetDataPresent(DataFormats.FileDrop) Then 'Dateiliste wird gedroppt
      93. Dim data As Object = e.Data.GetData(DataFormats.FileDrop)
      94. 'zugelassene Typen beschraenken
      95. If TypeOf data Is IEnumerable(Of String) Then
      96. e.Effect = effect
      97. ElseIf TypeOf data Is IEnumerable Then
      98. 'wenn die Auflistung mindestens einen String enthaelt, ist sie gueltig
      99. For Each entry As Object In DirectCast(data, IEnumerable)
      100. If TypeOf entry Is String Then
      101. e.Effect = effect
      102. Exit Sub
      103. End If
      104. Next
      105. Else
      106. e.Effect = DragDropEffects.None
      107. End If
      108. 'hier andere Faelle behandeln
      109. Else
      110. 'Alles Andere wird nicht unterstuetzt
      111. e.Effect = DragDropEffects.None
      112. End If
      113. End If
      114. Else
      115. e.Effect = DragDropEffects.None
      116. End If
      117. End Sub
      118. 'DragEnter und DragOver behandeln (DragOver kann bei Gleichgueltigkeit der Modifizierer weggelassen werden)
      119. Private Sub ControlInstance_DragOver(ByVal sender As Object, ByVal e As System.Windows.Forms.DragEventArgs) Handles ControlInstance.DragOver
      120. 'Effekt bestimmen, der verwendet wird (hier ist nur Link erlaubt)
      121. Dim effect As DragDropEffects = e.AllowedEffect And (DragDropEffects.Link)
      122. If effect <> DragDropEffects.None Then
      123. 'Auf zugelassene Modifizierer ueberpruefen (hier ist nur LButton erlaubt)
      124. 'Die Mausbuttons LButton, RButton, MButton, XButton1, XButton2 und die Modifizierer Shift, Alt, Control
      125. 'lassen sich mit Or kombinieren
      126. If e.KeyState = Keys.LButton Then 'e.KeyState auf alle erlaubten Kombinationen ueberpruefen
      127. 'Erlaubte Daten ueberpruefen
      128. If e.Data.GetDataPresent(DataFormats.FileDrop) Then 'Dateiliste wird gedroppt
      129. e.Effect = effect
      130. Else
      131. 'Alles Andere wird nicht unterstuetzt
      132. e.Effect = DragDropEffects.None
      133. End If
      134. End If
      135. Else
      136. e.Effect = DragDropEffects.None
      137. End If
      138. End Sub


      Gruß
      ~blaze~

      Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von „~blaze~“ ()

      Du kannst aber auch eigene Klasse von IDataObject ableiten und die OnDragDrop-Prozeduren etc. mit entsprechenden EventArgs füttern. Da man Tutorials und Tipps möglichst allgemeingültig halten sollte, würde ich daher dazu tendieren, das so handzuhaben - zumindest würde ich das immer so machen. Bei meinen Verbesserungsvorschlägen handelt es sich nur um Vorschläge. Ob sie angenommen werden oder nicht ist dem Leser überlassen.

      Gruß
      ~blaze~
      Du kannst aber auch eigene Klasse von IDataObject ableiten
      Du meinst: Implementieren.
      Das ist immerhin eine theoretische Möglichkeit, und wollte ich auch gleich ausprobieren:

      VB.NET-Quellcode

      1. Public Class MyDataObject : Implements IDataObject
      2. Public Function GetData(ByVal format As String) As Object Implements System.Windows.Forms.IDataObject.GetData
      3. End Function
      4. Public Function GetData(ByVal format As String, ByVal autoConvert As Boolean) As Object Implements System.Windows.Forms.IDataObject.GetData
      5. End Function
      6. Public Function GetData(ByVal format As System.Type) As Object Implements System.Windows.Forms.IDataObject.GetData
      7. End Function
      8. Public Function GetDataPresent(ByVal format As String) As Boolean Implements System.Windows.Forms.IDataObject.GetDataPresent
      9. End Function
      10. Public Function GetDataPresent(ByVal format As String, ByVal autoConvert As Boolean) As Boolean Implements System.Windows.Forms.IDataObject.GetDataPresent
      11. End Function
      12. Public Function GetDataPresent(ByVal format As System.Type) As Boolean Implements System.Windows.Forms.IDataObject.GetDataPresent
      13. End Function
      14. Public Function GetFormats() As String() Implements System.Windows.Forms.IDataObject.GetFormats
      15. End Function
      16. Public Function GetFormats(ByVal autoConvert As Boolean) As String() Implements System.Windows.Forms.IDataObject.GetFormats
      17. End Function
      18. Public Sub SetData(ByVal data As Object) Implements System.Windows.Forms.IDataObject.SetData
      19. End Sub
      20. Public Sub SetData(ByVal format As String, ByVal autoConvert As Boolean, ByVal data As Object) Implements System.Windows.Forms.IDataObject.SetData
      21. End Sub
      22. Public Sub SetData(ByVal format As String, ByVal data As Object) Implements System.Windows.Forms.IDataObject.SetData
      23. End Sub
      24. Public Sub SetData(ByVal format As System.Type, ByVal data As Object) Implements System.Windows.Forms.IDataObject.SetData
      25. End Sub
      26. End Class
      Allerdings sehe ich mich außerstande, all diese von IDataObject geforderten Methoden-Stubs sinnvoll auszufüllen.
      In .Net würde auch niemand so programmieren, sondern stattdessen vom Forms.DataObject erben (und damit das ganze Gedöhns), statt es selbst zu implementieren.

      Ich empfehle dennoch den Cast aufs Window.Forms.DataObject, wegen seiner einfacheren Bedienung der Standard-Fälle.
      Will man hingegen auch mit ausgefallenen Anwendungen kommunizieren, die tatsächlich abweichende IDataObjekte verschicken, so muß man sich halt auf die Bedienung dieser Schnittstelle verlegen.
      Aber ich modifiziere mal, indem ich einen TryCast verwende, der solche "Exoten" einfach als Drag-Quelle ausschließt:

      VB.NET-Quellcode

      1. 'DropEffect festlegen - da hier eh nur eine Aktion pro Control stattfindet, ist der
      2. 'gewählte DropEffect schnurz - Hauptsache, er ist nicht DropEffects.None
      3. Private Sub Control_DragOver(ByVal sender As Object, ByVal e As DragEventArgs) _
      4. Handles ListBox1.DragOver, TextBox1.DragOver
      5. Dim data = TryCast(e.Data, DataObject)
      6. If data Is Nothing Then
      7. Return ' DragQuelle ablehnen, weils DataObject nicht verarbeitet werden kann
      8. End If
      9. Select Case True
      10. Case sender Is TextBox1
      11. If data.ContainsText Then e.Effect = DragDropEffects.All
      12. Case sender Is ListBox1
      13. If data.ContainsFileDropList Then e.Effect = DragDropEffects.All
      14. End Select
      15. End Sub

      Die Abfrage mit Select Case ist mMn ebenfalls etwas unschön anzusehen.
      Also ich finde die besonders schön :). Dadurch wird gleiches in einer Methode zusammengefasst, und unterschiedliches unterschieden (Select Case halt).
      Der unterschiedliche Code beschränkt sich ja jeweils auf eine (maximal 3) Zeilen - dafür braucht man IMO nicht eine Extra-Methode in der Klasse rumfahren zu haben.
      Wie immer bei vielen Fallunterscheidungen wird Code unübersichtlich (deiner ist kein schlechtes Beispiel dafür ;)) - also dieses Zusammenfassen von Gleichem ist tatsächlich recht begrenzt, und sollte ab einer gewissen Komplexität aufgelöst werden in verschiedene Methoden.

      Aber zu deim Code habich auch Anmerkungen (plusse und minusse):
      • zunächstmal liegt er mehr auf der sicheren Seite. Wegen der Sache mit den exotischen DataObjekten, und auch, dass du überprüfst, wenn "FileDropList" angegeben ist, dass dann auch ein IEnumerable(Of String) drinne ist - je länger ich darüber hirne, desto besser gefällt mir das - ist ja auch nicht soo viel aufwändiger ;)
      • Du testest e.KeyState, wo ich Control.ModifierKeys bevorzuge. Letztere bevorzuge ich, weil sie als Keys typisiert sind, und Intellisense mir dann gleich die richtigen Optionen anzeigt. e.KeyState hingegen hat den Vorteil, dass auch die MouseButtons drin enthalten sind.
        (Ich finde allerdings, man sollte der Konvention folgen, dass Draggen mit die linke Maustaste gehört, und die rechte für KontextMenüs ist. DropEffects sollten per ModifierTasten bestimmt werden, nicht über die Maus)
      • Das explizite Setzen von DropEffect.None ist nicht nötig - .None ist default
      • Das _DragEnter braucht ühaupt nicht berücksichtigt werden, wenn man das _DragOver verarbeitet.
      • Im DragDrop braucht nicht auf DropEffect.None geprüft werden, denn wenn im _DragOver nichts gesetzt ist (->DropEffect.None), kann es zu keinem Drop-Event kommen.
      • Auch braucht im _DragDrop nicht erneut der Effect geprüft werden (in diesem Fall), und ganz allgemein sind dort keine Modifiers zu testen, sondern es ist einfach die Aktion umzusetzen, die der User durch den zuvor gesetzten DropEffekt spezifiziert hat.
      • Ich stauche mal dein Code zusammen, ohne iwas vonne Funktionalität zu ändern (die höhere Sicherheit erkenne ich ja an)

      VB.NET-Quellcode

      1. Private Sub ControlInstance_DragDrop(ByVal sender As Object, ByVal e As DragEventArgs) Handles ControlInstance.DragDrop
      2. 'Spezifische Verarbeitung (hier Ausgabe in der Text-Eigenschaft des senders.
      3. 'keinerlei Absicherung, denn Absicherung ist Sache des DragOver-Events
      4. Dim files = e.Data.GetData(DataFormats.FileDrop)
      5. Dim sb As New System.Text.StringBuilder(2048)
      6. For Each file As String In DirectCast(files, IEnumerable(Of String))
      7. sb.AppendLine(file)
      8. Next
      9. DirectCast(sender, Control).Text = sb.ToString()
      10. End Sub
      11. Private Sub ControlInstance_DragOver(ByVal sender As Object, ByVal e As DragEventArgs) Handles ControlInstance.DragOver
      12. 'Effekt bestimmen, der verwendet wird (hier ist nur Link erlaubt)
      13. If Not e.AllowedEffect.HasFlag(DragDropEffects.Link) Then Return
      14. 'Auf zugelassene Modifizierer ueberpruefen (hier ist nur LButton erlaubt)
      15. 'Die Mausbuttons LButton, RButton, MButton, XButton1, XButton2 und die Modifizierer Shift, Alt, Control
      16. 'lassen sich mit Or kombinieren
      17. If e.KeyState <> Keys.LButton Then Return 'e.KeyState auf alle erlaubten Kombinationen ueberpruefen
      18. 'gewünschtes DatenFormat enthalten?
      19. If Not e.Data.GetDataPresent(DataFormats.FileDrop) Then Return
      20. 'liegen die Daten in verarbeitbarer Form vor?
      21. Dim files = e.Data.GetData(DataFormats.FileDrop)
      22. If TypeOf files Is IEnumerable(Of String) Then e.Effect = DragDropEffects.Link
      23. End Sub

      Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von „ErfinderDesRades“ ()

      Ich denke mal, in den meisten Fällen dürfte dein jetziger Code richtig funktionieren. Im Prinzip ist eigentlich noch eine kleine Unschönheit mit drin. e.Effect müsste mit Or verknüpft werden, da es ja mehrere EventHandler geben kann. Das hatte ich oben vergessen. Ansonsten ist es eine reine Vorsichtsmaßnahme für Programmierer, die OnDragDrop mit einem EventArgs verwenden, das bereits einen Effect eingetragen hat ;). Naja, man kanns einfach weglassen. Das Kontextmenü muss man eh manuell einblenden, oder? Im Kontextmenü kann man dann ja alle zugelassenen Effekte auflisten und den Benutzer auswählen lassen. Dazu wäre die Verwendung eines neuen Threads vmtl. am besten. Den Thread, auf dem der Drag-Drop-Vorgang stattgefunden hat, kann man ja per Anwendungskontext zur Nachrichtenverarbeitung übergehen lassen, bis das ContextMenuStrip geschlossen wird.
      Ich machs meistens doppelt genau, wenns um so Sachen geht, wie MouseMove, DragOver und MouseEnter und DragEnter. Da hast du aber eigentlich schon recht. Das werd' ich zukünftig auch so handhaben. Im DragOver- & DragEnter-Handler hatte ich ursprünglich e.Effect auf e.AllowedEffect And (DragDropEffects.Copy Or DragDropEffects.Move) gesetzt, was aber aufgrund der letztendlichen Implementierung von DragDrop keinen Sinn mehr gemacht hat. Bei DragDrop wurde dann noch entschieden, welcher der beiden Effekte letztendlich angewendet wird. Daher macht's schon Sinn, dass ich den Effekt noch mal extra behandle und sicher ist sicher. Wenn die Maus nicht mehr bewegt wird, sich die Modifizierer aber noch mal ändern, ändert sich der Effekt halt nicht.
      Ansonsten kanns unter Umständen zu Fehlern kommen, wenn eine DragOver-Message ankommt, die Form aber gerade dabei ist, eine Message zu verarbeiten, die etwas mehr Zeit benötigt. Dadurch ändert sich aber auch der Wert der ModifierKeys-Eigenschaft und die Benutzung der e.KeyState-Eigenschaft wäre fast besser.

      Wenn man von DataObject erbt, sind halt eventuell ungenutzte Felder drin.

      Gruß
      ~blaze~
      Ich habe den Code noch mal überarbeitet. ErfinderDesRades hat mich darauf aufmerksam gemacht, dass die StringCollection das IEnumerable(Of String)-Interface gar nicht implementiert. Ich habe das jetzt einfach mal über eine Überprüfung auf IEnumerable gelöst, deren Inhalt auf Strings überprüft wird. Wenn mindestens ein String enthalten ist, wird sie als gültig angesehen, ansonsten nicht. Aus Performancegründen habe ich außerdem DragEnter und DragOver getrennt. DragEnter überprüft ab sofort auf die Gültigkeit der Daten, während in DragOver nur noch die Modifizierer behandelt werden. Das bedeutet, man muss ab sofort DragEnter vor DragOver aufrufen, wenn man selber Gebrauch von den OnDragEnter-, OnDragOver- oder OnDragDrop-Methoden macht.
      Ich gehe übrigens immer davon aus, dass der Drag-Drop-Mechanismus der Quellanwendung korrekt implementiert wird und den Normen folgt. Wenn also jmd. auf die Idee kommt URIs zu Draggen, funktioniert das nicht, weil URIs eine andere Syntax haben, als Dateinamen. Daher wird auch nicht auf die Gültigkeit der Dateinamen eingegangen (das darf dürft dann ihr implementieren, wenn ihr den Code verwendet und das sicherstellen wollt).

      Gruß
      ~blaze~

      Fortsetzung: Drag-Quelle implementieren

      Bisher wurde ja nur gezeigt, wie Listbox und Textbox einzurichten sind, will man aus beliebiger Anwendung heraus was drauf droppen.
      Jetzt aber wird die Listbox zusätzlich als Quelle von D&D eingerichtet.

      An Funktionalität verlangt dieses:
      • Der Drag-Vorgang ist zu starten, wenn mit gedrückter Maustaste ein kleines Stück gezogen wurde. Das ist wichtig, damit nicht bei jedem MouseDown ein D&D-Vorgang startet, denn MousDown ereignet sich ja auch bei Klick und Doppelklick, wenn also D&D garnet gemeint ist.
      • Nach Abschluß des Draggens kann (muß nicht) eine Auswertung des zurückgegebenen DropEffekts erfolgen, zB. bei DropEffekt.Move kann das Quell-Item aus der Quell-Auflistung gelöscht werden, im Unterschied zu DropEffekt.Copy, bei dem das Quell-Item nur kopiert wird, und also in der Quelle verbleibt.

      Der Code:

      VB.NET-Quellcode

      1. Public Class frmDragDropSample
      2. Private _GrabOffset As Size
      3. Private Const _Tolerance As Double = 5 ^ 2
      4. #Region "receive Drop"
      5. Private Sub Control_DragOver(ByVal sender As Object, ByVal e As DragEventArgs) _
      6. Handles ListBox1.DragOver, TextBox1.DragOver
      7. 'erlaubte DropEffects festlegen
      8. Dim eff = If(Control.ModifierKeys.HasFlag(Keys.Control), DragDropEffects.Copy, DragDropEffects.Move)
      9. Select Case True
      10. Case sender Is ListBox1
      11. If TypeOf e.Data.GetData(DataFormats.FileDrop) Is IEnumerable(Of String) Then e.Effect = e.AllowedEffect And eff
      12. Case sender Is TextBox1
      13. If TypeOf e.Data.GetData(DataFormats.Text) Is String Then e.Effect = e.AllowedEffect And eff
      14. End Select
      15. End Sub
      16. Private Sub Control_DragDrop(ByVal sender As Object, ByVal e As DragEventArgs) _
      17. Handles TextBox1.DragDrop, ListBox1.DragDrop
      18. 'auf Datenempfang reagieren
      19. Select Case True
      20. Case sender Is TextBox1
      21. 'zum Testen kann Text aussm VS-Editor auf die Textbox gezogen werden
      22. Dim txt As String = DirectCast(e.Data.GetData(DataFormats.Text), String)
      23. TextBox1.AppendText(txt & Environment.NewLine)
      24. Case sender Is ListBox1
      25. 'zum Testen können Dateien/Ordner aussm DateiBrowser auf die ListBox gezogen werden
      26. For Each fl As String In DirectCast(e.Data.GetData(DataFormats.FileDrop), IEnumerable(Of String))
      27. ListBox1.Items.Add(fl)
      28. Next
      29. End Select
      30. lbLastAction.Text = String.Concat("drop item(s) on ", sender.GetType.Name, ", with DropEffect.", e.Effect)
      31. End Sub
      32. #End Region 'receive Drop
      33. #Region "send DragDrop"
      34. Private Sub ListBox1_MouseDown(ByVal sender As Object, ByVal e As MouseEventArgs) Handles ListBox1.MouseDown
      35. If e.Button = MouseButtons.Left Then _GrabOffset = New Size(e.Location)
      36. End Sub
      37. Private Sub ListBox1_MouseMove(ByVal sender As Object, ByVal e As MouseEventArgs) Handles ListBox1.MouseMove
      38. If Not e.Button.HasFlag(MouseButtons.Left) Then Return
      39. With e.Location - _GrabOffset
      40. If .X ^ 2 + .Y ^ 2 > _Tolerance Then
      41. PerformDragFileItem(Point.Empty + _GrabOffset)
      42. End If
      43. End With
      44. End Sub
      45. Private Sub PerformDragFileItem(ByVal pt As Point)
      46. Dim indx = ListBox1.IndexFromPoint(pt)
      47. If indx < 0 Then Return
      48. Dim file = DirectCast(ListBox1.Items(indx), String)
      49. Dim dto = New DataObject(DataFormats.FileDrop, {file}) 'DataObject mit FileDropList
      50. dto.SetData(DataFormats.StringFormat, file) 'Daten zusätzlich außerdem als Text einpacken
      51. Dim allowed = DragDropEffects.Copy Or DragDropEffects.Move
      52. If ListBox1.DoDragDrop(dto, allowed) = DragDropEffects.Move Then
      53. ListBox1.Items.RemoveAt(indx)
      54. End If
      55. End Sub
      56. #End Region 'send DragDrop
      57. End Class
      Die Receive-Drop-Region ist im wesentlichen bekannt - wurde aber erweitert, um verschiedene DropEffects festlegen zu können - zeile #10:

      VB.NET-Quellcode

      1. Dim eff = If(Control.ModifierKeys.HasFlag(Keys.Control), DragDropEffects.Copy, DragDropEffects.Move)
      In eff wird ein DropEffekt erstellt, entweder DragDropEffects.Copy oder .Move, je nachdem, ob die Control-Taste (Keys.Control) gedrückt ist.

      VB.NET-Quellcode

      1. Select Case True
      2. Case sender Is ListBox1
      3. If TypeOf e.Data.GetData(DataFormats.FileDrop) Is IEnumerable(Of String) Then e.Effect = e.AllowedEffect And eff
      Bei der Listbox wird nun nicht mehr auf Vorhandensein des FileDrop-Datenformats getestet, sondern die Daten werden tatsächlich abgerufen, und getestet, ob sie einen für die Verarbeitung tauglichen Datentypen aufweisen. Ich denke, sicherer kann mans nicht machen - die externe Quell-Anwendung kann jetzt die dollsten Kapriolen implementiert haben - ein DropEffekt wird nur dann gesetzt, wenn die Daten auch wirklich verwendbar sind.
      Und gesetzt wird vorgenanntes eff, also entweder .Copy oder .Move, und wird zusätzlich mit dem durch die Eventargs vorgegebenen AllowedEffects ver-undet (e.Effect = e.AllowedEffect And eff), sodaß DragDropEffect.None gesetzt wird, falls zb. eff .Move aufweist, .Move aber garnet zu den erlaubten Effekten gehört.

      Die DragDrop-Methode ist noch weniger modifiziert - als DropEffekt-spezifische Reaktion habe ich einfach zugefügt, dass der angekommene DropEffekt im StatusLabel notiert wird (lbLastAction - zeile#36).

      Nun aber die DragDrop-Quell-Region. Betrachte nochmal den ersten Punkt der erforderlichen Funktionalität: Draggen erst, wenn mit gehaltener Maustaste ein kleines Stück gezogen wurde. Das "Kleine Stück" habe ich als _Tolerance eingeführt, und ist das Quadrat der Distanz zwischen _GrabOffset und e.Location. So kann man mit Pythagoras sehr effizient testen, wann genau ein Drag-Vorgang gemeint ist.

      VB.NET-Quellcode

      1. Private Sub ListBox1_MouseDown(ByVal sender As Object, ByVal e As MouseEventArgs) Handles ListBox1.MouseDown
      2. If e.Button = MouseButtons.Left Then _GrabOffset = New Size(e.Location)
      3. End Sub
      4. Private Sub ListBox1_MouseMove(ByVal sender As Object, ByVal e As MouseEventArgs) Handles ListBox1.MouseMove
      5. If Not e.Button.HasFlag(MouseButtons.Left) Then Return
      6. With e.Location - _GrabOffset
      7. If .X ^ 2 + .Y ^ 2 > _Tolerance Then
      8. PerformDragFileItem(Point.Empty + _GrabOffset)
      9. End If
      10. End With
      11. End Sub
      Beachte, dass _GrabOffset praktischerweise als Size deklariert ist, nicht als Point, denn eine Size kann man von einem Point direkt subtrahieren, um den Positions-Unterschied von e.Location und _GrabOffset zu erhalten (zeile #6).

      Jo, und dann wird PerformDragFileItem() aufgerufen:

      VB.NET-Quellcode

      1. Private Sub PerformDragFileItem(ByVal pt As Point)
      2. Dim indx = ListBox1.IndexFromPoint(pt)
      3. If indx < 0 Then Return
      4. Dim file = DirectCast(ListBox1.Items(indx), String)
      5. Dim dto = New DataObject(DataFormats.FileDrop, {file}) 'DataObject mit FileDropList
      6. dto.SetData(DataFormats.StringFormat, file) 'Daten zusätzlich außerdem als Text einpacken
      7. Dim allowed = DragDropEffects.Copy Or DragDropEffects.Move
      8. If ListBox1.DoDragDrop(dto, allowed) = DragDropEffects.Move Then
      9. ListBox1.Items.RemoveAt(indx)
      10. End If
      11. End Sub
      Es wird der Index des Items unter dem Point ermittelt, und abgebrochen, wenn da nix drunner ist.
      Dann wird ein DataObject erstellt, mit zweifacher Befüllung: Einmal als FileDrop-Liste, und einmal als Text. Dann wird als AllowedEffect .Copy und .Move angegeben, und dann der D&D-Vorgang mit Listbox1.DoDragDrop() abgefahren.
      Dabei wird aber der Rückgabewert ausgewertet, und falls der D&D-Vorgang sich als .Move-Vorgang erweist, wird aus der Listbox das QuellItem entfernt (zeile #9).

      Jo, ihr könnt son ListboxItem auch auf euern Explorer draggen - meiner jdfs. nimmt diese Aktion an, und setzt sie um.
      D.h.: Wenn ich aus dieser Listbox eine Datei oder einen Ordner mit .Move auf den Explorer droppe, dann führt letzterer tatsächlich die .Move-Operation aus, also er verschiebt die Datei (bzw. den Ordner).
      Bisserl Vorsicht also! ;)

      Aber ebensogut könnt ihr das ListboxItem auch auf die Textbox plumpsen lassen, die wird es auch akzeptieren - das DataObject ist ja mit beiderlei Formaten befüllt.
      Dateien

      Dieser Beitrag wurde bereits 3 mal editiert, zuletzt von „ErfinderDesRades“ ()

      Draggen innerhalb der Anwendung (DiA)

      Draggen innerhalb der Anwendung ist eine sehr andere Baustelle. Genau genommen ists ein FehlDesign, dass DiA mit derselben DoDragDrop()-Methode gestartet werden muss wie anwendungsübergreifendes Dragging.
      Diese Methode erzwingt die Angabe eines DataObjekts, was ganzngar unnötig ist, und jedermann denken macht (und programmieren), dass beim DiA Daten von einem Ort zum anderen zu transportieren seien.
      Dem ist nämlich nicht so.
      Da beim DiA so gut wie immer sowohl Start- als auch Ziel-Control bekannt sind erübrigt es sich, im StartControl die Daten ins DataObjekt zu stopfen und im ZielControl wieder auszupuhlen, mit all den Fußangeln, die das mit sich bringt.
      Stattdessen sollte man den Drag-Vorgang auffassen als eine Mausgeste, die von einem Punkt über dem StartControl zu einem Punkt über dem ZielControl führt, zuzüglich eines DropEffects, der aussagt, ob kopieren, verschieben, canceln etc.. Aber mehr ist es nicht: nur eine Geste - kein Daten-Transport.
      Nach Abschluss dieser Geste kann man die Daten dann aus dem StartControl holen, und im ZielControl einpflegen, an der richtigen Ziel-Maus-Position.
      Also nix mit DataObject und rein und raus da.

      Auch das DragDrop-Event ist beim DiA ein überflüssiger Kropf.
      Wesentlich gradlieniger ist, wenn das Draggen genau dort zum Abschluss gebracht wird, wo es auch gestartet wurde, und zwar durch direkten Aufruf der Methode, die die Bedeutung der Geste umsetzt (ich nenne sie "PerformDropped").
      Dazu braucht es kein Extra-Event.

      Also ich fasse zusammen:
      • Man braucht das StartControl.MouseDown-Event, um den Startpunkt zu identifizieren.
      • Dragging wird nicht im MouseDown gestartet, sondern erst, wenn mit gehaltener Maustaste ein kleines Stück gezogen wurde.
      • Im StartControl.MouseMove detekted und startet man den DragVorgang.
      • Da die DoDragDrop-Methode so lange wartet, bis das Dragging beendet ist, kann gleich anschließend - also noch im MouseMove! - die PerformDropped-Methode aufgerufen werden.
      • Im ZielControl.DragOver-Event wird während des Draggens der DragDropEffect festgelegt, also ob die Daten kopiert werden sollen, verschoben, oder noch was anderes.
      • Ein besonders kniffliges Problem dabei ist, die Ziel-Zone zu highlighten, denn wenn der User zB nicht genau sehen kann, zwischen welche beiden ListboxItems seine Daten eingefügt werden, dann ist ein gut Teil der Intuitivität des Draggens wieder beim Deibel.

      Hier mal die Grund-Struktur, allerdings mit suboptimal gelöstem Highlighting der Ziel-Zone:
      Aber zuvor noch ein paar kleine Helperlein

      VB.NET-Quellcode

      1. Imports System.Runtime.CompilerServices, System.Diagnostics, System.Drawing
      2. <System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)> _
      3. Public Module Extensions
      4. ''' <summary> SquareDistance is much faster calculated than the real distance </summary>
      5. ''' <exception cref="System.OverflowException"> SqareDistance exceeds Int32.MaxValue </exception>
      6. <DebuggerStepThrough(), Extension()> _
      7. Public Function SquareDistance(ByVal pt0 As Point, ByVal pt1 As Point) As Integer
      8. With New Point(pt1.X - pt0.X, pt1.Y - pt0.Y)
      9. Return .X * .X + .Y * .Y
      10. End With
      11. End Function
      12. ''' <summary> supports a more compact notation of small ForEach-loops </summary>
      13. <DebuggerStepThrough(), Extension()> _
      14. Public Sub ForEach(Of T)(ByVal items As IEnumerable(Of T), ByVal Action As Action(Of T))
      15. For Each itm In items : Action(itm) : Next
      16. End Sub
      17. ''' <summary> more compact notation for IsNothing-Tests </summary>
      18. <DebuggerStepThrough(), Extension()> _
      19. Public Function Null(Of T As Class)(ByVal Subj As T) As Boolean
      20. Return Subj Is Nothing
      21. End Function
      22. ''' <summary> more compact notation for IsNot-Nothing-Tests </summary>
      23. <DebuggerStepThrough(), Extension()> _
      24. Public Function NotNull(Of T As Class)(ByVal Subj As T) As Boolean
      25. Return Subj IsNot Nothing
      26. End Function
      27. Private _Counter As Integer
      28. ''' <summary> eases doing Debug-Output </summary>
      29. Public Sub Dbg(ByVal ParamArray msgSegments() As Object)
      30. Console.WriteLine(_Counter & " " & String.Join(" ", msgSegments))
      31. _Counter = (_Counter + 1) Mod 10
      32. End Sub
      33. End Module

      VB.NET-Quellcode

      1. Private Shared _DragStart As Point, _DummiData As New DataObject
      2. Public Sub New()
      3. InitializeComponent()
      4. PrepareData()
      5. End Sub
      6. Private Sub PrepareData()
      7. '2 Dgv-Spalten und viele Treeview-Nodes sind bereits im Designer angelegt
      8. Enumerable.Range(0, 3).Select(Function(i) {"Column1." & i, "Column2." & i}).ForEach(AddressOf DataGridView1.Rows.Add)
      9. TreeView1.ExpandAll()
      10. TreeView1.Sorted = True
      11. TreeView1.HideSelection = False
      12. End Sub
      13. Private Sub DataGridView1_MouseDown(ByVal sender As Object, ByVal e As MouseEventArgs) Handles DataGridView1.MouseDown
      14. If e.Button <> Windows.Forms.MouseButtons.Left Then Return
      15. _DragStart = e.Location
      16. End Sub
      17. ''' <summary>
      18. ''' Bei ziehen über 3 pix Draggen starten und mit dem Ergebnis PerformDragDrop aufrufen
      19. ''' </summary>
      20. Private Sub DataGridView1_MouseMove(ByVal sender As Object, ByVal e As MouseEventArgs) Handles DataGridView1.MouseMove
      21. If e.Button <> Windows.Forms.MouseButtons.Left Then Return
      22. With e.Location ' draggen erst bei Ziehen über 3 Pix ( Quadrat-Distanz > 9 )
      23. If (_DragStart.X - .X) ^ 2 + (_DragStart.Y - .Y) ^ 2 <= 9 Then Return
      24. End With
      25. Dim result = DataGridView1.DoDragDrop(_DummiData, DragDropEffects.Copy Or DragDropEffects.Link Or DragDropEffects.Move Or DragDropEffects.Scroll)
      26. If result <> DragDropEffects.None Then PerformTreeviewDrop(result)
      27. End Sub
      28. ''' <summary> DropEffect wählen von: .None, .Move, .Link, ggfs. Ziel-Node selecten</summary>
      29. Private Sub TreeView1_DragOver(ByVal sender As Object, ByVal e As DragEventArgs) Handles TreeView1.DragOver
      30. Select Case Control.ModifierKeys
      31. Case Keys.None
      32. e.Effect = DragDropEffects.Move
      33. Case Keys.Shift
      34. e.Effect = DragDropEffects.Link
      35. End Select
      36. Dim nd = TreeView1.GetNodeAt(TreeView1.PointToClient(Control.MousePosition))
      37. TreeView1.SelectedNode = nd
      38. End Sub
      39. ''' <summary> DgvCell-Text in Treeview hängen - ggfs. DgvCell-Text löschen </summary>
      40. Private Sub PerformTreeviewDrop(ByVal result As DragDropEffects)
      41. Dim nd = TreeView1.GetNodeAt(TreeView1.PointToClient(Control.MousePosition))
      42. Dim nodes = If(nd.Null, TreeView1.Nodes, nd.Nodes)
      43. Dim cell = DataGridView1.SelectedCells(0)
      44. Select Case result
      45. Case DragDropEffects.Copy
      46. nodes.Add(cell.Value.ToString)
      47. Case DragDropEffects.Move
      48. nodes.Add(cell.Value.ToString)
      49. cell.Value = ""
      50. End Select
      51. If nd.NotNull Then nd.Expand()
      52. End Sub
      Dieses Sample draggt die selektierte Dgv-Zelle in einen Treenode.
      Vlt irre ich mich, aber mir ist, als sei gar nichts zu erläutern - steht ja alles da.

      Ah - hinzuweisen ist darauf, dass Controls es ganz unterschiedlich umsetzen, wenns drum geht, das grade unter der Maus befindliche Item zu identifizieren:
      Beim Treeview ists die .GetNodeAt()-Methode und beim ListView heißts .GetItemAt(). Hingegen Listbox.IndexFromPoint() liefert gar kein Item, sondern nur den Index, und DatagridView.HitTest() liefert gleich ein Bündel an Informationen zur Auswertung an.

      Dieser Beitrag wurde bereits 12 mal editiert, zuletzt von „ErfinderDesRades“ ()

      Ja, obiges hübsches Sample hat den gravierenden Nachteil, dass der Ziel-Bereich nur ungenügend highlighted wird: Soll nämlich auf oberster Treeview-Ebene eingehängt werden, so kann kein Ziel-Node selectiert werden.
      Ausserdem löst das Verstellen der Selection natürlich das Treeview.AfterSelect-Event aus, an welches in anspruchsvollen Oberflächen meist weitere umfangreiche Funktionalität angehängt ist.
      Das wird ganz schön rumklappern, wenn der User über verschiedene Nodes fährt.

      Daher muss ein ownerdrawn Highlighting her, welches einfach ein Rechteck auf dem Bildschirm hervorheben kann, ohne in irgendeiner Weise sonst in die Anwendungslogik einzugreifen.
      Ich habe das mit einigen Extensions gelöst, und wo ich schon dabei war, habe ich das StartControl_MouseMove gleich mit hineingenommen, denn das ist auch recht komplex, dabei immer dasselbe - somit klarer Kandidat fürs Auslagern wiederverwendbaren Codes.
      Der entspechende Ausschnitt aus Module DraggingX:

      VB.NET-Quellcode

      1. <System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)> _
      2. Public Module DraggingX
      3. ' Das Highlighten erfolgt durch Farb-Inversion des Target-Rectangles. Ausschalten
      4. ' des Highlights durch wiederholte Inversion desselben Rectangles. Fehl-Highlighting
      5. ' ergibt sich also, wenn zwischen Highlight() und Off() das Rectangle anderweitig
      6. ' übermalt wurde.
      7. Private _HighlightedRect As Rectangle = Rectangle.Empty
      8. ' Farb-Inversion ist eine Art "Spiegelung" über eine Farb-Achse.
      9. ' Dieses komische Rot ergibt (bisher) immer deutliche "Spiegel-Farben"
      10. Private ReadOnly _AxisColor As Color = Color.FromArgb(255, 0, 127)
      11. Private _DragEventNone As New DragEventArgs(Nothing, Nothing, Nothing, Nothing, Nothing, Nothing)
      12. Private _DummiData As New DataObject, _AllAllowed As DragDropEffects = DragDropEffects.Copy Or DragDropEffects.Link Or DragDropEffects.Move Or DragDropEffects.Scroll
      13. Private _Target As Control, _Callback As Action(Of DragDropEffects), _DragStart As Point, _DragMouseButton As MouseButtons
      14. Private WithEvents _Origin As Control
      15. <DebuggerStepThrough(), Extension()> _
      16. Public Function DragOrigin(ByVal target As Control) As Control
      17. Return _Origin
      18. End Function
      19. <DebuggerStepThrough(), Extension()> _
      20. Public Function DragTarget(ByVal origin As Control) As Control
      21. Return _Target
      22. End Function
      23. <Extension()> _
      24. Public Sub ListenForDragging(ByVal origin As Control, ByVal callback As Action(Of DragDropEffects))
      25. _Origin = origin
      26. _DragMouseButton = Control.MouseButtons
      27. _Callback = callback
      28. _DragStart = _Origin.PointToClient(Control.MousePosition)
      29. End Sub
      30. Private Sub Origin_MouseMove(ByVal sender As Object, ByVal e As MouseEventArgs) Handles _Origin.MouseMove
      31. If e.Button <> _DragMouseButton Then Return
      32. If e.Location.SquareDistance(_DragStart) > 9 Then ' erst draggen, wenn 3 pix mit gehaltener Maus gezogen wurde
      33. Dim result = _Origin.DoDragDrop(_DummiData, _AllAllowed) ' DoDragDrop kehrt erst nach Ende des Drag-Vorgangs zurück
      34. If result <> DragDropEffects.None Then
      35. _Callback(result) ' ggfs den callback rufen
      36. _Target.Highlight(Nothing) ' HighLight löschen
      37. End If
      38. _Origin = Nothing
      39. End If
      40. End Sub
      41. '...
      In diesem Modul werden viele Variablen gehalten - insbesondere _DummiData und _DragStart kann nun aus dem Form-Code herausgehalten werden.
      In ListenForDragging() werden diese initialisiert, vor allem der _Callback, mit dem das Modul die "PerformDrop"-Methode im Form zurückrufen kann, wenn das Dragging ein Ergebnis erzielt.

      Das komplette DraggingX-Modul gibts nur im Spoiler, und erkläre ich auch nur im Ansatz:
      Das Highlighting beruht auf der ControlPaint.FillReversibleRectangle()-Methode, und es muss recht trickreich verwaltet werden, ob und welches Rechteck auffm Bildschirm grade invertiert ist, und obs zurück-invertiert werden muss.
      Weitere Komplexität ergibt sich durch die vielfältigen Anforderungen: Das Highlighting muss beim Treeview zB das Item selbst highlighten können, aber zb. bei Listbox muss er auch zwischen zwei Item highlighten können, aber auch hinter einem Item, und auch einen Bereich in einem ganz leeren Control.
      ausgelagerte komplexe Funktionalität für DragStart und Highlighting

      VB.NET-Quellcode

      1. Imports System.Runtime.CompilerServices, System.Diagnostics, System.Drawing, System.Windows.Forms
      2. <System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)> _
      3. Public Module DraggingX
      4. ' Das Highlighten erfolgt durch Farb-Inversion des Target-Rectangles. Ausschalten
      5. ' des Highlights durch wiederholte Inversion desselben Rectangles. Fehl-Highlighting
      6. ' ergibt sich also, wenn zwischen Highlight() und Off() das Rectangle anderweitig
      7. ' übermalt wurde.
      8. Private _HighlightedRect As Rectangle = Rectangle.Empty
      9. ' Farb-Inversion ist eine Art "Spiegelung" über eine Farb-Achse.
      10. ' Dieses komische Rot ergibt (bisher) immer deutliche "Spiegel-Farben"
      11. Private ReadOnly _AxisColor As Color = Color.FromArgb(255, 0, 127)
      12. Private _DragEventNone As New DragEventArgs(Nothing, Nothing, Nothing, Nothing, Nothing, Nothing)
      13. Private _DummiData As New DataObject, _AllAllowed As DragDropEffects = DragDropEffects.Copy Or DragDropEffects.Link Or DragDropEffects.Move Or DragDropEffects.Scroll
      14. Private _Target As Control, _Callback As Action(Of DragDropEffects), _DragStart As Point, _DragMouseButton As MouseButtons
      15. Private WithEvents _Origin As Control
      16. <DebuggerStepThrough(), Extension()> _
      17. Public Function DragOrigin(ByVal target As Control) As Control
      18. Return _Origin
      19. End Function
      20. <DebuggerStepThrough(), Extension()> _
      21. Public Function DragTarget(ByVal origin As Control) As Control
      22. Return _Target
      23. End Function
      24. <Extension()> _
      25. Public Sub ListenForDragging(ByVal origin As Control, ByVal callback As Action(Of DragDropEffects))
      26. _Origin = origin
      27. _DragMouseButton = Control.MouseButtons
      28. _Callback = callback
      29. _DragStart = _Origin.PointToClient(Control.MousePosition)
      30. End Sub
      31. Private Sub Origin_MouseMove(ByVal sender As Object, ByVal e As MouseEventArgs) Handles _Origin.MouseMove
      32. If e.Button <> _DragMouseButton Then Return
      33. If e.Location.SquareDistance(_DragStart) > 9 Then ' erst draggen, wenn 3 pix mit gehaltener Maus gezogen wurde
      34. Dim result = _Origin.DoDragDrop(_DummiData, _AllAllowed) ' DoDragDrop kehrt erst nach Ende des Drag-Vorgangs zurück
      35. If result <> DragDropEffects.None Then
      36. _Callback(result) ' ggfs den callback rufen
      37. _Target.Highlight(Nothing) ' HighLight löschen
      38. End If
      39. _Origin = Nothing
      40. End If
      41. End Sub
      42. <Extension()> _
      43. Public Sub Highlight(ByVal target As Control, ByVal itemRect As Rectangle, Optional ByVal e As DragEventArgs = Nothing)
      44. Dim rct = target.ClientRectangle
      45. rct = New Rectangle(rct.X, rct.Y + 1, rct.Width, rct.Height - 1)
      46. If e.NotNull Then
      47. If Not rct.Contains(target.PointToClient(Control.MousePosition)) Then
      48. ' DragOver ausserhalb des ClientRectangles ablehnen
      49. e.Effect = DragDropEffects.None
      50. End If
      51. If e.Effect = DragDropEffects.None Then itemRect = Rectangle.Empty
      52. End If
      53. itemRect.Intersect(rct)
      54. ' FillReversibleRectangle verwendet bildschirmbezogene Koordinaten, daher muß ItemRect
      55. ' um die Bildschirmposition des Controls verschoben werden
      56. itemRect.Offset(target.PointToScreen(Point.Empty))
      57. If itemRect = _HighlightedRect Then Return ' ist schon highlighted
      58. ' altes Highlight löschen und neues Highlight setzen
      59. If Not _HighlightedRect.IsEmpty Then ControlPaint.FillReversibleRectangle(_HighlightedRect, _AxisColor)
      60. _HighlightedRect = itemRect
      61. If _HighlightedRect.IsEmpty Then
      62. _Target = Nothing
      63. Else
      64. _Target = target
      65. ControlPaint.FillReversibleRectangle(_HighlightedRect, _AxisColor)
      66. End If
      67. End Sub
      68. <Extension()> _
      69. Public Sub HighlightBefore(ByVal Target As Control, ByVal e As DragEventArgs, ByVal ItemRect As Rectangle)
      70. ItemRect.Offset(0, -ItemRect.Height \ 2)
      71. ItemRect.Inflate(0, -2)
      72. Highlight(Target, ItemRect, e)
      73. End Sub
      74. <Extension()> _
      75. Public Sub HighlightAfter(ByVal Target As Control, ByVal e As DragEventArgs, ByVal ItemRect As Rectangle)
      76. ItemRect.Offset(0, ItemRect.Height)
      77. HighlightBefore(Target, e, ItemRect)
      78. End Sub
      79. <Extension()> _
      80. Public Sub HighlightFullWidth(ByVal Target As Control, ByVal e As DragEventArgs, ByVal Y As Integer, ByVal Height As Integer)
      81. 'bei leeren ListenControls, oder bei Treeview.TopLevel als Ziel kann kein ItemRect angegeben werden
      82. DraggingX.Highlight(Target, New Rectangle(0, Y, Target.Width, Height), e)
      83. End Sub
      84. End Module
      Soweit die auslager- und wiederverwend-bare Komplexität - aber leider verbleibt noch genügend komplexes zB im Listview_DragOver, denn es muss ja sorgfältig differenziert werden, ob ein Ziel-Item existiert, und ob vor oder nach diesem zu highlighten ist, oder ob ins leere Control highlighten, oder obs Highlighting aus soll.
      Aber dass _DragStart, _DummiData und der ganze ListBox1_MouseMove-Handler nun weg können, ist doch auch schon was, oder?

      VB.NET-Quellcode

      1. Public Class ListboxListview
      2. Private Sub ListBox1_MouseDown(ByVal sender As Object, ByVal e As MouseEventArgs) Handles ListBox1.MouseDown
      3. If ListBox1.SelectedItem IsNot Nothing Then ListBox1.ListenForDragging(AddressOf ListviewPerformDropped)
      4. End Sub
      5. Private Sub ListView1_DragOver(ByVal sender As Object, ByVal e As DragEventArgs) Handles ListView1.DragOver
      6. If ListView1.DragOrigin IsNot ListBox1 Then Return
      7. Select Case Control.ModifierKeys
      8. Case Keys.None
      9. e.Effect = DragDropEffects.Move
      10. Case Keys.Shift
      11. e.Effect = DragDropEffects.Copy
      12. End Select
      13. Dim pt = ListView1.PointToClient(Control.MousePosition)
      14. Dim lvi = ListView1.GetItemAt(pt.X, pt.Y)
      15. ' beim Highlighting sind 3 Varianten abzudecken - Highlight.Off erkennt er selbst
      16. If ListView1.Items.Count = 0 Then 'Highlight in leeres Listview
      17. ListView1.HighlightFullWidth(e, 16, 16)
      18. ElseIf lvi.Null Then 'Highlight hinter das letzte Item
      19. ListView1.HighlightAfter(e, ListView1.Items(ListView1.Items.Count - 1).Bounds)
      20. Else
      21. ListView1.HighlightBefore(e, lvi.Bounds) 'Highlight vor dem Ziel-Item
      22. End If
      23. End Sub
      24. Private Sub ListviewPerformDropped(ByVal result As DragDropEffects)
      25. Dim pt = ListView1.PointToClient(Control.MousePosition)
      26. Dim lvi = ListView1.GetItemAt(pt.X, pt.Y)
      27. Dim indx = If(lvi.Null, ListView1.Items.Count, lvi.Index)
      28. Dim txt = DirectCast(ListBox1.SelectedItem, String)
      29. Select Case result
      30. Case DragDropEffects.Move
      31. ListView1.Items.Insert(indx, txt)
      32. ListBox1.Items.RemoveAt(ListBox1.SelectedIndex)
      33. Case DragDropEffects.Copy
      34. ListView1.Items.Insert(indx, txt)
      35. End Select
      36. End Sub
      37. End Class

      Dieser Beitrag wurde bereits 5 mal editiert, zuletzt von „ErfinderDesRades“ ()

      Das ItemDrag ist so ziemlich ListView-eigen.
      Bei einer LisBox ist das so nicht möglich.
      Dazu hab ich bei CodeProject ein älteres konvertierungsbedürftiges Projekt gefunden, hier das im Prinzip komplette Projekt
      (der Designer-Code und der Form-Code müssen separat in eine neue FormX.cs bzw. FormX.Designer.cs kopiert werden.
      Das Beispiel drag-dropt Strings von einer ListBox in eine andere und auch in eine Notepad-Instanz.
      Spoiler anzeigen

      C#-Quellcode

      1. using System;
      2. using System.Windows.Forms;
      3. namespace DragDropNish
      4. {
      5. /// <summary>
      6. /// Summary description for Form1.
      7. /// </summary>
      8. public class Form1 : System.Windows.Forms.Form
      9. {
      10. private System.Windows.Forms.ListBox listBox1;
      11. private System.Windows.Forms.ListBox listBox2;
      12. private System.Windows.Forms.Label label1;
      13. /// <summary>
      14. /// Required designer variable.
      15. /// </summary>
      16. private System.ComponentModel.Container components = null;
      17. public Form1()
      18. {
      19. //
      20. // Required for Windows Form Designer support
      21. //
      22. this.InitializeComponent();
      23. this.listBox1.Items.Add("Nish [BusterBoy]");
      24. this.listBox1.Items.Add("Colin Davies");
      25. this.listBox1.Items.Add("Paul Watson");
      26. this.listBox1.Items.Add("David Wulff");
      27. this.listBox1.Items.Add("Christian Graus");
      28. this.listBox1.Items.Add("Chris Maunder");
      29. this.listBox1.Items.Add("Tweety");
      30. this.listBox1.Items.Add("Qomi");
      31. this.listBox1.Items.Add("Lauren");
      32. //
      33. // TODO: Add any constructor code after InitializeComponent call
      34. //
      35. }
      36. /// <summary>
      37. /// Clean up any resources being used.
      38. /// </summary>
      39. protected override void Dispose(bool disposing)
      40. {
      41. if (disposing)
      42. {
      43. if (this.components != null)
      44. {
      45. this.components.Dispose();
      46. }
      47. }
      48. base.Dispose(disposing);
      49. }
      50. #region Windows Form Designer generated code
      51. /// <summary>
      52. /// Required method for Designer support - do not modify
      53. /// the contents of this method with the code editor.
      54. /// </summary>
      55. private void InitializeComponent()
      56. {
      57. this.listBox1 = new System.Windows.Forms.ListBox();
      58. this.listBox2 = new System.Windows.Forms.ListBox();
      59. this.label1 = new System.Windows.Forms.Label();
      60. this.SuspendLayout();
      61. //
      62. // listBox1
      63. //
      64. this.listBox1.Font = new System.Drawing.Font("Microsoft Sans Serif", 14F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, 0);
      65. this.listBox1.ItemHeight = 24;
      66. this.listBox1.Location = new System.Drawing.Point(32, 16);
      67. this.listBox1.Name = "listBox1";
      68. this.listBox1.Size = new System.Drawing.Size(176, 196);
      69. this.listBox1.TabIndex = 0;
      70. this.listBox1.MouseDown += new System.Windows.Forms.MouseEventHandler(this.listBox1_MouseDown);
      71. //
      72. // listBox2
      73. //
      74. this.listBox2.AllowDrop = true;
      75. this.listBox2.Font = new System.Drawing.Font("Microsoft Sans Serif", 14F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, 0);
      76. this.listBox2.ItemHeight = 24;
      77. this.listBox2.Location = new System.Drawing.Point(344, 16);
      78. this.listBox2.Name = "listBox2";
      79. this.listBox2.Size = new System.Drawing.Size(176, 196);
      80. this.listBox2.TabIndex = 1;
      81. this.listBox2.DragOver += new System.Windows.Forms.DragEventHandler(this.listBox2_DragOver);
      82. this.listBox2.DragDrop += new System.Windows.Forms.DragEventHandler(this.listBox2_DragDrop);
      83. //
      84. // label1
      85. //
      86. this.label1.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
      87. this.label1.Font = new System.Drawing.Font("Microsoft Sans Serif", 13F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, 0);
      88. this.label1.Location = new System.Drawing.Point(88, 224);
      89. this.label1.Name = "label1";
      90. this.label1.Size = new System.Drawing.Size(328, 40);
      91. this.label1.TabIndex = 2;
      92. this.label1.Text = "Drag and drop names from the list box on the left to the list box on the right";
      93. this.label1.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
      94. //
      95. // Form1
      96. //
      97. this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
      98. this.ClientSize = new System.Drawing.Size(568, 278);
      99. this.Controls.AddRange(new System.Windows.Forms.Control[] {
      100. this.label1,
      101. this.listBox2,
      102. this.listBox1});
      103. this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog;
      104. this.MaximizeBox = false;
      105. this.Name = "Form1";
      106. this.Text = "Playing with drag and drop";
      107. this.ResumeLayout(false);
      108. }
      109. #endregion
      110. /// <summary>
      111. /// The main entry point for the application.
      112. /// </summary>
      113. [STAThread]
      114. private static void Main()
      115. {
      116. Application.Run(new Form1());
      117. }
      118. private void listBox1_MouseDown(object sender, System.Windows.Forms.MouseEventArgs e)
      119. {
      120. if (this.listBox1.Items.Count == 0)
      121. {
      122. return;
      123. }
      124. string s = this.listBox1.Items[this.listBox1.IndexFromPoint(e.X, e.Y)].ToString();
      125. DragDropEffects dde1 = this.DoDragDrop(s, DragDropEffects.All);
      126. if (dde1 == DragDropEffects.All)
      127. {
      128. this.listBox1.Items.RemoveAt(this.listBox1.IndexFromPoint(e.X, e.Y));
      129. }
      130. }
      131. private void listBox2_DragDrop(object sender, System.Windows.Forms.DragEventArgs e)
      132. {
      133. if (e.Data.GetDataPresent(DataFormats.StringFormat))
      134. {
      135. string str = (string)e.Data.GetData(DataFormats.StringFormat);
      136. this.listBox2.Items.Add(str);
      137. }
      138. }
      139. private void listBox2_DragOver(object sender, System.Windows.Forms.DragEventArgs e)
      140. {
      141. e.Effect = DragDropEffects.All;
      142. }
      143. }
      144. }


      ausgelagert aus Drag & Drop von Dateien aus einer Anwendung (Listview) in das Windows-Explorer-Dateisystem ~VaporiZed
      Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
      Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
      Ein guter .NET-Snippetkonverter (der ist verfügbar).
      Programmierfragen über PN / Konversation werden ignoriert!

      Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „VaporiZed“ ()

      @RodFromGermany

      Dein Code ist doch recht lang .
      Da würde ich auf eher auf die Listbox verzichten und ein Listview dafür verwenden. Codemässig weniger und der Effekt für den Nutzer identisch.
      Liebe Grüße
      Roland Berghöfer

      Meine aktuellen und kostenlos verwendbaren Tools (mit VB.NET erstellt): freeremarkabletools.com | priconman.com | SimpleCalendar | AudibleTouch | BOComponent.com | bonit.at

      dive26 schrieb:

      Dein Code ist doch recht lang .
      Dem muss ich widersprechen.
      Du hast offensichtlich den Aufbau dieses uralten Dateiformats noch nicht durchdrungen. ;)
      Für Dich relevant wäre dieser Code:
      Spoiler anzeigen

      C#-Quellcode

      1. private void listBox1_MouseDown(object sender, System.Windows.Forms.MouseEventArgs e)
      2. {
      3. if (this.listBox1.Items.Count == 0)
      4. {
      5. return;
      6. }
      7. string s = this.listBox1.Items[this.listBox1.IndexFromPoint(e.X, e.Y)].ToString();
      8. DragDropEffects dde1 = this.DoDragDrop(s, DragDropEffects.All);
      9. if (dde1 == DragDropEffects.All)
      10. {
      11. this.listBox1.Items.RemoveAt(this.listBox1.IndexFromPoint(e.X, e.Y));
      12. }
      13. }
      14. private void listBox2_DragDrop(object sender, System.Windows.Forms.DragEventArgs e)
      15. {
      16. if (e.Data.GetDataPresent(DataFormats.StringFormat))
      17. {
      18. string str = (string)e.Data.GetData(DataFormats.StringFormat);
      19. this.listBox2.Items.Add(str);
      20. }
      21. }
      22. private void listBox2_DragOver(object sender, System.Windows.Forms.DragEventArgs e)
      23. {
      24. e.Effect = DragDropEffects.All;
      25. }
      Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
      Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
      Ein guter .NET-Snippetkonverter (der ist verfügbar).
      Programmierfragen über PN / Konversation werden ignoriert!
      Tja, diese LösungB für Draggen innerhalb der Anwendung (DiA) fällt aber qualitativ um einiges hinter der in post#9 vorgestellten LösungA zurück:
      1. LösungB startet beim MouseDown sofort den Drag-Vorgang.
        Das entspricht nicht der üblichen und sinnvollen Maus-Geste zum Starten eines Drag-Vorgangs: Man muss nämlich mit gehaltener Taste erst ein paar Pixel ziehen.
        Ohne diese Zusatzbedingung kommt es zu zuvielen (abgebrochenden) Drag-Vorgängen, und vor allem zu Konflikten, wenn man das MouseDown-Event für etwas anderes nutzen möchte.
      2. LösungB startet ausserdem einen anwendungs-übergreifenden Drag-Vorgang, der aber gleichzeitig auch Drops von anderen Anwendungen auf Listbox2 zulässt.
        Es ist also beides: sowohl DiA als auch anwendung-übergreifend - und diese Vermischung ist wohl nur in den seltensten Fällen gewollt.
        Es ergibt sich ein für DiA unerwünschtes Verhalten, wenn von Listbox1 in eine andere Anwendung gedraggt wird.
        Noch fataler wohl in Gegenrichtung: ZB irgendwelche (auch seiten-lange) Word-Texte können nun in listBox2 gedropt werden.
      Diesen Unerfreulichkeiten ist durch die DiA-Lösung in post#9 wirkungsvoll begegnet.

      Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „ErfinderDesRades“ ()