/// <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, DragInfo dragInfo) { this.DragInfo = dragInfo; this.KeyStates = e.KeyStates; var dataFormat = dragInfo?.DataFormat; this.Data = dataFormat != null && e.Data.GetDataPresent(dataFormat.Name) ? e.Data.GetData(dataFormat.Name) : e.Data; this.VisualTarget = sender as UIElement; // if there is no drop target, find another if (!this.VisualTarget.IsDropTarget()) { // try to find next element var element = this.VisualTarget.TryGetNextAncestorDropTargetElement(); if (element != null) { this.VisualTarget = element; } } // try find ScrollViewer if (this.VisualTarget is TabControl) { var tabPanel = this.VisualTarget.GetVisualDescendent <TabPanel>(); this.TargetScrollViewer = tabPanel?.GetVisualAncestor <ScrollViewer>(); } else { this.TargetScrollViewer = this.VisualTarget?.GetVisualDescendent <ScrollViewer>(); } this.TargetScrollingMode = this.VisualTarget != null?DragDrop.GetDropScrollingMode(this.VisualTarget) : ScrollingMode.Both; // visual target can be null, so give us a point... this.DropPosition = this.VisualTarget != null?e.GetPosition(this.VisualTarget) : new Point(); if (this.VisualTarget is TabControl) { if (!HitTestUtilities.HitTest4Type <TabPanel>(this.VisualTarget, this.DropPosition)) { return; } } if (this.VisualTarget is ItemsControl) { var itemsControl = (ItemsControl)this.VisualTarget; //System.Diagnostics.Debug.WriteLine(">>> Name = {0}", itemsControl.Name); // get item under the mouse item = itemsControl.GetItemContainerAt(this.DropPosition); var directlyOverItem = item != null; this.TargetGroup = itemsControl.FindGroup(this.DropPosition); this.VisualTargetOrientation = itemsControl.GetItemsPanelOrientation(); this.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(this.DropPosition, this.VisualTargetOrientation); directlyOverItem = DropPosition.DirectlyOverElement(this.item, itemsControl); } if (item == null && this.TargetGroup != null && this.TargetGroup.IsBottomLevel) { var itemData = this.TargetGroup.Items.FirstOrDefault(); if (itemData != null) { item = itemsControl.ItemContainerGenerator.ContainerFromItem(itemData) as UIElement; directlyOverItem = DropPosition.DirectlyOverElement(this.item, itemsControl); } } if (item != null) { itemParent = ItemsControl.ItemsControlFromItemContainer(item); this.VisualTargetOrientation = itemParent.GetItemsPanelOrientation(); this.VisualTargetFlowDirection = itemParent.GetItemsPanelFlowDirection(); this.InsertIndex = itemParent.ItemContainerGenerator.IndexFromContainer(item); this.TargetCollection = itemParent.ItemsSource ?? itemParent.Items; var tvItem = item as TreeViewItem; if (directlyOverItem || tvItem != null) { this.VisualTargetItem = item; this.TargetItem = itemParent.ItemContainerGenerator.ItemFromContainer(item); } var expandedTVItem = tvItem != null && tvItem.HasHeader && tvItem.HasItems && tvItem.IsExpanded; var itemRenderSize = expandedTVItem ? tvItem.GetHeaderSize() : item.RenderSize; if (this.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 && (currentYPos < topGap || currentYPos > bottomGap)) { this.VisualTargetItem = tvItem.ItemContainerGenerator.ContainerFromIndex(0) as UIElement; this.TargetItem = this.VisualTargetItem != null?tvItem.ItemContainerGenerator.ItemFromContainer(this.VisualTargetItem) : null; this.TargetCollection = tvItem.ItemsSource ?? tvItem.Items; this.InsertIndex = 0; this.InsertPosition = RelativeInsertPosition.BeforeTargetItem; } else { this.InsertIndex++; this.InsertPosition = RelativeInsertPosition.AfterTargetItem; } } else { this.InsertPosition = RelativeInsertPosition.BeforeTargetItem; } if (currentYPos > topGap && currentYPos < bottomGap) { if (tvItem != null) { this.TargetCollection = tvItem.ItemsSource ?? tvItem.Items; this.InsertIndex = this.TargetCollection != null?this.TargetCollection.OfType <object>().Count() : 0; } this.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 (this.VisualTargetFlowDirection == FlowDirection.RightToLeft) { if (currentXPos > targetWidth / 2) { this.InsertPosition = RelativeInsertPosition.BeforeTargetItem; } else { this.InsertIndex++; this.InsertPosition = RelativeInsertPosition.AfterTargetItem; } } else if (this.VisualTargetFlowDirection == FlowDirection.LeftToRight) { if (currentXPos > targetWidth / 2) { this.InsertIndex++; this.InsertPosition = RelativeInsertPosition.AfterTargetItem; } else { this.InsertPosition = RelativeInsertPosition.BeforeTargetItem; } } if (currentXPos > targetWidth * 0.25 && currentXPos < targetWidth * 0.75) { if (tvItem != null) { this.TargetCollection = tvItem.ItemsSource ?? tvItem.Items; this.InsertIndex = this.TargetCollection != null?this.TargetCollection.OfType <object>().Count() : 0; } this.InsertPosition |= RelativeInsertPosition.TargetItemCenter; } //System.Diagnostics.Debug.WriteLine("==> DropInfo: pos={0}, idx={1}, X={2}, Item={3}", this.InsertPosition, this.InsertIndex, currentXPos, item); } } else { this.TargetCollection = itemsControl.ItemsSource ?? itemsControl.Items; this.InsertIndex = itemsControl.Items.Count; //System.Diagnostics.Debug.WriteLine("==> DropInfo: pos={0}, item=NULL, idx={1}", this.InsertPosition, this.InsertIndex); } } else { this.VisualTargetItem = this.VisualTarget; } }
/// <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 = this.DropInfo; var itemsControl = dropInfo.VisualTarget as ItemsControl; if (itemsControl != null) { // 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; var visualTargetItem = dropInfo.VisualTargetItem; if (visualTargetItem != null) { itemParent = ItemsControl.ItemsControlFromItemContainer(visualTargetItem); } else { itemParent = 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(), this.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 += this.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 += this.Pen.Thickness; } } else if (dropInfo.VisualTargetFlowDirection == FlowDirection.RightToLeft && dropInfo.InsertIndex != itemsCount) { if (itemsCount > 0) { itemRect.X += itemContainer.RenderSize.Width; } else { itemRect.X += this.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(this.Pen, point1, point2); this.DrawTriangle(drawingContext, point1, rotation); this.DrawTriangle(drawingContext, point2, 180 + rotation); } } }
private static void DragSourceOnMouseMove(object sender, MouseEventArgs e) { var dragInfo = m_DragInfo; if (dragInfo != null && !m_DragInProgress) { // the start from the source var dragStart = dragInfo.DragStartPosition; // do nothing if mouse left/right button is released or the pointer is captured if (dragInfo.MouseButton == MouseButton.Left && e.LeftButton == MouseButtonState.Released) { m_DragInfo = null; return; } if (DragDrop.GetCanDragWithMouseRightButton(dragInfo.VisualSource) && dragInfo.MouseButton == MouseButton.Right && e.RightButton == MouseButtonState.Released) { m_DragInfo = null; return; } // current mouse position var position = e.GetPosition((IInputElement)sender); // prevent selection changing while drag operation dragInfo.VisualSource?.ReleaseMouseCapture(); // only if the sender is the source control and the mouse point differs from an offset if (dragInfo.VisualSource == sender && (Math.Abs(position.X - dragStart.X) > DragDrop.GetMinimumHorizontalDragDistance(dragInfo.VisualSource) || Math.Abs(position.Y - dragStart.Y) > DragDrop.GetMinimumVerticalDragDistance(dragInfo.VisualSource))) { dragInfo.RefreshSelectedItems(sender, e); var dragHandler = TryGetDragHandler(dragInfo, sender as UIElement); if (dragHandler.CanStartDrag(dragInfo)) { dragHandler.StartDrag(dragInfo); if (dragInfo.Effects != DragDropEffects.None) { var dataObject = dragInfo.DataObject; if (dataObject == null) { if (dragInfo.Data == null) { // it's bad if the Data is null, cause the DataObject constructor will raise an ArgumentNullException m_DragInfo = null; // maybe not necessary or should not set here to null return; } dataObject = new DataObject(dragInfo.DataFormat.Name, dragInfo.Data); } try { m_DragInProgress = true; var dragDropEffects = System.Windows.DragDrop.DoDragDrop(dragInfo.VisualSource, dataObject, dragInfo.Effects); if (dragDropEffects == DragDropEffects.None) { dragHandler.DragCancelled(); } dragHandler.DragDropOperationFinished(dragDropEffects, dragInfo); } catch (Exception ex) { if (!dragHandler.TryCatchOccurredException(ex)) { throw; } } finally { m_DragInProgress = false; m_DragInfo = null; } } } } } }
/// <summary> /// Performs a drop. /// </summary> /// <param name="dropInfo">Information about the drop.</param> public virtual void Drop(IDropInfo dropInfo) { if (dropInfo == null || dropInfo.DragInfo == null) { return; } var insertIndex = dropInfo.InsertIndex != dropInfo.UnfilteredInsertIndex ? dropInfo.UnfilteredInsertIndex : dropInfo.InsertIndex; var itemsControl = dropInfo.VisualTarget as ItemsControl; if (itemsControl != null) { var editableItems = itemsControl.Items as IEditableCollectionView; if (editableItems != null) { 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) { var cloneable = o as ICloneable; if (cloneable != null) { 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 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) { this.Effects = DragDropEffects.None; this.MouseButton = e.ChangedButton; this.VisualSource = sender as UIElement; this.DragStartPosition = e.GetPosition(this.VisualSource); this.DragDropCopyKeyState = DragDrop.GetDragDropCopyKeyState(this.VisualSource); var dataFormat = DragDrop.GetDataFormat(this.VisualSource); if (dataFormat != null) { this.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) { sourceElement = ((FrameworkContentElement)e.OriginalSource).Parent as UIElement; } if (sender is ItemsControl) { var itemsControl = (ItemsControl)sender; this.SourceGroup = itemsControl.FindGroup(this.DragStartPosition); this.VisualSourceFlowDirection = itemsControl.GetItemsPanelFlowDirection(); UIElement item = null; if (sourceElement != null) { item = itemsControl.GetItemContainer(sourceElement); } if (item == null) { if (DragDrop.GetDragDirectlySelectedOnly(this.VisualSource)) { item = itemsControl.GetItemContainerAt(e.GetPosition(itemsControl)); } else { item = itemsControl.GetItemContainerAt(e.GetPosition(itemsControl), itemsControl.GetItemsPanelOrientation()); } } if (item != null) { // Remember the relative position of the item being dragged this.PositionInDraggedItem = e.GetPosition(item); var itemParent = ItemsControl.ItemsControlFromItemContainer(item); if (itemParent != null) { this.SourceCollection = itemParent.ItemsSource ?? itemParent.Items; if (itemParent != itemsControl) { var tvItem = item as TreeViewItem; if (tvItem != null) { var tv = tvItem.GetVisualAncestor <TreeView>(); if (tv != null && tv != itemsControl && !tv.IsDragSource()) { return; } } else if (itemsControl.ItemContainerGenerator.IndexFromContainer(itemParent) < 0 && !itemParent.IsDragSource()) { return; } } this.SourceIndex = itemParent.ItemContainerGenerator.IndexFromContainer(item); this.SourceItem = itemParent.ItemContainerGenerator.ItemFromContainer(item); } else { this.SourceIndex = -1; } var selectedItems = itemsControl.GetSelectedItems().OfType <object>().Where(i => i != CollectionView.NewItemPlaceholder).ToList(); this.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 || this.SourceItem != null && !selectedItems.Contains(this.SourceItem)) { this.SourceItems = Enumerable.Repeat(this.SourceItem, 1); } this.VisualSourceItem = item; } else { this.SourceCollection = itemsControl.ItemsSource ?? itemsControl.Items; } } else { this.SourceItem = (sender as FrameworkElement)?.DataContext; if (this.SourceItem != null) { this.SourceItems = Enumerable.Repeat(this.SourceItem, 1); } this.VisualSourceItem = sourceElement; this.PositionInDraggedItem = sourceElement != null?e.GetPosition(sourceElement) : this.DragStartPosition; } if (this.SourceItems == null) { this.SourceItems = Enumerable.Empty <object>(); } }