WynApse Home Page
Home    Blog    About    Contact       
Latest Article:


My Tags:
My Sponsors:






Lifetime Member:

Silverlight With Java Script Tutorial 11 - Intuitive Object Dragging



Overview

Once more, maybe for the last time, I'm going to touch on Drag and Drop. Superficially, this looks very similar to the canvas of Tutorial 10, but to perform some of the manipulations in this tutorial, I had to make some significant changes to the XAML:

  • To avoid some nasty code, I moved the mouse handlers to the canvases containing the rectangles. This works fine in this situation because the rectangles are filled.
  • To enable the traversal of the canvases to change ZIndex values, I added x:Name values to the Canvases
  • To avoid possible Loaded= issues with IE6, I've removed that code, and get the outer canvas by name when needed
As always, I'd like to give a reference to the Microsoft Silverlight SDK Online.


What This code adds

I covered Drag and drop in Tutorial 10, but I left off some of the more useful fun things that we all get involved in when we really try to use this. Specifically I left off the ZIndex code that would pull a 'lower' rectangle to the top when clicked, and I left off code that deals with the outer edge of the canvas. I'll discuss these separately.


Off the Canvas

The "Off the Canvas" code is actually pretty simple to implement. To keep this in perspective, I've placed a black-stroked rectangle at the outer edge of the container canvas. This serves no purpose other than a visual. I could have just as easily made the canvas be gray.

In the MouseMove code, after the object is moved, I call a function I've named "CheckForOffCanvas". This function checks the sender Canvas.Left and Canvas.Top to see if the value is less than 0. If so, the value is set to 0, and the mouse is released.

Next, the code checks for the right-hand side of the rectangle going beyond the right side of the canvas, or the bottom of the rectangle going beyond the bottom of the canvas. In either of these cases, the rectangle is left at the border, and the mouse released.

The code for this is fairly straightforward and once you've read it, it should make sense pretty quickly.


ZIndex

Changing the ZIndex of the object on MouseDown is a bit more involved only because we have to consider the ZIndex of all the other objects.

In this case, I'm ignoring everything but the "Canvas" objects. There is an outer marker Rectangle, but I don't set it's ZIndex, and don't care what it might be. The three sub canvas objects have x:Names "Canvas1", "Canvas2", and "Canvas3".

To make this extensible for you, my code simply iterates through the children of the outer canvas, ensuring the child's x:Name begins "Canvas", and then performs the ZIndex manipulation.

I do have a 'magic number' of 3 in the code, to denote the 3 objects. The presumption is that the ZIndex of the 3 are 1, 2, or 3. We only need to change the value of the one clicked, and any with a ZIndex greater than it's original value.

As an example:
If #1 is clicked, then it must become #3. #2 becomes #1, and #3 becomes #2.

If # 2 is clicked, however, then it must become #3, and #3 becomes #2.

This may seem obvious with 3 objects, but if there were 20 objects, the process would still hold true.


Other Notes

Another note on this code is that I'm not depending upon "Loaded=" on my outer canvas. I've had issues with that, I believe, and they have not been resolved. For that reason, I've x:Name'd the outer canvas, and in both functions that I need that name, I do findName to get my canvas.


Our JavaScript

Here is the JavaScript to run this page:

<script type="text/javascript">
var isMouseDown = false;
var beginX = 0;
var beginY = 0;

function OnMouseLeftButtonDown(sender, mouseEventArgs)
{

// Ensure this object is the only one receiving mouse events.
sender.captureMouse();

// Set the beginning position of the mouse.
beginX = mouseEventArgs.getPosition(null).x;
beginY = mouseEventArgs.getPosition(null).y;

isMouseDown = true;

// Run some code to set the selected one to ZIndex 3
// and decrement as needed the others

var oldZIndex = sender["Canvas.ZIndex"];
var outerCanvas = sender.findName("OuterCanvas");

for (i = 0; i < outerCanvas.children.count; i++)
{
var child = outerCanvas.children.getItem(i);

// only do it if it is a named canvas
if (child.name.substring(0, 6) == "Canvas")
{

var childZ = child["Canvas.ZIndex"];
if ((childZ > oldZIndex) && (childZ <= 3) && (child != sender))
{
child["Canvas.ZIndex"] = childZ - 1;
}
}
}
sender["Canvas.ZIndex"] = 3;

}

