EasyPainter Source Pack 3: Adorners, Mouse Cursors and Frames

Mar 17, 2010

Adorners went a long way since their first release few months ago.

They integrate with zoom now, have more options for controlling functionality and can use a shared "cursor plane" for displaying mouse cursors consistently across your application. Extensibility is improved allowing definition of custom adorner frames. There are 7 custom cursors and 4 custom frames to get you started :)

Download source code

I use the same adorner implementation in EasyPainter.

Here's a live demo with different adorner frame for each image:

Features

  • Custom, Extensible Mouse Cursors allow you to use the system or your own mouse cursors even if you don't use the adorner functionality. See CursorPlane.cs for more info

The custom cursors support:

    • Size (for example, bigger cursor for bigger brush size)
    • Rotation (used to make the resize cursor perpendicular to the adorner frame)

The current list supports all system cursors as well as these:

Size, // Size cursor that supports angle
Rotate, // Rotate cursor supporting angle
Move, // Move cursor
Brush, // Brush cursor, supports size
SelectionBrush, // Selection brush cursor
Crosshair, // Crosshair cursor - like the one used on snipers or in image editing programs to pinpoint that pixel
MarkerSelect, // Cursor used to select a marker on the surface (e.g. used to select point for applying origin for zoom blur effect)

To use the cursors you have to set up a cursor plane in XAML:

<adorners:CursorPlane Name='cursorPlane' />

And then initialize it in code-behind:

    InitializeComponent();
    ctlCursorPlane.SetSystemCursorElement(frameRoot);

The frameRoot specifies which control will display the cursors. For example, in EasyPainter the Crop adorner spans only the image (the adorner is parented to a different parent than the cursor plane), but the rotate/resize cursor go outside of the image. The frameRoot is used to calculate the offsets so that the adorners (or someone else using the cursor plane) can account for the different parents and draw the cursors at the correct locations on screen.

  • Customizable Adorners with extensible adorner frames

You can now define your custom adorner frames by creating a UserControl and implementing the functionality you need from the IAdornerFrame interface.

There are 4 existing frames currently: AnimatedRectangleFrame, CropFrame, GlassFrame and PointFrame. The last one is just a dot being used to specify locations of effects in EasyPainter – e.g. Zoom motion effect.

The existing frames can be zoom-aware. For example the CropFrame displays the correct image width and height in the top-left corner regardless of zoom.

/// Represents an adorner frame, e.g. glass border
public interface IAdornerFrame {
    /// Called when the frame is attached to adorner
    void Attached();

    /// Called when the frame is detached from adorner
    void Detached();

    /// Sets the zoom level for this adorner frame. The zoom level is used by the frame to display correct coordinates (e.g. Width and Height)
    void SetZoom(double newZoomLevel);

    void SizeChanged(double newWidth, double newHeight);
}

  • More adorner options: bool CanRotate, CanResize, CanMove, void SetBounds(x, y, width, height), SetZoom(double zoomLevel) allow you to change adorner capabilities, restrict it to specific area, and make it zoom-aware

I didn't have time to add the behaviours back since I don't use those yet and I'm mainly using Visual Studio for development. If anyone is interested in porting these over to the current version Andrej Tozon created the Behaviours for the original version. Here's a link to his version. Thanks Andrej!

The source code is licensed under MS-PL.

Pictures are licensed under the following Creative Commons License: http://creativecommons.org/licenses/by/2.0/
Picture locations:
http://www.flickr.com/photos/proimos/3954987905/
http://www.flickr.com/photos/proimos/4300827122/
http://www.flickr.com/photos/tenioman/2771903123/

Hope you like it! Please comment!

    

Comments

3/17/2010 11:37:33 PM #

Ali Daneshmandi

Hi Nokola,

These are really cool and thanks for sharing them with us.

I am considering to use your adorner in my current SL4 app (FaceMaker). I have a question. Is it possible to just change the Canvas.ZIndex of the border of an image to top when selecting an image? Better say, when selecting an image, I do not want to change the z index of the image but instead I want to change its adorner z index(setting the top z index) to be able to manipulate the image. In this way the image will not change its design time z index but when it gets focus on run time its adorner border will appear on top(receives the greatest z index)of all the contents inside the canvas. Similar to what you see in Blend designer. when an item is selected in the design area it does not change its z index but you see an adorner around it and you are able to change the item.

