Skip to content

Instantly share code, notes, and snippets.

@mfakane
Created May 5, 2012 12:25
Show Gist options
  • Save mfakane/2601988 to your computer and use it in GitHub Desktop.
Save mfakane/2601988 to your computer and use it in GitHub Desktop.
Heavymoon ListBox DragDrop Behavior
/*
<ListBox>
<i:Interaction.Behaviors>
<v:DragDropBehavior />
</i:Interaction.Behaviors>
</ListBox>
*/
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Interactivity;
using System.Windows.Media;
using System.Windows.Shapes;
namespace Linearstar.Heavymoon.Views
{
public class DragDropBehavior : Behavior<ItemsControl>
{
readonly CompositeDisposable observer = new CompositeDisposable();
public static readonly DependencyProperty OrientationProperty = DependencyProperty.Register("Orientation", typeof(Orientation), typeof(DragDropBehavior), new PropertyMetadata(Orientation.Vertical));
public static readonly DependencyProperty UseThumbProperty = DependencyProperty.Register("UseThumb", typeof(bool), typeof(DragDropBehavior), new PropertyMetadata(false));
public bool UseThumb
{
get
{
return (bool)GetValue(UseThumbProperty);
}
set
{
SetValue(UseThumbProperty, value);
}
}
public Orientation Orientation
{
get
{
return (Orientation)GetValue(OrientationProperty);
}
set
{
SetValue(OrientationProperty, value);
}
}
void OnDrag(FrameworkElement element)
{
}
void OnDrop(FrameworkElement element)
{
}
protected override void OnAttached()
{
observer.Add(Observable.FromEvent<MouseButtonEventHandler, MouseButtonEventArgs>(_ => (sender, e) => _(e), _ => this.AssociatedObject.PreviewMouseDown += _, _ => this.AssociatedObject.PreviewMouseDown -= _)
.Where(_ => _.LeftButton == MouseButtonState.Pressed)
.Select(_ => new
{
EventArgs = _,
Container = this.AssociatedObject.ContainerFromElement((DependencyObject)_.OriginalSource),
})
.Where(_ => _.Container != null)
.Where(_ => !this.UseThumb || FindDescendant<Thumb>(_.Container).Any(t => t.IsMouseOver))
.Where(_ => FindDescendant<ScrollBar>(_.Container).All(t => !t.IsMouseOver))
.Where(_ => !FindDescendant<ItemsControl>(_.Container).Select(d => Tuple.Create(d, Interaction.GetBehaviors(d))).Any(d => d.Item2.OfType<DragDropBehavior>().Any(b => b != this) && d.Item1.ContainerFromElement((DependencyObject)_.EventArgs.OriginalSource) != null))
.Select(_ => new
{
Point = _.EventArgs.GetPosition(this.AssociatedObject),
_.Container,
})
.SelectMany(pt => Observable.FromEvent<MouseEventHandler, MouseEventArgs>(_ => (sender, e) => _(e), _ => this.AssociatedObject.PreviewMouseMove += _, _ => this.AssociatedObject.PreviewMouseMove -= _)
.TakeUntil(Observable.FromEvent<MouseButtonEventHandler, MouseButtonEventArgs>(_ => (sender, e) => _(e), _ => this.AssociatedObject.PreviewMouseUp += _, _ => this.AssociatedObject.PreviewMouseUp -= _))
.Select(_ => new
{
EventArgs = _,
Items = this.AssociatedObject is Selector
? this.AssociatedObject.Items.Cast<object>().Select(i => new
{
Container = this.AssociatedObject.ItemContainerGenerator.ContainerFromItem(i),
Item = i
}).Where(i => Selector.GetIsSelected(i.Container)).Select(i => i.Item).ToArray()
: EnumerableEx.Return(this.AssociatedObject.ItemContainerGenerator.ItemFromContainer(pt.Container)),
Vector = pt.Point - _.GetPosition(this.AssociatedObject),
pt.Container,
})
.Where(_ => this.Orientation == Orientation.Horizontal && Math.Abs(_.Vector.X) > SystemParameters.MinimumHorizontalDragDistance || this.Orientation == Orientation.Vertical && Math.Abs(_.Vector.Y) > SystemParameters.MinimumVerticalDragDistance)
.Take(1))
.Subscribe(e =>
{
var layer = AdornerLayer.GetAdornerLayer(this.AssociatedObject);
var da = new DragAdorner(this.AssociatedObject, this.Orientation, FindAncestor<Panel>(e.Container).First());
layer.Add(da);
using (var adorner = FinallyBlock.Create(da, layer.Remove))
using (Observable.FromEvent<QueryContinueDragEventHandler, QueryContinueDragEventArgs>(_ => (sender, e2) => _(e2), _ => this.AssociatedObject.QueryContinueDrag += _, _ => this.AssociatedObject.QueryContinueDrag -= _)
.Subscribe(_ =>
{
IList list;
FrameworkElement container;
object item;
if (FindDestination(out list, out container, out item, CursorInfo.GetPosition))
{
((DragAdorner)adorner).Visibility = Visibility.Visible;
if (this.Orientation == Orientation.Horizontal)
{
var margin = (container.Margin.Left + container.Margin.Right) / 2;
((DragAdorner)adorner).X = container.TranslatePoint(new Point(CursorInfo.GetPosition(container).X > container.ActualWidth / 2 ? container.ActualWidth + margin : -margin, 0), this.AssociatedObject).X;
}
else
{
var margin = (container.Margin.Top + container.Margin.Bottom) / 2;
((DragAdorner)adorner).Y = container.TranslatePoint(new Point(0, CursorInfo.GetPosition(container).Y > container.ActualHeight / 2 ? container.ActualHeight + margin : -margin), this.AssociatedObject).Y;
}
}
else
((DragAdorner)adorner).Visibility = Visibility.Collapsed;
}))
using (Observable.FromEvent<DragEventHandler, DragEventArgs>(_ => (sender, e2) => _(e2), _ => this.AssociatedObject.Drop += _, _ => this.AssociatedObject.Drop -= _)
.Where(_ => _.Data.GetDataPresent(typeof(DragDropInfo)))
.Select(_ => new
{
EventArgs = _,
Data = (DragDropInfo)_.Data.GetData(typeof(DragDropInfo)),
})
.Subscribe(p =>
{
IList list;
FrameworkElement container;
object item;
var found = FindDestination(out list, out container, out item, CursorInfo.GetPosition);
var move = list.GetType().GetMethod("Move", new[] { typeof(int), typeof(int), });
if (found)
{
var down = this.Orientation == Orientation.Horizontal
? p.EventArgs.GetPosition(container).X > container.ActualWidth / 2
: p.EventArgs.GetPosition(container).Y > container.ActualHeight / 2;
p.Data.Items.Where(_ => _ != item && !object.Equals(_, item)).ForEach(_ =>
{
if (move != null)
{
var idx = list.IndexOf(_);
var target = list.IndexOf(item);
target += target > idx ? down ? 0 : -1 : down ? 1 : 0;
if (idx != target)
move.Invoke(list, new object[] { idx, Math.Max(target, 0) });
}
else
{
list.Remove(_);
list.Insert(list.IndexOf(item) + (down ? 1 : 0), _);
}
});
}
else if (move != null)
p.Data.Items.ForEach(_ => move.Invoke(list, new object[] { list.IndexOf(_), list.Count - 1 }));
else
{
p.Data.Items.ForEach(list.Remove);
p.Data.Items.ForEach(_ => list.Add(_));
}
}))
using (Observable.FromEvent<DragEventHandler, DragEventArgs>(_ => (sender, e2) => _(e2), _ => this.AssociatedObject.DragOver += _, _ => this.AssociatedObject.DragOver -= _)
.Where(_ => _.Data.GetDataPresent(typeof(DragDropInfo)))
.Subscribe(_ => _.Effects = DragDropEffects.Move))
using (FinallyBlock.Create(this.AssociatedObject.AllowDrop, _ => this.AssociatedObject.AllowDrop = _))
{
this.AssociatedObject.AllowDrop = true;
DragDrop.DoDragDrop(this.AssociatedObject, new DragDropInfo(e.Items), DragDropEffects.Move);
}
}));
base.OnAttached();
}
static IEnumerable<T> FindDescendant<T>(DependencyObject self)
where T : DependencyObject
{
return Enumerable.Range(0, VisualTreeHelper.GetChildrenCount(self))
.SelectMany(_ => FindDescendant<T>(VisualTreeHelper.GetChild(self, _)).Cast<DependencyObject>())
.StartWith(self)
.OfType<T>();
}
static IEnumerable<T> FindAncestor<T>(DependencyObject self)
where T : DependencyObject
{
var d = self;
while ((d = VisualTreeHelper.GetParent(d)) != null)
if (d is T)
yield return (T)d;
}
bool FindDestination(out IList list, out FrameworkElement container, out object item, Func<FrameworkElement, Point> getPosition)
{
list = this.AssociatedObject.ItemsSource as IList ?? this.AssociatedObject.Items;
var rt = list.Cast<object>()
.Select((_, i) => Tuple.Create(_ as FrameworkElement ?? (FrameworkElement)this.AssociatedObject.ItemContainerGenerator.ContainerFromIndex(i), _))
.Where(_ => _.Item1 != null)
.LastOrDefault(_ => VisualTreeHelper.HitTest(_.Item1, getPosition(_.Item1)) != null);
if (rt != null)
{
container = rt.Item1;
item = rt.Item2;
return true;
}
else
{
container = null;
item = null;
return false;
}
}
protected override void OnDetaching()
{
observer.Dispose();
base.OnDetaching();
}
static class CursorInfo
{
[DllImport("user32")]
static extern void GetCursorPos(out POINT pt);
struct POINT
{
public int X;
public int Y;
}
public static Point GetPosition(Visual v)
{
POINT p;
GetCursorPos(out p);
return v.PointFromScreen(new Point(p.X, p.Y));
}
}
class DragDropInfo
{
public IEnumerable<object> Items
{
get;
private set;
}
public DragDropInfo(IEnumerable<object> items)
{
this.Items = items;
}
}
class DragAdorner : Adorner
{
readonly Line visual;
double x;
double y;
public DragAdorner(FrameworkElement adornerElement, Orientation orientation, Panel panel)
: base(adornerElement)
{
visual = new Line
{
X1 = 0,
Y1 = 0,
X2 = orientation == Orientation.Vertical ? 1 : 0,
Y2 = orientation == Orientation.Vertical ? 0 : 1,
Width = orientation == Orientation.Vertical ? panel.ActualWidth : double.NaN,
Height = orientation == Orientation.Vertical ? double.NaN : panel.ActualHeight,
Stretch = Stretch.Fill,
StrokeThickness = 1,
Stroke = (Brush)App.Current.FindResource("MetroAccentBrush"),
};
}
protected override int VisualChildrenCount
{
get
{
return 1;
}
}
public double X
{
get
{
return x;
}
set
{
x = value;
if (this.Parent != null)
((AdornerLayer)this.Parent).Update(this.AdornedElement);
}
}
public double Y
{
get
{
return y;
}
set
{
y = value;
if (this.Parent != null)
((AdornerLayer)this.Parent).Update(this.AdornedElement);
}
}
protected override Visual GetVisualChild(int index)
{
return visual;
}
protected override Size MeasureOverride(Size constraint)
{
visual.Measure(constraint);
return visual.DesiredSize;
}
protected override Size ArrangeOverride(Size finalSize)
{
visual.Arrange(new Rect(visual.DesiredSize));
return finalSize;
}
public override GeneralTransform GetDesiredTransform(GeneralTransform transform)
{
var rt = new GeneralTransformGroup();
rt.Children.Add(base.GetDesiredTransform(transform));
rt.Children.Add(new TranslateTransform(x, y));
return rt;
}
}
class FinallyBlock<T> : IDisposable
{
T value;
Action<T> dispose;
public FinallyBlock(T value, Action<T> dispose)
{
this.value = value;
this.dispose = dispose;
}
public void Dispose()
{
dispose(value);
}
public static implicit operator T(FinallyBlock<T> self)
{
return self.value;
}
}
static class FinallyBlock
{
public static FinallyBlock<object> Create(Action post)
{
return Create(null, post);
}
public static FinallyBlock<object> Create(Action pre, Action post)
{
if (pre != null)
pre();
return new FinallyBlock<object>(null, _ => post());
}
public static FinallyBlock<T> Create<T>(T value, Action<T> dispose)
{
return new FinallyBlock<T>(value, dispose);
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment