Playing With Blocks Listing 2 – DragHandle.xaml.cs

/******************************************************************************
 * DragHandle.xaml.cs
 * 
 * This module implements the code behind for the DragHandle class.
 *  
 * Date:   2/2009
 * 
 * Copyright (c) 2009, Mark Betz 
 * 
 * All rights reserved. 
 * Redistribution and use in source and binary forms, with or without 
 * modification, are permitted provided that the following conditions are met: 
 * 
 *   * Redistributions of source code must retain the above copyright 
 *     notice, this list of conditions and the following disclaimer. 
 *   * Redistributions in binary form must reproduce the above copyright 
 *     notice, this list of conditions and the following disclaimer in the 
 *     documentation and/or other materials provided with the distribution. 
 *   * Neither the name of the Author nor the names of contributors may be 
 *     used to endorse or promote products derived from this software
 *     without specific prior written permission. 
 *     
 * THIS SOFTWARE IS PROVIDED BY MARK BETZ ''AS IS'' AND ANY  EXPRESS
 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PART-
 * ICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL MARK BETZ BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
 * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
 * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE. 
 */
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Shapes;

namespace DrawControls
{
	/// <summary>
	/// DragConstraint enumerates possible constraints on the motion of a
	/// DragHandle. Motion constraints are relative to the HandleOrigin
	/// of the handle. For more information 
	/// see the <see cref="P:DrawControls.DragHandle.HandleOrigin">
	/// HandleOrigin property documentation.</see>.
	/// </summary>
	public enum DragConstraint
	{
		/// <summary>
		/// Specifies that no movement constraint will be placed on the Drag
		/// Handle. This is the default.
		/// </summary>
		None,

		/// <summary>
		/// Specifies that the DragHandle will be able to move on the east-
		/// west horizontal axis to both sides of its 
		/// <see cref="P:DrawControls.DragHandle.HandleOrigin">origin;</see>
		/// (+/-x, y).
		/// </summary>
		EW,

		/// <summary>
		/// Specifies that the DragHandle will be able to move on the north-
		/// south vertical axis above and below its
		/// <see cref="P:DrawControls.DragHandle.HandleOrigin">origin;</see>
		/// (x, +/-y).
		/// </summary>
		NS,

		/// <summary>
		/// Specifies that the DragHandle will be able to move on the north-
		/// east-southwest diagonal axis, to both sides of its
		/// <see cref="P:DrawControls.DragHandle.HandleOrigin">origin;</see>
		/// (-x, +y) or (+x, -y).
		/// </summary>
		NESW,

		/// <summary>
		/// Specifies that the DragHandle will be able to move on the south-
		/// east-northwest diagonal axis, to both sides of its
		/// <see cref="P:DrawControls.DragHandle.HandleOrigin">origin;</see>
		/// (-x, -y) or (+x, +y).
		/// </summary>
		SENW,

		/// <summary>
		/// Specifies that the DragHandle will be able to move from its
		/// <see cref="P:DrawControls.DragHandle.HandleOrigin">origin</see>
		/// to the northeast; (+x, -y).
		/// </summary>
		NE,

		/// <summary>
		/// Specifies that the DragHandle will be able to move from its
		/// <see cref="P:DrawControls.DragHandle.HandleOrigin">origin</see>
		/// to the northwest; (-x, -y).
		/// </summary>
		NW,

		/// <summary>
		/// Specifies that the DragHandle will be able to move from its
		/// <see cref="P:DrawControls.DragHandle.HandleOrigin">origin</see>
		/// to the southeast; (+x, +y).
		/// </summary>
		SE,

		/// <summary>
		/// Specifies that the DragHandle will be able to move from its
		/// <see cref="P:DrawControls.DragHandle.HandleOrigin">origin</see>
		/// to the southwest; (-x, +y).
		/// </summary>
		SW,

		/// <summary>
		/// Specifies that the DragHandle will be able to move from its
		/// <see cref="P:DrawControls.DragHandle.HandleOrigin">origin</see>
		/// to the east (+x, y).
		/// </summary>
		E,

		/// <summary>
		/// Specifies that the DragHandle will be able to move from its
		/// <see cref="P:DrawControls.DragHandle.HandleOrigin">origin</see>
		/// to the north (x, -y).
		/// </summary>
		N,

		/// <summary>
		/// Specifies that the DragHandle will be able to move from its
		/// <see cref="P:DrawControls.DragHandle.HandleOrigin">origin</see>
		/// to the south (x, +y).
		/// </summary>
		S,

		/// <summary>
		/// Specifies that the DragHandle will be able to move from its
		/// <see cref="P:DrawControls.DragHandle.HandleOrigin">origin</see>
		/// to the west (-x, y).
		/// </summary>
		W
	};

	/// <summary>
	/// Event arguments for the DragHandle class events
	/// </summary>
	public class DragHandleEventArgs : EventArgs
	{
		/// <summary>
		/// The position of the handle when the event was raised. The
		/// passed coordinates are relative to the 
		/// <see cref="P:DrawControls.DragHandle.ReferenceElement"> coordinate
		/// space, if one was set. Otherwise they are relative to the parent
		/// UIElement of the handle.</see>
		/// </summary>
		public Point Position { get; set; }

