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
}
}