/// <summary> /// When overridden in a derived class, participates in rendering operations that are directed by the layout system. /// The rendering instructions for this element are not used directly when this method is invoked, and are instead preserved for /// later asynchronous use by layout and drawing. /// </summary> /// <param name="drawingContext">The drawing instructions for a specific element. This context is provided to the layout system.</param> protected override void OnRender(DrawingContext drawingContext) { var dropInfo = DropInfo; if (dropInfo.VisualTarget is ItemsControl itemsControl) { // Get the position of the item at the insertion index. If the insertion point is // to be after the last item, then get the position of the last item and add an // offset later to draw it at the end of the list. var visualTargetItem = dropInfo.VisualTargetItem; var itemParent = visualTargetItem != null?ItemsControl.ItemsControlFromItemContainer(visualTargetItem) : itemsControl; // this could be happen with a thread scenario where items are removed very quickly if (itemParent == null) { return; } var itemsCount = itemParent.Items.Count; var index = Math.Min(dropInfo.InsertIndex, itemsCount - 1); var lastItemInGroup = false; var targetGroup = dropInfo.TargetGroup; if (targetGroup != null && targetGroup.IsBottomLevel && dropInfo.InsertPosition.HasFlag(RelativeInsertPosition.AfterTargetItem)) { var indexOf = targetGroup.Items.IndexOf(dropInfo.TargetItem); lastItemInGroup = indexOf == targetGroup.ItemCount - 1; if (lastItemInGroup && dropInfo.InsertIndex != itemsCount) { index--; } } var itemContainer = (UIElement)itemParent.ItemContainerGenerator.ContainerFromIndex(index); var showAlwaysDropTargetAdorner = itemContainer == null && DragDrop.GetShowAlwaysDropTargetAdorner(itemParent); if (showAlwaysDropTargetAdorner) { itemContainer = itemParent; } if (itemContainer != null) { var itemRect = new Rect(itemContainer.TranslatePoint(new Point(), AdornedElement), itemContainer.RenderSize); Point point1, point2; double rotation = 0; var viewportWidth = DropInfo.TargetScrollViewer?.ViewportWidth ?? double.MaxValue; var viewportHeight = DropInfo.TargetScrollViewer?.ViewportHeight ?? double.MaxValue; if (dropInfo.VisualTargetOrientation == Orientation.Vertical) { if ((dropInfo.InsertIndex == itemsCount) || lastItemInGroup) { if (itemsCount > 0) { itemRect.Y += itemContainer.RenderSize.Height; } else { if ((itemsControl as ListView)?.View is GridView) { var header = itemsControl.GetVisualDescendent <GridViewHeaderRowPresenter>(); if (header != null) { itemRect.Y += header.RenderSize.Height; } } else if (itemsControl is DataGrid) { var header = itemsControl.GetVisualDescendent <DataGridColumnHeadersPresenter>(); if (header != null) { itemRect.Y += header.RenderSize.Height; } } itemRect.Y += Pen.Thickness; } } var itemRectRight = Math.Min(itemRect.Right, viewportWidth); var itemRectLeft = itemRect.X < 0 ? 0 : itemRect.X; point1 = new Point(itemRectLeft, itemRect.Y); point2 = new Point(itemRectRight, itemRect.Y); } else { if (dropInfo.VisualTargetFlowDirection == FlowDirection.LeftToRight && dropInfo.InsertIndex == itemsCount) { if (itemsCount > 0) { itemRect.X += itemContainer.RenderSize.Width; } else { itemRect.X += Pen.Thickness; } } else if (dropInfo.VisualTargetFlowDirection == FlowDirection.RightToLeft && dropInfo.InsertIndex != itemsCount) { if (itemsCount > 0) { itemRect.X += itemContainer.RenderSize.Width; } else { itemRect.X += Pen.Thickness; } } var itemRectTop = itemRect.Y < 0 ? 0 : itemRect.Y; var itemRectBottom = Math.Min(itemRect.Bottom, viewportHeight); point1 = new Point(itemRect.X, itemRectTop); point2 = new Point(itemRect.X, itemRectBottom); rotation = 90; } drawingContext.DrawLine(Pen, point1, point2); DrawTriangle(drawingContext, point1, rotation); DrawTriangle(drawingContext, point2, 180 + rotation); } } }
/// <summary> /// Initializes a new instance of the DragInfo class. /// </summary> /// /// <param name="sender"> /// The sender of the mouse event that initiated the drag. /// </param> /// /// <param name="e"> /// The mouse event that initiated the drag. /// </param> public DragInfo(object sender, MouseButtonEventArgs e) { Effects = DragDropEffects.None; MouseButton = e.ChangedButton; VisualSource = sender as UIElement; DragStartPosition = e.GetPosition(VisualSource); DragDropCopyKeyState = DragDrop.GetDragDropCopyKeyState(VisualSource); var dataFormat = DragDrop.GetDataFormat(VisualSource); if (dataFormat != null) { DataFormat = dataFormat; } var sourceElement = e.OriginalSource as UIElement; // If we can't cast object as a UIElement it might be a FrameworkContentElement, if so try and use its parent. if (sourceElement == null && e.OriginalSource is FrameworkContentElement element) { sourceElement = element.Parent as UIElement; } if (sender is ItemsControl itemsControl) { SourceGroup = itemsControl.FindGroup(DragStartPosition); VisualSourceFlowDirection = itemsControl.GetItemsPanelFlowDirection(); UIElement item = null; if (sourceElement != null) { item = itemsControl.GetItemContainer(sourceElement); } if (item == null) { item = DragDrop.GetDragDirectlySelectedOnly(VisualSource) ? itemsControl.GetItemContainerAt(e.GetPosition(itemsControl)) : itemsControl.GetItemContainerAt(e.GetPosition(itemsControl), itemsControl.GetItemsPanelOrientation()); } if (item != null) { // Remember the relative position of the item being dragged PositionInDraggedItem = e.GetPosition(item); var itemParent = ItemsControl.ItemsControlFromItemContainer(item); if (itemParent != null) { SourceCollection = itemParent.ItemsSource ?? itemParent.Items; if (itemParent != itemsControl) { if (item is TreeViewItem tvItem) { var tv = tvItem.GetVisualAncestor <TreeView>(); if (tv != null && !Equals(tv, itemsControl) && !tv.IsDragSource()) { return; } } else if (itemsControl.ItemContainerGenerator.IndexFromContainer(itemParent) < 0 && !itemParent.IsDragSource()) { return; } } SourceIndex = itemParent.ItemContainerGenerator.IndexFromContainer(item); SourceItem = itemParent.ItemContainerGenerator.ItemFromContainer(item); } else { SourceIndex = -1; } var selectedItems = itemsControl.GetSelectedItems().OfType <object>().Where(i => i != CollectionView.NewItemPlaceholder).ToList(); SourceItems = selectedItems; // Some controls (I'm looking at you TreeView!) haven't updated their // SelectedItem by this point. Check to see if there 1 or less item in // the SourceItems collection, and if so, override the control's SelectedItems with the clicked item. // // The control has still the old selected items at the mouse down event, so we should check this and give only the real selected item to the user. if (selectedItems.Count <= 1 || SourceItem != null && !selectedItems.Contains(SourceItem)) { SourceItems = Enumerable.Repeat(SourceItem, 1); } VisualSourceItem = item; } else { SourceCollection = itemsControl.ItemsSource ?? itemsControl.Items; } } else { SourceItem = (sender as FrameworkElement)?.DataContext; if (SourceItem != null) { SourceItems = Enumerable.Repeat(SourceItem, 1); } VisualSourceItem = sourceElement; PositionInDraggedItem = sourceElement != null?e.GetPosition(sourceElement) : DragStartPosition; } if (SourceItems == null) { SourceItems = Enumerable.Empty <object>(); } }
/// <summary> /// Performs a drop. /// </summary> /// <param name="dropInfo">Information about the drop.</param> public virtual void Drop(IDropInfo dropInfo) { if (dropInfo?.DragInfo == null) { return; } var insertIndex = dropInfo.InsertIndex != dropInfo.UnfilteredInsertIndex ? dropInfo.UnfilteredInsertIndex : dropInfo.InsertIndex; var itemsControl = dropInfo.VisualTarget as ItemsControl; if (itemsControl != null) { if (itemsControl.Items is IEditableCollectionView editableItems) { var newItemPlaceholderPosition = editableItems.NewItemPlaceholderPosition; if (newItemPlaceholderPosition == NewItemPlaceholderPosition.AtBeginning && insertIndex == 0) { ++insertIndex; } else if (newItemPlaceholderPosition == NewItemPlaceholderPosition.AtEnd && insertIndex == itemsControl.Items.Count) { --insertIndex; } } } var destinationList = dropInfo.TargetCollection.TryGetList(); var data = ExtractData(dropInfo.Data).OfType <object>().ToList(); var copyData = ShouldCopyData(dropInfo); if (!copyData) { var sourceList = dropInfo.DragInfo.SourceCollection.TryGetList(); if (sourceList != null) { foreach (var o in data) { var index = sourceList.IndexOf(o); if (index != -1) { sourceList.RemoveAt(index); // so, is the source list the destination list too ? if (destinationList != null && Equals(sourceList, destinationList) && index < insertIndex) { --insertIndex; } } } } } if (destinationList != null) { var objects2Insert = new List <object>(); // check for cloning var cloneData = dropInfo.Effects.HasFlag(DragDropEffects.Copy) || dropInfo.Effects.HasFlag(DragDropEffects.Link); foreach (var o in data) { var obj2Insert = o; if (cloneData) { if (o is ICloneable cloneable) { obj2Insert = cloneable.Clone(); } } objects2Insert.Add(obj2Insert); destinationList.Insert(insertIndex++, obj2Insert); } var selectDroppedItems = itemsControl is TabControl || (itemsControl != null && DragDrop.GetSelectDroppedItems(itemsControl)); if (selectDroppedItems) { SelectDroppedItems(dropInfo, objects2Insert); } } }
/// <summary> /// Initializes a new instance of the DropInfo class. /// </summary> /// /// <param name="sender"> /// The sender of the drag event. /// </param> /// /// <param name="e"> /// The drag event. /// </param> /// /// <param name="dragInfo"> /// Information about the source of the drag, if the drag came from within the framework. /// </param> public DropInfo(object sender, DragEventArgs e, [CanBeNull] IDragInfo dragInfo) { DragInfo = dragInfo; KeyStates = e.KeyStates; var dataFormat = dragInfo?.DataFormat; Data = dataFormat != null && e.Data.GetDataPresent(dataFormat.Name) ? e.Data.GetData(dataFormat.Name) : e.Data; VisualTarget = sender as UIElement; // if there is no drop target, find another if (!VisualTarget.IsDropTarget()) { // try to find next element var element = VisualTarget.TryGetNextAncestorDropTargetElement(); if (element != null) { VisualTarget = element; } } // try find ScrollViewer if (VisualTarget is TabControl) { var tabPanel = VisualTarget.GetVisualDescendent <TabPanel>(); TargetScrollViewer = tabPanel?.GetVisualAncestor <ScrollViewer>(); } else { TargetScrollViewer = VisualTarget?.GetVisualDescendent <ScrollViewer>(); } TargetScrollingMode = VisualTarget != null?DragDrop.GetDropScrollingMode(VisualTarget) : ScrollingMode.Both; // visual target can be null, so give us a point... DropPosition = VisualTarget != null?e.GetPosition(VisualTarget) : new Point(); if (VisualTarget is TabControl) { if (!HitTestUtilities.HitTest4Type <TabPanel>(VisualTarget, DropPosition)) { return; } } if (VisualTarget is ItemsControl itemsControl) { //System.Diagnostics.Debug.WriteLine(">>> Name = {0}", itemsControl.Name); // get item under the mouse var item = itemsControl.GetItemContainerAt(DropPosition); var directlyOverItem = item != null; TargetGroup = itemsControl.FindGroup(DropPosition); VisualTargetOrientation = itemsControl.GetItemsPanelOrientation(); VisualTargetFlowDirection = itemsControl.GetItemsPanelFlowDirection(); if (item == null) { // ok, no item found, so maybe we can found an item at top, left, right or bottom item = itemsControl.GetItemContainerAt(DropPosition, VisualTargetOrientation); directlyOverItem = DropPosition.DirectlyOverElement(_item, itemsControl); } if (item == null && TargetGroup != null && TargetGroup.IsBottomLevel) { var itemData = TargetGroup.Items.FirstOrDefault(); if (itemData != null) { item = itemsControl.ItemContainerGenerator.ContainerFromItem(itemData) as UIElement; directlyOverItem = DropPosition.DirectlyOverElement(_item, itemsControl); } } if (item != null) { _itemParent = ItemsControl.ItemsControlFromItemContainer(item); VisualTargetOrientation = _itemParent.GetItemsPanelOrientation(); VisualTargetFlowDirection = _itemParent.GetItemsPanelFlowDirection(); InsertIndex = _itemParent.ItemContainerGenerator.IndexFromContainer(item); TargetCollection = _itemParent.ItemsSource ?? _itemParent.Items; var tvItem = item as TreeViewItem; if (directlyOverItem || tvItem != null) { VisualTargetItem = item; TargetItem = _itemParent.ItemContainerGenerator.ItemFromContainer(item); } var expandedTVItem = tvItem != null && tvItem.HasHeader /*&& tvItem.HasItems*/ && tvItem.IsExpanded; Size itemRenderSize; if (expandedTVItem) { itemRenderSize = tvItem.GetHeaderSize(); } else { itemRenderSize = item.RenderSize; } if (VisualTargetOrientation == Orientation.Vertical) { var currentYPos = e.GetPosition(item).Y; var targetHeight = itemRenderSize.Height; var topGap = targetHeight * 0.25; var bottomGap = targetHeight * 0.75; if (currentYPos > targetHeight / 2) { if (expandedTVItem && tvItem.HasItems && currentYPos < topGap && currentYPos > bottomGap) { VisualTargetItem = tvItem.ItemContainerGenerator.ContainerFromIndex(0) as UIElement; TargetItem = VisualTargetItem != null ? tvItem.ItemContainerGenerator.ItemFromContainer(VisualTargetItem) : null; TargetCollection = tvItem.ItemsSource ?? tvItem.Items; InsertIndex = 0; InsertPosition = RelativeInsertPosition.BeforeTargetItem; } else { InsertIndex++; InsertPosition = RelativeInsertPosition.AfterTargetItem; } } else { InsertPosition = RelativeInsertPosition.BeforeTargetItem; } if (currentYPos > topGap && currentYPos < bottomGap || (!tvItem.HasItems && expandedTVItem)) { if (tvItem != null) { TargetCollection = tvItem.ItemsSource ?? tvItem.Items; InsertIndex = TargetCollection?.OfType <object>().Count() ?? 0; } InsertPosition |= RelativeInsertPosition.TargetItemCenter; } //System.Diagnostics.Debug.WriteLine("==> DropInfo: pos={0}, idx={1}, Y={2}, Item={3}", this.InsertPosition, this.InsertIndex, currentYPos, item); } else { var currentXPos = e.GetPosition(item).X; var targetWidth = itemRenderSize.Width; if (VisualTargetFlowDirection == FlowDirection.RightToLeft) { if (currentXPos > targetWidth / 2) { InsertPosition = RelativeInsertPosition.BeforeTargetItem; } else { InsertIndex++; InsertPosition = RelativeInsertPosition.AfterTargetItem; } } else if (VisualTargetFlowDirection == FlowDirection.LeftToRight) { if (currentXPos > targetWidth / 2) { InsertIndex++; InsertPosition = RelativeInsertPosition.AfterTargetItem; } else { InsertPosition = RelativeInsertPosition.BeforeTargetItem; } } if (currentXPos > targetWidth * 0.25 && currentXPos < targetWidth * 0.75) { if (tvItem != null) { TargetCollection = tvItem.ItemsSource ?? tvItem.Items; InsertIndex = TargetCollection?.OfType <object>().Count() ?? 0; } InsertPosition |= RelativeInsertPosition.TargetItemCenter; } //System.Diagnostics.Debug.WriteLine("==> DropInfo: pos={0}, idx={1}, X={2}, Item={3}", this.InsertPosition, this.InsertIndex, currentXPos, item); } } else { TargetCollection = itemsControl.ItemsSource ?? itemsControl.Items; InsertIndex = itemsControl.Items.Count; //System.Diagnostics.Debug.WriteLine("==> DropInfo: pos={0}, item=NULL, idx={1}", this.InsertPosition, this.InsertIndex); } } else { VisualTargetItem = VisualTarget; } }