[WPF] Drag 'n' Drop

    • C#
    • .NET (FX) 4.0

    Es gibt 1 Antwort in diesem Thema. Der letzte Beitrag () ist von ErfinderDesRades.

      [WPF] Drag 'n' Drop

      Moin,

      nachdem ich festgestellt habe was man alles mit Behaviors machen kann und ich bereits mehrmals drüber gestolpert bin, dass Drag 'n' Drop in WPF nicht ganz so einfach, und vor allem MVVM verträglich, zu realisieren ist, hab ich mal versucht das Ganze in Behaviors schön aufgeräumt und vor allem wiederverwertbar zu gestalten.
      Vorneweg: Wer keine Ahnung von Drag 'n' Drop im .NET Framework sollte sich Dieses aneignen bevor er weiterliest. Hier reicht auch WinForms-Wissen aus!
      Wichtig ist, dass die Lösung so in dieser Form nur für .NET 4.0 oder höher funktioniert. Es gibt das notwendige Assembly auch für 3.5. Hier heißt es Microsoft.Expression.Interactivity und wird mit dem Blend SDK verteilt.

      Also zuerst der Code für eine DragSource, also FrameworkElement aus dem sich Daten ziehen lassen sollen:
      DragSource

      C#-Quellcode

      1. public class DragSourceBehavior : Behavior<FrameworkElement>
      2. {
      3. public static readonly DependencyProperty DragDataProperty =
      4. DependencyProperty.Register("DragData", typeof(object), typeof(DragSourceBehavior));
      5. public static readonly DependencyProperty DragDataObjectProperty =
      6. DependencyProperty.Register("DragDataObject", typeof(IDataObject), typeof(DragSourceBehavior));
      7. public static readonly DependencyProperty DragTypeProperty =
      8. DependencyProperty.Register("DragType", typeof(string), typeof(DragSourceBehavior));
      9. public static readonly DependencyProperty DragEffectsProperty =
      10. DependencyProperty.Register("DragEffects", typeof(DragDropEffects), typeof(DragSourceBehavior), new PropertyMetadata(DragDropEffects.All));
      11. public static readonly DependencyProperty isEnabledProperty =
      12. DependencyProperty.Register("isEnabled", typeof(bool), typeof(DragSourceBehavior), new PropertyMetadata(true));
      13. public object DragData
      14. {
      15. get { return GetValue(DragDataProperty); }
      16. set { SetValue(DragDataProperty, value); }
      17. }
      18. public IDataObject DragDataObject
      19. {
      20. get { return (IDataObject)GetValue(DragDataObjectProperty); }
      21. set { SetValue(DragDataObjectProperty, value); }
      22. }
      23. public string DragType
      24. {
      25. get { return (string)GetValue(DragTypeProperty); }
      26. set { SetValue(DragTypeProperty, value); }
      27. }
      28. public DragDropEffects DragEffects
      29. {
      30. get { return (DragDropEffects)GetValue(DragEffectsProperty); }
      31. set { SetValue(DragEffectsProperty, value); }
      32. }
      33. public bool isEnabled
      34. {
      35. get { return (bool)GetValue(isEnabledProperty); }
      36. set { SetValue(isEnabledProperty, value); }
      37. }
      38. protected override void OnAttached()
      39. {
      40. AssociatedObject.MouseMove += (s, e) =>
      41. {
      42. if (isEnabled && e.LeftButton == System.Windows.Input.MouseButtonState.Pressed)
      43. {
      44. var data = getDataObject();
      45. if (data != null)
      46. DragDrop.DoDragDrop(AssociatedObject, getDataObject(), DragEffects);
      47. }
      48. };
      49. }
      50. protected IDataObject getDataObject()
      51. {
      52. if (DragDataObject != null) return DragDataObject;
      53. if (DragData == null) return null;
      54. DataObject dobj = new DataObject();
      55. if (!String.IsNullOrWhiteSpace(DragType))
      56. dobj.SetData(DragType, DragData);
      57. else
      58. dobj.SetData(DragData);
      59. return dobj;
      60. }
      61. protected override void OnDetaching()
      62. {
      63. isEnabled = false;
      64. base.OnDetaching();
      65. }
      66. }


      Das Ganze sieht im XAML in etwa so aus:
      DragSource XAML

      XML-Quellcode

      1. <ListView x:Name="liste">
      2. <i:Interaction.Behaviors>
      3. <Behaviors:DragSourceBehavior DragData="{Binding SelectedItem, ElementName=liste}" />
      4. </i:Interaction.Behaviors>
      5. <sys:Int32>0</sys:Int32>
      6. <sys:Int32>1</sys:Int32>
      7. <sys:Int32>2</sys:Int32>
      8. <sys:Int32>3</sys:Int32>
      9. <sys:Int32>4</sys:Int32>
      10. <sys:Int32>5</sys:Int32>
      11. <sys:String>Hallo Welt</sys:String>
      12. </ListView>





      Der einfachste Weg, um das Draggen zu ermöglichen ist, an DragData zu binden. DragDataObject erlaubt direkt an ein IDataObject zu binden.
      Die restlichen DependencyProperties erklären sich eigentlich mehr oder weniger von selbst.

      Das Behavior um Daten zu empfangen (es ist hierbei ob die Daten aus einer eigenen WPF Anwendung stammen oder zum Beispiel aus dem Explorer) sieht so aus:
      DropTarget

      C#-Quellcode

      1. public class DragTargetBehavior : Behavior<FrameworkElement>
      2. {
      3. public static readonly DependencyProperty DropDataProperty =
      4. DependencyProperty.Register("DropData", typeof(object), typeof(DragTargetBehavior), new FrameworkPropertyMetadata(null) { BindsTwoWayByDefault = true });
      5. public static readonly DependencyProperty DropTypeProperty =
      6. DependencyProperty.Register("DropType", typeof(string), typeof(DragTargetBehavior), new FrameworkPropertyMetadata("") { BindsTwoWayByDefault = true });
      7. public static readonly DependencyProperty DropEffectProperty =
      8. DependencyProperty.Register("DropEffect", typeof(DragDropEffects), typeof(DragTargetBehavior), new FrameworkPropertyMetadata(null) { BindsTwoWayByDefault = true });
      9. public static readonly DependencyProperty DropPositionProperty =
      10. DependencyProperty.Register("DropPosition", typeof(Point), typeof(DragTargetBehavior), new FrameworkPropertyMetadata(new Point(0, 0)) { BindsTwoWayByDefault = true });
      11. public static readonly DependencyProperty AcceptedDataTypesProperty =
      12. DependencyProperty.Register("AcceptedDataTypes", typeof(List<string>), typeof(DragTargetBehavior), new PropertyMetadata(new List<string>()));
      13. public static readonly DependencyProperty AcceptedEffectsProperty =
      14. DependencyProperty.Register("AcceptedEffects", typeof(DragDropEffects), typeof(DragTargetBehavior), new PropertyMetadata(DragDropEffects.All));
      15. public static readonly DependencyProperty DropCompletedCallbackProperty =
      16. DependencyProperty.Register("DropCompletedCallback", typeof(Action<object>), typeof(DragTargetBehavior), new PropertyMetadata(null));
      17. public static readonly DependencyProperty isEnabledProperty =
      18. DependencyProperty.Register("isEnabled", typeof(bool), typeof(DragTargetBehavior), new PropertyMetadata(true));
      19. public static readonly DependencyProperty DropDataObjectProperty =
      20. DependencyProperty.Register("DropDataObject", typeof(IDataObject), typeof(DragTargetBehavior), new PropertyMetadata(null));
      21. public object DropData
      22. {
      23. get { return GetValue(DropDataProperty); }
      24. set { SetValue(DropDataProperty, value); }
      25. }
      26. public string DropType
      27. {
      28. get { return (string)GetValue(DropTypeProperty); }
      29. set { SetValue(DropTypeProperty, value); }
      30. }
      31. public DragDropEffects DropEffect
      32. {
      33. get { return (DragDropEffects)GetValue(DropEffectProperty); }
      34. set { SetValue(DropEffectProperty, value); }
      35. }
      36. public Point DropPosition
      37. {
      38. get { return (Point)GetValue(DropPositionProperty); }
      39. set { SetValue(DropPositionProperty, value); }
      40. }
      41. public List<string> AcceptedDataTypes
      42. {
      43. get { return (List<string>)GetValue(AcceptedDataTypesProperty); }
      44. set { SetValue(AcceptedDataTypesProperty, value); }
      45. }
      46. public DragDropEffects AcceptedEffects
      47. {
      48. get { return (DragDropEffects)GetValue(AcceptedEffectsProperty); }
      49. set { SetValue(AcceptedEffectsProperty, value); }
      50. }
      51. public Action<object> DropCompletedCallback
      52. {
      53. get { return (Action<object>)GetValue(DropCompletedCallbackProperty); }
      54. set { SetValue(DropCompletedCallbackProperty, value); }
      55. }
      56. public bool isEnabled
      57. {
      58. get { return (bool)GetValue(isEnabledProperty); }
      59. set { SetValue(isEnabledProperty, value); }
      60. }
      61. public IDataObject DropDataObject
      62. {
      63. get { return (IDataObject)GetValue(DropDataObjectProperty); }
      64. set { SetValue(DropDataObjectProperty, value); }
      65. }
      66. public DragTargetBehavior()
      67. : base()
      68. {
      69. AcceptedDataTypes = new List<string>();
      70. }
      71. protected override void OnAttached()
      72. {
      73. AssociatedObject.AllowDrop = true;
      74. AssociatedObject.PreviewDragOver += (s, e) =>
      75. {
      76. e.Effects = DragDropEffects.None;
      77. if (isEnabled && AcceptedDataTypes.Count() == 0 || AcceptedDataTypes.Any(x => e.Data.GetFormats().Contains(x)))
      78. {
      79. e.Effects = AcceptedEffects;
      80. }
      81. e.Handled = true;
      82. };
      83. AssociatedObject.Drop += (s, e) =>
      84. {
      85. if (!isEnabled) return;
      86. DropEffect = e.Effects;
      87. DropPosition = e.GetPosition(AssociatedObject);
      88. if (AcceptedDataTypes.Count() == 0)
      89. DropType = e.Data.GetFormats()[0];
      90. else
      91. DropType = e.Data.GetFormats().First(x => AcceptedDataTypes.Contains(x));
      92. DropData = e.Data.GetData(DropType);
      93. DropDataObject = e.Data;
      94. e.Handled = true;
      95. if (DropCompletedCallback != null)
      96. DropCompletedCallback(DropData);
      97. };
      98. }
      99. protected override void OnDetaching()
      100. {
      101. AssociatedObject.AllowDrop = false;
      102. isEnabled = false;
      103. base.OnDetaching();
      104. }
      105. }



      DropData ist hierbei nicht das gesamte DatenObjekt sondern nur das erste Objekt, auf das ein in AcceptedDataTypes angegebener Daten Type zutrifft. Dies macht es leichter direkt an Dieses zu binden. Das vollständige DatenObjekt findet man in DropDataObject (zum Beispiel zum binden an das ViewModel).
      Die weiteren Properties sollten selbsterklärend sein, mit Ausnahme von DropCompletedCallback. An dieses kann eine Action<object> gebunden werden, die ausgeführt wird, wenn ein Drop abgeschlossen wurde. Ich verwende sie in dem Beispiel Projekt zum Beispiel dafür, das gedroppte Bild zu laden.

      Das Ganze sieht in XAML so aus:
      DropTarget XAML

      XML-Quellcode

      1. <TextBlock Text="{Binding DropType, ElementName=dragTargetBehavior}">
      2. <i:Interaction.Behaviors>
      3. <Behaviors:DragTargetBehavior x:Name="dragTargetBehavior">
      4. <Behaviors:DragTargetBehavior.AcceptedDataTypes>
      5. <sys:String>System.Int32</sys:String>
      6. <sys:String>System.String</sys:String>
      7. </Behaviors:DragTargetBehavior.AcceptedDataTypes>
      8. </Behaviors:DragTargetBehavior>
      9. </i:Interaction.Behaviors>
      10. </TextBlock>


      Da es einige Controls gibt, die Drag 'n' Drop nicht unterstützen (z.B. Grid und alle weiteren Pannels und Image) hab ich auch noch einen einfachen Container geschrieben, der die Drop Funktionalität für alle Children bereitstelt.
      DropPanel

      C#-Quellcode

      1. public class DropPanel : Panel
      2. {
      3. private Brush transparentBrush = new SolidColorBrush(Color.FromArgb(0, 0, 0, 0));
      4. public DropPanel()
      5. : base()
      6. {
      7. this.AllowDrop = true;
      8. this.Background = transparentBrush;
      9. }
      10. protected override System.Windows.Size MeasureOverride(System.Windows.Size availableSize)
      11. {
      12. return availableSize;
      13. }
      14. protected override System.Windows.Size ArrangeOverride(System.Windows.Size finalSize)
      15. {
      16. foreach (UIElement child in InternalChildren)
      17. child.Arrange(new Rect(finalSize));
      18. return finalSize;
      19. }
      20. }


      Das DropPanel mag in der einen oder andere Situation hilfreich sein, ist aber ausdrücklich nicht ausgiebig von mir getestet worden!

      Wichtig: Damit die Behaviors funktionieren, muss ein Verweis auf System.Windows.Interactivity gesetzt werden und ein xml Namespace. Hierfür oben im <Window> die Zeile xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity" hinzufügen. Für die Drag and Drop Behaviors sowie das DropPanel müssen ebenfalls xml Namespaces hinzugefügt werden.

      Anbei ist ein Demo Project in dem das Ganze mal in Aktion sehen kann.
      Ich hoffe jemandem ist damit geholfen, oder mein Codeerguss dient als Basis für etwas Ausgefeielteres.

      Gruß
      Zakath
      Dateien
      • WPFDragnDrop.zip

        (27,61 kB, 207 mal heruntergeladen, zuletzt: )

      Zakath schrieb:

      Da es einige Controls gibt, die Drag 'n' Drop nicht unterstützen (z.B. Grid und alle weiteren Pannels und Image) hab ich auch noch einen einfachen Container geschrieben, der die Drop Funktionalität für alle Children bereitstelt.
      Hmm - afaik unterstützen die Controls schon D&D.
      Hab dein DropPanel auch gleichmal runtergeschmissen, und ging ebenso gut.
      Spoiler anzeigen

      XML-Quellcode

      1. <Grid>
      2. <Grid.ColumnDefinitions>
      3. <ColumnDefinition Width="*" />
      4. <ColumnDefinition Width="*" />
      5. <ColumnDefinition Width="*" />
      6. </Grid.ColumnDefinitions>
      7. <ListView x:Name="liste" Grid.Column="0">
      8. <i:Interaction.Behaviors>
      9. <Behaviors:DragSourceBehavior DragData="{Binding SelectedItem, ElementName=liste}" />
      10. </i:Interaction.Behaviors>
      11. <sys:Int32>0</sys:Int32>
      12. <sys:Int32>1</sys:Int32>
      13. <sys:Int32>2</sys:Int32>
      14. <sys:Int32>3</sys:Int32>
      15. <sys:Int32>4</sys:Int32>
      16. <sys:Int32>5</sys:Int32>
      17. <sys:String>Hallo Welt</sys:String>
      18. </ListView>
      19. <StackPanel Grid.Column="1" HorizontalAlignment="Center" VerticalAlignment="Center" Orientation="Vertical">
      20. <i:Interaction.Behaviors>
      21. <Behaviors:DragTargetBehavior x:Name="dragTargetBehavior">
      22. <Behaviors:DragTargetBehavior.AcceptedDataTypes>
      23. <sys:String>System.Int32</sys:String>
      24. <sys:String>System.String</sys:String>
      25. </Behaviors:DragTargetBehavior.AcceptedDataTypes>
      26. </Behaviors:DragTargetBehavior>
      27. </i:Interaction.Behaviors>
      28. <StackPanel Orientation="Horizontal">
      29. <TextBlock Margin="0,0,5,0">Data:</TextBlock>
      30. <TextBox HorizontalAlignment="Center" VerticalAlignment="Center" RenderTransformOrigin="0.5,0.5" Text="{Binding DropData, ElementName=dragTargetBehavior}" />
      31. </StackPanel>
      32. <StackPanel Orientation="Horizontal">
      33. <TextBlock Margin="0,0,5,0">Type:</TextBlock>
      34. <TextBlock Text="{Binding DropType, ElementName=dragTargetBehavior}" />
      35. </StackPanel>
      36. </StackPanel>
      37. </Grid>
      Ich hab mich übrigens auch mit Wpf-D&D beschäftigt, fand aber, dass es bei D&D irrsinnig viele unterschiedliche Anforderungen gibt, dass man einen Vogel kriegt, wenn man mit Behaviors versucht, das alles abzudecken.
      Etwa in meinen Augen ist eine Standard-Anforderung an D&D, dass man Item in Listen einfügen kann. Und dabei muss während des Draggens auch markiert werden, wo das Item hin-droppen wird.
      Nächste Forderung geht dann an Treeview, da gibt es 2 Möglichkeiten zu droppen: Entweder einfach in den Ziel-Node hinein, das ist sinnvoll, wenn der TV seine Nodes selbst sortiert (wies etwa bei FileBrowsern sinnvoll ist).
      Oder dass man auch in TV ein DropItem gezielt zwischen 2 Nodes einfügen kann.
      Und dann will man vlt. noch Scroll-Verhalten, damit man während des Draggens noch hinscrollen kann, wo eingefügt werden soll, oder Treenodes sollen expandieren beim Drag-Over-Hover.
      Und bei meine letzten Übung damit musste auch individuell rückgefragt werden ins Viewmodel, ob auf diesen Node gedropt werden durfte oder nicht.
      und und und...