ドラッグアンドドロップで並べ替えできるListBox
カスタムコントロールにするのはちょっと…
じゃあどうするか?ビヘイビアでしょ!
public class ListBoxDragDropBehavior : Behavior<ListBox> { private ListBoxItem dragItem { get; set; } private Point dragStartPosition { get; set; } private DragAdorner dragAdorner { get; set; } private int MoveItemIndex { get; set; } protected override void OnAttached() { base.OnAttached(); AssociatedObject.PreviewMouseLeftButtonDown += AssociatedObject_PreviewMouseLeftButtonDown; AssociatedObject.PreviewMouseMove += AssociatedObject_PreviewMouseMove; AssociatedObject.QueryContinueDrag += AssociatedObject_QueryContinueDrag; AssociatedObject.Drop += AssociatedObject_Drop; } protected override void OnDetaching() { base.OnDetaching(); AssociatedObject.PreviewMouseLeftButtonDown -= AssociatedObject_PreviewMouseLeftButtonDown; AssociatedObject.PreviewMouseMove -= AssociatedObject_PreviewMouseMove; AssociatedObject.QueryContinueDrag -= AssociatedObject_QueryContinueDrag; AssociatedObject.Drop -= AssociatedObject_Drop; } void AssociatedObject_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e) { var index = GetItemIndex(e.GetPosition(AssociatedObject)); if (index < 0) return; dragItem = AssociatedObject.ItemContainerGenerator.ContainerFromIndex(index) as ListBoxItem; dragStartPosition = e.GetPosition(dragItem); } void AssociatedObject_PreviewMouseMove(object sender, MouseEventArgs e) { MoveItemIndex = GetItemIndex(e.GetPosition(AssociatedObject)); if (MoveItemIndex < 0) return; var listboxitem = AssociatedObject.ItemContainerGenerator.ContainerFromIndex(MoveItemIndex) as ListBoxItem; if (e.LeftButton == MouseButtonState.Pressed && dragAdorner == null && dragItem == listboxitem) { var nowPos = e.GetPosition(listboxitem); if (Math.Abs(nowPos.X - dragStartPosition.X) > SystemParameters.MinimumHorizontalDragDistance || Math.Abs(nowPos.Y - dragStartPosition.Y) > SystemParameters.MinimumVerticalDragDistance) { var layer = AdornerLayer.GetAdornerLayer(AssociatedObject); dragAdorner = new DragAdorner(AssociatedObject, listboxitem, 0.5, dragStartPosition); layer.Add(dragAdorner); DragDrop.DoDragDrop(listboxitem, listboxitem, DragDropEffects.Move); layer.Remove(dragAdorner); dragAdorner = null; dragItem = null; } } } void AssociatedObject_QueryContinueDrag(object sender, QueryContinueDragEventArgs e) { if (dragAdorner != null) { var mousePosition = NativeMethods.GetMousePosition(AssociatedObject); var listboxPosition = (Window.GetWindow(AssociatedObject)).PointFromScreen(AssociatedObject.PointToScreen(new Point(0, 0))); dragAdorner.LeftOffset = mousePosition.X - listboxPosition.X; dragAdorner.TopOffset = mousePosition.Y - listboxPosition.Y; } } void AssociatedObject_Drop(object sender, DragEventArgs e) { var items = AssociatedObject.ItemsSource as IList<IDragDropItem>; var dropPosition = e.GetPosition(AssociatedObject); var moveItem = AssociatedObject.Items[MoveItemIndex] as IDragDropItem; for (int i = 0; i < AssociatedObject.Items.Count; i++) { var item = AssociatedObject.ItemContainerGenerator.ContainerFromIndex(i) as ListBoxItem; var pos = AssociatedObject.PointFromScreen(item.PointToScreen(new Point(0, item.ActualHeight / 2))); if (dropPosition.Y < pos.Y) { items.Remove(moveItem); items.Insert((MoveItemIndex < i) ? i - 1 : i, moveItem); return; } } int last = AssociatedObject.Items.Count - 1; items.Remove(moveItem); items.Add(moveItem); } //位置からインデックスを取得する private int GetItemIndex(Point position) { var result = VisualTreeHelper.HitTest(AssociatedObject, position); if (result != null) { var visual = result.VisualHit; while (visual != null) { if (visual is ListBoxItem) { return AssociatedObject.Items.IndexOf(((ListBoxItem)visual).Content); } visual = VisualTreeHelper.GetParent(visual); } } return -1; } }
ドラッグ中に表示される半透明のアレはAdornerを追加して実装しました
public class DragAdorner : Adorner { protected UIElement _child; private double DragPositionX; private double DragPositionY; public DragAdorner(UIElement adornedElement, UIElement dragItem, double opacity, Point dragPosition) : base(adornedElement) { var _brush = new VisualBrush(dragItem) { Opacity = opacity }; var b = VisualTreeHelper.GetDescendantBounds(dragItem); var r = new Rectangle() { Width = b.Width, Height = b.Height }; DragPositionX = dragPosition.X; DragPositionY = dragPosition.Y; r.Fill = _brush; _child = r; } private double _leftOffset; public double LeftOffset { get { return _leftOffset; } set { _leftOffset = value - DragPositionX; UpdatePosition(); } } private double _topOffset; public double TopOffset { get { return _topOffset; } set { _topOffset = value - DragPositionY; UpdatePosition(); } } private void UpdatePosition() { var adorner = this.Parent as AdornerLayer; if (adorner != null) { adorner.Update(this.AdornedElement); } } protected override Visual GetVisualChild(int index) { return _child; } protected override int VisualChildrenCount { get { return 1; } } protected override Size MeasureOverride(Size finalSize) { _child.Measure(finalSize); return _child.DesiredSize; } protected override Size ArrangeOverride(Size finalSize) { _child.Arrange(new Rect(_child.DesiredSize)); return finalSize; } public override GeneralTransform GetDesiredTransform(GeneralTransform transform) { var result = new GeneralTransformGroup(); result.Children.Add(base.GetDesiredTransform(transform)); result.Children.Add(new TranslateTransform(LeftOffset, TopOffset)); return result; } }
public static class NativeMethods { [StructLayout(LayoutKind.Sequential)] public struct POINT { public int X; public int Y; } [DllImport("user32.dll")] [return: MarshalAs(UnmanagedType.Bool)] private static extern bool GetCursorPos(out POINT lpPoint); [DllImport("user32.dll")] [return: MarshalAs(UnmanagedType.Bool)] private static extern bool ScreenToClient(IntPtr hWnd, ref POINT lpPoint); public static Point GetMousePosition(Visual v) { POINT p; GetCursorPos(out p); var source = HwndSource.FromVisual(v) as HwndSource; ScreenToClient(source.Handle, ref p); return new Point(p.X, p.Y); } }
ItemsSourceにはIList<IDragDropItem>なリストを入れて使います
public class DDItem : IDragDropItem { public DDItem(string text) { this.Text = text; } public string Text { get; set; } }
private ObservableSynchronizedCollection<IDragDropItem> _ListItems; public ObservableSynchronizedCollection<IDragDropItem> ListItems { get { return _ListItems; } set { if (_ListItems == value) return; _ListItems = value; RaisePropertyChanged(); } } public void Initialize() { ListItems = new ObservableSynchronizedCollection<IDragDropItem>(); ListItems.Add(new DDItem("hogehoge")); ListItems.Add(new DDItem("hugahuga")); ListItems.Add(new DDItem("foobar")); }
<ListBox AllowDrop="True" ItemsSource="{Binding ListItems}"> <ListBox.ItemTemplate> <DataTemplate> <Label Content="{Binding Text}" FontSize="24" /> </DataTemplate> </ListBox.ItemTemplate> <i:Interaction.Behaviors> <behaviors:ListBoxDragDropBehavior /> </i:Interaction.Behaviors> </ListBox>