		/// <summary>
		/// The position of the handle's 
		/// <see cref="P:DrawControls.DragHandle.HandleOrigin">HandleOrigin</see>
		/// at the time the event was raised.
		/// </summary>
		public Point Origin { get; set; }
	}

	/// <summary>
	/// DragHandle represents a control that can contain a shape, and that
	/// can be moved around the screen with a mouse. Its movement can be
	/// constrained in various directions, and it supports several events
	/// to provide clients with notifications when it is moved or clicked.
	/// </summary>
	public partial class DragHandle : UserControl
	{
		/// <summary>
		/// Creates a DragHandle initially positioned at 0,0 with its
		/// <see cref="P:DrawControls.DragHandle.HandleOrigin">origin</see>
		/// at 0,0.
		/// </summary>
		public DragHandle()
		{
			InitializeComponent();
			InitHandle();
		}

		/// <summary>
		/// Creates a DragHandle initially positioned at <c>position</c> with its
		/// <see cref="P:DrawControls.DragHandle.HandleOrigin">origin</see>
		/// at <c>position</c>.
		/// </summary>
		public DragHandle(Point position)
		: base()
		{
			InitializeComponent();
			HandlePosition = position;
			InitHandle();
		}

		/// <summary>
		/// Causes the DragHandle to relocate to its
		/// <see cref="P:DrawControls.DragHandle.HandleOrigin">origin</see>.
		/// </summary>
		public void SnapToOrigin()
		{
			HandlePosition = HandleOrigin;
		}

		/// <summary>
		/// The Click event is raised when the MouseLeftButtonDown event occurs
		/// on the DragHandle's shape. The handle responds by capturing the
		/// mouse and raising this event.
		/// </summary>
		public event EventHandler<DragHandleEventArgs> Click;

		/// <summary>
		/// The BeginDrag event is raised when the mouse is moved with the left
		/// button down, within the confines of the handle's shape, and the
		/// handle is not already dragging.
		/// </summary>
		public event EventHandler<DragHandleEventArgs> BeginDrag;

		/// <summary>
		/// The Drag event is raised when the mouse is moved with the left
		/// button down, and the handle is in the dragging state (BeginDrag
		/// has been raised).
		/// </summary>
		public event EventHandler<DragHandleEventArgs> Drag;

		/// <summary>
		/// The EndDrag event is raised when the MouseLeftButtonUp event 
		/// occurs with the handle in the dragging state. The handle releases
		/// mouse capture and raises this event.
		/// </summary>
		public event EventHandler<DragHandleEventArgs> EndDrag;

		/// <summary>
		/// Backing DP for the HandleCursor property.
		/// </summary>
		public static readonly DependencyProperty HandleCursorProperty =
			DependencyProperty.Register("HandleCursor", typeof(Cursor),
			typeof(DragHandle), new PropertyMetadata(Cursors.Arrow,
			new PropertyChangedCallback(OnHandleCursorChange)));

		/// <summary>
		/// Gets and sets the <see cref="T:System.Windows.Input.Cursor">
		/// mosue cursor that will be displayed when the mouse is hovered
		/// over the handle. Defaults to Cursors.Arrow.</see>
		/// </summary>
		public Cursor HandleCursor
		{
			get { return (Cursor)GetValue(HandleCursorProperty); }
			set { SetValue(HandleCursorProperty, value); }
		}

		/// <summary>
		/// Handles the property changed event for the HandleCursor DP.
		/// </summary>
		private static void OnHandleCursorChange(DependencyObject source,
		DependencyPropertyChangedEventArgs e)
		{
			((DragHandle)source).UpdateHandleCursor((Cursor)e.NewValue);
		}

		/// <summary>
		/// Called from the property changed event handler for the HandleCursor
		/// DP. If the handle has a shape it sets the cursor.
		/// </summary>
		/// <param name="newCursor">A Cursor</param>
		private void UpdateHandleCursor(Cursor newCursor)
		{
			if (null != _shape)
				_shape.Cursor = newCursor;
		}

		/// <summary>
		/// Backing DP for the HandleDragConstraint property
		/// </summary>
		public static readonly DependencyProperty HandleDragConstraintProperty =
			DependencyProperty.Register("HandleDragConstraint", typeof(DragConstraint),
			typeof(DragHandle), new PropertyMetadata(DragConstraint.None, 
			new PropertyChangedCallback(OnHandleDragConstraintChange) ));

		/// <summary>
		/// Gets and sets the <see cref="T:DrawControls.DragConstraint">DragConstraint</see>
		/// that determines where this handle is allowed to move. Using the compass rose
		/// as a visualization tool, with north at the top of the screen, handles can be
		/// constrained to move EW, NS, NESW, SENW, N, E, S, W, NE, SE, SW, NW.
		/// </summary>
		public DragConstraint HandleDragConstraint
		{
			get { return (DragConstraint)GetValue(HandleDragConstraintProperty); }
			set { SetValue(HandleDragConstraintProperty, value); }
		}

		/// <summary>
		/// Handles the property changed event for the HandleDragConstraint DP
		/// </summary>
		/// <param name="source"></param>
		/// <param name="e"></param>
		private static void OnHandleDragConstraintChange( DependencyObject source,
			DependencyPropertyChangedEventArgs e )
		{
			((DragHandle)source).UpdateHandleDragConstraint( (DragConstraint)e.NewValue );
		}

