diff --git a/src/GongSolutions.WPF.DragDrop/DragDrop.cs b/src/GongSolutions.WPF.DragDrop/DragDrop.cs index 8debed4b..2d32ffb5 100644 --- a/src/GongSolutions.WPF.DragDrop/DragDrop.cs +++ b/src/GongSolutions.WPF.DragDrop/DragDrop.cs @@ -14,89 +14,64 @@ namespace GongSolutions.Wpf.DragDrop { public static partial class DragDrop { - private static DragDropPreview GetDragDropPreview(DropInfo dropInfo, UIElement target) + internal static DataTemplate TryGetDragAdornerTemplate(UIElement source, UIElement sender) { - var dragInfo = dropInfo.DragInfo; - var template = GetDropAdornerTemplate(dropInfo.VisualTarget) ?? GetDragAdornerTemplate(dragInfo.VisualSource); - var templateSelector = GetDropAdornerTemplateSelector(dropInfo.VisualTarget) ?? GetDragAdornerTemplateSelector(dragInfo.VisualSource); - - UIElement adornment = null; - - var useDefaultDragAdorner = template == null && templateSelector == null && GetUseDefaultDragAdorner(dragInfo.VisualSource); - var useVisualSourceItemSizeForDragAdorner = GetUseVisualSourceItemSizeForDragAdorner(dragInfo.VisualSource); - - if (useDefaultDragAdorner) + var template = source is not null ? GetDragAdornerTemplate(source) : null; + if (template is null && sender is not null) { - template = dragInfo.VisualSourceItem.GetCaptureScreenDataTemplate(dragInfo.VisualSourceFlowDirection); + template = GetDragAdornerTemplate(sender); } - if (template != null || templateSelector != null) - { - if (dragInfo.Data is IEnumerable items && !(items is string)) - { - var itemsCount = items.Cast().Count(); - var maxItemsCount = TryGetDragPreviewMaxItemsCount(dragInfo, target); - if (!useDefaultDragAdorner && itemsCount <= maxItemsCount) - { - var itemsControl = new ItemsControl(); - - // sort items if necessary before creating the preview - var sorter = TryGetDragPreviewItemsSorter(dragInfo, target); - if (sorter != null) - { - itemsControl.ItemsSource = sorter.SortDragPreviewItems(items); - } - else - { - itemsControl.ItemsSource = items; - } + return template; + } - itemsControl.ItemTemplate = template; - itemsControl.ItemTemplateSelector = templateSelector; - itemsControl.Tag = dragInfo; + internal static DataTemplateSelector TryGetDragAdornerTemplateSelector(UIElement source, UIElement sender) + { + var templateSelector = source is not null ? GetDragAdornerTemplateSelector(source) : null; + if (templateSelector is null && sender is not null) + { + templateSelector = GetDragAdornerTemplateSelector(sender); + } - if (useVisualSourceItemSizeForDragAdorner) - { - var bounds = VisualTreeExtensions.GetVisibleDescendantBounds(dragInfo.VisualSourceItem); - itemsControl.SetValue(FrameworkElement.MinWidthProperty, bounds.Width); - } + return templateSelector; + } - // The ItemsControl doesn't display unless we create a grid to contain it. - // Not quite sure why we need this... - var grid = new Grid(); - grid.Children.Add(itemsControl); - adornment = grid; - } - } - else - { - var contentPresenter = new ContentPresenter(); - contentPresenter.Content = dragInfo.Data; - contentPresenter.ContentTemplate = template; - contentPresenter.ContentTemplateSelector = templateSelector; - contentPresenter.Tag = dragInfo; + internal static DataTemplate TryGetDropAdornerTemplate(UIElement source, UIElement sender) + { + var template = source is not null ? GetDropAdornerTemplate(source) : null; + if (template is null && sender is not null) + { + template = GetDropAdornerTemplate(sender); + } - if (useVisualSourceItemSizeForDragAdorner) - { - var bounds = VisualTreeExtensions.GetVisibleDescendantBounds(dragInfo.VisualSourceItem); - contentPresenter.SetValue(FrameworkElement.MinWidthProperty, bounds.Width); - contentPresenter.SetValue(FrameworkElement.MinHeightProperty, bounds.Height); - } + return template; + } - adornment = contentPresenter; - } + internal static DataTemplateSelector TryGetDropAdornerTemplateSelector(UIElement source, UIElement sender) + { + var templateSelector = source is not null ? GetDropAdornerTemplateSelector(source) : null; + if (templateSelector is null && sender is not null) + { + templateSelector = GetDropAdornerTemplateSelector(sender); } - if (adornment != null) + return templateSelector; + } + + private static DragDropPreview GetDragDropPreview(IDragInfo dragInfo, UIElement visualTarget, UIElement sender) + { + var visualSource = dragInfo?.VisualSource; + if (visualSource is null) { - if (useDefaultDragAdorner) - { - adornment.Opacity = GetDefaultDragAdornerOpacity(dragInfo.VisualSource); - } + return null; + } - var rootElement = TryGetRootElementFinder(target).FindRoot(dropInfo.VisualTarget ?? dragInfo.VisualSource); + var rootElement = TryGetRootElementFinder(sender).FindRoot(visualTarget ?? visualSource); - var preview = new DragDropPreview(rootElement, adornment, GetDragAdornerTranslation(dragInfo.VisualSource), GetDragMouseAnchorPoint(dragInfo.VisualSource)) { IsOpen = true }; + var preview = new DragDropPreview(rootElement, dragInfo, visualTarget ?? visualSource, sender); + if (preview.Child != null) + { + preview.IsOpen = true; return preview; } @@ -106,7 +81,7 @@ private static DragDropPreview GetDragDropPreview(DropInfo dropInfo, UIElement t private static DragDropEffectPreview GetDragDropEffectPreview(DropInfo dropInfo, UIElement sender) { var dragInfo = dropInfo.DragInfo; - var template = GetDragDropEffecTemplate(dragInfo.VisualSource, dropInfo); + var template = GetDragDropEffectTemplate(dragInfo.VisualSource, dropInfo); if (template != null) { @@ -114,14 +89,18 @@ private static DragDropEffectPreview GetDragDropEffectPreview(DropInfo dropInfo, var adornment = new ContentPresenter { Content = dragInfo.Data, ContentTemplate = template }; - var preview = new DragDropEffectPreview(rootElement, adornment, GetEffectAdornerTranslation(dragInfo.VisualSource), dropInfo.Effects, dropInfo.EffectText, dropInfo.DestinationText) { IsOpen = true }; + var preview = new DragDropEffectPreview(rootElement, adornment, GetEffectAdornerTranslation(dragInfo.VisualSource), dropInfo.Effects, dropInfo.EffectText, dropInfo.DestinationText) + { + IsOpen = true + }; + return preview; } return null; } - private static DataTemplate GetDragDropEffecTemplate(UIElement target, DropInfo dropInfo) + private static DataTemplate GetDragDropEffectTemplate(UIElement target, DropInfo dropInfo) { if (target is null) { @@ -303,7 +282,7 @@ private static IRootElementFinder TryGetRootElementFinder(UIElement sender) return rootElementFinder ?? DefaultRootElementFinder; } - private static int TryGetDragPreviewMaxItemsCount(IDragInfo dragInfo, UIElement sender) + internal static int TryGetDragPreviewMaxItemsCount(IDragInfo dragInfo, UIElement sender) { var itemsCount = dragInfo?.VisualSource != null ? GetDragPreviewMaxItemsCount(dragInfo.VisualSource) : -1; if (itemsCount < 0 && sender != null) @@ -314,7 +293,7 @@ private static int TryGetDragPreviewMaxItemsCount(IDragInfo dragInfo, UIElement return itemsCount < 0 || itemsCount >= int.MaxValue ? 10 : itemsCount; } - private static IDragPreviewItemsSorter TryGetDragPreviewItemsSorter(IDragInfo dragInfo, UIElement sender) + internal static IDragPreviewItemsSorter TryGetDragPreviewItemsSorter(IDragInfo dragInfo, UIElement sender) { var itemsSorter = dragInfo?.VisualSource != null ? GetDragPreviewItemsSorter(dragInfo.VisualSource) : null; if (itemsSorter is null && sender != null) @@ -490,7 +469,7 @@ private static void DragSourceOnTouchMove(object sender, TouchEventArgs e) return; } - DoDragSourceMove(sender, e.GetTouchPoint((IInputElement)sender).Position); + DoDragSourceMove(sender, element => e.GetTouchPoint(element).Position); } } @@ -512,11 +491,11 @@ private static void DragSourceOnMouseMove(object sender, MouseEventArgs e) return; } - DoDragSourceMove(sender, e.GetPosition((IInputElement)sender)); + DoDragSourceMove(sender, element => e.GetPosition(element)); } } - private static void DoDragSourceMove(object sender, Point position) + private static void DoDragSourceMove(object sender, Func getPosition) { var dragInfo = _dragInfo; if (dragInfo != null && !_dragInProgress) @@ -528,6 +507,7 @@ private static void DoDragSourceMove(object sender, Point position) dragInfo.VisualSource?.ReleaseMouseCapture(); // only if the sender is the source control and the mouse point differs from an offset + var position = getPosition((IInputElement)sender); if (dragInfo.VisualSource == sender && (Math.Abs(position.X - dragStart.X) > DragDrop.GetMinimumHorizontalDragDistance(dragInfo.VisualSource) || Math.Abs(position.Y - dragStart.Y) > DragDrop.GetMinimumVerticalDragDistance(dragInfo.VisualSource))) @@ -561,6 +541,12 @@ private static void DoDragSourceMove(object sender, Point position) { _dragInProgress = true; + if (DragDropPreview is null) + { + DragDropPreview = GetDragDropPreview(dragInfo, null, sender as UIElement); + DragDropPreview?.Move(getPosition(DragDropPreview.PlacementTarget)); + } + hookId = MouseHelper.HookMouseMove(point => { DragDropPreview?.Move(CursorHelper.GetCurrentCursorPosition(DragDropPreview.PlacementTarget, point)); @@ -659,10 +645,15 @@ private static void DropTargetOnDragOver(object sender, DragEventArgs e, EventTy dropHandler.DragOver(dropInfo); - if (DragDropPreview is null && dragInfo is { }) + if (dragInfo is not null) { - DragDropPreview = GetDragDropPreview(dropInfo, sender as UIElement); - DragDropPreview?.Move(e.GetPosition(DragDropPreview.PlacementTarget)); + if (DragDropPreview is null) + { + DragDropPreview = GetDragDropPreview(dragInfo, dropInfo.VisualTarget, sender as UIElement); + DragDropPreview?.Move(e.GetPosition(DragDropPreview.PlacementTarget)); + } + + DragDropPreview?.UpdatePreviewPresenter(dragInfo, dropInfo.VisualTarget, sender as UIElement); } Scroll(dropInfo, e); @@ -736,7 +727,7 @@ private static void DropTargetOnDragOver(object sender, DragEventArgs e, EventTy DragDropEffectPreview.EffectText = dropInfo.EffectText; DragDropEffectPreview.DestinationText = dropInfo.DestinationText; - var template = GetDragDropEffecTemplate(dragInfo.VisualSource, dropInfo); + var template = GetDragDropEffectTemplate(dragInfo.VisualSource, dropInfo); if (template is null) { DragDropEffectPreview = null; diff --git a/src/GongSolutions.WPF.DragDrop/DragDropPreview.cs b/src/GongSolutions.WPF.DragDrop/DragDropPreview.cs index 48655aa9..b5639ca5 100644 --- a/src/GongSolutions.WPF.DragDrop/DragDropPreview.cs +++ b/src/GongSolutions.WPF.DragDrop/DragDropPreview.cs @@ -1,7 +1,12 @@ -using System; +using System; +using System.Collections; +using System.Linq; using System.Windows; +using System.Windows.Controls; using System.Windows.Controls.Primitives; +using System.Windows.Data; using System.Windows.Interop; +using System.Windows.Media; using GongSolutions.Wpf.DragDrop.Utilities; namespace GongSolutions.Wpf.DragDrop @@ -26,25 +31,89 @@ public DragDropPreview(UIElement rootElement, UIElement previewElement, Point tr this.AnchorPoint = anchorPoint; } + public DragDropPreview(UIElement rootElement, IDragInfo dragInfo, UIElement visualTarget, UIElement sender) + { + this.PlacementTarget = rootElement; + this.Placement = PlacementMode.Relative; + this.AllowsTransparency = true; + this.Focusable = false; + this.PopupAnimation = PopupAnimation.Fade; + this.StaysOpen = true; + this.HorizontalOffset = -9999; + this.VerticalOffset = -9999; + this.IsHitTestVisible = false; + this.AllowDrop = false; + this.HorizontalAlignment = HorizontalAlignment.Left; + this.VerticalAlignment = VerticalAlignment.Top; + + this.DragInfo = dragInfo; + this.Child = this.CreatePreviewPresenter(dragInfo, visualTarget, sender); + this.Translation = DragDrop.GetDragAdornerTranslation(dragInfo.VisualSource); + this.AnchorPoint = DragDrop.GetDragMouseAnchorPoint(dragInfo.VisualSource); + } + + private IDragInfo DragInfo { get; } + + private Rect VisualSourceItemBounds { get; set; } = Rect.Empty; + public Point Translation { get; } public Point AnchorPoint { get; } - internal void Move(Point point) + public bool UseDefaultDragAdorner { get; private set; } + + public static readonly DependencyProperty ItemTemplateProperty + = DependencyProperty.Register(nameof(ItemTemplate), + typeof(DataTemplate), + typeof(DragDropPreview), + new FrameworkPropertyMetadata((DataTemplate)null)); + + public DataTemplate ItemTemplate + { + get => (DataTemplate)this.GetValue(ItemTemplateProperty); + set => this.SetValue(ItemTemplateProperty, value); + } + + public static readonly DependencyProperty ItemTemplateSelectorProperty + = DependencyProperty.Register(nameof(ItemTemplateSelector), + typeof(DataTemplateSelector), + typeof(DragDropPreview), + new FrameworkPropertyMetadata((DataTemplateSelector)null)); + + public DataTemplateSelector ItemTemplateSelector + { + get => (DataTemplateSelector)this.GetValue(ItemTemplateSelectorProperty); + set => this.SetValue(ItemTemplateSelectorProperty, value); + } + + public void Move(Point point) { var translation = this.Translation; var translationX = point.X + translation.X; var translationY = point.Y + translation.Y; - var renderSize = this.Child.RenderSize; - - if (renderSize.Width > 0 && renderSize.Height > 0) + if (this.Child is not null) { - var offsetX = renderSize.Width * -this.AnchorPoint.X; - var offsetY = renderSize.Height * -this.AnchorPoint.Y; + var renderSize = this.Child.RenderSize; + + var renderSizeWidth = renderSize.Width; + var renderSizeHeight = renderSize.Height; + + // Only set if the template contains a Canvas. + if (!this.VisualSourceItemBounds.IsEmpty) + { + renderSizeWidth = Math.Min(renderSizeWidth, this.VisualSourceItemBounds.Width); + renderSizeHeight = Math.Min(renderSizeHeight, this.VisualSourceItemBounds.Height); + } - translationX += offsetX; - translationY += offsetY; + if (renderSizeWidth > 0 && renderSizeHeight > 0) + { + var offsetX = renderSizeWidth * -this.AnchorPoint.X; + var offsetY = renderSizeHeight * -this.AnchorPoint.Y; + + translationX += offsetX; + translationY += offsetY; + } } this.SetCurrentValue(HorizontalOffsetProperty, translationX); @@ -67,5 +136,156 @@ protected override void OnOpened(EventArgs e) WindowStyleHelper.SetWindowStyleEx(windowHandle, wsex); } } + + public void UpdatePreviewPresenter(IDragInfo dragInfo, UIElement visualTarget, UIElement sender) + { + if (this.UseDefaultDragAdorner) + { + return; + } + + var visualSource = dragInfo.VisualSource; + + DataTemplate template = DragDrop.TryGetDropAdornerTemplate(visualTarget, sender) ?? DragDrop.TryGetDragAdornerTemplate(visualSource, sender); + DataTemplateSelector templateSelector = DragDrop.TryGetDropAdornerTemplateSelector(visualTarget, sender) ?? DragDrop.TryGetDragAdornerTemplateSelector(visualSource, sender); + + if (template is not null) + { + templateSelector = null; + } + + this.SetCurrentValue(ItemTemplateProperty, template); + this.SetCurrentValue(ItemTemplateSelectorProperty, templateSelector); + } + + public UIElement CreatePreviewPresenter(IDragInfo dragInfo, UIElement visualTarget, UIElement sender) + { + var visualSource = dragInfo.VisualSource; + + DataTemplate template = DragDrop.TryGetDropAdornerTemplate(visualTarget, sender) ?? DragDrop.TryGetDragAdornerTemplate(visualSource, sender); + DataTemplateSelector templateSelector = DragDrop.TryGetDropAdornerTemplateSelector(visualTarget, sender) ?? DragDrop.TryGetDragAdornerTemplateSelector(visualSource, sender); + + var useDefaultDragAdorner = template is null && templateSelector is null && DragDrop.GetUseDefaultDragAdorner(visualSource); + var useVisualSourceItemSizeForDragAdorner = dragInfo.VisualSourceItem != null && DragDrop.GetUseVisualSourceItemSizeForDragAdorner(visualSource); + + if (useDefaultDragAdorner) + { + template = dragInfo.VisualSourceItem.GetCaptureScreenDataTemplate(dragInfo.VisualSourceFlowDirection); + useDefaultDragAdorner = template is not null; + } + + if (template is not null) + { + templateSelector = null; + } + + this.SetCurrentValue(ItemTemplateProperty, template); + this.SetCurrentValue(ItemTemplateSelectorProperty, templateSelector); + + this.UseDefaultDragAdorner = useDefaultDragAdorner; + + UIElement adornment = null; + + if (template != null || templateSelector != null) + { + if (dragInfo.Data is IEnumerable items && !(items is string)) + { + var itemsCount = items.Cast().Count(); + var maxItemsCount = DragDrop.TryGetDragPreviewMaxItemsCount(dragInfo, sender); + if (!useDefaultDragAdorner && itemsCount <= maxItemsCount) + { + // sort items if necessary before creating the preview + var sorter = DragDrop.TryGetDragPreviewItemsSorter(dragInfo, sender); + + var itemsControl = new ItemsControl + { + ItemsSource = sorter?.SortDragPreviewItems(items) ?? items, + Tag = dragInfo + }; + + itemsControl.SetBinding(ItemsControl.ItemTemplateProperty, new Binding(nameof(this.ItemTemplate)) { Source = this }); + itemsControl.SetBinding(ItemsControl.ItemTemplateSelectorProperty, new Binding(nameof(this.ItemTemplateSelector)) { Source = this }); + + if (useVisualSourceItemSizeForDragAdorner) + { + var bounds = VisualTreeExtensions.GetVisibleDescendantBounds(dragInfo.VisualSourceItem); + itemsControl.SetCurrentValue(MinWidthProperty, bounds.Width); + } + + // The ItemsControl doesn't display unless we create a grid to contain it. + var grid = new Grid(); + grid.Children.Add(itemsControl); + adornment = grid; + } + } + else + { + var contentPresenter = new ContentPresenter + { + Content = dragInfo.Data, + Tag = dragInfo + }; + + contentPresenter.SetBinding(ContentPresenter.ContentTemplateProperty, new Binding(nameof(this.ItemTemplate)) { Source = this }); + contentPresenter.SetBinding(ContentPresenter.ContentTemplateSelectorProperty, new Binding(nameof(this.ItemTemplateSelector)) { Source = this }); + + if (useVisualSourceItemSizeForDragAdorner) + { + var bounds = VisualTreeExtensions.GetVisibleDescendantBounds(dragInfo.VisualSourceItem); + contentPresenter.SetCurrentValue(MinWidthProperty, bounds.Width); + contentPresenter.SetCurrentValue(MinHeightProperty, bounds.Height); + } + + contentPresenter.Loaded += this.ContentPresenter_OnLoaded; + + adornment = contentPresenter; + } + } + + if (adornment != null && useDefaultDragAdorner) + { + adornment.Opacity = DragDrop.GetDefaultDragAdornerOpacity(visualSource); + } + + return adornment; + } + + private void ContentPresenter_OnLoaded(object sender, RoutedEventArgs e) + { + if (sender is ContentPresenter contentPresenter) + { + contentPresenter.Loaded -= this.ContentPresenter_OnLoaded; + + // If the template contains a Canvas then we get a strange size. + if (this.UseDefaultDragAdorner && this.DragInfo?.VisualSourceItem.GetVisualDescendent() is not null) + { + this.VisualSourceItemBounds = this.DragInfo?.VisualSourceItem != null ? VisualTreeHelper.GetDescendantBounds(this.DragInfo.VisualSourceItem) : Rect.Empty; + + contentPresenter.SetCurrentValue(MaxWidthProperty, this.VisualSourceItemBounds.Width); + contentPresenter.SetCurrentValue(MaxHeightProperty, this.VisualSourceItemBounds.Height); + this.SetCurrentValue(MaxWidthProperty, this.VisualSourceItemBounds.Width); + this.SetCurrentValue(MaxHeightProperty, this.VisualSourceItemBounds.Height); + } + else + { + contentPresenter.ApplyTemplate(); + if (contentPresenter.GetVisualDescendent() is not null) + { + // Get the first element and set it's vertical alignment to top. + if (contentPresenter.GetVisualDescendent() is FrameworkElement fe) + { + fe.SetCurrentValue(VerticalAlignmentProperty, VerticalAlignment.Top); + } + + this.VisualSourceItemBounds = this.DragInfo?.VisualSourceItem != null ? VisualTreeHelper.GetDescendantBounds(this.DragInfo.VisualSourceItem) : Rect.Empty; + + contentPresenter.SetCurrentValue(MaxWidthProperty, this.VisualSourceItemBounds.Width); + contentPresenter.SetCurrentValue(MaxHeightProperty, this.VisualSourceItemBounds.Height); + this.SetCurrentValue(MaxWidthProperty, this.VisualSourceItemBounds.Width); + this.SetCurrentValue(MaxHeightProperty, this.VisualSourceItemBounds.Height); + } + } + } + } } } \ No newline at end of file diff --git a/src/GongSolutions.WPF.DragDrop/Utilities/DragDropExtensions.cs b/src/GongSolutions.WPF.DragDrop/Utilities/DragDropExtensions.cs index b6d0d518..4aacbcef 100644 --- a/src/GongSolutions.WPF.DragDrop/Utilities/DragDropExtensions.cs +++ b/src/GongSolutions.WPF.DragDrop/Utilities/DragDropExtensions.cs @@ -120,8 +120,8 @@ private static BitmapSource CaptureScreen(Visual target, FlowDirection flowDirec { var vb = new VisualBrush(target); - vb.ViewportUnits = BrushMappingMode.Absolute; - vb.Viewport = bounds; + // vb.ViewportUnits = BrushMappingMode.Absolute; + // vb.Viewport = bounds; if (flowDirection == FlowDirection.RightToLeft) { diff --git a/src/Showcase/Views/SettingsView.xaml b/src/Showcase/Views/SettingsView.xaml index bc15abc3..eb5ece56 100644 --- a/src/Showcase/Views/SettingsView.xaml +++ b/src/Showcase/Views/SettingsView.xaml @@ -32,6 +32,9 @@ +