// Stop drag and drop operation.
function OnMouseLeftButtonUp(sender, mouseEventArgs)
{
isMouseDown = false;

sender.releaseMouseCapture();
}

function OnMouseMove(sender, mouseEventArgs)
{
// Determine whether the mouse button is down.
// If so, move the object.
if (isMouseDown == true)
{
// Retrieve the current position of the mouse.
var currX = mouseEventArgs.getPosition(null).x;
var currY = mouseEventArgs.getPosition(null).y;

// Reset the location of the object.
sender["Canvas.Left"] += currX - beginX;
sender["Canvas.Top"] += currY - beginY;

CheckForOffCanvas(sender);

// Update the beginning position of the mouse.
beginX = currX;
beginY = currY;
}
}

function CheckForOffCanvas(sender)
{
var releaseMouse = false;
var outerCanvas = sender.findName("OuterCanvas");
if (sender["Canvas.Left"] < 0)
{
sender["Canvas.Left"] = 1;
releaseMouse = true;
}
if (sender["Canvas.Top"] < 0)
{
sender["Canvas.Top"] = 1;
releaseMouse = true;
}

if ((sender["Canvas.Left"] + sender.Width) > outerCanvas.Width)
{
sender["Canvas.Left"] = outerCanvas.Width - sender.Width - 1;
releaseMouse = true;
}
if ((sender["Canvas.Top"] + sender.Height) > outerCanvas.Height)
{
sender["Canvas.Top"] = outerCanvas.Height - sender.Height - 1;
releaseMouse = true;
}

if (releaseMouse == true)
{
isMouseDown = false;
sender.releaseMouseCapture();
}
}

</script>



Our XAML

Here is the XML for the canvas above:

<Canvas
xmlns="http://schemas.microsoft.com/client/2007"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Width="300"
Height="300"
x:Name="OuterCanvas">

<Rectangle Width="300"
Height="300"
StrokeThickness="1"
Stroke="Black"
/>

<Canvas x:Name="Canvas1"
Canvas.Top="129"
Canvas.Left="124"
Width="50"
Height="40"
MouseLeftButtonDown="OnMouseLeftButtonDown"
MouseLeftButtonUp="OnMouseLeftButtonUp"
MouseMove="OnMouseMove"
Cursor="Hand"
Canvas.ZIndex="1"
>

<Rectangle Width="50"
Height="40"
StrokeThickness="1"
Stroke="Black"
Fill="Blue"
/>
</Canvas>

<Canvas x:Name="Canvas2"
Canvas.Top="137"
Canvas.Left="132"
Width="50"
Height="40"
MouseLeftButtonDown="OnMouseLeftButtonDown"
MouseLeftButtonUp="OnMouseLeftButtonUp"
MouseMove="OnMouseMove"
Cursor="Hand"
Canvas.ZIndex="2"
>

<Rectangle Width="50"
Height="40"
StrokeThickness="1"
Stroke="Black"
Fill="White"
/>
</Canvas>

<Canvas x:Name="Canvas3"
Canvas.Top="145"
Canvas.Left="140"
Width="50"
Height="40"
MouseLeftButtonDown="OnMouseLeftButtonDown"
MouseLeftButtonUp="OnMouseLeftButtonUp"
MouseMove="OnMouseMove"
Cursor="Hand"
Canvas.ZIndex="3"
>

<Rectangle Width="50"
Height="40"
StrokeThickness="1"
Stroke="Black"
Fill="Red"
/>
</Canvas>

</Canvas>


Summary

I've seen some people write that their solution to the "Off Canvas" issue is to retain the value of the last captured object, then restore that when the mouse re-enters the canvas. Obviously that can only be done if the Canvas OnEnter is functional, and that would mean covering it with a filled rectangle. Even assuming that fits the situation on your page, what if your user moves the mouse around and comes back into the canvas from the opposite side? Personally, I'd rather not have my objects jumping from one side of the canvas to the other. I think a small explanation about moving the objects off the canvas would suffice.

It's unfortunate that I had to alter the code from Tutorial 10 to produce 11. I had hopes originally of doing this one by simply adding on, not moving things around. Had I done so, I think the code would have been more difficult to follow.

I think it will be safe to use a Silverlight Drag in code now without explaining it in detail, but if there is some portion of this that seems confusing, be sure to let me know!

Stay in the 'Light!

Copyright © 2006-2017, WynApse