Thanks again for this great post.

Ali Daneshmandi | Reply

3/17/2010 11:51:12 PM #

nokola

Thanks Ali!

Yes, this functionality is entirely in your control (outside of the adorner code). For the sample attached to this post, just comment the 2 lines I marked below from MainPage.xaml.cs to get the behaviour you need.

        private void Image_MouseLeftButtonDown(object sender, MouseButtonEventArgs e) {
            Image img = (Image)sender;

*** comment these:
***            //panelDisplay.Children.Remove(img);
***            //panelDisplay.Children.Insert(panelDisplay.Children.Count - 1, img);
            _adorner.SetAdornedObject((IAdornedObject)img.Tag);
            if (img == image1) _adorner.SetAdornerFrame(frame1);
            if (img == image2) _adorner.SetAdornerFrame(frame2);
            if (img == image3) _adorner.SetAdornerFrame(frame3);
            _adorner.StartMoveDrag();
        }

nokola | Reply

3/18/2010 12:06:31 AM #

Ali Daneshmandi

Wow!!! That's awesome. Thanks. You really saved my time. Smile

Am I able to use it inside my current project? I will mention you and your website in an acknowledgment screen inside my Silverlight application. Is that OK with you?

Cheers,
Ali

Ali Daneshmandi | Reply

3/18/2010 1:38:01 AM #

nokola

Yes - no problem! Smile It's MS-PL Licensed (see complete License.txt in the Adorners folder)

nokola United States | Reply

3/19/2010 5:48:54 PM #

Stan

Great work!

Is it possible to implement an adjustment of a grid? For example rotation, moving and resizing is only allowed in 5 px steps?

Stan | Reply

3/20/2010 12:50:45 AM #

nokola

Thanks! It should be relatively easy - you just have to add 2 public variables: int SnapPixelsX, int SnapPixelsY and then change the functions that update the Width/Height/Position of the adorner and adorned object to do an integer divide by the SnapPixels values. You'd still keep the original double-precision values in the position and size properties.

I don't have time to do it now. If you do it and publish it for everyone it will be great! Otherwise I'm not sure when I'll get to it...

nokola United States | Reply

3/25/2010 6:56:24 AM #

pingback

Pingback from topsy.com

Twitter Trackbacks for
        
        EasyPainter Source Pack 3: Adorners, Mouse Cursors and Frames
        [nokola.com]
        on Topsy.com

topsy.com | Reply

4/26/2010 10:34:09 PM #

Gary Paul

I want to detect when the user clicks on the canvas an shut of the current adorner. Any suggestions on doing that?

Gary Paul United States | Reply

6/4/2010 5:20:54 PM #

nokola

Yes - you can handle the MouseDown event for the canvas and assign the AdornedObject to null or just hide the adorner - this should work.

nokola United States | Reply

4/28/2010 7:59:49 PM #

Gary Paul

Having problems with loosing the mouse. Most of the time I'm able to move objects around fine. Then the mouse disapears and the action of the object becomes erratic. I have duplicated the implementation from your example. Any ideas?

Gary Paul United States | Reply

6/4/2010 5:21:47 PM #

nokola

Not sure - could you send/publish the sample that doesn't work somewhere? If I can try it out it would be easier to debug...

nokola | Reply

5/3/2010 10:42:32 PM #

Ken

Amazing work! really awesome!! thanks for sharing the source code of your hard work!

Ken

Ken Israel | Reply

6/4/2010 5:21:55 PM #

nokola

Thanks!

nokola | Reply

5/20/2010 7:12:00 PM #

pingback

Pingback from 13.mfbattle.com

S60r Ebay, Order Volvo S60r Tail Light Lens

13.mfbattle.com | Reply

7/2/2010 9:56:02 AM #

Maarja

Hi,


2 questions:

* Is it somehow possible that the image (or any other control surrounded by the adorner) also receives the events i.e. MouseLeftButtonUp?
* Why isn't the DragCompleted event fired if I drag the image for the first time (when it gets adorner)? Next time it is fired.



