/// <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. ItemsControl itemParent; // Set to drop target's items container var visualTargetItem = dropInfo.VisualTargetItem; 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) { // AdornedElement: control's ItemsPresenter var itemRect = new Rect(itemContainer.TranslatePoint(new Point(), AdornedElement), itemContainer.RenderSize); Point point1, point2; double rotation = 0; // I really don't know why I did this // // var viewportWidth = double.MaxValue; // var viewportHeight = double.MaxValue; // if (DropInfo.TargetScrollViewer != null) // { // if (DropInfo.TargetScrollViewer.ScrollableWidth != 0) // { // viewportWidth = DropInfo.TargetScrollViewer.ViewportWidth; // } // // if (DropInfo.TargetScrollViewer.ScrollableHeight != 0) // { // viewportHeight = DropInfo.TargetScrollViewer.ViewportHeight; // } // } 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.FindVisualParent <GridViewHeaderRowPresenter>(); if (header != null) { itemRect.Y += header.RenderSize.Height; } } else if (itemsControl is DataGrid) { var header = itemsControl.FindVisualParent <DataGridColumnHeadersPresenter>(); if (header != null) { itemRect.Y += header.RenderSize.Height; } } itemRect.Y += Pen.Thickness; } } var itemRectRight = itemRect.Right; //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 = itemRect.Bottom; //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> /// Default Constructor /// Initializes a new instance of the DragInfo class. /// <param name="sender">The sender of the mouse event that initiated the drag.</param> /// <param name="e">The mouse event args that initiated the drag.</param> /// </summary> public DragInfo(object sender, MouseButtonEventArgs e) { // Set Default properties Effects = DragDropEffects.None; MouseButton = e.ChangedButton; VisualSource = sender as UIElement; DragStartPosition = e.GetPosition(VisualSource); DragDropCopyKeyState = DragDrop.GetDragDropCopyKeyState(VisualSource); // Set data format 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 (e.OriginalSource is FrameworkContentElement frameworkContentElement) { sourceElement = frameworkContentElement.Parent as UIElement; } // If source control is normal items control... 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.FindVisualParent <TreeView>(); if (tv != null && 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 || SourceItems != null && !selectedItems.Contains(SourceItem)) { SourceItems = Enumerable.Repeat(SourceItem, 1); } VisualSourceItem = item; } else { SourceCollection = itemsControl.ItemsSource ?? itemsControl.Items; } } // we clicked a item element else { SourceItem = (sender as FrameworkElement)?.DataContext; if (SourceItem != null) { SourceItems = Enumerable.Repeat(SourceItems, 1); } VisualSourceItem = sourceElement; PositionInDraggedItem = sourceElement != null?e.GetPosition(sourceElement) : DragStartPosition; } if (SourceItems == null) { SourceItems = Enumerable.Empty <object>(); } }
/// <summary> /// Initializes a new instance of the DropInfo class. /// <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> /// <param name="eventType">The type of the underlying event (tunneled or bubbled).</param> /// </summary> public DropInfo(object sender, DragEventArgs e, [CanBeNull] DragInfo dragInfo, EventType eventType) { DragInfo = dragInfo; KeyStates = e.KeyStates; EventType = eventType; 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 var dropTargetScrollViewer = DragDrop.GetDropTargetScrollViewer(VisualTarget); if (dropTargetScrollViewer != null) { TargetScrollViewer = dropTargetScrollViewer; } else if (VisualTarget is TabControl) { var tabPanel = VisualTarget.FindVisualChild <TabPanel>(); TargetScrollViewer = tabPanel?.FindVisualParent <ScrollViewer>(); } else { TargetScrollViewer = VisualTarget?.FindVisualChild <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 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; var itemRenderSize = expandedTVItem ? tvItem.GetHeaderSize() : item.RenderSize; if (VisualTargetOrientation == Orientation.Vertical) { // Get the current position relative to the items container var currentYPos = e.GetPosition(item).Y; // Get items container height var targetHeight = itemRenderSize.Height; var topGap = targetHeight * 0.25; var bottomGap = targetHeight * 0.75; if (currentYPos > targetHeight / 2) { // If items control is tree viewer if (expandedTVItem && (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) { if (tvItem != null) { TargetCollection = tvItem.ItemsSource ?? tvItem.Items; InsertIndex = TargetCollection != null?TargetCollection.OfType <object>().Count() : 0; } InsertPosition |= RelativeInsertPosition.TargetItemCenter; } //System.Diagnostics.Debug.WriteLine("==> DropInfo: pos={0}, idx={1}, Y={2}, Item={3}", InsertPosition, 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 != null?TargetCollection.OfType <object>().Count() : 0; } InsertPosition |= RelativeInsertPosition.TargetItemCenter; } //System.Diagnostics.Debug.WriteLine("==> DropInfo: pos={0}, idx={1}, X={2}, Item={3}", InsertPosition, InsertIndex, currentXPos, item); } } else { TargetCollection = itemsControl.ItemsSource ?? itemsControl.Items; InsertIndex = itemsControl.Items.Count; //System.Diagnostics.Debug.WriteLine("==> DropInfo: pos={0}, item=NULL, idx={1}", InsertPosition, InsertIndex); } } else { VisualTargetItem = VisualTarget; } }
/// <summary> /// Determines whether the given element is ignored on drop action (<see cref="DragDrop.IsDropTarget"/>). /// </summary> /// <param name="element">The given element.</param> /// <returns>Element is ignored or not.</returns> public static bool IsDropTarget(this UIElement element) { return(element != null && DragDrop.GetIsDropTarget(element)); }
/// <summary> /// Determines whether the given element is ignored on drop action (<see cref="DragDrop.IsDragSource"/>). /// </summary> /// <param name="element">The given element.</param> /// <returns>Element is ignored or not.</returns> public static bool IsDragSource(this UIElement element) { return(element != null && DragDrop.GetIsDragSource(element)); }
/// <inheritdoc /> public virtual void Drop(IDropInfo dropInfo) { if (dropInfo == null || dropInfo.DragInfo == null) { return; } var insertIndex = dropInfo.UnfilteredInsertIndex; var itemsControl = dropInfo.VisualTarget as ItemsControl; 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 != null && DragDrop.GetSelectDroppedItems(itemsControl)); if (selectDroppedItems) { SelectDroppedItems(dropInfo, objects2Insert); } } }