Following on from this post, I wanted to try and connect my DraggableBehavior to my DropTrigger but I wasn’t quite sure how to make that work and so I just went for a pretty cheap-and-cheerful mechanism that’s a long way from perfect as it stands at the moment.
I modified my DraggableBehavior a little – from the previous post it’s changed in that there’s now a simple PropertyPath and when the mouse button comes up at the end of the drag, I just use reflection to try and get the property indicated by the PropertyPath and pass that through to any DropActions that I find ( if there is no PropertyPath then I just pass the AssociatedObject from the Behavior itself );
using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using Expression = Microsoft.Expression.Interactivity;
using System.Linq;
using System.Windows.Data;
using System.Collections.Generic;
using System.Reflection;namespace MikesDragDropBits
{
public class DraggableBehavior : Expression.Behavior<FrameworkElement>
{
public DraggableBehavior()
{
}
public string PropertyPath { get; set; }protected override void OnAttached()
{
base.OnAttached();
this.AssociatedObject.MouseLeftButtonDown += OnMouseDown;
this.AssociatedObject.MouseLeftButtonUp += OnMouseUp;
this.AssociatedObject.MouseMove += OnMouseMove;
}
protected override void OnDetaching()
{
base.OnDetaching();
this.AssociatedObject.MouseLeftButtonDown -= OnMouseDown;
this.AssociatedObject.MouseLeftButtonUp -= OnMouseUp;
this.AssociatedObject.MouseMove -= OnMouseMove;
}
void OnMouseMove(object sender, MouseEventArgs e)
{
if (isDragging)
{
if (injectedUI == null)
{
Point localPoint = e.GetPosition(this.AssociatedObject);
Point globalPoint = e.GetPosition(Application.Current.RootVisual);
capturePoint = globalPoint;
globalPoint.X -= localPoint.X;
globalPoint.Y -= localPoint.Y;
injectedUI = new DragDropUIInjector(this.AssociatedObject, globalPoint);
injectedUI.InjectUI();
}
else
{
Point currentPoint = e.GetPosition(Application.Current.RootVisual);
injectedUI.ApplyDelta(currentPoint.X – capturePoint.X,
currentPoint.Y – capturePoint.Y);
capturePoint = currentPoint;
}
}
}
void OnMouseUp(object sender, MouseButtonEventArgs e)
{
this.AssociatedObject.ReleaseMouseCapture();
isDragging = false;if (injectedUI != null)
{
injectedUI.RemoveUI();
injectedUI = null;
HitTestAndInvokeTriggers(e.GetPosition(Application.Current.RootVisual));
}
}
void HitTestAndInvokeTriggers(Point p)
{
object dataObject = GetDataObject();foreach (DropTrigger trigger in GetDropTriggersOfFirstElement(p))
{
trigger.InvokeActions(dataObject);
}
}
object GetDataObject()
{
object dataObject = this.AssociatedObject;if (!string.IsNullOrEmpty(this.PropertyPath))
{
dataObject = null;Type t = this.AssociatedObject.GetType();
PropertyInfo propertyInfo = t.GetProperty(this.PropertyPath,
BindingFlags.Public | BindingFlags.Instance);if (propertyInfo != null)
{
dataObject = propertyInfo.GetValue(this.AssociatedObject, null);
}
}
return (dataObject);
}
static IEnumerable<DropTrigger> GetDropTriggersOfFirstElement(Point p)
{
var elements = VisualTreeHelper.FindElementsInHostCoordinates(p,
Application.Current.RootVisual);if (elements != null)
{
foreach (var item in elements)
{
var triggers = Expression.Interaction.GetTriggers(item);if ((triggers != null) && (triggers.Count > 0))
{
foreach (Expression.TriggerBase trigger in triggers)
{
if (trigger is DropTrigger)
{
yield return (DropTrigger)trigger;
}
}
break;
}
}
}
}
void OnMouseDown(object sender, MouseButtonEventArgs e)
{
if (!isDragging)
{
isDragging = true;
this.AssociatedObject.CaptureMouse();
}
}
bool isDragging;
DragDropUIInjector injectedUI;
Point capturePoint;
}
}
This uses a modified ( I think ) DragDropInjectorUI as below – you might spot that I’ve hacked in a little test code as I’ve been having some trouble with WriteableBitmap ( more on that below );
//#define dragImageTest
using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Windows.Media.Imaging;namespace MikesDragDropBits
{
internal class DragDropUIInjector
{
public DragDropUIInjector(FrameworkElement draggedUI, Point position)
{
CreateDragImage(draggedUI, position);
}
void CreateDragImage(FrameworkElement draggedUI, Point position)
{
#if dragImageTest
dragImage = new Rectangle()
{
Width = draggedUI.ActualWidth,
Height = draggedUI.ActualHeight,
Fill = new SolidColorBrush(Color.FromArgb(255, 255, 0, 0))
};
#else
WriteableBitmap picture = new WriteableBitmap(
(int)draggedUI.ActualWidth, (int)draggedUI.ActualHeight,
PixelFormats.Pbgra32);picture.Render(draggedUI, new TranslateTransform());
dragImage = new Image();
dragImage.Source = picture;
dragImage.Width = draggedUI.ActualWidth;
dragImage.Height = draggedUI.ActualHeight;
dragImage.Stretch = Stretch.Fill;
dragImage.IsHitTestVisible = false;
#endif // dragImageTestCanvas.SetLeft(dragImage, position.X);
Canvas.SetTop(dragImage, position.Y);
}
public void InjectUI()
{
originalContent =
UserControlContentAccessor.GetContent(
(UserControl)Application.Current.RootVisual);injectedGrid = new Grid();
injectedGrid.IsHitTestVisible = false;UserControlContentAccessor.SetContent(
(UserControl)Application.Current.RootVisual, injectedGrid);injectedGrid.Children.Add(originalContent);
dragCanvas = new Canvas()
{
Background = new SolidColorBrush(Color.FromArgb(0x10, 0, 0, 0))
};
dragCanvas.IsHitTestVisible = false;
dragCanvas.Children.Add(dragImage);injectedGrid.Children.Add(dragCanvas);
}
public void RemoveUI()
{
injectedGrid.Children.Clear();UserControlContentAccessor.SetContent(
(UserControl)Application.Current.RootVisual, originalContent);
}
internal void ApplyDelta(double X, double Y)
{
Canvas.SetLeft(dragImage, Canvas.GetLeft(dragImage) + X);
Canvas.SetTop(dragImage, Canvas.GetTop(dragImage) + Y);
}
Grid injectedGrid;
Canvas dragCanvas;
UIElement originalContent;#if dragImageTest
Rectangle dragImage;
#else
Image dragImage;
#endif // dragImageTest
}
}With that in place, I produced some very simple actions;
public class PanelAddChild : TargetedTriggerAction<Panel> { protected override void Invoke(object parameter) { this.Target.Children.Add((UIElement)parameter); } } public class PanelRemoveChild : TargetedTriggerAction<Panel> { protected override void Invoke(object parameter) { this.Target.Children.Remove((UIElement)parameter); } }
and that then allows me to use these pieces in a UI like this one below where I have a Grid called sourceGrid which contains an Ellipse with a DraggableBehavior on it. I have another Grid which is unnamed but has a DropTrigger on it with 2 actions – a PanelRemoveChild action which targets the sourceGrid and a PanelAddChild which has no TargetName and so targets the Grid itself;
<Grid
Grid.Row="1"
Margin="10">
<Grid.ColumnDefinitions>
<ColumnDefinition
Width="*" />
<ColumnDefinition
Width="*" />
</Grid.ColumnDefinitions>
<Grid
x:Name="sourceGrid">
<Ellipse
Width="96"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Height="96"
Fill="Orange">
<e:Interaction.Behaviors>
<m:DraggableBehavior />
</e:Interaction.Behaviors>
</Ellipse>
</Grid>
<Grid
Grid.Column="1"
Background="DarkGray">
<e:Interaction.Triggers>
<m:DropTrigger>
<m:PanelRemoveChild
TargetName="sourceGrid" />
<m:PanelAddChild />
</m:DropTrigger>
</e:Interaction.Triggers>
</Grid>
</Grid>
The net effect of this is that I can drag the Ellipse across to the second Grid and then drop it there where it becomes parented by that Grid;
A quick note: There’s a reason why I use HorizontalAlignment=Left, VerticalAlignment=Top on my Ellipse in that sample. I find that with WriteableBitmap in the current build I hit an offsetting problem where the resultant bitmap has its content offset if the content was offset in its original container. I strongly suspect that this will be different in later builds so I’ve not spent any time on it here.
With some other very simple actions;
public class ListBoxAddOne : TargetedTriggerAction<ListBox> { protected override void Invoke(object parameter) { this.Target.Items.Add(parameter); } } public class ListBoxRemoveOne : TargetedTriggerAction<ListBox> { protected override void Invoke(object parameter) { IList list = this.Target.Items as IList; if (list != null) { list.Remove(parameter); } } }
I can then build another little UI with a ListBox in it that’s data-bound to some Person objects ( FirstName, LastName, Age ) and a second ListBox like this;
<Grid Margin="10">
<Grid.ColumnDefinitions><ColumnDefinition
Width="*" />
<ColumnDefinition
Width="*" />
</Grid.ColumnDefinitions>
<ListBox
x:Name="sourceBox"
Margin="24,24,24,24"
ItemTemplate="{StaticResource DragTemplate}" />
<ListBox
x:Name="listBox"
Margin="24,24,24,24"
Grid.Column="1"
ItemTemplate="{StaticResource NonDragTemplate}">
<e:Interaction.Triggers>
<m:DropTrigger>
<m:ListBoxRemoveOne
TargetName="sourceBox" />
<m:ListBoxAddOne />
</m:DropTrigger>
</e:Interaction.Triggers>
</ListBox>
</Grid>
where the DragTemplate looks like this;
<DataTemplate
x:Key="DragTemplate"><StackPanel
Background="Red">
<e:Interaction.Behaviors>
<m:DraggableBehavior
PropertyPath="DataContext" />
</e:Interaction.Behaviors>
<TextBlock
Text="{Binding Path=FirstName}"
Margin="1"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Width="150" />
<TextBlock
Text="{Binding Path=LastName}"
Margin="1"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Width="150" />
<TextBlock
Text="{Binding Path=Age}"
Margin="1"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Width="150" />
</StackPanel>
</DataTemplate>
where the significant thing is that the PropertyPath on my DraggableBehavior is set to “DataContext” so that a drag operation will cause the bound Person instance to be dragged-dropped. The NonDragTemplate looks identical except that it does not have the Interaction.Behaviors property set on it for the StackPanel because I only want dragging from the one ListBox.
With that UI (where my ListBox named sourceBox has some data added to its Items collection ) I can then drag drop as in;
This is a long, long way from being perfect but it’s part-way to what I want to get to and a lot closer than the lost post.
The project with source-code is here for download if you want to take it further, I’m leaving it for the minute 🙂