Trying to get a few things cleared away before I start a new position on Monday morning. Shortly after the January release of my GSearch libraries for .NET 3.5 and Silverlight 2, Codeplex user FBrink discovered that my conversions for lattitude/longitude in the local search class were naive, in that they failed when the current culture uses the comma character as a decimal separator. Tonight I released version 1.1 of the .NET and SL libraries, which corrects that issue, as well as updating some similarly naive code in the search class event raising machinery. You can check out the release notes and grab the latest source and runtimes at the CodePlex project page.
Month: February 2009
Gradient Editor Update
I’ve released version 1.0 of the Silverlight Gradient Editor. This version fixes a few small bugs and usability issues, and adds support for transparent gradient stops using the system color picker. This will probably be the last release of the editor unless I discover some problems. There’s not really much more to do with it. It came into existance because I needed a gradient editor control for the drawing application I’m working on, and once I had one it was a very small leap to add some code output and slap it into a web page. Hope you find it useful. If you do, or if you run into some problems, drop me a note and let me know.
Playing With Blocks: Demo Time
Since my last “Playing with Blocks” post, on the DragHandleConnector class, I’ve been hard at work (when time permitted) fleshing out the application to get to the point where I could post a demo. That point has finally arrived, and you can see the results for yourself. Here’s a screen shot:
There is a lot you still can’t do in the demo. You can’t select objects and change them, move them, or delete them, for example. That’s actually the next area I’ll be working on. What you can do is edit styles and lay down shapes and text. The style editing encompasses all of the visual properties that apply to shapes and text, including opacity, fill and stroke, etc.
As often happens a ton of work has gone into producing only a very few features, because so much infrastructure had to be designed, implemented, and tweaked. There have been quite a few changes along the way, some major, as I figured out that some earlier ideas wouldn’t work as I had hoped they would. But a lot of it did, and since I like the way most of it has turned out I’ll say a couple of words about how it works.
The bulk of the work involved how tools would be defined, and interfaced to. I wanted an extensible mechanism that loaded tools dynamically. Currently tools are defined in a library that is loaded when the application starts. That’s the slight delay between the display of the main window and the appearance of the controls. Each tool defines how it will appear in the toolbox, the editor class that should be invoked to do whatever the tool does, a default Style to be applied to new instances of whatever shape it creates, and potentially the style control needed to manipulate the properties of the shape. The library provides a list of those style controls to the application when it is first loaded.
Styles are used to carry information about the appearance of shapes between the various subsystems in the app. When a tool is first loaded it provides a default style to the StyleBox, which loads the appropriate controls for editing that style, and gives each control a chance to initialize itself from the setter values in the Style. When the user makes changes to the style these are preserved in the editor for future use until the application is restarted. A lot of the tweaking I had to do involved learning how to create, manipulate, and apply styles dynamically. Some of that was interesting, and may be the subject of a future post. For now it’s back to the drawing board to begin working on shape selection and manipulation.
Deep Doodoo
The New York Times has an interesting article today on various efforts to search the “deep web”, including a project at Google (of course). The deep web is defined as all the information in web-connected databases, in contrast with all the information that is encoded in HTML on static pages.
I’m using the Times’ descriptions, but obviously there’s a lot of slop here. First, the deep web would really be anything that is hidden behind a form POST or querystring, as opposed to being statically encoded in a page. Querystrings can be linked to, and search engines can already crawl those links when they are presented statically. For example, Google does a very good job of indexing messages written on forums, which are always accessed through a querystring or posted form data.
So, the term “web-connected” database must be taken loosely, even though it applies to the vast majority of cases. The real challenge is how to get past the data submission layer which human surfers use to tell the back-end engine what they are interested in. This is, as Alon Halevy of Google says in the piece, “the most interesting data integration problem imaginable.”
But it is also a business problem, and one that is unlikely to be solved. This isn’t really distinguishable from issues I wrestled with back in the late 1990’s, while engaged in the development of online banking technology. The holy grail of such technology, for companies that are not banks, has always been to integrate all the account information from various financial institutions. Quicken and Microsoft have been pseudo-collaborating for years to try and make this happen. They may have given up by now.
If they have, then it’s because such integration is not at all the holy grail for companies that are banks. They have no interest in having their information, which they pay to create, store, maintain, and retrieve on demand, de-branded and used to add value to some third party’s website.
These “deep web” initiatives face the same challenge. The NYT piece talks about the difficulties of understanding the semantics of the search interfaces, with solutions described that include querying on the entire dictionary to find out what the database contains. Well those semantics are perfectly understandable to the customers and users of the people who paid to create those interfaces. If I owned one, and noticed Google’s bot driving up my database utilization by trying to query huge blocks of semantically-related terms I would block it in an instant.
These efforts are misguided, because they attempt to do an end-run around the whole value proposition for putting this data on the web in the first place. It would be better to focus on a collaborative API that allows database owners to make certain slices of their data visible to search engines via semantic queries, in return for greater traffic and more convenience for their users.
How Var Will You Go?
A snippet of code I saw posted on a forum today goes a long way toward explaining what I don’t like about the var keyword in C#:
var something = new Size(double.PositiveInfinity, double.PositiveInfinity);
The writer was simply trying to illustrate a point about calling Measure() on a newly-constructed element in Silverlight. What, you might wonder, is wrong with that code? Well, syntactically nothing is wrong with it. The var keyword is a placeholder, and the compiler will look at this statement and figure out that the type of ‘something’ is typeof(Size). The problem? Any programmer who comes along later and has to read and maintain this code now has to perform the same exercise as the compiler: see ‘var’ keyword; parse RHS of statement; figure out type; nod head wearily and continue.
Why? For no reason at all. There was no benefit to using var in this scenario. It simply saved the writer from typing one additional letter:
Size something = new Size (double.PositiveInfinity, double.PositiveInfinity);
And that, ladies and gentlemen, is why I don’t like seeing the rules relaxed just so we can have neato stuff like LINQ. Maybe I’m a hoary old luddite, but I use strongly-typed languages for a reason: namely that they are better. Once you relax the rules humans will take advantage, and who can blame them? It’s a burden to type that extra letter, and obviously in the .NET framework there are a metric ton of examples where the advantage of three-letter var is much greater:
var args = new DependencyPropertyChangedEventArgs();
Now that’s some keystrokes saved right there. Who could blame me? It’s not a huge problem when the type is right there next to the operator, but what about when the type is embedded in a method return?
var args = SomeMethod();
Well, that’s a little harder to figure out, isn’t it? Thank God we have Intellisense to show us what the return type of that method is.
Silverlight ComboBox and The White Screen of Death
Windows users are familiar with the Blue Screen of Death, or BSOD. ASP.NET developers know all about the Yellow Screen of Death (YSOD, naturally). Silverlight development has its own iconic sign of catastrophic failure: the White Screen of Death. Actually, I’m stretching that a little, because I suppose that based on how you have the styles set up for the page hosting the Silverlight 2 plug-in, it could be the Chartreuse Screen of Death, or the Cornflower Blue Screen of Death.
Whatever, the symptoms are the same: something mysterious trashes the plug-in and it dies, and stops rendering to the piece of browser window real-estate it owns. No exception is thrown, and the fault typically happens after you return control to the framework from some operation that later proves to have been ill-conceived. Finding the problem is therefore a classic exercise in cutting stuff out until it goes away. That’s what I did when I ran into an interesting issue with a ComboBox recently. Take a look at this code:
<UserControl x:Class="SilverTests.Page" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Width="400" Height="300"> <Grid x:Name="LayoutRoot" Background="White"> <StackPanel Orientation="Horizontal"> <ComboBox x:Name="TestCombo" Width="100" Height="20" /> <Button x:Name="Test" Click="Test_Click" Width="100" Height="20" Content="Test"/> </StackPanel> </Grid> </UserControl>
Just a simple user control with a ComboBox and a Button we can click to make stuff happen. Here is the code-behind for the control:
using System; using System.Windows; using System.Windows.Controls; namespace SilverTests { public partial class Page : UserControl { public Page() { InitializeComponent(); Random rand = new Random(); for ( int i = 0; i < 20; i++ ) TestCombo.Items.Add( new ComboBoxItem() { Content = rand.Next().ToString() } ); TestCombo.SelectedIndex = rand.Next(20); } private void Test_Click(object sender, RoutedEventArgs e) { Random rand = new Random(); TestCombo.Items.Clear(); for (int i = 0; i < 20; i++) TestCombo.Items.Add(new ComboBoxItem() { Content = rand.Next().ToString() }); TestCombo.SelectedIndex = rand.Next(20); } } }
As you can see this is very straightforward. The constructor for the control adds twenty random numbers to the ComboBox.Items collection, and then randomly selects one of them. The click event handler for the button does the same thing again, but first it calls TestCombo.Items.Clear() to get rid of the old list.
If you run this code and click the ‘Test’ button repeatedly without opening the list you will see that the contents are being regenerated and the selected item updated, and you can keep on like that presumably forever or until your computer dies. If you open the list, however, the next time you click ‘Test’ you’ll get a WSOD. I was fairly puzzled by this, once I narrowed it down (which wasn’t easy because I was working on much more complicated stuff at the time, and felt sure the answer must there, and not in some simple operations on a control).
After some conversation on the Silverlight.net forums Mark Salsbery figured out that you could prevent the failure by using a combination of InvalidateArrange() and UpdateLayout(), as shown:
using System; using System.Windows; using System.Windows.Controls; namespace SilverTests { public partial class Page : UserControl { public Page() { InitializeComponent(); Random rand = new Random(); for ( int i = 0; i < 20; i++ ) TestCombo.Items.Add( new ComboBoxItem() { Content = rand.Next().ToString() } ); TestCombo.SelectedIndex = rand.Next(20); } private void Test_Click(object sender, RoutedEventArgs e) { Random rand = new Random(); TestCombo.Items.Clear(); TestCombo.InvalidateArrange(); TestCombo.UpdateLayout(); for (int i = 0; i < 20; i++) TestCombo.Items.Add(new ComboBoxItem() { Content = rand.Next().ToString() }); TestCombo.SelectedIndex = rand.Next(20); } } }
Why this works is a mystery to me, and I guess to Mark as well. He did mention that this can still fail if you click it very fast, although I haven’t reproduced that. Amanda Wang from MS offered the suggestion that InvalidateArrange() made Items.Clear() unnecessary, but so far my testing doesn’t bear that out. However, she also mentioned that the update to the ComboBox takes place asynchronously after the UpdateLayout() call is made, so I suspect some issue of timing or thread contention is at the root of the problem. So, no real resolution yet, but at least a reasonable work-around if you bang into this issue. If there are more revelations forthcoming I will post an update.
Playing With Blocks: Episode 4
Last time out I introduced the DragHandle class, a small control that can be placed on a Canvas and moved around with the mouse. DragHandle is meant to provide one of the building blocks of shape editing in an application I have been working on. I ended that post with the DragHandle built and working, but still requiring quite a bit of plumbing to do what it was designed for: affect properties of shapes. In this post I’m going to show you the class that provides the rest of the mechanism for dragging objects: the DragHandleConnector. After that I am probably going to take a break and actually write some more code. As with the previous post I have linked the source file(s) at the bottom. I am not going to distributed binaries on these because they are part of larger libraries that I am not ready to publish, but you can easily build the control and supporting classes from the source files.
Playing With Blocks Listing 3 – DragHandleConnector.cs
/****************************************************************************** * DragHandleConnector.cs * * This module implements the code behind for the DragHandleConnector 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.Collections.Generic; using System.Windows; namespace DrawControls { /// <summary> /// Delegate type for position coercion callbacks used to /// adjust DragHandle position properties for consumption /// by clients /// </summary> /// <param name="p">A <see cref="T:System.Windows.Point">Point</see></param> /// <returns></returns> public delegate Point CoercePointCallback(Point p); /// <summary> /// Enumerates the position axes that should serve as update data /// for a given client /// </summary> public enum SourceAxes { /// <summary> /// Specifies that the X axis will be reported. The /// implied update type is double. /// </summary> X, /// <summary> /// Specifies that the Y axis will be reported. The /// implied update type is double. /// </summary> Y, /// <summary> /// Specifies that both axes will be reported. The /// implied update type is Point. /// </summary> Both }; /// <summary> /// DragHandleTarget encapsulates a single update point from a DragHandle /// to a client object property. Multiple targets can be defined to send /// values to multiple properties. This is primarily used to send Point /// data to a double X/Y pair, as in the case of the Line shape. /// </summary> public class DragHandleTarget { /// <summary> /// Constructs a new DragHandle target for the specified property, /// owner object, and source /// </summary> /// <param name="p">A DependencyProperty to be updated when the /// DragHandle position changes</param> /// <param name="o">The DependencyObject that owns the property to be updated</param> /// <param name="s">A value from the <see cref="DrawControls.SourceAxes">SourceAxes</see> /// enum specifying which axis should be used to update the property.</param> public DragHandleTarget( DependencyProperty p, DependencyObject o, SourceAxes s ) { Property = p; Owner = o; Axes = s; } /// <summary> /// The property which this target should update when the DragHandle /// position changes. It's type must be compatible with the type /// implied by the Axes property. /// </summary> public DependencyProperty Property { get; set; } /// <summary> /// The DependencyObject which owns the property to be updated /// </summary> public DependencyObject Owner { get; set; } /// <summary> /// A value from the <see cref="DrawControls.SourceAxes">SourceAxes</see> /// enum specifying which axis of handle movement should be used to /// update the target property. /// </summary> public SourceAxes Axes { get; set; } } /// <summary> /// A DragHandleConnection is a list of DragHandleTargets and a callback /// used to examine and coerce position values before they are reported /// to a client. /// </summary> public class DragHandleConnection { public void AddTarget(DependencyProperty p, DependencyObject o, SourceAxes s) { _targets.Add( new DragHandleTarget(p, o, s) ); } /// <summary> /// The CoercePointCallback will be invoked when the DragHandle /// position changes, before the position is sent to clients. /// </summary> public CoercePointCallback CoercePoint; /// <summary> /// Gets the list of DragHandleTargets associated with this connection /// </summary> public List<DragHandleTarget> Targets { get { return _targets; } } /// <summary> /// Implements the Targets property /// </summary> List<DragHandleTarget> _targets = new List<DragHandleTarget>(); } /// <summary> /// DragHandleConnector is the overall container for a set of /// connections attached to a specific DragHandler /// </summary> public class DragHandleConnector { /// <summary> /// Create a DragHandleConnector attached to the specified /// DragHandle /// </summary> /// <param name="dh">An instance of DragHandle</param> public DragHandleConnector( DragHandle dh ) { SetHandle( dh ); } /// <summary> /// Disconnects the connection from a DragHandle by unwiring its /// event handler. This should when the connection is no /// longer needed. /// </summary> public void Disconnect() { if (null != _dh) _dh.Drag -= new EventHandler<DragHandleEventArgs>(DragHandle_Drag); } /// <summary> /// Gets and sets the DragHandle associated with this connector /// </summary> public DragHandle Handle { get { return _dh; } set { SetHandle(value); } } /// <summary> /// Gets the list of DragHandleConnections for this connector /// </summary> public List<DragHandleConnection> Connections { get { return _connections; } } /// <summary> /// Used internally to connect a drag handle, while making sure that /// the previous handle, if any, is disconnected. /// </summary> /// <param name="dh"></param> private void SetHandle( DragHandle dh ) { Disconnect(); _dh = dh; _dh.Drag += new EventHandler<DragHandleEventArgs>(DragHandle_Drag); } /// <summary> /// Handles the Drag event for connected DragHandles /// </summary> /// <param name="sender">a DragHandle</param> /// <param name="e">event arguments</param> private void DragHandle_Drag( object sender, DragHandleEventArgs e ) { for( int i = 0; i < _connections.Count; i++ ) { Point p; DragHandleConnection d = _connections[i]; if (null != d.CoercePoint) p = d.CoercePoint(e.Position); else p = e.Position; for ( int j = 0; j < d.Targets.Count; j++ ) { DragHandleTarget t = d.Targets[j]; switch ( t.Axes ) { case SourceAxes.Both: t.Owner.SetValue(t.Property, p); break; case SourceAxes.X: t.Owner.SetValue(t.Property, p.X); break; case SourceAxes.Y: t.Owner.SetValue(t.Property, p.Y); break; } } } } private DragHandle _dh; private List<DragHandleConnection> _connections = new List<DragHandleConnection>(); } }
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; } }
Playing With Blocks Listing 1 – DragHandle.xaml
<!-- /****************************************************************************** * DragHandle.xaml * * This module the XAML markup 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 PARTICULAR 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. * */ --> <UserControl x:Class="DrawControls.DragHandle" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> <Grid> <Canvas x:Name="HandleCanvas"> </Canvas> </Grid> </UserControl>