/// <summary> /// Called when the user releases a drag. Moves the item within the source list and then resets everything. /// </summary> private void dragInterceptor_ManipulationCompleted(object sender, ManipulationCompletedEventArgs e) { if (dragItem == null) { return; } if (dropTargetIndex >= 0) { MoveItem(dragItem, dropTargetIndex); } if (dragItemContainer != null) { dragItemContainer.Visibility = Visibility.Visible; dragItemContainer.Opacity = 0; AnimateDrop(dragItemContainer); dragItemContainer = null; } dragScrollDelta = 0; dropTargetIndex = -1; ClearDropTarget(); }
/// <summary> /// Called when the user presses down on the transparent drag-interceptor. Identifies the targed /// drag handle and list item and prepares for a drag operation. /// </summary> private void dragInterceptor_ManipulationStarted(object sender, ManipulationStartedEventArgs e) { if (dragItem != null) { return; } if (itemsPanel == null) { var scrollItemsPresenter = (ItemsPresenter) scrollViewer.Content; itemsPanel = (Panel) VisualTreeHelper.GetChild(scrollItemsPresenter, 0); } GeneralTransform interceptorTransform = dragInterceptor.TransformToVisual( Application.Current.RootVisual); Point targetPoint = interceptorTransform.Transform(e.ManipulationOrigin); targetPoint = GetHostCoordinates(targetPoint); List<UIElement> targetElements = VisualTreeHelper.FindElementsInHostCoordinates( targetPoint, itemsPanel).ToList(); ReorderListBoxItem targetItemContainer = targetElements.OfType<ReorderListBoxItem>().FirstOrDefault(); if (targetItemContainer != null && targetElements.Contains(targetItemContainer.DragHandle)) { VisualStateManager.GoToState(targetItemContainer, ReorderListBoxItem.DraggingState, true); GeneralTransform targetItemTransform = targetItemContainer.TransformToVisual(dragInterceptor); Point targetItemOrigin = targetItemTransform.Transform(new Point(0, 0)); Canvas.SetLeft(dragIndicator, targetItemOrigin.X); Canvas.SetTop(dragIndicator, targetItemOrigin.Y); dragIndicator.Width = targetItemContainer.RenderSize.Width; dragIndicator.Height = targetItemContainer.RenderSize.Height; dragItemContainer = targetItemContainer; dragItem = dragItemContainer.Content; isDragItemSelected = dragItemContainer.IsSelected; dragInterceptorRect = interceptorTransform.TransformBounds( new Rect(new Point(0, 0), dragInterceptor.RenderSize)); dropTargetIndex = -1; } }
/// <summary> /// Creates a storyboard to animate the visible moves of a rearrange. /// </summary> private Storyboard CreateRearrangeStoryboard(IEnumerable<RearrangeItemInfo> visibleMoves, Duration animationDuration) { var storyboard = new Storyboard(); ReorderListBoxItem temporaryItemContainer = null; foreach (RearrangeItemInfo move in visibleMoves) { var itemSize = new Size(rearrangeCanvas.RenderSize.Width, move.Height); ReorderListBoxItem itemContainer = null; if (move.ToIndex >= 0) { itemContainer = (ReorderListBoxItem) ItemContainerGenerator.ContainerFromIndex(move.ToIndex); } if (itemContainer == null) { if (temporaryItemContainer == null) { temporaryItemContainer = new ReorderListBoxItem(); } itemContainer = temporaryItemContainer; itemContainer.Width = itemSize.Width; itemContainer.Height = itemSize.Height; rearrangeCanvas.Children.Add(itemContainer); PrepareContainerForItemOverride(itemContainer, move.Item); itemContainer.UpdateLayout(); } var itemSnapshot = new WriteableBitmap((int) itemSize.Width, (int) itemSize.Height); itemSnapshot.Render(itemContainer, null); itemSnapshot.Invalidate(); var itemImage = new Image(); itemImage.Width = itemSize.Width; itemImage.Height = itemSize.Height; itemImage.Source = itemSnapshot; itemImage.RenderTransform = new TranslateTransform(); rearrangeCanvas.Children.Add(itemImage); if (itemContainer == temporaryItemContainer) { rearrangeCanvas.Children.Remove(itemContainer); } if (!Double.IsNaN(move.FromY) && !Double.IsNaN(move.ToY)) { Canvas.SetTop(itemImage, move.FromY); if (move.FromY != move.ToY) { var moveAnimation = new DoubleAnimation(); moveAnimation.Duration = animationDuration; Storyboard.SetTarget(moveAnimation, itemImage.RenderTransform); Storyboard.SetTargetProperty(moveAnimation, new PropertyPath(TranslateTransform.YProperty)); moveAnimation.To = move.ToY - move.FromY; storyboard.Children.Add(moveAnimation); } } else if (Double.IsNaN(move.FromY) != Double.IsNaN(move.ToY)) { if (move.FromIndex >= 0 && move.ToIndex >= 0) { var moveAnimation = new DoubleAnimation(); moveAnimation.Duration = animationDuration; Storyboard.SetTarget(moveAnimation, itemImage.RenderTransform); Storyboard.SetTargetProperty(moveAnimation, new PropertyPath(TranslateTransform.YProperty)); const double animationDistance = 200; if (!Double.IsNaN(move.FromY)) { Canvas.SetTop(itemImage, move.FromY); if (move.FromIndex < move.ToIndex) { moveAnimation.To = animationDistance; } else if (move.FromIndex > move.ToIndex) { moveAnimation.To = -animationDistance; } } else { Canvas.SetTop(itemImage, move.ToY); if (move.FromIndex < move.ToIndex) { moveAnimation.From = -animationDistance; } else if (move.FromIndex > move.ToIndex) { moveAnimation.From = animationDistance; } } storyboard.Children.Add(moveAnimation); } var fadeAnimation = new DoubleAnimation(); fadeAnimation.Duration = animationDuration; Storyboard.SetTarget(fadeAnimation, itemImage); Storyboard.SetTargetProperty(fadeAnimation, new PropertyPath(OpacityProperty)); if (Double.IsNaN(move.FromY)) { itemImage.Opacity = 0.0; fadeAnimation.To = 1.0; Canvas.SetTop(itemImage, move.ToY); } else { itemImage.Opacity = 1.0; fadeAnimation.To = 0.0; Canvas.SetTop(itemImage, move.FromY); } storyboard.Children.Add(fadeAnimation); } } return storyboard; }
/// <summary> /// Slides the drag indicator (item snapshot) to the location of the dropped item, /// then performs the visibility swap and removes the dragging visual state. /// </summary> private void AnimateDrop(ReorderListBoxItem itemContainer) { GeneralTransform itemTransform = itemContainer.TransformToVisual(dragInterceptor); Rect itemRect = itemTransform.TransformBounds(new Rect(new Point(0, 0), itemContainer.RenderSize)); double delta = Math.Abs(itemRect.Y - Canvas.GetTop(dragIndicator) - ((TranslateTransform) dragIndicator.RenderTransform).Y); if (delta > 0) { // Adjust the duration based on the distance, so the speed will be constant. //Anton Sizikov: an OverflowExceptionOverflow when itemRect.Height equals to zero. TimeSpan duration; if (itemRect.Height == 0.0d) { duration = TimeSpan.FromSeconds(0); } else { double seconds = 0.25*delta/itemRect.Height; duration = TimeSpan.FromSeconds(seconds); } var dropStoryboard = new Storyboard(); var moveToDropAnimation = new DoubleAnimation(); Storyboard.SetTarget(moveToDropAnimation, dragIndicator.RenderTransform); Storyboard.SetTargetProperty(moveToDropAnimation, new PropertyPath(TranslateTransform.YProperty)); moveToDropAnimation.To = itemRect.Y - Canvas.GetTop(dragIndicator); moveToDropAnimation.Duration = duration; dropStoryboard.Children.Add(moveToDropAnimation); dropStoryboard.Completed += delegate { dragItem = null; itemContainer.Opacity = 1; dragIndicator.Visibility = Visibility.Collapsed; dragIndicator.Source = null; ((TranslateTransform) dragIndicator.RenderTransform).Y = 0; VisualStateManager.GoToState(itemContainer, ReorderListBoxItem.NotDraggingState, true); }; dropStoryboard.Begin(); } else { // There was no need for an animation, so do the visibility swap right now. dragItem = null; itemContainer.Opacity = 1; dragIndicator.Visibility = Visibility.Collapsed; dragIndicator.Source = null; VisualStateManager.GoToState(itemContainer, ReorderListBoxItem.NotDraggingState, true); } }
/// <summary> /// Ensures that a possibly-recycled item container (ReorderListBoxItem) is ready to display a list item. /// </summary> protected override void PrepareContainerForItemOverride(DependencyObject element, object item) { base.PrepareContainerForItemOverride(element, item); var itemContainer = (ReorderListBoxItem) element; itemContainer.ApplyTemplate(); // Loads visual states. // Set this state before binding to avoid showing the visual transition in this case. string reorderState = IsReorderEnabled ? ReorderListBoxItem.ReorderEnabledState : ReorderListBoxItem.ReorderDisabledState; VisualStateManager.GoToState(itemContainer, reorderState, false); itemContainer.SetBinding(ReorderListBoxItem.IsReorderEnabledProperty, new Binding(IsReorderEnabledPropertyName) {Source = this}); if (item == dragItem) { itemContainer.IsSelected = isDragItemSelected; VisualStateManager.GoToState(itemContainer, ReorderListBoxItem.DraggingState, false); if (dropTargetIndex >= 0) { // The item's dragIndicator is currently being moved, so the item itself is hidden. itemContainer.Visibility = Visibility.Collapsed; dragItemContainer = itemContainer; } else { itemContainer.Opacity = 0; Dispatcher.BeginInvoke(() => AnimateDrop(itemContainer)); } } else { VisualStateManager.GoToState(itemContainer, ReorderListBoxItem.NotDraggingState, false); } }
/// <summary> /// Called when an item container (ReorderListBoxItem) is being removed from the list panel. /// This may be because the item was removed from the list or because the item is now outside /// the virtualization region (because ListBox uses a VirtualizingStackPanel as its items panel). /// </summary> protected override void ClearContainerForItemOverride(DependencyObject element, object item) { base.ClearContainerForItemOverride(element, item); var itemContainer = (ReorderListBoxItem) element; if (itemContainer == dragItemContainer) { dragItemContainer.Visibility = Visibility.Visible; dragItemContainer = null; } }
/// <summary> /// Updates the targeted index -- that is the index where the item will be moved to if dropped at this point. /// </summary> private void UpdateDropTargetIndex(ReorderListBoxItem targetItemContainer, bool after) { int dragItemIndex = Items.IndexOf(dragItem); int targetItemIndex = Items.IndexOf(targetItemContainer.Content); int newDropTargetIndex; if (targetItemIndex == dragItemIndex) { newDropTargetIndex = dragItemIndex; } else { newDropTargetIndex = targetItemIndex + (after ? 1 : 0) - (targetItemIndex >= dragItemIndex ? 1 : 0); } if (newDropTargetIndex != dropTargetIndex) { dropTargetIndex = newDropTargetIndex; } }