Welcome to MSDN Blogs Sign in | Join | Help

Working with Collections in Deep Zoom.

In the Deep Zoom run-time, you can load two types of compositions:

  • A single image composition is when you interact with a single image at run-time.  This does not mean you started with a single image, you could start with many images and compose a scene, then you export from Composer and all of the images get 'stitched' into a single composition that you can interact with at run-time.  You interact (like zooming, opacity, etc.) with your image by changing the properties of your MultiScaleImage.
  • A "Collection of Images" is when you compose a scene but export it as a collection of images (duh jaime do u get paid by the word? word, word?)
    At run-time, you can still interact with each of the individual images in your composition.  
    The images are exposed via the SubImages collection of your MultiScaleImage.   You can still set properties in the MultiScaleImage and these properties will affect all the images in the collection [for example if I Zoom-in to 200% in MSI, that would impact which SubImages are visible] but with collections I also get the benefit of interacting with the SubImages directly.

Collections have a lot more flexibility of course, but I also caution of two tiny concerns:

  • when dealing with collections,  you likely end up downloading a few more tiles as you go.  Not a huge deal
  • Your images load independently;  this again is not a huge deal unless you have huge discrepancies in size; in that case you will see your small images load earlier than your bigger ones.  [To solve this you could play with Opacity]

This post is about working with Collections, so let's assume I decided the two issues above are not in play (that is the case for most people). 

To find out what we can do with a MultiScaleSubImage, all we need is to look at the class definition:

  • AspectRatio - readonly property ==  width/height.
  • Opacity = self-explains;  0== transparent.  1.0 == Opaque
  • ViewportOrigin == top-left corner of the image.  Stay tuned for a lot more below. This is a really interesting property and 1/3  the point of this post
  • ViewportWidth == width of the area to be displayed.  This value is in logical coordinates. For example:
    • a value of 1 displays the entire image (no zoom),
    • a value of 0.5 is 200% zoomed in and a value of 0 is completely zoomed (user cannot see the image at all).
    • A value above 1 is zooming out from the image. For example, a value of 2 means that the image will take up half the size of the MultiScaleImage  control area (50% zoom).
  • ZIndex -- self explains;  higher numbers are in front of lower ones.

A few of the less obvious options for dealing with collections are:

Tags
If you have used Deep Zoom Composer you might have noticed that composer has a property called "Tag" for each image. As of beta2 tag is not exposed via the MultiScaleSubImage.  So, how can you get to this Tag?

The tags are stored in the metadata.xml  file generated by Deep Zoom Composer. 
You can easily get to this file using WebClient or HttpWebRequest networking APIs in SL2.  It is a trivial two step process:

  1. Make call to read XML file.  I did it from ImageOpenSucceded to not compete with download traffic for image and to know for sure that when WebClient callback happened I could access the images in the collection.

    void
    msi_ImageOpenSucceeded(objectsender, RoutedEventArgs e)
    {
       
            WebClient wc = newWebClient();
            wc.DownloadStringCompleted += newDownloadStringCompletedEventHandler(wc_DownloadStringCompleted);
            wc.DownloadStringAsync(newUri("GeneratedImages/Metadata.xml", UriKind.Relative));     
    }
  2. Then we read the results.    The code is below.  I used LINQ to XML -that makes it trivial :)
    The only thing worth highlighting from the code is the "map" between tags and MultiScaleSubImages. 
    metadata.xml has a ZOrder, which is a 1-based index of the position of the image in the collection.   Despite its name (ZOrder), this has nothing to do with MultiScaleImage.ZIndex .

    The actual collection is 0 based, so we  subtract one to the value read from metadata.xml    I have put red, highlighted comments on top the two most relevant lines .

void wc_DownloadStringCompleted(objectsender, DownloadStringCompletedEventArgs e)
{
    if(e.Cancelled == false&& e.Error == null)
    {
        strings = e.Result;
        XDocument doc = XDocument.Parse(s);
        var images = froma indoc.Element("Metadata").Descendants("Image")  
                     selecta;

       
        foreach ( XElement image inimages )
        { 
            CollectionImage ci =
            newCollectionImage
           
{
                 Height = (double) image.Element("Height"),
                 Width = (double) image.Element("Width"),

//here we read the ZOrder from metadata.xml and subtract one
                 ZOrder = ((int) image.Element("ZOrder")) -1 ,
                 Tag = (string) image.Element("Tag"),
                 Location = newPoint{ X = (double)image.Element("x"), Y = (double) image.Element("y")}
             }  
             ;

//here we map from the SubImages collection based on the ZOrder we read
            ci.Image = msi.SubImages[ci.ZOrder];
            _images.Add ( ci ) ;
            
        }

        items.ItemsSource = _images;

    }
}

If you look at the code, I created a CollectionImage which aggregates the stuff from metadata.xml and the corresponding MultiScaleSubImage. 
This means I could now filter, or do any thing since the data is merged.  Kirupa has an example of using tags to filter (so I will stop here on that topic and move to Viewports). 
 

ViewportOrigin:

ViewportOrigin represents the left(x), top(y) corner of your SubImage relative to the MultiScaleImage control.   
The surprise (for me at least) is that:

  • They are normalized relative to the viewportWidth of the subimage you are dealing with.
  • Moving towards the right in the horizontal ( X) direction is actually a negative offset, so is moving towards the bottom.

Got it?? OK! you are done.  
If you are like me  you might want to see a sample.  Here are some below: 

image This is a DeepZoom composition w/ two images. 
Blue is 1200x800  ( Aspect ratio = 1.5 )
Yellow is 600x400 ( AR = 1.5 )

At this point ViewportOrigin = 0,0 for both...   Looks good to me.

It is worth mentioning [if you try to make sense as you go ] that the
ViewportWidth for blue == 1.0  [takes the whole width available]
ViewportWidth for yellow == 2.0  [takes 1/2 the width available to the control]

The numbers on the images are "approximations".. if you read a coord of say 900,600 that means it is around there, but not quite exact

Let's now start changing ViewportOrigin.
image Here I simply changed viewportOrigin in yellow image.

My first instinct looking at this one would be  1,0 ... [since it is 600 pixels left of 0,0]
I was wrong!!
This is actually ViewportOrigin =  -1, 0..
Remember, when you move an image to right or bottom, the offsets are negative.

You want to know what would happen if VO was 1,0??
The demo is at  http://www.cookingwithxaml.com/recipes/DeepZoomCollection/default.html

