using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Animation; using System.Windows.Media.Imaging; using System.Windows.Threading; using Tango.Core; using Tango.Core.EventArguments; using Tango.Logging; namespace Tango.DragAndDrop { /// /// Represents a drag and drop service which exposes several attached properties and events to create an interactive drag and drop functionality. /// /// /// public static class DragAndDropService { private static WeakList _dragElements; private static WeakList _dropElements; private static Point _mouseDownLocation; private static FrameworkElement _currentDragedElement; private static FrameworkElement _currentDropElement; private static bool _isMouseDown; private static DispatcherTimer _dragTimer; private static Border _dragBorder; private static TouchDevice _touchDevice; //private const int MIN_DRAG_OFFSET = 8; #region Events public static event EventHandler DragStarted; public static event EventHandler DragEnded; #endregion #region Delegates /// /// Represents the attached event delegate. /// /// The sender. /// The instance containing the event data. public delegate void DropEventHandler(object sender, DropEventArgs e); #endregion #region Constructors /// /// Initializes the class. /// static DragAndDropService() { _dragElements = new WeakList(); _dropElements = new WeakList(); _dragTimer = new DispatcherTimer(); _dragTimer.Interval = TimeSpan.FromMilliseconds(30); _dragTimer.Tick += DragTimer_Tick; _dragTimer.IsEnabled = false; } #endregion #region Attached Properties #region MinDragOffset /// /// Determines whether an element is MinDragOffset by the drag and drop service. /// public static readonly DependencyProperty MinDragOffsetProperty = DependencyProperty.RegisterAttached("MinDragOffset", typeof(int), typeof(DragAndDropService), new FrameworkPropertyMetadata(8)); /// /// Sets the MinDragOffset attached property. /// /// The element. /// if set to true [value]. public static void SetMinDragOffset(FrameworkElement element, int value) { element.SetValue(MinDragOffsetProperty, value); } /// /// Gets the MinDragOffset attached property. /// /// The element. /// public static int GetMinDragOffset(FrameworkElement element) { return (int)element.GetValue(MinDragOffsetProperty); } #endregion #region DropCommand /// /// Determines whether an element is DropCommand by the drag and drop service. /// public static readonly DependencyProperty DropCommandProperty = DependencyProperty.RegisterAttached("DropCommand", typeof(ICommand), typeof(DragAndDropService), new FrameworkPropertyMetadata(null)); /// /// Sets the DropCommand attached property. /// /// The element. /// if set to true [value]. public static void SetDropCommand(FrameworkElement element, ICommand value) { element.SetValue(DropCommandProperty, value); } /// /// Gets the DropCommand attached property. /// /// The element. /// public static ICommand GetDropCommand(FrameworkElement element) { return (ICommand)element.GetValue(DropCommandProperty); } #endregion #region Draggable /// /// Determines whether an element is draggable by the drag and drop service. /// public static readonly DependencyProperty DraggableProperty = DependencyProperty.RegisterAttached("Draggable", typeof(bool), typeof(DragAndDropService), new FrameworkPropertyMetadata(false, DraggableChanged)); /// /// Draggable changed. /// /// The d. /// The instance containing the event data. private static void DraggableChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { if ((bool)e.NewValue) { RegisterDraggable(d as FrameworkElement); } else { UnRegisterDraggable(d as FrameworkElement); } } /// /// Sets the draggable attached property. /// /// The element. /// if set to true [value]. public static void SetDraggable(FrameworkElement element, bool value) { element.SetValue(DraggableProperty, value); } /// /// Gets the draggable attached property. /// /// The element. /// public static bool GetDraggable(FrameworkElement element) { return (bool)element.GetValue(DraggableProperty); } #endregion #region IsDragging /// /// Determines whether the draggable element is currently being dragged. /// public static readonly DependencyProperty IsDraggingProperty = DependencyProperty.RegisterAttached("IsDragging", typeof(bool), typeof(DragAndDropService), new FrameworkPropertyMetadata(false)); /// /// Sets the IsDragging attached property. /// /// The element. /// if set to true [value]. public static void SetIsDragging(FrameworkElement element, bool value) { element.SetValue(IsDraggingProperty, value); } /// /// Gets the is dragging attached property. /// /// The element. /// public static bool GetIsDragging(FrameworkElement element) { return (bool)element.GetValue(IsDraggingProperty); } #endregion #region DraggableBackground /// /// Determines the draggable element replication background. /// public static readonly DependencyProperty DraggableBackgroundProperty = DependencyProperty.RegisterAttached("DraggableBackground", typeof(Brush), typeof(DragAndDropService), new FrameworkPropertyMetadata(null)); /// /// Sets the draggable background attached property. /// /// The element. /// The value. public static void SetDraggableBackground(FrameworkElement element, Brush value) { element.SetValue(DraggableBackgroundProperty, value); } /// /// Gets the draggable background attached property. /// /// The element. /// public static Brush GetDraggableBackground(FrameworkElement element) { return (Brush)element.GetValue(DraggableBackgroundProperty); } #endregion #region DraggableBorderBrush /// /// Determines the draggable element replication border brush. /// public static readonly DependencyProperty DraggableBorderBrushProperty = DependencyProperty.RegisterAttached("DraggableBorderBrush", typeof(Brush), typeof(DragAndDropService), new FrameworkPropertyMetadata(null)); /// /// Sets the draggable border brush attached property. /// /// The element. /// The value. public static void SetDraggableBorderBrush(FrameworkElement element, Brush value) { element.SetValue(DraggableBorderBrushProperty, value); } /// /// Gets the draggable border brush attached property. /// /// The element. /// public static Brush GetDraggableBorderBrush(FrameworkElement element) { return (Brush)element.GetValue(DraggableBorderBrushProperty); } #endregion #region DraggableOpacity /// /// Determines the draggable element replication opacity. /// public static readonly DependencyProperty DraggableOpacityProperty = DependencyProperty.RegisterAttached("DraggableOpacity", typeof(double), typeof(DragAndDropService), new FrameworkPropertyMetadata(0.8)); /// /// Sets the draggable opacity attached property. /// /// The element. /// The value. public static void SetDraggableOpacity(FrameworkElement element, double value) { element.SetValue(DraggableOpacityProperty, value); } /// /// Gets the draggable opacity attached property. /// /// The element. /// public static double GetDraggableOpacity(FrameworkElement element) { return (double)element.GetValue(DraggableOpacityProperty); } #endregion #region Droppable /// /// Determines whether an element is a dropable target by the drag and drop service. /// public static readonly DependencyProperty DroppableProperty = DependencyProperty.RegisterAttached("Droppable", typeof(bool), typeof(DragAndDropService), new FrameworkPropertyMetadata(false, DroppableChanged)); /// /// Droppables the changed attached property. /// /// The d. /// The instance containing the event data. private static void DroppableChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { if ((bool)e.NewValue) { RegisterDroppable(d as FrameworkElement); } else { UnRegisterDroppable(d as FrameworkElement); } } /// /// Sets the droppable attached property. /// /// The element. /// if set to true [value]. public static void SetDroppable(FrameworkElement element, bool value) { element.SetValue(DroppableProperty, value); } /// /// Gets the droppable attached property. /// /// The element. /// public static bool GetDroppable(FrameworkElement element) { return (bool)element.GetValue(DroppableProperty); } #endregion #region IsDraggableOver /// /// Determines whether a draggable element currently hovers over the droppable element. /// public static readonly DependencyProperty IsDraggableOverProperty = DependencyProperty.RegisterAttached("IsDraggableOver", typeof(bool), typeof(DragAndDropService), new FrameworkPropertyMetadata(false)); /// /// Sets the is draggable over attached property. /// /// The element. /// if set to true [value]. public static void SetIsDraggableOver(FrameworkElement element, bool value) { element.SetValue(IsDraggableOverProperty, value); } /// /// Gets the is draggable over attached property. /// /// The element. /// public static bool GetIsDraggableOver(FrameworkElement element) { return (bool)element.GetValue(IsDraggableOverProperty); } #endregion #region Dragging Surface /// /// Determines the Draggable dragging surface. /// public static readonly DependencyProperty DraggingSurfaceProperty = DependencyProperty.RegisterAttached("DraggingSurface", typeof(DraggingSurface), typeof(DragAndDropService), new FrameworkPropertyMetadata(null, DraggingSurfaceChanged)); /// /// Dragging the surface changed. /// /// The d. /// The instance containing the event data. private static void DraggingSurfaceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { } /// /// Sets the dragging surface attached property. /// /// The element. /// The value. public static void SetDraggingSurface(FrameworkElement element, DraggingSurface value) { element.SetValue(DraggingSurfaceProperty, value); } /// /// Gets the dragging surface attached property. /// /// The element. /// public static DraggingSurface GetDraggingSurface(FrameworkElement element) { return (DraggingSurface)element.GetValue(DraggingSurfaceProperty); } #endregion #endregion #region Attached Events /// /// Occurs when a successful drop has occurred. /// public static readonly RoutedEvent DropEvent = EventManager.RegisterRoutedEvent("Drop", RoutingStrategy.Bubble, typeof(DropEventHandler), typeof(DragAndDropService)); /// /// Adds the drop event handler. /// /// The o. /// The handler. public static void AddDropHandler(DependencyObject o, DropEventHandler handler) { ((UIElement)o).AddHandler(DropEvent, handler); } /// /// Removes the drop event handler. /// /// The o. /// The handler. public static void RemoveDropHandler(DependencyObject o, DropEventHandler handler) { ((UIElement)o).RemoveHandler(DropEvent, handler); } #endregion #region Drag Timer /// /// Handles the Tick event of the DragTimer control. /// /// The source of the event. /// The instance containing the event data. private static void DragTimer_Tick(object sender, EventArgs e) { if ((Mouse.LeftButton == MouseButtonState.Pressed || (_touchDevice != null && _touchDevice.IsActive)) && _currentDragedElement != null) { DragStarted?.Invoke(_currentDragedElement, _currentDragedElement); var surface = GetDraggingSurface(_currentDragedElement); var rootElement = surface.Parent as FrameworkElement; if (_touchDevice != null) { DraggingSurface.SetLeft(_dragBorder, _touchDevice.GetTouchPoint(surface).Position.X - _mouseDownLocation.X); DraggingSurface.SetTop(_dragBorder, _touchDevice.GetTouchPoint(surface).Position.Y - _mouseDownLocation.Y); } else { DraggingSurface.SetLeft(_dragBorder, Mouse.GetPosition(surface).X - _mouseDownLocation.X); DraggingSurface.SetTop(_dragBorder, Mouse.GetPosition(surface).Y - _mouseDownLocation.Y); } _dragBorder.Visibility = Visibility.Visible; bool found = false; foreach (var dropElement in _dropElements.Where(x => x != null && x != _currentDragedElement)) { if (!dropElement.IsLoaded) { dropElement.UpdateLayout(); dropElement.InvalidateArrange(); dropElement.InvalidateMeasure(); dropElement.InvalidateVisual(); continue; } //Point dropPoint = dropElement.TransformToAncestor(rootElement).Transform(new Point(0, 0)); Point dropPoint = dropElement.PointToScreen(new Point(0, 0)); var dropElementScaledSize = dropElement.PointToScreen(new Point(dropElement.ActualWidth, dropElement.ActualHeight)) - dropElement.PointToScreen(new Point(0, 0)); Rect dropRect = new Rect(dropPoint, new Size(dropElementScaledSize.X, dropElementScaledSize.Y)); Rect dragRect = new Rect(Canvas.GetLeft(_dragBorder), Canvas.GetTop(_dragBorder), _dragBorder.Width / 2, _dragBorder.Height); Point mousePoint = new Point(); if (_touchDevice != null) { mousePoint = rootElement.PointToScreen(_touchDevice.GetTouchPoint(rootElement).Position); } else { mousePoint = rootElement.PointToScreen(Mouse.GetPosition(rootElement)); } if (dropRect.IntersectsWith(new Rect(mousePoint, new Size(1, 1)))) { SetIsDraggableOver(dropElement, true); foreach (var drop in _dropElements.Where(x => x != null && x != dropElement)) { SetIsDraggableOver(drop, false); } _currentDropElement = dropElement; found = true; } else { SetIsDraggableOver(dropElement, false); } } if (!found) { _currentDropElement = null; } } else { DragEnded?.Invoke(_currentDragedElement, _currentDragedElement); try { DropDraggable(); } catch (Exception ex) { LogManager.Default.Log(ex, "Error on drop draggable."); } } } #endregion #region Private Methods /// /// Registers a framework element as a draggable element. /// /// The element. private static void RegisterDraggable(FrameworkElement element) { foreach (var item in _dragElements.ToList()) { if (item == null) { _dragElements.Remove(item); } } if (!_dragElements.Contains(element)) { _dragElements.Add(element); element.RegisterForPreviewMouseOrTouchDown(Draggable_PreviewMouseDown); element.RegisterForMouseOrTouchMove(Draggable_MouseMove); //element.PreviewMouseUp += Draggable_PreviewMouseUp; element.AddHandler(FrameworkElement.PreviewMouseUpEvent, new MouseButtonEventHandler(Draggable_PreviewMouseUp)); //element.IsManipulationEnabled = true; //element.AddHandler(FrameworkElement.PreviewMouseDownEvent, (MouseButtonEventHandler)Draggable_PreviewMouseDown, true); //element.AddHandler(FrameworkElement.MouseMoveEvent, (MouseEventHandler)Draggable_MouseMove, true); //element.AddHandler(FrameworkElement.PreviewMouseUpEvent, (MouseButtonEventHandler)Draggable_PreviewMouseUp, true); //element.Unloaded += Draggable_Unloaded; } } /// /// Unregisters a framework element as a draggable element. /// /// The element. private static void RegisterDroppable(FrameworkElement element) { foreach (var item in _dropElements.ToList()) { if (item == null) { _dropElements.Remove(item); } } if (!_dropElements.Contains(element)) { _dropElements.Add(element); //element.Unloaded += Droppable_Unloaded; } } /// /// Handles the Unloaded event of the Element control. /// /// The source of the event. /// The instance containing the event data. private static void Draggable_Unloaded(object sender, RoutedEventArgs e) { FrameworkElement element = sender as FrameworkElement; UnRegisterDraggable(element); } /// /// Handles the Unloaded1 event of the Element control. /// /// The source of the event. /// The instance containing the event data. private static void Droppable_Unloaded(object sender, RoutedEventArgs e) { FrameworkElement element = sender as FrameworkElement; UnRegisterDroppable(element); } /// /// Registers a framework element as a droppable element. /// /// The element. private static void UnRegisterDraggable(FrameworkElement element) { _dragElements.Remove(element); element.PreviewMouseUp -= Draggable_PreviewMouseUp; element.Unloaded -= Draggable_Unloaded; } /// /// Unregisters a framework element as a droppable element. /// /// The element. private static void UnRegisterDroppable(FrameworkElement element) { _dropElements.Remove(element); element.Unloaded -= Droppable_Unloaded; } /// /// Creates the dragging element replication. /// private static void CreateDraggingElement() { if (_currentDragedElement != null) { BitmapSource source = _currentDragedElement.TakeSnapshot(); _dragBorder = new Border(); var surface = GetDraggingSurface(_currentDragedElement); _dragBorder.Background = GetDraggableBackground(_currentDragedElement); _dragBorder.BorderThickness = new Thickness(1); _dragBorder.BorderBrush = GetDraggableBorderBrush(_currentDragedElement); //dragBorder.CornerRadius = new CornerRadius(currentDragedElement.BorderRadius); _dragBorder.Visibility = Visibility.Hidden; _dragBorder.IsHitTestVisible = false; _dragBorder.Width = source.PixelWidth; _dragBorder.Height = source.PixelHeight; _dragBorder.Opacity = GetDraggableOpacity(_currentDragedElement); Image img = new Image(); _dragBorder.Child = img; img.Stretch = System.Windows.Media.Stretch.Fill; img.Source = source; ScaleTransform scale = new ScaleTransform(1, 1, 0.5, 0.5); _dragBorder.RenderTransformOrigin = new Point(0.5, 0.5); _dragBorder.RenderTransform = scale; surface.Children.Add(_dragBorder); DoubleAnimation ani = new DoubleAnimation(0.95, new Duration(TimeSpan.FromMilliseconds(300))); ani.AutoReverse = true; ani.RepeatBehavior = RepeatBehavior.Forever; DoubleAnimation aniOp = new DoubleAnimation(_dragBorder.Opacity, 0.5, new Duration(TimeSpan.FromMilliseconds(300))); aniOp.AutoReverse = true; aniOp.RepeatBehavior = RepeatBehavior.Forever; scale.BeginAnimation(ScaleTransform.ScaleXProperty, ani); scale.BeginAnimation(ScaleTransform.ScaleYProperty, ani); _dragBorder.BeginAnimation(ContentControl.OpacityProperty, aniOp); } } /// /// Removes the dragging element replication. /// private static void RemoveDraggingElement() { if (_dragBorder != null) { _dragBorder.RenderTransform.BeginAnimation(ScaleTransform.ScaleXProperty, null); _dragBorder.RenderTransform.BeginAnimation(ScaleTransform.ScaleYProperty, null); _dragBorder.BeginAnimation(ContentControl.OpacityProperty, null); } var surface = GetDraggingSurface(_currentDragedElement); surface.Children.Clear(); } /// /// Drops the draggable. /// /// if set to true [no drop]. private static void DropDraggable(bool noDrop = false) { _isMouseDown = false; RemoveDraggingElement(); _dragTimer.Stop(); SetIsDragging(_currentDragedElement, false); foreach (var dropElement in _dropElements.Where(x => x != null)) { SetIsDraggableOver(dropElement, false); } if (noDrop) { _currentDragedElement = null; _currentDropElement = null; return; } if (_currentDropElement != null && _currentDragedElement != null) { _currentDropElement.RaiseEvent(new DropEventArgs(DropEvent, _currentDragedElement, _currentDropElement, _mouseDownLocation)); var command = GetDropCommand(_currentDropElement); if (command != null) { command.Execute(new DropEventArgs(DropEvent, _currentDragedElement, _currentDropElement, _mouseDownLocation)); } _currentDragedElement = null; _currentDropElement = null; } else if (_currentDropElement == null && _currentDragedElement != null) { //if (DroppedNoWhere != null) DroppedNoWhere(this, currentDragedElement.Element); //TODO: ?? } } #endregion #region Draggable Event Handlers private static void Draggable_PreviewMouseDown(object sender, MouseOrTouchEventArgs e) { var dragThumb = (sender as FrameworkElement).FindChild(); if ((e.Source != sender && dragThumb == null) || (dragThumb != null && dragThumb != e.OriginalSource)) { _currentDragedElement = null; return; } FrameworkElement element = sender as FrameworkElement; if (!_dragElements.Where(x => x != null).ToList().Exists(x => x == element)) { e.Handled = false; return; } _mouseDownLocation = e.GetPosition(element); _currentDragedElement = element; _isMouseDown = true; } /// /// Handles the MouseMove event of the Draggable control. /// /// The source of the event. /// The instance containing the event data. private static void Draggable_MouseMove(object sender, MouseOrTouchEventArgs e) { if (_isMouseDown) { FrameworkElement element = _currentDragedElement; if (element != null) { int minDragOffset = GetMinDragOffset(element); var surface = GetDraggingSurface(element); if (surface != null) { if ((e.GetPosition(element).X > _mouseDownLocation.X + minDragOffset || e.GetPosition(element).X < _mouseDownLocation.X - minDragOffset) || (e.GetPosition(element).Y > _mouseDownLocation.Y + minDragOffset || e.GetPosition(element).Y < _mouseDownLocation.Y - minDragOffset)) { element.ReleaseMouseCapture(); surface.ReleaseMouseCapture(); if (e.TouchDevice != null) { element.ReleaseTouchCapture(e.TouchDevice); surface.ReleaseTouchCapture(e.TouchDevice); } _isMouseDown = false; CreateDraggingElement(); _touchDevice = e.TouchDevice; _dragTimer.Start(); SetIsDragging(element, true); } } } } } /// /// Handles the PreviewMouseUp event of the Draggable control. /// /// The source of the event. /// The instance containing the event data. private static void Draggable_PreviewMouseUp(object sender, System.Windows.Input.MouseButtonEventArgs e) { FrameworkElement element = _currentDragedElement; if (element != null) { e.Handled = true; if ((e.GetPosition(element).X > _mouseDownLocation.X + 10 || e.GetPosition(element).X < _mouseDownLocation.X - 10) || (e.GetPosition(element).Y > _mouseDownLocation.Y + 10 || e.GetPosition(element).Y < _mouseDownLocation.Y - 10)) { DropDraggable(); } else { DropDraggable(true); } } } #endregion } }