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
Also zuerst der Code für eine DragSource, also
DragSource
Das Ganze sieht im XAML in etwa so aus:
DragSource XAML
Der einfachste Weg, um das Draggen zu ermöglichen ist, an
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
Die weiteren Properties sollten selbsterklärend sein, mit Ausnahme von
Das Ganze sieht in XAML so aus:
DropTarget XAML
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
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
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
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:C#-Quellcode
- public class DragSourceBehavior : Behavior<FrameworkElement>
- {
- public static readonly DependencyProperty DragDataProperty =
- DependencyProperty.Register("DragData", typeof(object), typeof(DragSourceBehavior));
- public static readonly DependencyProperty DragDataObjectProperty =
- DependencyProperty.Register("DragDataObject", typeof(IDataObject), typeof(DragSourceBehavior));
- public static readonly DependencyProperty DragTypeProperty =
- DependencyProperty.Register("DragType", typeof(string), typeof(DragSourceBehavior));
- public static readonly DependencyProperty DragEffectsProperty =
- DependencyProperty.Register("DragEffects", typeof(DragDropEffects), typeof(DragSourceBehavior), new PropertyMetadata(DragDropEffects.All));
- public static readonly DependencyProperty isEnabledProperty =
- DependencyProperty.Register("isEnabled", typeof(bool), typeof(DragSourceBehavior), new PropertyMetadata(true));
- public object DragData
- {
- get { return GetValue(DragDataProperty); }
- set { SetValue(DragDataProperty, value); }
- }
- public IDataObject DragDataObject
- {
- get { return (IDataObject)GetValue(DragDataObjectProperty); }
- set { SetValue(DragDataObjectProperty, value); }
- }
- public string DragType
- {
- get { return (string)GetValue(DragTypeProperty); }
- set { SetValue(DragTypeProperty, value); }
- }
- public DragDropEffects DragEffects
- {
- get { return (DragDropEffects)GetValue(DragEffectsProperty); }
- set { SetValue(DragEffectsProperty, value); }
- }
- public bool isEnabled
- {
- get { return (bool)GetValue(isEnabledProperty); }
- set { SetValue(isEnabledProperty, value); }
- }
- protected override void OnAttached()
- {
- AssociatedObject.MouseMove += (s, e) =>
- {
- if (isEnabled && e.LeftButton == System.Windows.Input.MouseButtonState.Pressed)
- {
- var data = getDataObject();
- if (data != null)
- DragDrop.DoDragDrop(AssociatedObject, getDataObject(), DragEffects);
- }
- };
- }
- protected IDataObject getDataObject()
- {
- if (DragDataObject != null) return DragDataObject;
- if (DragData == null) return null;
- DataObject dobj = new DataObject();
- if (!String.IsNullOrWhiteSpace(DragType))
- dobj.SetData(DragType, DragData);
- else
- dobj.SetData(DragData);
- return dobj;
- }
- protected override void OnDetaching()
- {
- isEnabled = false;
- base.OnDetaching();
- }
- }
Das Ganze sieht im XAML in etwa so aus:
XML-Quellcode
- <ListView x:Name="liste">
- <i:Interaction.Behaviors>
- <Behaviors:DragSourceBehavior DragData="{Binding SelectedItem, ElementName=liste}" />
- </i:Interaction.Behaviors>
- <sys:Int32>0</sys:Int32>
- <sys:Int32>1</sys:Int32>
- <sys:Int32>2</sys:Int32>
- <sys:Int32>3</sys:Int32>
- <sys:Int32>4</sys:Int32>
- <sys:Int32>5</sys:Int32>
- <sys:String>Hallo Welt</sys:String>
- </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:
C#-Quellcode
- public class DragTargetBehavior : Behavior<FrameworkElement>
- {
- public static readonly DependencyProperty DropDataProperty =
- DependencyProperty.Register("DropData", typeof(object), typeof(DragTargetBehavior), new FrameworkPropertyMetadata(null) { BindsTwoWayByDefault = true });
- public static readonly DependencyProperty DropTypeProperty =
- DependencyProperty.Register("DropType", typeof(string), typeof(DragTargetBehavior), new FrameworkPropertyMetadata("") { BindsTwoWayByDefault = true });
- public static readonly DependencyProperty DropEffectProperty =
- DependencyProperty.Register("DropEffect", typeof(DragDropEffects), typeof(DragTargetBehavior), new FrameworkPropertyMetadata(null) { BindsTwoWayByDefault = true });
- public static readonly DependencyProperty DropPositionProperty =
- DependencyProperty.Register("DropPosition", typeof(Point), typeof(DragTargetBehavior), new FrameworkPropertyMetadata(new Point(0, 0)) { BindsTwoWayByDefault = true });
- public static readonly DependencyProperty AcceptedDataTypesProperty =
- DependencyProperty.Register("AcceptedDataTypes", typeof(List<string>), typeof(DragTargetBehavior), new PropertyMetadata(new List<string>()));
- public static readonly DependencyProperty AcceptedEffectsProperty =
- DependencyProperty.Register("AcceptedEffects", typeof(DragDropEffects), typeof(DragTargetBehavior), new PropertyMetadata(DragDropEffects.All));
- public static readonly DependencyProperty DropCompletedCallbackProperty =
- DependencyProperty.Register("DropCompletedCallback", typeof(Action<object>), typeof(DragTargetBehavior), new PropertyMetadata(null));
- public static readonly DependencyProperty isEnabledProperty =
- DependencyProperty.Register("isEnabled", typeof(bool), typeof(DragTargetBehavior), new PropertyMetadata(true));
- public static readonly DependencyProperty DropDataObjectProperty =
- DependencyProperty.Register("DropDataObject", typeof(IDataObject), typeof(DragTargetBehavior), new PropertyMetadata(null));
- public object DropData
- {
- get { return GetValue(DropDataProperty); }
- set { SetValue(DropDataProperty, value); }
- }
- public string DropType
- {
- get { return (string)GetValue(DropTypeProperty); }
- set { SetValue(DropTypeProperty, value); }
- }
- public DragDropEffects DropEffect
- {
- get { return (DragDropEffects)GetValue(DropEffectProperty); }
- set { SetValue(DropEffectProperty, value); }
- }
- public Point DropPosition
- {
- get { return (Point)GetValue(DropPositionProperty); }
- set { SetValue(DropPositionProperty, value); }
- }
- public List<string> AcceptedDataTypes
- {
- get { return (List<string>)GetValue(AcceptedDataTypesProperty); }
- set { SetValue(AcceptedDataTypesProperty, value); }
- }
- public DragDropEffects AcceptedEffects
- {
- get { return (DragDropEffects)GetValue(AcceptedEffectsProperty); }
- set { SetValue(AcceptedEffectsProperty, value); }
- }
- public Action<object> DropCompletedCallback
- {
- get { return (Action<object>)GetValue(DropCompletedCallbackProperty); }
- set { SetValue(DropCompletedCallbackProperty, value); }
- }
- public bool isEnabled
- {
- get { return (bool)GetValue(isEnabledProperty); }
- set { SetValue(isEnabledProperty, value); }
- }
- public IDataObject DropDataObject
- {
- get { return (IDataObject)GetValue(DropDataObjectProperty); }
- set { SetValue(DropDataObjectProperty, value); }
- }
- public DragTargetBehavior()
- : base()
- {
- AcceptedDataTypes = new List<string>();
- }
- protected override void OnAttached()
- {
- AssociatedObject.AllowDrop = true;
- AssociatedObject.PreviewDragOver += (s, e) =>
- {
- e.Effects = DragDropEffects.None;
- if (isEnabled && AcceptedDataTypes.Count() == 0 || AcceptedDataTypes.Any(x => e.Data.GetFormats().Contains(x)))
- {
- e.Effects = AcceptedEffects;
- }
- e.Handled = true;
- };
- AssociatedObject.Drop += (s, e) =>
- {
- if (!isEnabled) return;
- DropEffect = e.Effects;
- DropPosition = e.GetPosition(AssociatedObject);
- if (AcceptedDataTypes.Count() == 0)
- DropType = e.Data.GetFormats()[0];
- else
- DropType = e.Data.GetFormats().First(x => AcceptedDataTypes.Contains(x));
- DropData = e.Data.GetData(DropType);
- DropDataObject = e.Data;
- e.Handled = true;
- if (DropCompletedCallback != null)
- DropCompletedCallback(DropData);
- };
- }
- protected override void OnDetaching()
- {
- AssociatedObject.AllowDrop = false;
- isEnabled = false;
- base.OnDetaching();
- }
- }
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:
XML-Quellcode
- <TextBlock Text="{Binding DropType, ElementName=dragTargetBehavior}">
- <i:Interaction.Behaviors>
- <Behaviors:DragTargetBehavior x:Name="dragTargetBehavior">
- <Behaviors:DragTargetBehavior.AcceptedDataTypes>
- <sys:String>System.Int32</sys:String>
- <sys:String>System.String</sys:String>
- </Behaviors:DragTargetBehavior.AcceptedDataTypes>
- </Behaviors:DragTargetBehavior>
- </i:Interaction.Behaviors>
- </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.
C#-Quellcode
- public class DropPanel : Panel
- {
- private Brush transparentBrush = new SolidColorBrush(Color.FromArgb(0, 0, 0, 0));
- public DropPanel()
- : base()
- {
- this.AllowDrop = true;
- this.Background = transparentBrush;
- }
- protected override System.Windows.Size MeasureOverride(System.Windows.Size availableSize)
- {
- return availableSize;
- }
- protected override System.Windows.Size ArrangeOverride(System.Windows.Size finalSize)
- {
- foreach (UIElement child in InternalChildren)
- child.Arrange(new Rect(finalSize));
- return finalSize;
- }
- }
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