Maarja

Maarja Estonia | Reply

7/14/2010 12:31:14 AM #

nokola

Hi Maarja,

* yes, there are few possible solutions to achieve this. Note that
  1: Make part of the adorners IsHitTestVisible= False (e.g., if you only need rotate, you can disable the move adorner)
OR
  2: Make the adorners smaller (e.g. the thumbMove adorner), so that it does not overlay the control completely
OR
  3: Add some event handlers in IAdornedObject and invoke them manually
Could you be more specific on what you would like to achieve? I guess there might be a better way depending on what your project's requirements are...

* DragCompleted: Yes, indeed! It looks like a bug, I'm investigating...
btw How did you find that DragCompleted was not firing?


Thank you for the feedback and suggestions!
Nikola

nokola | Reply

7/24/2010 6:48:13 AM #

AASoft

Hi Nikola,

In case you are still working on solving the DragCompleted bug, I'd like to share my 2 cents.
You have the following (or equivalent) piece of code in the control (or Page) that uses the Adorners:
_adorner.SetAdornedObject((IAdornedObject)ctl.Tag);
_adorner.SetAdornerFrame(_defaultAdornerFrame);
_adorner.Visibility = Visibility.Visible;
_adorner.StartMoveDrag();

That ".StartMoveDrag()" puts the adorner's state into "i'm being dragged, to change my position". However, it doesnt let the control itself know that its being dragged (i.e. IsDragging is false). As a result, the DragCompleted is never fired on when the adorner first appears. Additionally, moving the mouse rapidly may, and often does, place the mouse cursor outside the adorner control, thus relieving it of focus, and thus making sure it doesnt receive MouseMove events - it stil thinks its being dragged, but it doesnt receive mouse events -> when the mouse is over it again, it automatically starts being dragged.

As for the solution to this: I dont believe there is any, though I would love to be proven wrong.

P.S. Adorners is a beautiful concept, and very useful in a number of projects. Have you considered putting the library on e.g. CodePlex, so that more people would know about it? I know I would be very interested in developing the concept further. One of the things I have in mind is adding Binding support. Another thing is developing the commands menu that you had in the first demonstration of adorners - would be very useful in a number of cases.

AASoft Canada | Reply

7/24/2010 7:16:46 AM #

AASoft

Aaaand a minute after posting this, I found the solution Smile

You original StartMoveDrag method was this:
public void StartMoveDrag()
{
  _lastMousePosInitialized = false;
  thumb_DragStarted(thumbMove, null);
}


I changed it to the following, and everything works great now:
public void StartMoveDrag()
{
  _lastMousePosInitialized = false;
  thumbMove.CaptureMouse(); // Added this line.
  thumb_DragStarted(thumbMove, null);
}


Do not that DragCompleted is still NOT firing, and I dont believe there is a way to make it fire. However, is this _really_ required? Considering that the real problem is that the control moves wierly during the first drag, I would consider the problem solved.

AASoft United States | Reply

8/13/2010 2:30:37 PM #

kapocris

I need to resize and rotate Canvas instead of Images, Is it possible to you your adorners ?

kapocris Spain | Reply

8/13/2010 2:32:18 PM #

kapocris

I need to resize and rotate Canvas instead of Images, Is it possible to use your adorners ?

kapocris Spain | Reply

8/13/2010 2:53:38 PM #

kapocris

Well, I've just tried that and it WORKS but the adorners are not visible (?)

kapocris Spain | Reply

9/2/2010 11:45:01 AM #

Clip Frames

Have you considered putting the library on e.g. CodePlex, so that more people would know about it? I know I would be very interested in developing the concept further. One of the things I have in mind is adding Binding support.

Clip Frames India | Reply

Add comment




  Country flag

biuquote
  • Comment
  • Preview
Loading



nokola.com | Terms | Log in

Recent

About the author

Happy & enjoying life. Software enthusiast.
The opinions I express here and on nokola.com are mine and not my employeer's (Microsoft).
This is the official blog of nokola.com. You can find Silverlight samples, coding stuff, and hopefully other interesting things here.