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