		/// <summary>
		/// Called from the HandleDragConstraint property changed event handler to
		/// update the HandleDragConstraint
		/// </summary>
		/// <param name="d">DragConstraint to be applied</param>
		private void UpdateHandleDragConstraint( DragConstraint d )
		{
			SetDragConstraint( d );
		}

		/// <summary>
		/// Backing DP for the HandleWidth property
		/// </summary>
		public static readonly DependencyProperty HandleWidthProperty =
			DependencyProperty.Register("HandleWidth", typeof(double),
			typeof(DragHandle), new PropertyMetadata(2.0,
			new PropertyChangedCallback(OnHandleWidthChange)));

		/// <summary>
		/// Gets and sets the width of the handle in pixels. Setting this
		/// property will set the width on the handle's shape, as well as
		/// the backing canvas.
		/// </summary>
		public double HandleWidth
		{
			get { return (double)GetValue(HandleWidthProperty); }
			set { SetValue(HandleWidthProperty, value); }
		}

		/// <summary>
		/// Handles property change events for the HandleWidth DP.
		/// </summary>
		private static void OnHandleWidthChange(DependencyObject source,
			DependencyPropertyChangedEventArgs e)
		{
			((DragHandle)source).UpdateHandleWidth((double)e.NewValue);
		}

		/// <summary>
		/// Called from the property change handler for the HandleWidth
		/// property.
		/// </summary>
		/// <param name="newWidth">A double representing the new width</param>
		private void UpdateHandleWidth(double newWidth)
		{
			this.Width = newWidth;
			if (null != _shape)
				_shape.Width = newWidth;
		}

		/// <summary>
		/// Backing DP for the HandleHeight property
		/// </summary>
		public static readonly DependencyProperty HandleHeightProperty =
			DependencyProperty.Register("HandleHeight", typeof(double),
			typeof(DragHandle), new PropertyMetadata(2.0,
			new PropertyChangedCallback(OnHandleHeightChange)));

		/// <summary>
		/// Gets and sets the height of the handle in pixels. Setting this
		/// property will set the height on the handle's shape, as well as
		/// the backing canvas.
		/// </summary>
		public double HandleHeight
		{
			get { return (double)GetValue(HandleHeightProperty); }
			set { SetValue(HandleHeightProperty, value); }
		}

		/// <summary>
		/// Handles property change events for the HandleWidth DP.
		/// </summary>
		private static void OnHandleHeightChange(DependencyObject source,
			DependencyPropertyChangedEventArgs e)
		{
			((DragHandle)source).UpdateHandleHeight((double)e.NewValue);
		}

		/// <summary>
		/// Called from the property change handler for the HandleHeight
		/// property.
		/// </summary>
		/// <param name="newWidth">A double representing the new height</param>
		private void UpdateHandleHeight(double newHeight)
		{
			this.Height = newHeight;
			if (null != _shape)
				_shape.Height = newHeight;
		}

		/// <summary>
		/// Backing DP for the HandleOffsetX property
		/// </summary>
		public static readonly DependencyProperty HandleOffsetXProperty =
			DependencyProperty.Register("HandleOffsetX", typeof(double),
			typeof(DragHandle), new PropertyMetadata(0.0, 
			new PropertyChangedCallback(OnHandleOffsetXChange)));

		/// <summary>
		/// Gets and sets the offset, in pixels, from the handle's reported position
		/// to its top-left corner. When the handle is drawn its x coordinate 
		/// will be adjusted by adding HandleOffsetX.
		/// </summary>
		public double HandleOffsetX
		{
			get { return (double)GetValue(HandleOffsetXProperty); }
			set { SetValue(HandleOffsetXProperty, value); }
		}

		/// <summary>
		/// Handles property change events for the HandleOffsetX DP
		/// </summary>
		private static void OnHandleOffsetXChange( DependencyObject source,
			DependencyPropertyChangedEventArgs e )
		{
			((DragHandle)source).UpdateHandleOffsetX( (double)e.NewValue );
		}

		/// <summary>
		/// Called from the property change event handler for HandleOffsetX
		/// DP. Updates the control's position on the parent canvas.
		/// </summary>
		/// <param name="newOffset">A double containing the offset</param>
		private void UpdateHandleOffsetX( double newOffset )
		{
			Canvas.SetLeft( this, HandlePosition.X + HandleOffsetX );
		}

		/// <summary>
		/// Backing DP for the HandleOffsetY property
		/// </summary>
		public static readonly DependencyProperty HandleOffsetYProperty =
			DependencyProperty.Register("HandleOffsetY", typeof(double),
			typeof(DragHandle), new PropertyMetadata(0.0, 
			new PropertyChangedCallback(OnHandleOffsetYChange)));

		/// <summary>
		/// Gets and sets the offset from the handle's reported position
		/// to its top-left corner. When the handle is drawn its y coordinate 
		/// will be adjusted by adding HandleOffsetY.
		/// </summary>
		public double HandleOffsetY
		{
			get { return (double)GetValue(HandleOffsetYProperty); }
			set { SetValue(HandleOffsetYProperty, value); }
		}

		/// <summary>
		/// Handles property change events for the HandleOffsetY DP
		/// </summary>
		private static void OnHandleOffsetYChange(DependencyObject source,
			DependencyPropertyChangedEventArgs e)
		{
			((DragHandle)source).UpdateHandleOffsetY((double)e.NewValue);
		}

