Imports System.Net
Imports System.Windows
Imports System.Windows.Controls
Imports System.Windows.Documents
Imports System.Windows.Input
Imports System.Windows.Media
Imports System.Windows.Media.Animation
Imports System.Windows.Shapes
Imports C1.Silverlight
Imports C1.Silverlight.FlexGrid

Namespace ExcelDragDrop
	Partial Public Class MainPage
		Inherits UserControl
		Private _dd As New C1DragDropManager()
		Private _dragOffset As Point
		Private Const DRAG_THRESHOLD As Integer = 4

		Public Sub New()
			InitializeComponent()

			_flex1.ItemsSource = Product.GetProducts(100)
			_flex2.ItemsSource = Product.GetProducts(100)

			' monitor mouse over the grids
			AddHandler _flex1.MouseMove, AddressOf _flex_MouseMove
			AddHandler _flex1.PreviewMouseLeftButtonDown, AddressOf _flex_PreviewMouseLeftButtonDown
			AddHandler _flex2.MouseMove, AddressOf _flex_MouseMove
			AddHandler _flex2.PreviewMouseLeftButtonDown, AddressOf _flex_PreviewMouseLeftButtonDown

			' initialize drag drop manager
			_dd.RegisterDropTarget(LayoutRoot, True)
			AddHandler _dd.DragOver, AddressOf _dd_DragOver
			AddHandler _dd.DragDrop, AddressOf _dd_DragDrop
		End Sub

		' offer to drag the selection when the mouse hovers over its edges
		Private Sub _flex_MouseMove(ByVal sender As Object, ByVal e As MouseEventArgs)
			Dim flex = TryCast(sender, C1FlexGrid)
			flex.Cursor = If(MouseInDragArea(flex, e), Cursors.Hand, Nothing)
		End Sub

		' start dragging when the user clicks the mouse in the drag zone
		Private Sub _flex_PreviewMouseLeftButtonDown(ByVal sender As Object, ByVal e As MouseButtonEventArgs)
			Dim flex = TryCast(sender, C1FlexGrid)
			If MouseInDragArea(flex, e) Then
				' create image for dragging (optional)
				Dim flexImage = TryCast(flex.GetGridImage(flex.Selection), C1FlexGrid)
				flexImage.HeadersVisibility = HeadersVisibility.None
				flexImage.Opacity = 0.5
				_dd.TargetMarker.Child = flexImage

				' start dragging
				_dd.DoDragDrop(flex.Marquee, e, DragDropEffect.Copy)
				e.Handled = True
			End If
		End Sub

		' provide feedback while dragging
		Private Sub _dd_DragOver(ByVal source As Object, ByVal e As DragDropEventArgs)
			' assume we can't drop here
			e.Effect = DragDropEffect.None

			' check whether the mouse is over one of the grids
			Dim dst = GetDropTarget(e)
			If dst IsNot Nothing Then
				' allow drop
				e.Effect = DragDropEffect.Copy

				' select range to show drop location
				Dim dstRange = GetDropRange(e)
				dst.Select(dstRange, True)
			End If
		End Sub

#If true Then ' transfer data using the clipboard

		' finish dragging
		Private Sub _dd_DragDrop(ByVal source As Object, ByVal e As DragDropEventArgs)
			' get source grid
			Dim src = GetParentOfType(Of C1FlexGrid)(e.DragSource)

			' get target grid
			Dim dst = GetDropTarget(e)
            If Not IsNothing(dst) Then
                ' transfer data using the clipboard
                ' (selection is already set on the destination grid)
                src.ClipboardCopyMode = ClipboardCopyMode.ExcludeHeader
                src.Copy()
                dst.Paste()
            End If
		End Sub

