I tried to take this post a little futher by trying to build a drag-and-drop behaviour. It’s still something that I’m working on.
I built a DraggableBehavior which is pretty similar to the one that’s found in the Sample Silverlight 3 Behaviors in the gallery. The difference between mine and that sample was that during a drag operation I don’t want the original UI to move. I want a copy of it to be moved. That is, if my original UI looks like this;
and I make the ellipse on the left hand side draggable then when I drag it I want to see;
ideally I’d also want to see a little “+” sign on it or similar but that could come later.
Now, in Silverlight 3 this feels like it might be do-able in the sense that I could make a copy of whatever is being dragged by getting it to draw itself into a WriteableBitmap and then just putting that into an Image and dragging the Image around. Fine…but where do I parent that Image (I’ll call it the drag image) as it’s dragged around?
I can (maybe) assume that my top level UI is a UserControl but, from there-on in, it’s hard to know whether you can find a Panel to inject the drag image. So, I took an approach that I took with this post a while ago which is to essentially;
- grab the Application.Current.RootVisual as a UserControl
- grab its current content C
- create a new Grid
- put the content C into the Grid as a Child
- add a new Canvas into the Grid as a Child on top of the content C
- put my drag image onto that Canvas
and then try to reverse that process when the drag finishes. With that in place, I can have a DraggableBehaviour as in;
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;
namespace MikesDragDropBits
{
public class DraggableBehavior : Expression.Behavior<FrameworkElement>
{
public DraggableBehavior()
{
}
protected override void OnAttached()
{
base.OnAttached();
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;
injectedUI.RemoveUI();
injectedUI = null;
}
void OnMouseDown(object sender, MouseButtonEventArgs e)
{
if (!isDragging)
{
isDragging = true;
this.AssociatedObject.CaptureMouse();
}
}
protected override void OnDetaching()
{
base.OnDetaching();
this.AssociatedObject.MouseLeftButtonDown -= OnMouseDown;
this.AssociatedObject.MouseLeftButtonUp -= OnMouseUp;
this.AssociatedObject.MouseMove -= OnMouseMove;
}
bool isDragging;
DragDropUIInjector injectedUI;
Point capturePoint;
}
}
and that’s using this little class to swap the UI in and out;
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)
{
WriteableBitmap picture = new WriteableBitmap(
(int)draggedUI.ActualWidth, (int)draggedUI.ActualHeight,
PixelFormats.Pbgra32);
picture.Render(draggedUI, new TranslateTransform()
{
X = 0 - position.X,
Y = 0 - position.Y
});
dragImage = new Image();
dragImage.Source = picture;
dragImage.Width = draggedUI.ActualWidth;
dragImage.Height = draggedUI.ActualHeight;
dragImage.Stretch = Stretch.Fill;
dragImage.IsHitTestVisible = false;
Canvas.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(16, 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;
Image dragImage;
Canvas dragCanvas;
UIElement originalContent;
}
}
and this class which tries to perform a trick that lets me set the content of a UserControl;
internal class UserControlContentAccessor : UserControl
{
public static UIElement GetContent(UserControl uc)
{
return ((UIElement)uc.GetValue(UserControl.ContentProperty));
}
public static void SetContent(UserControl uc, UIElement element)
{
uc.SetValue(UserControl.ContentProperty, element);
}
}
so that all seems ok and it gives me a basic drag experience.
The problem I get to from there is how to manage a drop? How do I look for drop-sites? Do I have them;
- Implement some interface e.g. IDropSite ? That’s tricky because how does a regular piece of UI implement that?
- Use some custom kind of Trigger e.g. DropTrigger.
can I avoid coupling my DraggableBehavior to a particular kind of Trigger like the DropTrigger?
What I’ve gone with for now is a DropTrigger;
public class DropTrigger : TriggerBase<DependencyObject>
{
public void InvokeActions(object o)
{
base.InvokeActions(o);
}
}
and then I can change my DraggableBehavior so that when the drag ends I try to see what UI element we are over and then to see if it has any DropTriggers and if I find any DropTriggers then I ask them to invoke actions as in;
void OnMouseUp(object sender, MouseButtonEventArgs e)
{
this.AssociatedObject.ReleaseMouseCapture();
isDragging = false;
injectedUI.RemoveUI();
injectedUI = null;
HitTestAndInvokeTriggers(e.GetPosition(Application.Current.RootVisual));
}
private void HitTestAndInvokeTriggers(Point p)
{
UIElement element = VisualTreeHelper.FindElementsInHostCoordinates(p,
Application.Current.RootVisual).FirstOrDefault();
if (element != null)
{
var triggers = Expression.Interaction.GetTriggers(element);
if (triggers != null)
{
foreach (Expression.TriggerBase trigger in triggers)
{
if (trigger is DropTrigger)
{
((DropTrigger)trigger).InvokeActions(null);
}
}
}
}
}
that I can take these into Blend and draw (e.g.) this circle and rectangle and then set the Circle so that it has DraggableBehavior as in;


and I can then go and add an action to my orange rectangle (I’m using the sample ShowMessageBoxAction);
and then configure that action to fire on my DropTrigger as in;
and with that in place, when I drag my circle to my rectangle and drop it I get;
What I’m now puzzling with is how do I transfer data from the drag site to the drop site and get it into the action running at the drop site? I’ve had a few attempts at this but nothing’s worked out quite right for me just yet.
Posted
Fri, May 8 2009 4:44 PM
by
mtaulty