		/// <summary>
		/// Called from the property change event handler for HandleOffsetX
		/// DP. Updates the control's position on the parent canvas.
		/// </summary>
		/// <param name="newOffset">A double containing the offset</param>
		private void UpdateHandleOffsetY(double newOffset)
		{
			Canvas.SetTop(this, HandlePosition.Y + HandleOffsetY);
		}

		/// <summary>
		/// Backing DP for the HandlePosition property
		/// </summary>
		public static readonly DependencyProperty HandlePositionProperty =
			DependencyProperty.Register("HandlePosition", typeof(Point),
			typeof(DragHandle), new PropertyMetadata(new Point(0, 0),
			new PropertyChangedCallback(OnHandlePositionChange)));

		/// <summary>
		/// Gets and sets the position of the handle. Unless a
		/// <see cref="T:DrawControls.DragHandle.ReferenceElement">reference element</see>
		/// has been set, this position will be relative to the Silverlight plug-in's
		/// overall screen area.
		/// </summary>
		public Point HandlePosition
		{
			get { return (Point)GetValue(HandlePositionProperty); }
			set { SetValue(HandlePositionProperty, value); }
		}

		/// <summary>
		/// Handles property change events for the HandlePosition DP
		/// </summary>
		private static void OnHandlePositionChange(DependencyObject source,
			DependencyPropertyChangedEventArgs e)
		{
			((DragHandle)source).UpdateHandlePosition((Point)e.NewValue);
		}

		/// <summary>
		/// Called from the property change event handler for the HandlePosition
		/// DP/
		/// </summary>
		/// <param name="newPos">A Point containing the new position</param>
		private void UpdateHandlePosition(Point newPos)
		{
			Canvas.SetLeft( this, newPos.X + HandleOffsetX );
			Canvas.SetTop(this, newPos.Y + HandleOffsetY );
		}

		/// <summary>
		/// Backing DP for the HandleOrigin property
		/// </summary>
		public static readonly DependencyProperty HandleOriginProperty =
			DependencyProperty.Register("HandleOrigin", typeof(Point),
			typeof(DragHandle), new PropertyMetadata(new Point(0, 0), null));

		/// <summary>
		/// Gets and sets the <see cref="T:System.Windows.Point">Point</see>
		/// that serves as the handle's origin, or point of reference for
		/// movement calculations.
		/// </summary>
		public Point HandleOrigin
		{
			get { return (Point)GetValue(HandleOriginProperty); }
			set { SetValue(HandleOriginProperty, value); }
		}

		/// <summary>
		/// Backing DP for the HandleShape property
		/// </summary>
		public static readonly DependencyProperty HandleShapeProperty =
			DependencyProperty.Register("HandleShape", typeof(Shape),
			typeof(DragHandle), new PropertyMetadata(null,
			new PropertyChangedCallback(OnHandleShapeChange)));

		/// <summary>
		/// Gets and sets the shape used to display the handle. By default
		/// the handle creates a rectangle and displays it on an unfilled
		/// canvas. Setting this property to a different shape will replace
		/// the rectangle with that shape. 
		/// </summary>
		public Shape HandleShape
		{
			get { return (Shape)GetValue(HandleShapeProperty); }
			set { SetValue(HandleShapeProperty, value); }
		}

		/// <summary>
		/// Handles property change events for the HandleShape DP
		/// </summary>
		/// <param name="source"></param>
		/// <param name="e"></param>
		private static void OnHandleShapeChange(DependencyObject source,
			DependencyPropertyChangedEventArgs e)
		{
			((DragHandle)source).UpdateHandleShape((Shape)e.NewValue);
		}

		/// <summary>
		/// Called from the property change event handler for the HandleShape
		/// DP. Calls UnwireShape() to disconnect event handles from the current
		/// shape, if any, then sets up the new shape's properties, adds it to
		/// the handle canvas, and calls WireShape() to connect its event
		/// handlers.
		/// </summary>
		/// <param name="newShape">The new shape for the handle</param>
		public void UpdateHandleShape(Shape newShape)
		{
			if (null != _shape)
			{
				UnwireShape();
				HandleCanvas.Children.Remove(_shape);
			}
			_shape = newShape;
			_shape.Width = HandleWidth;
			_shape.Height = HandleHeight;
			_shape.Fill = HandleFill;
			_shape.Stroke = HandleStroke;
			_shape.Cursor = HandleCursor;
			HandleCanvas.Children.Add(_shape);
			WireShape();
		}

		/// <summary>
		/// The backing DP for the HandleFill property
		/// </summary>
		public static readonly DependencyProperty HandleFillProperty =
			DependencyProperty.Register("HandleFill", typeof(Brush),
			typeof(DragHandle), new PropertyMetadata(null,
			new PropertyChangedCallback(OnHandleFillChange)));

		/// <summary>
		/// Gets and sets the <see cref="T:System.Windows.Brush">brush</see>
		/// used to fill the handle shape. Defaults to null, which will result
		/// in an unfilled shape.
		/// </summary>
		public Brush HandleFill
		{
			get { return (Brush)GetValue(HandleFillProperty); }
			set { SetValue(HandleFillProperty, value); }
		}

