Performing a hitTest with Silverlight
In my Silverlight coding travels, I've found the urgent need to carry out a hitTest to determine where my end-users mouse is currently located but also what elements are underneath it within Silverlight.
So what's a hitTest you ask? well essentially it's when you ask "Is this object colliding or in contact with that object? ". It's used majority of the time in Flash world to determine where a relationship stops and starts with a MovieClip and Mouse (X/Y) coordinates. It can also be commonly used for collision detection with other MovieClips when you make a point and click "fire" game (ie shoot bullets via your mouse at other objects on screen).
Silverlight at present doesn't have a hitTest method, well it does but it's only found within Inking.
Well before one and all start to curse Silverlight for limited functionality compared to Flash (heh), it's not all doom and gloom. Thanks to the hierarchy index within Silverlight, one is able to ask objects individually where the mouse currently resides, but also where it currently is within the elements themselves.
Now for some code..
An example, say you had a Rectangle1 within Canvas1, and you want to find out where the mouse currently is, but also where that mouse is within the actual Rectangle1 that's relative to the Rectangle1 itself (ie if the Mouse x is 30px in from the Rectangle1's edge?).
To do this you would do the following (this is JScript Code btw):
// Commit Default Properties.
private_commitProperties : function() {
var local_owner = this;
var cnvs = $get("slControl").Content.FindName("Canvas1");
// EVENT: onMouseMove
var local_onMouseMove = function(sender, eventArgs) {
local_owner.MouseObj = eventArgs;
local_owner.private_updateDisplayList();
}
// Create Events
cnvs.addEventListener("MouseMove", local_onMouseMove);
},
// Update the DisplayList.
private_updateDisplayList : function() {
var cnvs = $get("slControl").Content.FindName("Canvas1");
var rect = cnvs.FindName("Rectangle1");
// Determine where the X/Y Mouse Co-ordinates are
// within Silverlight itself.
var currentX = this.MouseObj.GetPosition(null).x;
var currentY = this.MouseObj.GetPosition(null).y;
// Determine where the X/Y Mouse is within the Canvas
var cnvsX = this.MouseObj.GetPosition( cnvs ).x;
var cnvsY = this.MouseObj.GetPosition( cnvs ).y;
// Determine where the X/Y Mouse is within the Rectangle
var rectX = this.MouseObj.GetPosition( rect ).x;
var rectY = this.MouseObj.GetPosition( rect ).y;
// IsMouseInsideRect ?
var isMouseInsideRect = false;
if( rectX >=0 && rectX <= rect.GetValue("Width")) {
isMouseInsideRect = true;
}
// IsMouseInsideCanvas ?
var IsMouseInsideCanvas = false;
if( cnvsX >=0 && cnvsX <= cnvs.GetValue("Width")) {
IsMouseInsideCanvas = true;
}
if( IsMouseInsideCanvas & IsMouseInsideCanvas ) {
window.status = "Mouse is Inside Rectangle";
} else {
window.status = "Mouse is Outside Rectangle";
}
},
So what's happening here? Firstly I'm using ASP.NET AJAX ToolKit so the $get() is found within this framework. I'm also wiring up a local object to react to the MouseMove event, which in turn invokes private_updateDisplayList() method.
I then inside JavaScript create two variables (cnvs & rect) as pointers to XAML elements within Silverlight.
MouseObj you'll note was declared inside the local_onMouseMove method, which essentially translates to Silverlights MouseEventArgs and inside this object, you have a method GetPosition() which returns a Pointer (x/y).
Using this, I'm able to then determine in the first round (currentX/currentY) where the Cursor inside Silverlight currently is (why, not important in the above example but thought it's worth noting you can achieve this simply by providing null as your reference object).
The second round, I then determine where the Cursor is currently at in relation to the Canvas1 (cnvsX/cnvsY), which should return the same results as currentX (seen as though Canvas1 is the root element).
I then determine where the Cursor is located within the Rectangle1 element itself. Now, what will happen here is if the Rectanle1.x is located on 100 pixels from the left, and the Cursor is located 150 pixels from the left, the rectX will return "50" as its result.
Ahhh so now you see, that the GetPosition() method returns the appropriate x/y coordinates relative to the reference object in question and local x/y as the result. Important to know this one!
Lastly, I then determine if the Cursor is within the Rectangle1 boundaries, and to do this it's a simple case of asking "Does the rectX sit between 0 and the width of Rectangle1?, if so then it's within"
Note: If Rectangle1 has a width of 200px and the Cursor is located at 400px from the edge of Rectangle1, rectX will still return a positive integer which is essentially cursor's actual position (Math.round(currentX-rectX)) relative to the Rectangle1 (Summary: rectX will always return positive integer never a negative one).
Where to from here?
Don't be afraid, Silverlight 1.1 is basically alpha and all this shows is that with enough brain power a CursorManager Class could be put together quite easily and managed code approach to using hitTest logic could apply here. The overall walk-away point for all is that, Silverlight has a lot of powerful primitives in place, enough for anyone to build upwards from and create their own approach to Silverlight. Combining this effort with AJAX and one could get some interesting HTML/Silverlight offerings on the table.
Comments
Anonymous
June 16, 2007
The comment has been removedAnonymous
June 27, 2007
I put together a little Silverlight app demonstrating how to drag and drop between different areas ofAnonymous
May 06, 2008
I'm trying a similar technique, but I just found that if I rotate the object collision don't understand rotation, any new idea? put @gmail after obsidianartAnonymous
September 17, 2008
Any way to make the hittest work for an ellipse or other non-rectangular object?Anonymous
November 22, 2010
Yout blog autoforwards to another page. This is very annoying. I just want to read this post.