You can play with ZIndex, Opacity and ViewportOrigin for each image [their values are databound on the grid].
image Having explained that the ViewportOrigins offsets (to right,bottom) are negative numbers.
Can you guess what the offset is for the image to the right?
My guess was ( 0, -1) but then  I was wrong again!  
The ViewportOrigin here is ( 0, -.66666666666)
Why?

Because the offsets are relative to the Width and in this case it is 600.
So a viewport of (0,-1) would have been lower in the y axis [instead of at 400, it would be at 600]
image This is 0,-1 and exactly what we expected for this one (after reading line above).
image I know you have it by now, but just for fun, this is  ( -1.5, -.3333)  aka ( 900,200)
Notice how half of our yellow image is clipped. 
image This is ViewportOrigin ( 0.5, -.3333 ) ... I figured I should show you some thing with a positive value for Viewport Origin...


 
Again, you can play with the ugly but hopefully useful sample here.. 
Just change the ViewportOrigin, or any other property and see what happens.
You can use the same sample to play with Opacity, ZIndex  and ViewportWidth..   this will show you the flexibility in collections.
Don't get tricky with the values as there is no validation.

Mapping SubImages to Element Coordinates
Now that we understand ViewportWidth and ViewportOrigin,  we can map from logical coordinates to element coordinates so we can put overlays on our MultiScaleImage.  Or do hit testing or any thing similar.

What I did is put a small pink rectangle in the page and I am going to listen to MouseMove on the MultiScaleImage and then do kind of a "hit testing" to see which Image I am over.  I used ZIndex to select only the single image on the front. If you did not use ZIndex you can select multiple.

So, what does the map look like??   The whole code is below commented in detail..  I hope that makes it easiest to explain -instead of my rambles-.

/// <summary>
/// Gets a rectangle representing the top-most image that the mouse is over
/// </summary>
/// <param name="elementPoint">Mouse Position, in Element Coordinates</param>
/// <returns> Rectangle reprsenting Element Coordinates for the image or 0,0,0,0 if not over an image</returns>
Rect SubImageHitTestUsingElement(Point elementPoint)
{
    Rect resultRect = new Rect(0, 0, 0, 0);
    int zIndex = 0;

    // We loop through all our images. 
    for (int i = 0; i < _images.Count; i++)
    {
        try
        {
            // Selct our MSSI. 
            MultiScaleSubImage subImage = _images[i].Image;

            // NOTICE the scale is a mutliplication of the size of our image (1 / subImage.ViewportWidth)
            // and the current Zoom level ( 1 / msi.ViewportWidth) 
            double scaleBy = 1 / subImage.ViewportWidth * 1 / msi.ViewportWidth;

            // The two lines below convert our image size us from Logical to Element Coords
            // Notice that for Height, we must take into account Aspect ratio. 
            double width = scaleBy * this.msi.ActualWidth;
            double height = (scaleBy * this.msi.ActualWidth * (1 / subImage.AspectRatio));

            // Now we convert our viewportorigin  (logical coords) to Element Coords
            // Reminder, this is top-left ..  Notice that we multiply by -1 since 
            // we saw the negative math for Viewport Origin. 
            Point p = msi.LogicalToElementPoint(
                new Point(
                     -subImage.ViewportOrigin.X * 1 / subImage.ViewportWidth,
                    - subImage.ViewportOrigin.Y * 1 / subImage.ViewportWidth));
             
            // Now we create a Rectangle in Element Coords. 
            Rect subImageRect = new
                Rect(p.X, p.Y, width, height);

            // here we hitTest, using Contains.. 
            // and we keep track of the front-most element only..                    
            if (subImageRect.Contains(elementPoint))
            {
                if (subImage.ZIndex >= zIndex)
                {

                    zIndex = subImage.ZIndex;
                    resultRect = subImageRect;                            
                }
            }
        }
        catch (Exception ex)
        {
            System.Diagnostics.Debug.WriteLine(ex.Message); 
        } 
    }
    System.Diagnostics.Debug.WriteLine("Done");
    return resultRect;
} 


I used Element Coords because that is what I was after. If you want logical coords, it should be easy from code above.. 
Just convert the point to Logical, do the scaling for zoom and hittest against a logical rect...

Fair enough???   The source is [you guessed it] at Skydrive.

You can see a few tiny issues I did not care much for:
1) My math is rounded so some times you see the 'Rectangle' I created be slightly off (adding some extra pixels should do fine) ...
2) I did the recalculation for rectangle only on mouse move..  and I did not do it on Pan... so if you Zoom using Wheel or you pan, it will take for you to move the mouse one more time in order for Rectangle overlay to update.

That is my part!!  Now it is up to you to build some thing really cool using real images and hittesting..

Posted by jaimer | 5 Comments
Filed under: ,

Installing Silverlight no longer requires a browser re-start… woohoo!!!

A few weeks ago I noticed changes in browser's behaviors… 

1) My IE  7.0.6000  no longer prompts for Click To Activate  :)

2) My firefox 2.0.0.13   no longer required a reboot after installing Silverlight …  If you had not noticed, using silverlight.js on IE allowed you to instantiate silverlight plugin right after installing , but on other browsers it required a re-start… 

I pinged Piotr, our deployment PM, asking if he was seeing the same and he upped it sharing that he had a way to instantiate silverlight on ANY PLATFORM and ANY browser WITH OUT requiring A RE-START…  [call me a geek, but I was happy]..   Today, he posted the blog with the magic call: navigator.plugins.refresh () …   Check it out yourself…  

I hear the  next version of Silverlight.js (when we next release an SDK) will include these changes; in the mean time you can take his advise and use navigator.plugins.refresh () to get around the re-boot issue and instantiate SL right after installing…  

[caveat: this does not work if silverlight is being updated, and the plugin was loaded, but that scenario is not common since silverlight auto-updates itself] …

Thanks Piotr, and welcome to blogging..

Posted by jaimer | 3 Comments
Filed under:

Only two weeks to go until WPF Codecamp in North Carolina ( May 17)…

On May 17,  Karl Shifflett and Josh Smith present their “WPF Multi-Tier Business Application Track at the Enterprise Developers Guild Code Camp”.
The event is at the CPCC Central Campus located in Charlotte, NC.  

If you are in the area or within driving/train distance I definitely recommend you try to attend…  Josh and Karl are two of the most knowledgeable and passionate WPF experts out there..   I have had the pleasure of seeing Josh present at the 08 WPB bootcamp and he is very good – he has this funny/casual way of sharing a lot of deep experience..    I have not seen Karl speak yet, but given his Mole accomplishments there is no doubt this event is going to be very insightful…   [btw, if MS has another WPF bootcamp in the future, we will have Karl :)]