		/// <summary>
		/// Property change event handler for the HandleFill DP
		/// </summary>
		private static void OnHandleFillChange(DependencyObject source,
			DependencyPropertyChangedEventArgs e)
		{
			((DragHandle)source).UpdateHandleFill((Brush)e.NewValue);
		}

		/// <summary>
		/// Called from the HandleFill property change event handler to
		/// update the current shape's fill.
		/// </summary>
		/// <param name="newFill">A Brush which will be used to fill the handle shape</param>
		private void UpdateHandleFill(Brush newFill)
		{
			if (null != _shape)
				_shape.Fill = newFill;
		}

		/// <summary>
		/// Backing DP for the HandleStroke property
		/// </summary>
		public static readonly DependencyProperty HandleStrokeProperty =
			DependencyProperty.Register("HandleStroke", typeof(Brush),
			typeof(DragHandle), new PropertyMetadata(null,
			new PropertyChangedCallback(OnHandleStrokeChange)));

		/// <summary>
		/// Gets and sets the <see cref="T:System.Windows.Brush">brush</see>
		/// used to outline the handle shape. Defaults to null, which results
		/// in an unoutlined shape. If <see cref="T:DrawControls.DragHandle.HandleFill">
		/// HandleFill</see> is also null the handle will be invisible.
		/// </summary>
		public Brush HandleStroke
		{
			get { return (Brush)GetValue(HandleStrokeProperty); }
			set { SetValue(HandleStrokeProperty, value); }
		}

		/// <summary>
		/// Property change event handler for the HandleStroke DP
		/// </summary>
		private static void OnHandleStrokeChange(DependencyObject source,
			DependencyPropertyChangedEventArgs e)
		{
			((DragHandle)source).UpdateHandleStroke((Brush)e.NewValue);
		}

		/// <summary>
		/// Called from the HandleStroke property change event handler to
		/// update the stroke brush for the current shape, if any.
		/// </summary>
		/// <param name="newStroke">A Brush which will be used to outline the handle shape</param>
		private void UpdateHandleStroke(Brush newStroke)
		{
			if (null != _shape)
				_shape.Stroke = newStroke;
		}

		/// <summary>
		/// Backing DP for the HandleStrokeThickness property
		/// </summary>
		public static readonly DependencyProperty HandleStrokeThicknessProperty =
			DependencyProperty.Register("HandleStrokeThickness", typeof(double),
			typeof(DragHandle), new PropertyMetadata(1.0,
			new PropertyChangedCallback(OnHandleStrokeThicknessChange)));

		/// <summary>
		/// Gets and sets the thickness of the stroke used to outline the handle
		/// shape. Defaults to a thickness of 1.0.
		/// </summary>
		public double HandleStrokeThickness
		{
			get { return (double)GetValue(HandleStrokeThicknessProperty); }
			set { SetValue(HandleStrokeThicknessProperty, value); }
		}

		/// <summary>
		/// Property change event handler for the HandleStrokeThickness DP.
		/// </summary>
		private static void OnHandleStrokeThicknessChange(DependencyObject source,
			DependencyPropertyChangedEventArgs e)
		{
			((DragHandle)source).UpdateHandleStrokeThickness((double)e.NewValue);
		}

		/// <summary>
		/// Called from the HandleStrokeThickness property change event handler
		/// to update the stroke thickness for the current shape, if any.
		/// </summary>
		/// <param name="newThickness">A double containing the thickness</param>
		private void UpdateHandleStrokeThickness(double newThickness)
		{
			if (null != _shape)
				_shape.StrokeThickness = newThickness;
		}

		/// <summary>
		/// Backing DP for the ReferenceElement property
		/// </summary>
		public static readonly DependencyProperty ReferenceElementProperty =
			DependencyProperty.Register("ReferenceElement", typeof(UIElement),
			typeof(DragHandle), new PropertyMetadata(null, null));

		/// <summary>
		/// Gets and sets the UIElement whose coordinate space will be used as
		/// the frame or reference for reported handle movements. If this
		/// property is left at the default null value then reported movement
		/// will be relative to the overall Silverlight plug-in onscreen
		/// area.
		/// </summary>
		public UIElement ReferenceElement
		{
			get { return (UIElement)GetValue(ReferenceElementProperty); }
			set { SetValue(ReferenceElementProperty, value); }
		}

		/// <summary>
		/// Backing DP for the HandleMinX property
		/// </summary>
		public static readonly DependencyProperty HandleMinXProperty =
			DependencyProperty.Register("HandleMinX", typeof(double),
			typeof(DragHandle), new PropertyMetadata(Double.MinValue, null));

		/// <summary>
		/// Gets and sets the minimum x coordinate to which the DragHandle will
		/// be allowed to move, regardless of other movement constraints.
		/// </summary>
		public double HandleMinX
		{
			get { return (double)GetValue(HandleMinXProperty); }
			set { SetValue(HandleMinXProperty, value); }
		}

		/// <summary>
		/// Backing DP for the HandleMaxX property
		/// </summary>
		public static readonly DependencyProperty HandleMaxXProperty =
			DependencyProperty.Register("HandleMaxX", typeof(double),
			typeof(DragHandle), new PropertyMetadata(Double.MaxValue, null));

