(1 item) |
|
(1 item) |
|
(5 items) |
|
(1 item) |
|
(1 item) |
|
(2 items) |
|
(2 items) |
|
(4 items) |
|
(1 item) |
|
(6 items) |
|
(2 items) |
|
(4 items) |
|
(1 item) |
|
(4 items) |
|
(2 items) |
|
(1 item) |
|
(1 item) |
|
(1 item) |
|
(1 item) |
|
(1 item) |
|
(1 item) |
|
(1 item) |
|
(1 item) |
|
(2 items) |
|
(2 items) |
|
(5 items) |
|
(3 items) |
|
(1 item) |
|
(1 item) |
|
(1 item) |
|
(3 items) |
|
(1 item) |
|
(1 item) |
|
(2 items) |
|
(8 items) |
|
(2 items) |
|
(7 items) |
|
(2 items) |
|
(2 items) |
|
(1 item) |
|
(2 items) |
|
(1 item) |
|
(2 items) |
|
(4 items) |
|
(1 item) |
|
(5 items) |
|
(1 item) |
|
(3 items) |
|
(2 items) |
|
(2 items) |
|
(8 items) |
|
(7 items) |
|
(3 items) |
|
(7 items) |
|
(6 items) |
|
(1 item) |
|
(2 items) |
|
(5 items) |
|
(5 items) |
|
(7 items) |
|
(3 items) |
|
(7 items) |
|
(16 items) |
|
(10 items) |
|
(27 items) |
|
(15 items) |
|
(15 items) |
|
(13 items) |
|
(16 items) |
|
(15 items) |
Earlier this week I decided to try writing a XAML editor. I thought it would be a good way to get to know Avalon better, and also a useful tool in its own right. I've not got very far with it - you can see pretty much the full extent of the spectacular visuals it currently supports in the example drawing on the right. However, I thought I'd make an early version available for download because I think the zoom feature is fun.
NOTE: If you download and use this editor, be aware that while it can save out XAML files, it can't load them back in yet. You can download it here. You'll need the PDC build of Longhorn (build 4051) to run it.
The zoom feature in most graphics programs tends to work in one two ways. Either you can zoom in and out by increments, or you can drag a rectangle around the area you'd like to zoom in on. But I thought I'd do something a bit different to show off Avalon's scaling support. The way to use my zoom tool is to hold down the left mouse button over the bit you want to zoom in on and then move the mouse up or down. (Keep the button held down.) As you move the mouse up, it zooms in in real time while you drag. And if you move the mouse down, it zooms out. On my Longhorn box (a 3GHz P4 with a GeForce 4 Ti4200 graphics card, and a Microsoft Intellimouse Explorer optical mouse), this looks gorgeously smooth. However, your mileage may vary.
(Obviously you'll want to draw some stuff before trying to zoom in. Your choices are currently rectangles or ellipses. If you're looking for the colour palette, there isn't one yet. Nor is there any facility to delete or undo. Like I said, I've not got very far yet.)
I'm not going to release the source code just yet, because, well, frankly it's a crock. I'd be embarrassed to let it see the light of day with my name associated with it... It's a work in progress and it shows.
But I'll explain how the zoom effect is done, in case you're wondering. If you guessed that I'm using animation...guess again. I actually tried the simplest thing I could think of that might work, which fortunately turned out to work pretty well after a little tweaking. However, I plan to move over to animation, because the approach I'm using is slightly flawed - how good it looks will depend, of all things, on what kind of mouse you are using!
I'm using a <TransformDecorator>
to do the zoom, as you would expect. Inside
this I've actually got two <Canvas>
elements - one to contain the drawing, and
another to contain the overlays - selection handles and the drag outline. (So for the selection outline
I'm using the same technique I described in this
article.) Because the <TransformDecorator>
transforms a single child element,
these two canvases are contained within yet another <Canvas>
.
The XAML looks like this:
<Canvas DockPanel.Dock="Fill" ID="untransformedWorkArea"> <TransformDecorator ID="transform" > <Canvas ID="workArea"> <Canvas Width="100%" Height="100%" ID="drawingPanel" /> <Canvas Width="100%" Height="100%" ID="annotationPanel"> <Canvas ID="selectionOutline" Visibility="Hidden"> <Rectangle Stroke="Black" StrokeThickness="2" Width="100%" Height="100%"> <Rectangle.Fill> <SolidColorBrush Color="Navy" Opacity="0.2" /> </Rectangle.Fill> </Rectangle> <Rectangle Stroke="White" StrokeThickness="1" Width="100%" Height="100%" /> </Canvas> </Canvas> </Canvas> </TransformDecorator> </Canvas>
Then, I have handlers for the MouseLeftButtonDown
, MouseLeftButtonDownUp
,
and MouseMove
events on the window. When the rectangle or ellipse tools are selected,
these add new children to the drawingPanel
element. When the zoom tool is selected, I just adjust the
Transform
property of the <TransformDecorator>
element.
Unfortunately, I also had to do a couple of hacks to make it work. First, it turns out that Avalon gets
really upset if you try to change the transform for every MouseMove
event you receive.
I'm not sure why, but I did speculate that perhaps you get an extra MouseMove
after having adjusted the
zoom, to let you know that the mouse is now over a different part of the UI as far as the coordinate
system is concerned, even though it hasn't moved on screen. But whatever the reason, I had to put
some code in to ignore every other MouseMove
. Without this, it exits, complaining, somewhat
bizarrely, that it has detected an infinite loop in the layout and then exits!
The other hack was the one I use to keep the zoom centred around the point you click on - you'll
notice that whichever point you click on when starting a zoom remains stationary and everything else
zooms in or out around that point. To make this work, the transform applied has to include both a
scale and a translation. (Unless you happen to zoom in on the origin, in which case a scale is
sufficient.) However, for some reason, the <Canvas>
inside the
<TransformDecorator>
completely ignores any translation aspect of the
transform. The <Canvas>
was scaling correctly, but was refusing to
move. I'm guessing this is due to some subtlety of the layout logic that I haven't fully understood.
To work around it, I'm actually moving the whole <TransformDecorator>
itself.
If you look at the XAML, you'll see that the decorator is inside a <Canvas>
of its own
(the one with the ID of untransformedWorkArea
). This enables me to set the
position of the decorator. This works around the fact that the <Canvas>
refuses
to budge - I just move its parent instead.
At some point I want to move this over to using animation. The reason is that right now, the
rate at which the repaints occur is entirely driven by the rate at which MouseMove
events get generated. This is undesirable - for perfect smoothness you want the repaints to be
keyed to the video card frame rate, not the mouse. (Actually, one of my pet rants about Windows
is the fact that the mouse position update isn't locked to the video refresh... But that's
a whole other topic.) So what I really need to do is dynamically build an animation that is based
on what the mouse is doing, and let Avalon's animation system choose the appropriate pace for
redrawing stuff. (That would also get rid of the need for the nasty hack in my MouseMove
handler.) However, for this release, I'm keeping it simple.