/******************************************************************************
* 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;
}
}