		/// <summary>
		/// Gets and sets the maximum x coordinate to which the DragHandle will
		/// be allowed to move, regardless of other movement constraints.
		/// </summary>
		public double HandleMaxX
		{
			get { return (double)GetValue(HandleMaxXProperty); }
			set { SetValue(HandleMaxXProperty, value); }
		}

		/// <summary>
		/// Backing DP for the HandleMinY property
		/// </summary>
		public static readonly DependencyProperty HandleMinYProperty =
			DependencyProperty.Register("HandleMinY", typeof(double),
			typeof(DragHandle), new PropertyMetadata(Double.MinValue, null));

		/// <summary>
		/// Gets and sets the minimum y coordinate to which the DragHandle will
		/// be allowed to move, regardless of other movement constraints.
		/// </summary>
		public double HandleMinY
		{
			get { return (double)GetValue(HandleMinYProperty); }
			set { SetValue(HandleMinYProperty, value); }
		}

		/// <summary>
		/// Backing DP for the HandleMaxY property
		/// </summary>
		public static readonly DependencyProperty HandleMaxYProperty =
			DependencyProperty.Register("HandleMaxY", typeof(double),
			typeof(DragHandle), new PropertyMetadata(Double.MaxValue, null));

		/// <summary>
		/// Gets and sets the maximum y coordinate to which the DragHandle will
		/// be allowed to move, regardless of other movement constraints.
		/// </summary>
		public double HandleMaxY
		{
			get { return (double)GetValue(HandleMaxYProperty); }
			set { SetValue(HandleMaxYProperty, value); }
		}

		/// <summary>
		/// Called to raise the Click event on the DragHandle
		/// </summary>
		/// <param name="e">DragHandleEventArgs</param>
		protected void OnClick(DragHandleEventArgs e)
		{
			if ( null != Click )
			Click( this, e );
		}

		/// <summary>
		/// Called to raise the BeginDrag event on the DragHandle
		/// </summary>
		/// <param name="e">DragHandleEventArgs</param>
		protected void OnBeginDrag(DragHandleEventArgs e)
		{
			if (null != BeginDrag)
			BeginDrag(this, e);
		}

		/// <summary>
		/// Called to raise the Drag event on the DragHandle
		/// </summary>
		/// <param name="e">DragHandleEventArgs</param>
		protected void OnDrag(DragHandleEventArgs e)
		{
			if (null != Drag)
			Drag(this, e);
		}

		/// <summary>
		/// Called to raise the EndDrag event on the DragHandle
		/// </summary>
		/// <param name="e">DragHandleEventArgs</param>
		protected void OnEndDrag(DragHandleEventArgs e)
		{
			if (null != EndDrag)
			EndDrag(this, e);
		}

		/// <summary>
		/// Sets up the default handle with a Rectangle for a shape
		/// </summary>
		private void InitHandle()
		{
			HandleShape = new Rectangle();
			HandleOrigin = HandlePosition;
		}

		/// <summary>
		/// Called at intialization, and when the handle shape changes. Wires up
		/// the shape's mouse events to handlers
		/// </summary>
		private void WireShape()
		{
			_shape.MouseLeftButtonDown += new MouseButtonEventHandler(_shape_MouseLeftButtonDown);
			_shape.MouseMove += new MouseEventHandler(_shape_MouseMove);
			_shape.MouseLeftButtonUp += new MouseButtonEventHandler(_shape_MouseLeftButtonUp);
		}

		/// <summary>
		/// Called when the handle's shape changes. Disconnects event handlers from the
		/// current shape.
		/// </summary>
		private void UnwireShape()
		{
			_shape.MouseLeftButtonDown -= new MouseButtonEventHandler(_shape_MouseLeftButtonDown);
			_shape.MouseMove -= new MouseEventHandler(_shape_MouseMove);
			_shape.MouseLeftButtonUp -= new MouseButtonEventHandler(_shape_MouseLeftButtonUp);
		}

		// The mouse event handlers operate according to the following rules:
		//
		// - If the left button is pressed on the shape the mouse is captured and
		//   Click is raised.
		// - If the mouse is moved over the shape with the left button down and
		//   the shape is not dragging it goes into drag state and BeginDrag is
		//   raised.
		// - If the mouse is moved over the shape with the left button down and
		//   the shape is dragging Drag is raised.
		// - If the mouse left button is released the mouse capture is released and
		//   EndDrag is raised.


		/// <summary>
		/// Handles the MouseLeftButtonDown event on the handle's shape
		/// </summary>
		/// <param name="sender">A Shape object</param>
		/// <param name="e">MouseButtonEventArgs</param>
		private void _shape_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
		{
			_shape.CaptureMouse();
			DragHandleEventArgs args = new DragHandleEventArgs();
			args.Position = e.GetPosition(ReferenceElement);
			args.Origin = HandleOrigin;
			OnClick(args);
			_mouseDown = true;
			e.Handled = true;
		}

		/// <summary>
		/// Handles the MouseLeftButtonUp event on the handle's shape
		/// </summary>
		/// <param name="sender">A Shape object</param>
		/// <param name="e">MouseButtonEventArgs</param>
		private void _shape_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
		{
			_shape.ReleaseMouseCapture();
			if ( _dragging )
			{
				DragHandleEventArgs args = new DragHandleEventArgs();
				args.Position = e.GetPosition(ReferenceElement);
				args.Origin = HandleOrigin;
				_dragging = false;
				OnEndDrag(args);
			}
			_mouseDown = false;
			e.Handled = true;
		}

