Playing with Blocks: Episode 3

In my last post on the Silverlight drawing project I said I was going to stay away from the design and talk about code next. Quite a bit has changed, though, since then. For one thing the DrawStylus class has fallen victim to shrinking relevance and has been removed. The operations I initially envisioned for it kept leaping to other classes until it was nothing more than a forlorn wrapper around the mouse events. Oh well. Sometimes you have to be ruthless. Some additional classes have appeared, notably the StyleBox and StyleControl, which have about the same relationship as ToolBox and Tool, but are focused on editing styles. StyleBox derived from a StackPanel, and I’ve found that works very well, so I will probably be going back and changing ToolBox to derive from one as well.

Here’s a look at the current object model as it has been evolving over the last week and half. One of the things that I really enjoy about solo projects like this is having the time to conceive features and then really think through how to fit them into the framework in a generic and reusable way. It’s an organic growth process that leads to a Cambrian explosion once you get a critical mass of the architecture in place.

Needless to say, I am not at the Cambrian phase yet, but I’m hopeful. With that out of the way I can turn to something with a bit more fun factor, and what could be more fun than moving stuff around on screen? For the rest of this post I’m going to dive from the heights of abstraction all the way down to the smallest thing that will appear on the application’s window: a drag handle.

“Drag handle” is my name for the little widgets that appear on the edges of graphical objects in a drawing application. They are used for moving, resizing, or skewing objects by clicking on them with the mouse and dragging them, usually with the change to the object showing in realtime as the handle moves. Take a look at my Silverlight Gradient Editor. If you look at the gradient sample on the left you’ll see two orange squares attached to the line showing the direction of the gradient. Click on one and move it around. That’s a drag handle, or in my case, a DragHandle. There is a surprising amount of plumbing necessary to implement a little widget like this. You need the basic event handling, a way of measuring movement, a way of transforming coordinates, and a way of mapping the resulting values onto the properties of things you want the handle to control. In the end I am not sure whether the DragHandle class involves writing less code to do all these things, but it involves writing simpler, higher level code in a consistent way.

A DragHandle has a few key properties. First, you need to be able to click on it and drag it around. So it’s a control, with a shape, that handles mouse events and updates its own position. Second, you need to be able to specify some constraints on how it moves. In the gradient editor the two orange DragHandles are constrained to move within the area of the sample box only. If you change the brush type to RadialGradientBrush, you’ll see an ellipse with three kinds of DragHandles: one in the center that can move anywhere within the sample box; one at the minimum Y coordinate of the ellipse that can move in the vertical between the center and the edge of the box; and one at the maximum X coordinate that can move in the horizontal between the center and the edge of the box.

A third property is that you need some convenient way to wire the DragHandle up to the thing it is supposed to control. In the case of the editor’s LinearGradientBrush each DragHandle is connected to an end point of a Line object. The RadialGradientBrush example is more complicated: the center DragHandle is wired to the center of the ellipse, but also has to update the positions of the other two DragHandles, whereas they merely need to adjust the ellipse Y and X radii. Both are also wired to the properties of the brush that is being edited. This wiring involves another class called DragHandleConnector that I’ll get to in the second part of this. The full definitions of these classes is too big a topic for a post here. I hope to hit the highlights. See the links at the bottom of the post to download the source code files.

DragHandle is a UserControl, and the XAML markup is very simple, containing only a grid and child Canvas on which I position the handle shape. That Canvas may not in fact be necessary, and it’s possible I’ll get rid of it somewhere down the road. Here’s the markup:

<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>

As you can see, not much to it. All of the fun stuff happens in the code-behind, and in the connector class, so let’s start with the code behind. DragHandle is a basic UserControl that exposes dependency properties for a number of key attributes. These include the shape to be used for the handle, the brush to fill the shape with, the brush and width for the outlining stroke, the cursor to display when the mouse is over the handle, etc.

dh_properties.png

As every WPF programmer knows, implementing DP’s is one of the joys of Windows development on the 3.5 framework. However tedious, they do make the class much more useful and accessible. The meaning of many of these properties is self-evident, however a couple are worth discussing in more detail. The HandleDragConstraint property takes a value from the DragConstraint enum, which specifies the possible motion constraints on a DragHandle. I considered a number of different ways to represent these constraints, but settled on an enumeration as the clearest way to express them. I also considered making them “OR-able”, and I can think of some scenarios where that would be handy, but for now each of these constraints is mutually exclusive of the rest.

public enum DragConstraint
{
	None,
	EW,
	NS,
	NESW,
	SENW,
	NE,
	NW,
	SE,	
	SW,
	E,
	N,
	S,
	W
};

The screen is divided into cardinal compass directions, where up (-Y) is North. Movement constraints are relative to the HandleOrigin, which is another dependency property. The HandleOrigin can either be set directly, or set by passing a Point to the DragHandle constructor. In either case it remains constant until explicitly reset. Other properties and methods rely on HandleOrigin. For example, the SnapToOrigin() method will move the handle to the origin. I’ll talk more about the mechanism for enforcing movement constraints relative to the origin a little further on.

