DZC can easily create collections, and using them is initially as easy as pointing at the generated items.bin file (instead of the info.bin file for a single image) but actually manipulating this collection requires a bit more code (although not much).
I found the trickiest thing was understanding how the MSI handled its coordinate space. Rather than using an absolute coordinate space it seems to always use a relative space, which makes it a little tricker to manipulate multiple images.
The first thing to understand is that images in a collection don't seem to have the concept of an absolute pixel size. Like the single MSI, subimages are sized according their ViewportWidth, but the wrinkle is that their ViewportOrigin, which sets the image's position, is relative to its ViewportWidth as well - not the parent MSI element's ViewportWidth. So a ViewportWidth of 1 will fill (by width) an image whose main ViewportWidth is 1 and a viewportWidth of 2 will be half the width. Also, a ViewportOrigin of (1,1) will map to a different position within the main element if the ViewportWidth is different. Which is why I got confused the first time I tried to line up all the subimages, and found that some overlapped the others.
One thing I wanted to be able to do is work out which subimage the mouse is over (to pop up a tooltip or update other parts of the UI with contextual information). There isn't a useful subimage equivalent to ElementPointToLogicalPoint which works with subimages, and they don't expose a HitTest method, so you have to roll your own code for things like hit testing. Here's the code I used:
/// Given an element point relative to the DeepZoom image
/// return whether the point hits any subimages in the collection
/// parentImage: Parent image whose subimages we want
/// to test
/// p: Point to test (in element coordinate space)
/// imageindex: index into the SubImages collection
/// returns: true if a hit was found
bool HitTest(MultiScaleImage parentImage, Point p, ref int imageindex)
{
bool gotHit = false;
for (int i = 0; i < parentImage.SubImages.Count; i++)
{
MultiScaleSubImage image = parentImage.SubImages[i];
// Start with the logical origin of the image
Point topLeft = image.ViewportOrigin;
// Relative to the parent image, this coordinate is
// scaled by ViewportWidth (and the coords are negative)
topLeft.X = -(topLeft.X / image.ViewportWidth);
topLeft.Y = -(topLeft.Y / image.ViewportWidth);
// Calculate the logical width relative to the parent
double width = 1 / image.ViewportWidth;
// And get the height from the aspect ratio
double height = width / image.AspectRatio;
// Create a point representing the bottom left logical point
Point bottomright = new Point(topLeft.X + width, topLeft.Y + height);
// Now we've got the topleft and bottom right points
// in coordinates relative to the parent MultiScaleImage
// We can now use LogicalToElement to convert to Silverlight
// coordinates
topLeft = parentImage.LogicalToElementPoint(topLeft);
bottomright = parentImage.LogicalToElementPoint(bottomright);
// Now do the hit test
Rect r = new Rect(topLeft, bottomright);
if (r.Contains(p))
{
gotHit = true;
imageindex = i;
}
}
return gotHit;
}
Note that this code calculates the screen coords of the bounding rectangle for subimages, so you can also use this maths to place other visual elements like labels or frames at the right place.
One thing to remember, though, is that if you've set the UseSprings property to True, it's possible to hit the images as they are moving. You might want to restrict your hit testing by hooking the MotionFinished event and setting your visual elements in that handler. Otherwise there's a danger that you'll set them while the image is in motion, and when it stops they'll be in the wrong place.
Next time: Shuffling the collection.
No comments:
Post a Comment