		/// <summary>
		/// Handles the MouseMove event on the handle's shape
		/// </summary>
		/// <param name="sender">A Shape object</param>
		/// <param name="e">MouseEventArgs</param>
		private void _shape_MouseMove(object sender, MouseEventArgs e)
		{
			if (_mouseDown)
			{
				Point pos = e.GetPosition(ReferenceElement);
				if (RequestDragTo(ref pos))
				{
					HandlePosition = pos;
					DragHandleEventArgs args = new DragHandleEventArgs();
					args.Position = pos;
					args.Origin = HandleOrigin;

					if ( !_dragging )
					{
						_dragging = true;
						OnBeginDrag( args );
					}
					else OnDrag(args);
				}
			}
		}

		/// <summary>
		/// Called when the HandleDragConstraint property changes. This method
		/// sets up the appropriate eval delegate to be called when the handle
		/// moves.
		/// </summary>
		/// <param name="d">DragConstraint to be applied</param>
		private void SetDragConstraint( DragConstraint d )
		{
			switch ( d )
			{
				case DragConstraint.None:
					_evalMove = null;
					break;

				case DragConstraint.E:
					_evalMove = new MovementEvalCallback(Eval_MoveE);
					break;

				case DragConstraint.W:
					_evalMove = new MovementEvalCallback(Eval_MoveW);
					break;

				case DragConstraint.N:
					_evalMove = new MovementEvalCallback(Eval_MoveN);
					break;

				case DragConstraint.S:
					_evalMove = new MovementEvalCallback(Eval_MoveS);
					break;

				case DragConstraint.EW:
					_evalMove = new MovementEvalCallback(Eval_MoveEW);
					break;

				case DragConstraint.NS:
					_evalMove = new MovementEvalCallback(Eval_MoveNS);
					break;

				case DragConstraint.NESW:
					_evalMove = new MovementEvalCallback(Eval_MoveNESW);
					break;

				case DragConstraint.SENW:
					_evalMove = new MovementEvalCallback(Eval_MoveSENW);
					break;

				case DragConstraint.NE:
					_evalMove = new MovementEvalCallback(Eval_MoveNE);
					break;

				case DragConstraint.SW:
					_evalMove = new MovementEvalCallback(Eval_MoveSW);
					break;

				case DragConstraint.SE:
					_evalMove = new MovementEvalCallback(Eval_MoveSE);
					break;

				case DragConstraint.NW:
					_evalMove = new MovementEvalCallback(Eval_MoveSW);
					break;
			}
		}

		// The following small methods each provide movement evaluation
		// for a specific DragConstraint

		/// <summary>
		/// Evaluate movement with DragConstraint.None applied
		/// </summary>
		/// <param name="dest">The point the DragHandle wants to move to</param>
		/// <returns>True if the movement is permitted, otherwise false. Note that
		/// even when the return value is true the values of dest.X and dest.Y
		/// may have changed to constrain movement.</returns>
		private bool Eval_MoveNone( ref Point dest )
		{
			return true;
		}

		/// <summary>
		/// Evaluate movement with DragConstraint.E applied
		/// </summary>
		/// <param name="dest">The point the DragHandle wants to move to</param>
		/// <returns>True if the movement is permitted, otherwise false. Note that
		/// even when the return value is true the values of dest.X and dest.Y
		/// may have changed to constrain movement.</returns>
		private bool Eval_MoveE(ref Point dest)
		{
			if (dest.X >= HandleOrigin.X)
			{
				dest.Y = HandleOrigin.Y;
				return true;
			}
			else return false;
		}

		/// <summary>
		/// Evaluate movement with DragConstraint.W applied
		/// </summary>
		/// <param name="dest">The point the DragHandle wants to move to</param>
		/// <returns>True if the movement is permitted, otherwise false. Note that
		/// even when the return value is true the values of dest.X and dest.Y
		/// may have changed to constrain movement.</returns>
		private bool Eval_MoveW(ref Point dest)
		{
			if (dest.X <= HandleOrigin.X)
			{
				dest.Y = HandleOrigin.Y;
				return true;
			}
			else return false;
		}

		/// <summary>
		/// Evaluate movement with DragConstraint.N applied
		/// </summary>
		/// <param name="dest">The point the DragHandle wants to move to</param>
		/// <returns>True if the movement is permitted, otherwise false. Note that
		/// even when the return value is true the values of dest.X and dest.Y
		/// may have changed to constrain movement.</returns>
		private bool Eval_MoveN(ref Point dest)
		{
			if (dest.Y <= HandleOrigin.Y)
			{
				dest.X = HandleOrigin.X;
				return true;
			}
			else return false;
		}

		/// <summary>
		/// Evaluate movement with DragConstraint.S applied
		/// </summary>
		/// <param name="dest">The point the DragHandle wants to move to</param>
		/// <returns>True if the movement is permitted, otherwise false. Note that
		/// even when the return value is true the values of dest.X and dest.Y
		/// may have changed to constrain movement.</returns>
		private bool Eval_MoveS(ref Point dest)
		{
			if (dest.Y >= HandleOrigin.Y)
			{
				dest.X = HandleOrigin.X;
				return true;
			}
			else return false;
		}