#Else ' transfer data without the clipboard

		' finish dragging
		Private Sub _dd_DragDrop(ByVal source As Object, ByVal e As DragDropEventArgs)
			' get source grid and cell coordinates
			Dim src = GetParentOfType(Of C1FlexGrid)(e.DragSource)
			Dim sel = src.Selection

			' get target grid and cell coordinates
			Dim dst = GetDropTarget(e)
			Dim dstRange = GetDropRange(e)

			' copy data from source to target
			Dim dstRow = dstRange.Row
			Dim dstCol = dstRange.Column
			For r As Integer = 0 To sel.RowSpan - 1
				For c As Integer = 0 To sel.ColumnSpan - 1
					Dim value = src(sel.TopRow + r, sel.LeftColumn + c)
					If dstRow < dst.Rows.Count AndAlso dstCol < dst.Columns.Count Then
						dst(dstRow + r, dstCol + c) = value
						dstRange.Row2 = Math.Max(dstRange.Row2, dstRow + r)
						dstRange.Column2 = Math.Max(dstRange.Column2, dstCol + c)
					End If
				Next c
			Next r

			' update selection on target grid
			dst.Select(dstRange, False)
		End Sub
#End If
		' get a valid drop target (mouse over a grid with dropping enabled)
		Private Function GetDropTarget(ByVal e As DragDropEventArgs) As C1FlexGrid
			Dim pt = e.GetPosition(LayoutRoot)
			Dim targets = VisualTreeHelper.FindElementsInHostCoordinates(pt, LayoutRoot)
			For Each target In targets
				If (target Is _flex1 AndAlso _chkDropLeft.IsChecked.Value) OrElse (target Is _flex2 AndAlso _chkDropRight.IsChecked.Value) Then
					Return TryCast(target, C1FlexGrid)
				End If
			Next target
			Return Nothing
		End Function

		' get a valid drop range
		Private Function GetDropRange(ByVal e As DragDropEventArgs) As CellRange
			' get source, target
			Dim src = GetParentOfType(Of C1FlexGrid)(e.DragSource)
			Dim dst = GetDropTarget(e)

			' get target range from mouse position
			Dim pt = e.GetPosition(dst)
			pt.X -= _dragOffset.X
			pt.Y -= _dragOffset.Y
			Dim ht = dst.HitTest(pt)
			Dim rng = ht.CellRange
			If rng.Row < 0 Then
				rng.Row = 0
			End If
			If rng.Column < 0 Then
				rng.Column = 0
			End If

			' expand to match size of source range
			Dim sel = src.Selection
			rng.Row2 = Math.Min(rng.Row + sel.RowSpan - 1, dst.Rows.Count - 1)
			rng.Column2 = Math.Min(rng.Column + sel.ColumnSpan - 1, dst.Columns.Count - 1)

			' done
			Return rng
		End Function

		' check whether the mouse is in the drag zone
		Private Function MouseInDragArea(ByVal flex As C1FlexGrid, ByVal e As MouseEventArgs) As Boolean
			' check that this grid can be used as a drag source
			If (flex Is _flex1 AndAlso (Not _chkDragLeft.IsChecked.Value)) OrElse (flex Is _flex2 AndAlso (Not _chkDragRight.IsChecked.Value)) Then
				Return False
			End If

			' check that we are over a cell
			Dim ht = flex.HitTest(e)
			If ht.CellType <> CellType.Cell OrElse (Not ht.CellRange.IsValid) Then
				Return False
			End If

			' get mouse position and selection rectangle
			Dim pt = e.GetPosition(flex.Cells)
			Dim rc = flex.Cells.GetCellRect(flex.Selection)
			_dragOffset = New Point(pt.X - rc.X - 20, pt.Y - rc.Y - 5)

			' check that the mouse is close to the edge of the selection
			Return Math.Abs(pt.X - rc.Left) < DRAG_THRESHOLD OrElse Math.Abs(pt.X - rc.Right) < DRAG_THRESHOLD OrElse Math.Abs(pt.Y - rc.Top) < DRAG_THRESHOLD OrElse Math.Abs(pt.Y - rc.Bottom) < DRAG_THRESHOLD
		End Function

		''' <summary>
		''' Gets an element's first ancestor of a given type.
		''' </summary>
		''' <typeparam name="T">Type to look for.</typeparam>
		''' <param name="e">Child element.</param>
		Public Shared Function GetParentOfType(Of T As DependencyObject)(ByVal e As DependencyObject) As T
			Do While e IsNot Nothing
				e = VisualTreeHelper.GetParent(e)
				If TypeOf e Is T Then
					Return CType(e, T)
				End If
			Loop
			Return Nothing
		End Function
	End Class
End Namespace