Another interesting dependency property is ReferenceElement. The purpose of this property is to set the element whose coordinate space will provide a frame of reference for drag events. When handling mouse events in Silverlight (or WPF for that matter), the MouseEventArgs class provides a method called GetPosition(UIElement) that returns a point representing where the mouse is. The single parameter specifies the UIElement the reported coordinates will be relative to. If it is null then they are relative to the entire plugin area. In most cases a DragHandle will be trying to stick itself to a shape on a canvas, and we’ll want to translate movement of the handle to movement on the canvas. By setting the ReferenceElement to the Canvas of interest we can simplify that task, however, as you’ll see below more manipulation of the reported movements is necessary to get any real usefulness out of this class.

The rest of the dependency properties are for things like appearance, and don’t require a lot of explanation, so I’ll move on to talk about the mechanisms that make some of these properties and methods work for the DragHandle class, beginning with motion constraints.

The HandleDragConstraint property defaults to DragConstraint.None. If it is set the OnHandleDragConstraint PropertyChanged event handler gets called, and in turn calls a method named SetDragConstraint. The purpose of this method is to determine which of a set of small movement evaluation functions should be called when the user tries to drag the handle. It does this using a select statement, as shown below.

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;

		// remainder of select cases skipped
	}
}

The delegate _eval is a private member of the DragHandle class, and can be in one of two states: either it is null, and there is no movement constraint, or it points to a valid eval method that should be called when the user moves the handle. The chain of events begins with the method that handles MouseMove events on the shape that represents the handle. This method first gets the mouse position relative to the reference element, and then calls a method named RequestDragTo(ref Point position). The RequestDragTo method evaluates the user’s movement, and returns true if it is allowed, or false if it is not. If the method returns true the passed position may have been modified, which is why it is passed by reference. Let’s take a look at RequestDragTo.

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);
}

RequestDragTo applies three rules to the incoming position. First, if the position is the same as the HandleOrigin it just returns true, since the handle can always move there. Second, if the movement is outside the bounding box specified by HandleMinX, HandleMinY, HandleMaxX, and HandleMaxY the method returns false, since the handle can never move there. If neither of those rules applies, the third rule requires the method to return true if _eval is null, or call _eval if it is non-null. If _eval is called it is passed the destination point by reference, and will return true if the movement is allowed, or false otherwise, and as described above it may modify the destination point. Each of the individual eval methods is very simple. I’ll show two of them by way of example.

private bool Eval_MoveE(ref Point dest)
{
	if (dest.X >= HandleOrigin.X)
	{
		dest.Y = HandleOrigin.Y;
		return true;
	}
	else return false;
}

private bool Eval_MoveNESW(ref Point dest)
{
	dest.Y = HandleOrigin.Y - (dest.X - HandleOrigin.X);
	return true;
}

As you can see these two methods behave slightly differently. The first is an asymmetric constraint, as it allows movement only in one cardinal direction; in this case from center to the East. So this method first applies a bounds rule, and then modifies dest.Y to make sure it is the same as HandleOrigin.Y. The second method doesn’t need bounds as it allows movement in two cardinal directions across the center, and movement too far in either direction will be caught in RequestDragTo when it violates the bounding box. So all this method does is coerce Y to move as much as X.
In addition to the dependency properties discussed above the DragHandle class exposes events that provide the main interface for client code, as well as an event arguments class to provide information to handlers.

private bool Eval_MoveE(ref Point dest)
{
	if (dest.X >= HandleOrigin.X)
	{
		dest.Y = HandleOrigin.Y;
		return true;
	}
	else return false;
}

private bool Eval_MoveNESW(ref Point dest)
{
	dest.Y = HandleOrigin.Y - (dest.X - HandleOrigin.X);
	return true;
}

Using these events the calling app’s code can be executed when the handle is clicked, when it begins to move, when it moves, and when it stops moving; fairly typical stuff for this kind of control. However, using these events directly would still require the application to put a lot of plumbing in place. The DragHandle as described so far is still a pretty dumb control. It can be dragged, and it will tell you when it’s dragged, and that’s about it. If you want the control to… ahem, control something, you have to provide the means to translate the changes in the DragHandle into changes to that something’s properties.

Take another look at the gradient editor. When you drag the orange handles around on the gradient sample box several things are happening. First, the end point of the line that is under that handle is being adjusted to move with it. Second, the properties of the LinearGradientBrush are being adjusted to reflect the change. No big deal, except that the drag handle and the line move in the coordinate space of the canvas, while the changes to the brush are in a 0 – 1.0 normalized coordinate space. To further mess with your head, the Line object doesn’t expose its end points as Points. Rather it uses X1, Y1, X2, and Y2. Why this is so I can’t say, anymore then I can say why occasionally a calf with two heads is born. They just are. So it isn’t simply a matter of assigning the X/Y values of the DragHandle’s position to the properties of the thing we want to control. We need more flexibility than that. In my next post I will discuss the thing that gives it to us: the DragHandleConnector class.

Source Code:

DragHandle.xamlDragHandle.xaml.cs

Leave a Reply

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