I wish I could be there for this.. but I have a conflict ;( …. if you are in the area sign-up soon, attend and have lots of fun …  [there will even be prizes]

Good luck Josh & Karl!!!   Thanks for volunteering and sharing!

J

Viewbox for Silverlight2

Viewbox is a pretty handy 'container' in WPF..   It is a decorator that scales its child content to the size available to the viewbox ( if child is smaller it scales it up >1, if child is bigger it scales it down such that it fits based on some stretch direction).

You can find source code for a sample viewbox here

If you want to see a ugly (yet still useful for those knowing viewbox) harness, there is a sample here..

Sorry for no docs; the control itself is straight forward.  Please check the docs for WPF viewbox to see how to use it; the SL2 version above mimics WPF.

Please do review the source if you are using it for a real project.

 

Cheers,

Posted by jaimer | 5 Comments
Filed under:

Some of the Silverlight blogs I read..

This one got stuck on my drafts on 4/10… sorry …   (FYI, I am back from my trip; more on that soon ) …

- –-  -—-— -- -

I am out all next 1.5 weeks talking to ISVs in EMEA about WPF and Silverlight… Should be so much fun that I won’t have time to blog :(..  in the mean time, here is an OPML of the Silverlight blogs I read…   

If your blog is missing or you want to recommend some one else’s blog, please email me or leave comments…

Again, OPML is here.

Posted by jaimer | 3 Comments
Filed under:

built-in Styling and generic.xaml

Most people already know (from ScottGu’s blog post for example)  that in Silverlight 2 you can override the ControlTemplate for a Control and ‘re-define’ the look of the control.  However, I have received a few questions around the use of generic.xaml to accomplish this same task; I will try to share a few thoughts on this to tease you into digging deeper on your own.   If you are short on time, skip to [FAQs on built-in styles below]

Some definitions on the recurrent “what is difference between style and template?” 

Style is an object (in Markup or in Code) that sets properties of a control. 
All that a style can do is set the value of existing properties on the control.  Imagine our control was a Car, a Style could say some thing like “wheelsize=17”, bodycolor=”cherry red”, “windowtreatment=”tinted”, etc.. 

A template actually defines the pieces or parts of the car. For example, a template for a cheap convertible might not have a roof at all :)  or a template for a Car can decide if it is two doors or four doors, if it has four wheels or eight, etc. 
When I explain it I always tell people, the template defines the skeleton, the Style dresses the pirate ( I like the pirate analogy cause some of them have one eye, or one leg, or one arm, etc. making good use of Templates).

Where things get interesting is that a Style can set any property in the control, and Template is itself a property, so what you see the tools (like Blend) do most often is when you want to override the Template of a Control, they override the whole style and the Template property with it. 

Stay with me… even if the above does not make sense, the rest of the article will help.

Where generic.xaml comes in is in the “magic” that defines the default look for a control.  Let’s imagine we want to create a GelButton .. 

public class GelButton : Button    
{

}

Simple enough, now we want to use it in our Page.xaml user control, we add the namespace and the control.
<UserControl x:Class="StylingSample.Page"
    xmlns="http://schemas.microsoft.com/client/2007" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    Width="400" Height="300" 
    xmlns:samples="clr-namespace:StylingSample">
    <StackPanel Width="50" >
        <Button Content="Top"  Height="50"/> 
        <samples:GelButton Content="Cream" Height="50"></samples:GelButton>
        <Button Content="Bottom" Height="50"> </Button>
    </StackPanel>    
</UserControl>

Would you be surprised if the outcome looked like this?

image

I can’t tell you if you should be surprised or not (I am undecided myself), but I can tell you what happened!

The control by default is lookless. You need to define the look for it. This is accomplished by assigning a valid ControlTemplate to the control  [via the Template property in the Control class].

To assign the Template property, you could do some thing like:

public GelButton ()
{
this
.Template = XamlReader.Load ("<ControlTemplate xmlns='http://schemas.microsoft.com/client/2007'
><Grid ..> </Grid></ControlTemplate>");

}

but a better way to do it is to store the control template in a generic.xaml Resource Dictionary and then magically the run-time, will pick it up from there. Your template would be associated to your control via the TargetType attribute when defining the resource.  This template would now become what we call the “built-in style”.

Here are the details on creating a built-in style.

Generic.xaml is a ResourceDictionary –a property bag for resources – that you include in your assembly, at the root of the project.  If you are a WPFer you might be thinking it should be in themes\generic.xaml, I hear that is where it might end up, but for now (Silverlight 2 beta1), it needs to be in the root of the project.  The default (empty)  resource generic.xaml could look like this:

<ResourceDictionary 
    xmlns="http://schemas.microsoft.com/client/2007" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
> 
</ResourceDictionary>

For defining the look & feel for our GelButton we need to start with some default template. Long term, this will be a right click inside Blend  (like in WPF); temporarily since Blend does not yet support styling,  I would recommend is using David Anson’s handy Stylebrowser application to copy the default Style for button and paste it into the resource dictionary.

[unfortunately the default button template is too verbose, so for practical purposes here I am going to use a much simpler template].

<ResourceDictionary 
    xmlns="http://schemas.microsoft.com/client/2007" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    xmlns:samples="clr-namespace:StylingSample;assembly=StylingSample"
>
    <Style TargetType="samples:GelButton">
        <Setter Property="Background" Value="Black" />
        <Setter Property="Foreground" Value="White" />
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate  TargetType="samples:GelButton">
                    <Grid x:Name="RootElement" >
                        <Ellipse Width="{TemplateBinding Width}" 
                            Height="{TemplateBinding Height}" 
                            Fill="{TemplateBinding Background}"
                        /> 
                        <ContentPresenter 
                        Content="{TemplateBinding Content}" 
                        ContentTemplate="{TemplateBinding ContentTemplate}" 
                        Foreground="{TemplateBinding Foreground}" 
                        VerticalAlignment="Center" HorizontalAlignment="Center"
                        />
                    </Grid>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</ResourceDictionary>

Let’s dissect the work needed to create this template:

  1. Added a xmlns:samples to the resource dictionary.
    Notice the slightly different syntax from the namespaces you add to say a UserControl; in generic.xaml I included the assembly (which is the name of my DLL).
    If I had tried xmlns:samples="clr-namespace:StylingSample; with out the assembly=”StylingSample” it would not work. [trust me, I make that mistake often].
  2. Next I defined the style and I told it the TargetType I wanted this Style to be applied to; you usually do this when defining a Style so your template can do discovery of the properties and validate these, but when doing this in generic.xaml, the magic that happens on built-in templates uses this information to create a relationship (or I would say bind) between this Style and the type; now when ever the type is instantiated, if no other style is applied, this style will be used as the default style.
  3. The rest is simple styling stuff.  TemplateBinding is probably the most interesting part, this creates a binding between the property we are setting and the actual controls’ property. For example:  <ContentPresenter Foreground={TemplateBinding Foreground}" > creates a bind between the content presenter’s foreground and the Foreground in the actual control.  This will our UI styleabe from within the tools. Inside Blend or in XAML you can declare a button <GelButton Foreground=”Red” > or a <GelButton Foreground=White> and get flexibility as the template will get the value carried through. F
    For more info on all of this you should watch Karen Corby’s MIX presentation on “Rich,Dynamic UIs” ..

Now, I can run the same code, with changing nothing other than the Resource dictionary I added and I get:

image

Since I did create a Templatebinding for background/Foreground , I can even have some fun..  After all, I promised some “meat”.. Need food!! sorry about that it is 1:30 PM ..

<UserControl x:Class="StylingSample.Page"
    xmlns="http://schemas.microsoft.com/client/2007" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    Width="400" Height="300" 
    xmlns:samples="clr-namespace:StylingSample">
    <StackPanel Width="50" Margin="0,20,0,0">
        <samples:GelButton Content="" Height="20.4" 
                RenderTransformOrigin="0.5,0.5" Width="48.8" Canvas.ZIndex="2">
            <samples:GelButton.Background>
                <RadialGradientBrush>
                    <GradientStop Color="#FFF5DEB3"/>
                    <GradientStop Color="#FFE0B05C" Offset="0.826"/>
                </RadialGradientBrush>
            </samples:GelButton.Background>
            
        </samples:GelButton> 
        <samples:GelButton Content="Ham" Height="16" Canvas.ZIndex="1">
            <samples:GelButton.Background>
                <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
                    <GradientStop Color="#FFD64141"/>
                    <GradientStop Color="#FFE23939" Offset="1"/>
                    <GradientStop Color="#FEDAB6B6" Offset="0.43299999833106995"/>
                </LinearGradientBrush>
            </samples:GelButton.Background>
            </samples:GelButton>
        <samples:GelButton Content="" Height="16"  >
            <samples:GelButton.Background>
                <RadialGradientBrush>
                    <GradientStop Color="#FFF5DEB3"/>
                    <GradientStop Color="#FFECC06E" Offset="0.991"/>
                </RadialGradientBrush>
            </samples:GelButton.Background>
        </samples:GelButton>
    </StackPanel>    
</UserControl>
image

So, I just wasted 10 mins of your time and 40 of mine introducing you to generic.xaml and built-in styles.  I had promised to answer a few questions, here they are:

[REF: FAQs on built-in styles]

What are the benefits of built-in styles, why use generic.xaml instead of hardcoding the template?

It is nice to store all your templates in a resource dictionary that you can easily swap – as opposed to having to do it in code-.   Imagine you needed to create three themes for your app, doing it with hardcoded templates would be hard.
Also, If you put your Template in the ResourceDictionary the template can now reference other resources in the dictionary itself.

Why is it that all examples I have seen are not using built-in styles?  we are always told to apply the style inline from App.xaml

Built-in styles are designed for control authors, when you write a control, you provide look & feel.  If you look at example above, I had to inherited from Button class.  Most samples are purely styling a button, so they take a different approach.

In the financials demonstrator, you inherited from Button and did nothing other than provide the built-in style, is this a best practice?

I liked that approach (but I come from an enterprise background where we create bloated frameworks that often inherit just to create an abstraction in case some thing changes later) ; the one benefit you get is you can use your button any where with out having to explicitly refer to a style. 
The disadvantage of course is that inheriting takes a bit of extra performance and memory; but this is pretty negligible from what I have seen. 

Again, I don’t call it a best practice, more of a personal preference for me.  If you look at financials demonstrator now, I ended up adding a property later UseReflection, so now the button does have a reason to be its own class.

Built-in styles sounds like I can change it all in one place? I don’t want to crowd my code with <Button Style=”{StaticResource GelButtonStyle}” >.

That is right, if you can afford inheriting and the classes are not sealed.  That said, after building a few solutions I realized I had a false sense of centralization  [yes I made up the term]. 
The argument is

1) With built-in styles, I can change the style in one place. It is the same using App.xaml you change the style itself in one place for all.  What you are replicating a lot is the name of the style, but the style itself is in one place.

When can I not use built-in styles? 
if the class was sealed or they had protected the Template property then you would not be able to use this.

Can I just create a generic.xaml and override the System.Windows.Controls templates with out inheriting?
Not that I know of. It does not sound like a good idea; I tried it just to see if it worked and it did not work for me.
 

Is applying a built-in style going to break or affect my state and parts?
No. As long as your style uses the same names, the code will still pick all that as if it was an inline Style.

We would not need built-in styles if you allowed TargetType every where, including on regular dictionaries, like WPF does.
Fair point, these features are all being considered for later versions after 2.0 stay tuned, right now this way works, it is flexible and comprehensive.

Will this approach work with Blend? Will I still be able to style in Blend.

Yes!  Blend works with this already; that is how it picks the look & feel for System.WIndows.Controls today.

Why do styles & Control templates always go together? Can I just do my Control template?
My personal opinion is that if you need a template and not the style your template might be too stringent or too hard coded; it would be the equivalent of writing a ControlTemplate that does not have any TemplateBinding on it; don’t get me wrong, I am not saying this is wrong, I am just saying 99% of the time, this does not happen. With regards to simply providing the ControlTemplate in generic.xaml,  I don’t believe that would work.

In the financials demonstrator, you named your class Button, for some thing that inherited from Sysetm.Windows.Controls.Button.. Is using the same name required?

Absolutely not. I chose the name because I was going to override all controls, but I ended up changing my mind and that made it more confusing. Sorry about that; the name does not matter (as long as it does not conflict); from experience calling it Button will confuse you, don’t do that.

If I use the built-in style, does that mean a ‘consumer’ of my button will not be able to style the button later?

No! A consumer will still be able to style the button later and as well as override your template.

--

OK, I need to go eat.  This at least answers the questions I had; will try to come back to this at a different time.

Posted by jaimer | 2 Comments
Filed under:

Silverlight instantiation on 2.0

On my Deep zoom Post, I recommended that for Deep Zoom applications you instantiate the control using Silverlight.js to avoid the Click To Activate.  

Some one picked on this and asked why the new default is using OBJECT. Here is what I know (and most of it comes from past emails with Piotr Puszkiewicz).

Using the OBJECT tag has a few advantages: 

  1. Customizing the experience is much easier ..  All you do is put your install experience inside the object tag.  For example, the defult puts an anchor with the GetSilverlight image:
    <object data="data:application/x-silverlight," …> 
    <!—This is the isntall experience –->
    <a href="http://go.microsoft.com/fwlink/?LinkID=108182" style="text-decoration: none;"> <img src=http://go.microsoft.com/fwlink/?LinkId=108181
    alt="Get Microsoft Silverlight" style="border-style: none"/> </a>
    <!—- end of install --> </
    object>
  2. No javascript dependency. If your page is hosted some where, your might not be allowed to bring javascript but might not have the object tag blocked, so now you can instantiate with no Javascript. 
  3. One less file to download – which is a huge deal for high traffic portals and hosters.
  4. Much easier to create the ‘embed’ or send to a friend feature if you are sending an object tag.  This one (and #2) are coupled with the fact that XAPs can be loaded across domains.

So why did we default to <OBJECT /> ??
Well, because Click To Activate is going away.  IE team announced it and given they said it was in april, we are within  28 days of this happening.. [Should we start a pool?].

For those of us creating samples today, we obviously have a choice for a few more weeks; I will remain partial to silverlight.js; if you want to come, here is how I get my instantiation:

  1. I created a dummy Silverlight 1.0 solution using Blend 2.5 March CTP.
    This creates a good default.html that I can reuse.
  2. I then copied the silverlight.js from “\program files\microsoft sdks\silverlight\v2.0\tools\ directory to my new solution.
    [I did it mostly because I noticed that was the one updated last and I know the install team put it there]
  3. Now you edit the default.html to:
    1. Comment out the <script> include for page.xaml.js
      <!--    <script type="text/javascript" src="Page.xaml.js"></script>—>
    2. Comment out the var scene = new Page() declaration.  
      //var scene = new SLOn.Page();
    3. Change the source parameter in createSilverlight call to point to your XAP instead of the XAML file
      Silverlight.createObjectEx({
                      source: "ClientBin/hello.xap",
    4. Comment out the onLoad handler, which refers to the scene we removed in #1.
      //  onLoad: Silverlight.createDelegate(scene, scene.handleLoad),
  4. An optional step that I usually do is change the control’s style so it takes 100%

    #silverlightControlHost {
                height: 100%;
                width: 100%;
            }

  5. Another optional step is adding the style for body and html so there is no padding:

    html, body {
            height: 100%;
            overflow: auto;
        }
        body {
            padding: 0;
            margin: 0;
        }

  6. If you were doing this for production you would probably also want to tweak the onError handler in createSilverlight..
  7. After that, I copy my default.html and silverlight.js to the web directory where my solution is at, and browse/navigate to it.

You can get my sample default.html and silverlight.js from here.   

Disclaimer: this approach is good for developer samples, for a production app please follow the Silverlight Installation Experience Guide

Posted by jaimer | 1 Comments
Filed under:

Open source charting library for Silverlight 2..

Visifire has a free, open-source library for 2D and “3D looking” charts built using Silverlight 2..  Very cool!!

image image image image



For me, this is obviously handy when creating demos and showing Silverlight capabilities; for commercial use they also have a commercial license..   

Emailing them to see if they would be interesting on working with us for their charts to be styled in Blend; in the mean time enjoy the charts and play with them..

Posted by jaimer | 2 Comments
Filed under:

A deepzoom primer ( explained and coded)..

I had to learn DeepZoom recently and along the way I put together some handy notes .. below are the notes organized in a near step-by-step explanation format.  This is a very long post, but I hope it has useful insights for any one wanting to do deepzoom so I recommend you read it all.  If you must skip, then the outline will help you.  imo, part 3 and 5 are the good stuff. 
 

Part 1 – The history and brief explanation on how DeepZoom works.

Part 2 – Constructing a DeepZoom image using Image Composer

Part 3 – Introduction to the DeepZoom object model – goes way beyond the docs I hope

Part 4 – Coding a deepZoom ‘host’ user control with Pan & Zoom

Part 4.1 – Adding a few extra features to our User Control

Part 5 – Lessons learned on the code, documenting the gotchas  **must read even if you know Deep Zoom already

Part 6 – Give me the code, just a zip file w/ the goodies 

Part 7 – Show me the outcome; what did we build?

Part 1 – The History & Math behind DeepZoom

A lot of people equate DeepZoom to SeaDragon – they assume SeaDragon was the code name and DeepZoom is the marketing name-. This assumption is not quite right (unless you equate your engine to your car model).  Seadragon is an incubation project resulting from the acquisition of Seadragon Software; the Seadragon team is part of the Live organization and are working on several projects (like Photosynth). DeepZoom is an implementation that exposes some of the SeaDragon technology to Silverlight.

DeepZoom provides the ability to smoothly present and navigate large amounts of visual information (images) regardless of the size of the size of the data, and optimizing the bandwidth available to download it.  

How does DeepZoom work?

DeepZoom accomplishes its goal by partitioning an image (or a composition of images) into tiles.  While tiling the image, the composer also creates a pyramid of lower resolution tiles for the original composition. 

The image to the right shows you what a pyramid; the original image is lowest in the pyramid, notice how it is tiled into smaller images, also notice the pyramid creates lower resolution images (also tiled).   A few of the docs I read said the tiles are 256x256, but from peeking through the files generated by the composer I am not convinced; I do know from reading through the internal stuff that there is some heavy math involved here, so I trust they tile for right size :).

All of this tiling is performed at design-time and gets accomplished using the DeepZoom composer.

At run-time a MultiScaleImage downloads a lower resolution tile of the image first and downloads the other images on demand (as you pan or zoom); DeepZoom make sure the transitions from lower to higher res images are smooth and seamless. 

PYRPSD

Given all this, how is Deepzoom different than say a ScaleTransform (for zoom) on a high resolution image?

With a ScaleTransform, usually you would download the whole high res image at once; this delays how quickly the end user gets to see the image when the page or application loads.  Some times people apply a trick where you use different resolutions images, since you are not tiled you will likely end up downloading several big images (consuming more network bandwidth) or the download time will continue to be high if the initial downloaded image is not small enough, also the transitions from low to higher res are going to be more noticeable unless your write the transitions yourself.

DeepZoom and its tiling make it possible to see bits quicker and can optimize for bandwidth.  In the worst case scenario where some one looked at every single one of the tiles at the highest resolution, DeepZoom would have an extra overhead of 33% compared to downloading the single highest resolution image at once, but this ‘worst case’ scenario is almost never hit, most of the time DeepZoom can save you from downloading too much.   

Another feature in DeepZoom is its ability to create ‘collections’ from the composite image.  This provides you the ability to compose a scene ( group of images ), optimize them for speed & download, but still maintain the ‘autonomy’ and identity of the image, you can programmatically manipulate (or position) these images from within the DeepZoom collection (more on collections in part 4).


Part 2 – Constructing a DeepZoom Image using DeepZoom composer 

  1. We begin by downloading the "DeepZoom composer" from Microsoft Downloads.
    If you want a great reference for the tool, try the DeepZoom Composer guide. In the steps below, I am going to keep it to the minimum steps needed and some of the gotchas when using the tool.
  2. After installing the DeepZoom Composer, we launch it. 
    Trivia facts: Composer is a WPF application, like most of the other Expression products. Also, codename was Mermaid (you can see this from the export window).
  3. Under the File Menu, select "New Project"
      1. Select a location to store the project.
        I recommend some thing with a short path like c:\users\jaimer\. The composer has some usability issues that make working with long paths a little hard; and the composer will append to your path later when you export.
      2. I called it "EasterEggHunt" as that is what my project will be.
    image
  4. Now click "Add Image" to import a few images.
    You can import multiple images at once.  In my case, I am importing 3 images: bummysmall.jpg, eggs.jpg, and world2.jpg). These are in the inputimages directory if your are following along with the source.
    image 
    This added all the images we are going to use in the composer.  All images must be added at design-time.
  5. Click "Compose"  on the Center toolbar to compose the DeepZoom image.
  6. Double click the world image to add it to the 'stage' or design surface.
  7. Click Fit To Screen to maximize our space.
  8. Click on the eggs image  to add it to the stage.
  9. Zoom many times into the image at a place where you want to drop some easter eggs.
    1. Hint:  the Usual short cuts of Ctrl+ and Ctrl-  do work for zooming. Unfortunately Pan(h) and Select(v) don't work.
  10. Shrink the easter eggs into a small size -- don't worry, with DeepZoom we will be able to Zoom a lot at run-time to find them and see them.
  11. Drop the easter eggs where you want to. He is an example of mine, I dropped them in Mexico. Notice I am quite zoomed into the map and the eggs are small.

    image 

  12. Repeat the steps in 11 for the bunny picture.  In my case,  I did it in Seattle area.
    Note: unfortunately I could not figure how to drag same image twice into stage area.  The work around I used is to make a copy of the image with different name, and add it to the image gallery ( Step 4).
  13. Click Ctrl-0 to see our DeepZoom Image with out the zooms.  You sized it right if you can't easily see the eggs and bunny in the map.
  14. CLick "Export" in the main toolbar. 
  15. Here we enter the settings for output.
  16. Leave the "generate collection" unchecked for now.
    What Generate Collection does is exports the DeepZoom Image with metadata and at run-time the images can be accessed via the MultiScaleImage.SubImages  property.   If you can get to these images, you can move them around the composed image ( for layout ) you can also tweak their Opacity.
    The reason I am leaving them unchecked is beause there seems to be a bug (at least on my machine) where if I click Generate Collections my images at run-time show at an offset of where they are supposed to be.   I have reported it to the DZC team and they are investigating.
  17. Enter a Name  ( "Easter" on the export Dialog).
  18. I leave the Output path untouched.
    This is where having entered a short path in Step 2 above would pay up because their Path dialog does not Wrap and it is fairly small. [Kirupa already said this is improving for next version]. If you opt to change the path, be attentive when you export again, it seems to reset to its default value.

    image
  19. Now, assuming your output looks similar to mine above, (Create Collection unchecked) Click Export and we are done when it says Export Completed.


Part 3 - DeepZoom Object Model

Once you have a DeepZoom image, you will need an instance of the MultiScaleImage class in your silverlight application to load that image.  Instantiating the MultiScaleImage class can be done from XAML

<MultiScaleImage x:Name="DeepZoom" Source="easter/info.bin" />

or from code:

MultiScaleImage DeepZoom = new MultiScaleImage () ;

DeepZoom.Source = new Uri ( “easter/info.bin”) ;

Before  going through the DeepZoom API it makes sense to understand the terminology used:

  • Logical Coordinates – is a normalized value (0 to 1) representing a coordinate in the image itself (not the control)
  • Element Coordinates – is the actual control coordinates. For example in a MultiScaleImage of Width=800, Height =400, when the mouse is at the center, the element coordinates are 400,400.  These coordinates are not normalized.

Now, we navigate through the interesting properties and methods in MultiScaleImage

  • Source – refers to the Image source; usually info.bin when not using collections or items.bin  if using collections. 
  • SubImages – when using collections, this is a reference to all the images in a composed DeepZoom Image.
  • ViewportWidth – Specifies the width of the parts of the image to be displayed. The value is in Logical coordinates.
    For example: 
    Width=2 means image is zoomed out and only takes half the space available. 
    To zoom in, a viewport < 1 is required.  ViewportWidth of 0.5 is a 200% zoom.
  • ViewportOrigin – the Top,Left corner for the parts of the image to be displayed.  This is returned in Logical coordinates.  For example, imagine I am panning by 10% each time and I pan twice to the right while zoomed in at 100% (so no zoom), my ViewportOrigin.X will be 0.2.
  • UseSprings – gets or set whether DeepZoom animates the transitions ( like ZoomAboutLogicalPoint, updates to ViewportOrigin, etc. ).

The interesting methods are:

  • ElementToLogicalPoint – takes a coordinate of the control, and gives you a logical ( normalized coordinate).
    For example, mouse at Center (400,400) with ViewportWidth=1 and you call ElementToLogical ( ) will return (0.5, 0.5)
  • LogicalToElementPoint – takes a logical coordinate (normalized) and returns a point in the MultiScaleImage control where that logical point corresponds to.
  • ZoomAboutLogicalPoint – implements the Zoom.  The two parameters are the new zoom multiplier - as an increment from current zoom factor in the image - and the Logical point at which to zoom around. 
    Example of the incremental zoom would be to ZoomAboutLogicalPoint  ( 1.5, 0.5, 0.5) .. I will be zoomed in to 1.5 times;  if I repeat this operation with same values I am zoomed in at 1.5 * 1.5  which is 2.25 times from size where I started.

In my opinion, surprisingly missing from the API were:

  • The original width and height of the DeepZoomImage  (so that I can translate normalized logical coords to physical on the image).
  • Zoom – to tell you the current total Zoom level; this one you can get around by keeping track of any zooms you implement. Another possible workaround is that Zoom appears to be 1/ViewportWidth; I can’t think of the scenario where this does not hold, if there is please let me know and again just keep track of your zooms if that happens.

Part 4 – Coding a  DeepZoom Host User Control

                     

The goal here is to code a sample  reusable control just to illustrate the points; along the way we will of course implement enough features for our Easter Egg Hunt.  [Update: Sorry about belatedness, I started this on 3/22 but had a trip that prevented me from playing around, so I am late from easter]

  1. Inside Visual Studio 2008, create a new Silverlight Application; I called it DeepZoomSample.
  2. Build the application so the Clientbin directory is created.
  3. Copy the output from the DeepZoom Composer to the Clientbin directory of our Silverlight application.
    In my case, I called the output “Easter” so I can go into the output directory from composer and just copy that whole directory to my Silverlight Application’s ClientBin.
  4. Now that we have our image, we can edit the XAML in Page.Xaml, to show the image.
    <UserControl x:Class="DeepZoomSample.Page"
        xmlns="http://schemas.microsoft.com/client/2007" 
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
        >
        <Grid x:Name="LayoutRoot" Background="White">
            <MultiScaleImage x:Name="DeepZoom" Source="easter/info.bin" /> 
        </Grid>
    </UserControl>
    If you run the application now, you will see the image loads  but there is no functionality: zoom and pan have not been implemented. 
    For zoom, we need to use the mouse wheel, but Silverlight has no native support for it. A good work around is to use Peter Blois’ MouseWheelHelper. This class uses HTML Bridge to listen to the mouse wheel event in the browser and exposes the events to managed code.
  5. Add a new code file to your project, I called it MouseWheelHelper.
  6. Copy Peter’s code below into the MouseWheelHelper file.

    using System;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Documents;
    using System.Windows.Ink;
    using System.Windows.Input;
    using System.Windows.Media;
    using System.Windows.Media.Animation;
    using System.Windows.Shapes;
    using System.Windows.Browser;

    namespace DeepZoomSample
    {
        // this code came from Peter Blois,  http://www.blois.us/blog
        // Code ported by Pete blois from Javascript version at http://adomas.org/javascript-mouse-wheel/
        public class MouseWheelEventArgs : EventArgs
        {
            private double delta;
            private bool handled = false;

            public MouseWheelEventArgs(double delta)
            {
                this.delta = delta;
            }

            public double Delta
            {
                get { return this.delta; }
            }

            // Use handled to prevent the default browser behavior!
            public bool Handled
            {
                get { return this.handled; }
                set { this.handled = value; }
            }
        }

        public class MouseWheelHelper
        {

            public event EventHandler<MouseWheelEventArgs> Moved;
            private static Worker worker;
            private bool isMouseOver = false;

            public MouseWheelHelper(FrameworkElement element)
            {

                if (MouseWheelHelper.worker == null)
                    MouseWheelHelper.worker = new Worker();

                MouseWheelHelper.worker.Moved += this.HandleMouseWheel;

                element.MouseEnter += this.HandleMouseEnter;
                element.MouseLeave += this.HandleMouseLeave;
                element.MouseMove += this.HandleMouseMove;
            }

            private void HandleMouseWheel(object sender, MouseWheelEventArgs args)
            {
                if (this.isMouseOver)
                    this.Moved(this, args);
            }

            private void HandleMouseEnter(object sender, EventArgs e)
            {
                this.isMouseOver = true;
            }

            private void HandleMouseLeave(object sender, EventArgs e)
            {
                this.isMouseOver = false;
            }

            private void HandleMouseMove(object sender, EventArgs e)
            {
                this.isMouseOver = true;
            }

            private class Worker
            {

                public event EventHandler<MouseWheelEventArgs> Moved;

                public Worker()
                {

                    if (HtmlPage.IsEnabled)
                    {
                        HtmlPage.Window.AttachEvent("DOMMouseScroll", this.HandleMouseWheel);
                        HtmlPage.Window.AttachEvent("onmousewheel", this.HandleMouseWheel);
                        HtmlPage.Document.AttachEvent("onmousewheel", this.HandleMouseWheel);
                    }

                }

                private void HandleMouseWheel(object sender, HtmlEventArgs args)
                {
                    double delta = 0;

                    ScriptObject eventObj = args.EventObject;

                    if (eventObj.GetProperty("wheelDelta") != null)
                    {
                        delta = ((double)eventObj.GetProperty("wheelDelta")) / 120;

                        if (HtmlPage.Window.GetProperty("opera") != null)
                            delta = -delta;
                    }
                    else if (eventObj.GetProperty("detail") != null)
                    {
                        delta = -((double)eventObj.GetProperty("detail")) / 3;

                        if (HtmlPage.BrowserInformation.UserAgent.IndexOf("Macintosh") != -1)
                            delta = delta * 3;
                    }

                    if (delta != 0 && this.Moved != null)
                    {
                        MouseWheelEventArgs wheelArgs = new MouseWheelEventArgs(delta);
                        this.Moved(this, wheelArgs);

                        if (wheelArgs.Handled)
                            args.PreventDefault();
                    }
                }
            }
        }
    }




    MouseWheelHelper fires a Moved Event whenever the Wheel moves. The EventArgs is a MouseWheelEventArgs, which has the delta property. Delta is a normalized property (0 to 1), for now all we look at is whether it is greater than 0 or not.
    If Delta is greater than 0, then the wheel has rotated away from the user; if Delta is a negative number, then the wheel has rotated toward the user.

  7. Before we handle the Moved event, let’s add a ZoomFactor property to our control, this will be the increment/decrement on a wheel operation. The default value is 1.3, which is a 30% increment.  Nothing scientific behind this number, I am pretty much just ‘copying’ what I see every other sample do. I think the number works OK.
     protected double _defaultZoom = 1.3; 
           public double DefaultZoomFactor
           {
               get
               {
                   return _defaultZoom; 
               }
               set
               {
                   _defaultZoom = value; 
               } 
           }
  8. We also add a CurrentTotalZoom property, this will be a cached version of overall zoom level (since we can’t query this from the MultiScaleImage API.  I also added a MaxZoomIn and MaxZoomOut to prevent the image from going too far in (is there such a thing?) or too far out. Too Far out did matter as the image can disapper if you go too far.  In my case I picked my Maximum values arbitrarily.


    private double _currentTotalZoom = 1.0;

           public double CurrentTotalZoom
           {
               get { return _currentTotalZoom; }
               set { _currentTotalZoom = value; }
           }

           private double _maxZoomIn = 5000;
           protected double MaxZoomIn
           {
               get { return _maxZoomIn; }
               set { _maxZoomIn = value; }
           }
           private double _maxZoomOut = 0.001;

           protected double MaxZoomOut
           {
               get { return _maxZoomOut; }
               set { _maxZoomOut = value; }
           }


  9. Now, we can add a DoZoom function to our class, this will be called when there is a Zoom operation.   The parameters for it are: the new Zoom level RELATIVE to where the image is at,  and  a point in Element Coordinates since most likely we will be zooming around the mouse, and we get Element coordiantes out of that.


    /// <summary>
          /// Performs a Zoom operation relative to where Image is at. 
          /// Example, call DoZoom twice with a Zoom of 1.25 will lead to an image that is zoomed at 
          /// 1.25 after first time and ( 1.25 * 1.25 for second time, which is a 1.56
          /// </summary>
          /// <param name="relativeZoom"> new zoom level; this is a RELATIVE value not absolute.</param>
          /// <param name="elementPoint"></param>
          void DoZoom(double relativeZoom , Point elementPoint)
          {
              if (  _currentTotalZoom * relativeZoom < MaxZoomOut ||
                    _currentTotalZoom * relativeZoom > MaxZoomIn) 
                  return; 
     
              Point p = DeepZoom.ElementToLogicalPoint(elementPoint);
              DeepZoom.ZoomAboutLogicalPoint(relativeZoom, p.X, p.Y);
              this.Zoom = relativeZoom;
              _currentTotalZoom *= relativeZoom; 
                    } 
  10. Now we are ready to handle the MouseWheelHelper.Moved event.   We will do it in three parts: 
    1. We will subscribe to MouseMove event in the MultiScaleImage, so we can keep track of where the mouse is; we need this because MouseWheelHelper.Moved does not give us a MousePosition, and there is no way to query MousePosition in Silverlight2 outside of a Mouse EventHandler.

      // inside the Loaded event for the user control
      //{
      DeepZoom.MouseMove += newMouseEventHandler(DeepZoom_MouseMove);
      _lastMousePosition = new Point ( DeepZoom.ActualWidth /2 , DeepZoom.ActualHeight /2);
      //}
       
      protected Point _lastMousePosition;

      void DeepZoom_MouseMove(objectsender, MouseEventArgs e)
      {
               _lastMousePosition = e.GetPosition(DeepZoom);
      }
    2. Now we instantiate a MouseWheelHelper and subscribe to Moved event

      // inside the Loaded Event for UserControl
      MouseWheelHelper mousewheelhelper = new MouseWheelHelper(this);
      mousewheelhelper.Moved += newEventHandler<MouseWheelEventArgs>(OnMouseWheelMoved);
               
    3. We add the OnMouseWheelMoved function to the UserControl class..
      void OnMouseWheelMoved(object sender, MouseWheelEventArgs e)
      {   
         // e.Delta > 0 == wheel moved away, zoom in 
         if (e.Delta > 0)
         {
            DoZoom( DefaultZoomFactor, _lastMousePosition);
         }
         else
         {
            // Zoom out 
            DoZoom( 1/ DefaultZoomFactor, _lastMousePosition);
         } 
      }

    4. NOTE: If you compare the source above with the code in the sample source, they are slightly different.
      In the sample source there is two approaches to handling Zoom, and there is a boolean flag called _useRelatives that controls this. if you set _useRelatives to true, it will zoom based in relation to a last zoom; I think this makes it more complicated but for some reason most samples I have seen of DeepZoom use this calculation.  I think the behavior is the same than the approach I took, but the math is simpler with the approach in the steps above.   I did add both in case I find later that there was a scenario addressed by the _useRelatives approach.

  11. At this point we should be able to run the application and get Zoom to work (in and out) around the mouse location.  Compile the app and run it to make sure we are making progress.
  12. To Pan, we need to detect the MouseLeftButtonDown and MouseLeftButtonUp,  the assumption is we will pan when the mouse is down, and pan in the direction of the Mouse movement and then stop panning when the mouse is up.
    1. Let’s add a handler for MouseLeftButtonDown, we add the listener in the UserControl’s Loaded event.  This handler will set a variable called _isDragging  to flag that the mouse is down; we will use this flag on the MouseMove handler.

      // inside the Loaded function, we add code behind our MouseWheelHelper code added earlier..
      DeepZoom.MouseLeftButtonDown += newMouseButtonEventHandler(DeepZoom_MouseLeftButtonDown);
    2. The handler looks like this:
      protected bool _isDragging = false;
      protected Point _lastDragViewportOrigin; 
      void DeepZoom_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
      {
                  this._lastDragViewportOrigin = DeepZoom.ViewportOrigin;
                  this._lastMousePosition = e.GetPosition(DeepZoom);
                  this._isDragging = true; 
                 
      }
    3. Now we subscribe to MouseLeftButtonUp, inside Loaded function  and we add the handler function for it.

      //  The one liner below goes in the Page_Loaded event handler
      DeepZoom.MouseLeftButtonUp += newMouseButtonEventHandler(DeepZoom_MouseLeftButtonUp);


      void DeepZoom_MouseLeftButtonUp(objectsender, MouseButtonEventArgs e)
      {
          this._isDragging = false;
      }
    4. Now we tweak the code inside MouseMove  to Change the ViewportOrigin to perform the Pan operation.
      void DeepZoom_MouseMove(object sender, MouseEventArgs e)
      {
        if (_isDragging)
        {
         Point newViewport = _lastDragViewportOrigin;
         Point currentMousePosition = e.GetPosition(DeepZoom);
         newViewport.X += (_lastMousePosition.X - currentMousePosition.X) 
      / this.DeepZoom.ActualWidth * this.DeepZoom.ViewportWidth; newViewport.Y += (_lastMousePosition.Y - currentMousePosition.Y)
      / this.DeepZoom.ActualWidth * this.DeepZoom.ViewportWidth; this.DeepZoom.ViewportOrigin = newViewport; _lastDragViewportOrigin = newViewport; } // NOTE: it is important this be after the isDragging check …
      // since this updates last position, which is used to compare for dragging. _lastMousePosition = e.GetPosition(DeepZoom); }
    5. We should also detect the MouseLeave event, and if we are in the middle of a Pan, we need to reset the _isDragging flag.

      // inside the UserControl Loaded handler DeepZoom.MouseLeave += newMouseEventHandler(DeepZoom_MouseLeave);
      void
      DeepZoom_MouseLeave(objectsender, MouseEventArgs e)
      {
      this._isDragging = false;
                this.DeepZoom.Cursor = Cursors.Arrow; 
      }


  13.   That is it for the basics and the ‘hard stuff’ … with not too many lines of code, we have Zoom & Pan in our host.  Along the way we added a few properties we can reuse to create UI around our DeepZoom image.

Part 4.1 Adding more UI to navigate in a DeepZoom Control.

In the last sections I to