Created
May 5, 2012 12:25
-
-
Save mfakane/2601988 to your computer and use it in GitHub Desktop.
Heavymoon ListBox DragDrop Behavior
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
<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