WynApse Home Page
Home    Blog    About    Contact       
Latest Article:


My Tags:
My Sponsors:






Lifetime Member:

How To Drag Objects in the Silverlight Canvas


I posted my Reflection Builder code last weekend, and haven't had a chance to discuss some of the things that are inside that block of code, so I thought I'd start with moving canvas elements around with the mouse, which can loosely be called drag and drop. Strictly-speaking I wouldn't call it drag and drop but more of a 'slide' because the elements still assume their canvas position in the xaml, so although you're moving things, their position relative to each other in the Z-Axis as it were, stays static.

I hacked my mouse-dragging from the code I used from the QuickStart in dealing with sliders. The thumb-element of the slider is a drag-and-drop of sorts. It stays in line horizontally of course, and has end-points, but you're grabbing it with the mouse and then letting go somewhere else.

Because I hacked this from the slider control, it's inefficient and somewhat confusing as explained below. I did this as a one-off late one night after I had deicded I wanted to move the two reflection images with the mouse as well as with the sliders. I played with it until I got everything working, then turned my back on it, and didn't consider a cleaner method because I had other things still hanging.

Pre-drag definitions

If you look at the Reflection Builder xaml, in each canvas you'll see code something like this (from the Main Image):

Cursor="Hand"
MouseLeftButtonDown="javascript:MainImage_MouseLeftButtonDown"
MouseLeftButtonUp="javascript:MainImage_MouseLeftButtonUp"
MouseMove="javascript:MainImage_MouseMove"

The cursor changes to the "Hand" image to let the user know they can do something, and other than clicking, the next logical thing to do might be to move the graphic.

Looking at the html file, first there are some JavaScript variables defined:

var MainImagemouseDownPositionx = 0;
var MainImagemouseDownPositiony = 0;
var MainImagemouseDownValuex = -1;
var MainImagemouseDownValuey = -1;

These are used as follows:

  • MainImagemouseDownPositionx & y: Captures the object canvas position at the time of clicking, see below
  • MainImagemouseDownValuex & y: Captures the position of the mouse when the mouse was clicked. This gives us relative-motion capability
How to begin the 'drag'

When the Left Mouse Button is pressed the following Java Script is executed:

function MainImage_MouseLeftButtonDown(sender, args) {
sender.captureMouse();
MainImagemouseDownValuex = Image_GetValueX(sender);
MainImagemouseDownValuey = Image_GetValueY(sender);
MainImagemouseDownPositionx = args.x;
MainImagemouseDownPositiony = args.y;
}


'sender' in this case is the Main Image. Since this is a specific mouse message handler, I really didn't need to do it that way, but I left myself open to re-use the handler. By 'capturing' the mouse, all mouse messages now will go to sender.

Image_GetValueX, for instance, is:

function Image_GetValueX(sender, newValue){
return sender["Canvas.Left"] + .5 * sender.findName("MainImagePNG").width;
}


I used the same technique as the sliders, so I'm always 'normalizing' to the center of the object. That's what the .5 multiplier is all about. With the sliders, the center point is important, because that's the value you're setting. It was just expedient for me to cut and paste the code, and have it all be the same, particularly if I were going to merge it later. The WPF/E SDK folks have a cleaner method of doing this in their Coco's Dress-up Kit example, and they simply += the current left or top with the delta, either works.

Dragging

While the mouse is still down, Mouse Move messages are then handled by this script:

function MainImage_MouseMove(sender, args){
    if (MainImagemouseDownValuex != -1) {
       var newValue = MainImagemouseDownValuex
       + (args.x - MainImagemouseDownPositionx);
       Image_SetValueX(sender, newValue);

    }
    if (MainImagemouseDownValuey != -1) {
       var newValue = MainImagemouseDownValuey
       + (args.y - MainImagemouseDownPositiony);
       Image_SetValueY(sender, newValue);
    }}


MainImagemouseDownValuex & y get reset to -1 when the mouse button is released, so using this value we can determine if we're really in a mouse down condition or not.

Assuming we're mouse-down, and once-again using code from the slider, a value is calculated as the position the object at the time the mouse went down plus the mouse delta since mouse-down. This value is then sent to the object via:

function Image_SetValueX(sender, newValue) {
    ...
    sender["Canvas.Left"] = newValue - .5 * sender.findName("MainImagePNG").width;
    ...
}


Remember we were using the center of the canvas as a placeholder, so to get Canvas.Left, we need to subtract half the width.

Releasing the mouse

When the mouse is released, we execute this code:

function MainImage_MouseLeftButtonUp(sender, args) {
    sender.releaseMouseCapture();
    MainImagemouseDownValuex = -1;
    MainImagemouseDownValuey = -1;
}


This releases the 'capture' on the mouse, and resets the two values that are used to determine if the mouse is down during motion.

Afterthoughts

There is very obviously room for improvement in this setup. I like the way the sdk folks did their delta calculation, but I got so used to thinking canvas-center for all the slider code that I didn't have any problems. My biggest motion problem was synchronizing the Canvas.Left and Canvas.Top sliders with the mouse motion and vice-versa, and normalizing to center certainly helped with that. The next app I do that I want to move objects with the mouse, I'm using their code :)

Copyright © 2006-2017, WynApse