		/// <summary>
		/// Evaluate movement with DragConstraint.EW applied
		/// </summary>
		/// <param name="dest">The point the DragHandle wants to move to</param>
		/// <returns>True if the movement is permitted, otherwise false. Note that
		/// even when the return value is true the values of dest.X and dest.Y
		/// may have changed to constrain movement.</returns>
		private bool Eval_MoveEW(ref Point dest)
		{
			dest.Y = HandleOrigin.Y;
			return true;
		}

		/// <summary>
		/// Evaluate movement with DragConstraint.NS applied
		/// </summary>
		/// <param name="dest">The point the DragHandle wants to move to</param>
		/// <returns>True if the movement is permitted, otherwise false. Note that
		/// even when the return value is true the values of dest.X and dest.Y
		/// may have changed to constrain movement.</returns>
		private bool Eval_MoveNS(ref Point dest)
		{
			dest.X = HandleOrigin.X;
			return true;
		}

		/// <summary>
		/// Evaluate movement with DragConstraint.NESW applied
		/// </summary>
		/// <param name="dest">The point the DragHandle wants to move to</param>
		/// <returns>True if the movement is permitted, otherwise false. Note that
		/// even when the return value is true the values of dest.X and dest.Y
		/// may have changed to constrain movement.</returns>
		private bool Eval_MoveNESW(ref Point dest)
		{
			dest.Y = HandleOrigin.Y - (dest.X - HandleOrigin.X);
			return true;
		}

		/// <summary>
		/// Evaluate movement with DragConstraint.SENW applied
		/// </summary>
		/// <param name="dest">The point the DragHandle wants to move to</param>
		/// <returns>True if the movement is permitted, otherwise false. Note that
		/// even when the return value is true the values of dest.X and dest.Y
		/// may have changed to constrain movement.</returns>
		private bool Eval_MoveSENW(ref Point dest)
		{
			dest.Y = HandleOrigin.Y + (dest.X - HandleOrigin.X);
			return true;
		}

		/// <summary>
		/// Evaluate movement with DragConstraint.NE applied
		/// </summary>
		/// <param name="dest">The point the DragHandle wants to move to</param>
		/// <returns>True if the movement is permitted, otherwise false. Note that
		/// even when the return value is true the values of dest.X and dest.Y
		/// may have changed to constrain movement.</returns>
		private bool Eval_MoveNE(ref Point dest)
		{
			if (dest.X < HandleOrigin.X)
				return false;
			else
			{
				dest.Y = HandleOrigin.Y - (dest.X - HandleOrigin.X);
				return true;
			}
		}

		/// <summary>
		/// Evaluate movement with DragConstraint.SW applied
		/// </summary>
		/// <param name="dest">The point the DragHandle wants to move to</param>
		/// <returns>True if the movement is permitted, otherwise false. Note that
		/// even when the return value is true the values of dest.X and dest.Y
		/// may have changed to constrain movement.</returns>
		private bool Eval_MoveSW(ref Point dest)
		{
			if (dest.X > HandleOrigin.X)
				return false;
			else
			{
				dest.Y = HandleOrigin.Y - (dest.X - HandleOrigin.X);
				return true;
			}
		}

		/// <summary>
		/// Evaluate movement with DragConstraint.SE applied
		/// </summary>
		/// <param name="dest">The point the DragHandle wants to move to</param>
		/// <returns>True if the movement is permitted, otherwise false. Note that
		/// even when the return value is true the values of dest.X and dest.Y
		/// may have changed to constrain movement.</returns>
		private bool Eval_MoveSE(ref Point dest)
		{
			if (dest.X < HandleOrigin.X)
				return false;
			else
			{
				dest.Y = HandleOrigin.Y + (dest.X - HandleOrigin.X);
				return true;
			}
		}

		/// <summary>
		/// Evaluate movement with DragConstraint.NW applied
		/// </summary>
		/// <param name="dest">The point the DragHandle wants to move to</param>
		/// <returns>True if the movement is permitted, otherwise false. Note that
		/// even when the return value is true the values of dest.X and dest.Y
		/// may have changed to constrain movement.</returns>
		private bool Eval_MoveNW(ref Point dest)
		{
			if (dest.X > HandleOrigin.X)
				return false;
			else
			{
				dest.Y = HandleOrigin.Y + (dest.X - HandleOrigin.X);
				return true;
			}
		}

		/// <summary>
		/// Called when the handle is dragged to a new position.
		/// </summary>
		/// <param name="dest">The Point to which the handle was moved. 
		/// Parameter may be modified by the evaluation.</param>
		/// <returns>True if movement is allowed, otherwise false</returns>
		private bool RequestDragTo(ref Point dest)
		{
			if (dest == HandleOrigin) 
				return true;

			if (dest.X < HandleMinX || dest.X > HandleMaxX ||
				dest.Y < HandleMinY || dest.Y > HandleMaxY)
				return false;

				return (null != _evalMove ? _evalMove(ref dest) : true);

		}

		private delegate bool MovementEvalCallback(ref Point p);

		private MovementEvalCallback _evalMove;

		private Shape _shape;
		private bool _dragging;
		private bool _mouseDown;
	}
}

Leave a Reply

Your email address will not be published. Required fields are marked *