Dot Net Solutions
George V Place,
4 Thames Avenue
Windsor
Berkshire
SL4 1QP
Great Britain
0845 402 1752
GEO: -0.606174, 51.4843
 
 
 
 

Silverlight Behaviors With DeepZoom 

 

Behaviors and triggers are two cool features in Silverlight 3. They allow to declaratively associate and action with an event or property value.

It is implemented as a combination of a Component + an attached property. The framework provides the logic of attaching/detaching behaviors.

The behavior exists as a separate piece of code and hence can be easily reused on similar components on which the behavior is authored.  Due to the integration of behaviors inside Blend, designers can easily apply the behaviors.

Say you have few series of images composed by DeepZoom and you would like to show details of image on mouse hover when a particular image is zoomed into. In this article I will be showing how to implement this as a silverlight behavior on a DeepZoom MultiScaleImage control.

Create a New Project

If you haven’t got DeepZoom Composer, download it here . Start the DeepZoom Composer and create a new project. Select the Import Tab and add the images. After adding, select the Compose tab and drag the images you wanted. Then click on Export tab, and select “Custom” tab. Ensure that the output type is selected as “Silverlight Deep zoom”, name it  as “DeepZoomProject” and export. This creates a Silverlight project with all the DeepZoom composed images.

Implementing Behavior

Open the project in Visual studio and add reference to System.Windows.Interactivity. This assembly contains the framework needed for silverlight behavior. Create a new folder and call it Behaviors. Add a new class and call it as DeepZoomBehavior and inherit it from Behavior<MultiScaleImage> This makes the behavior to be applied for any MultiScaleImage element.  Okay now, we need to define attached properties which are going to provide us the necessary information to write the behavior. The following are the properties we require.

- We need to show image specific information in a TextBlock.
- We need to capture the mouse position so that the current subimage index can be known. (This should be logical position of mouse on
  MultiScaleImage)
- We need to show the image specific information only if the MultiScaleImage zoomlevel (ViewPortWidth) is at certain level or less.

 

Once implemented the properties look like below.

 

  public static double GetViewportWidth(DependencyObject obj)
        {
            return (double)obj.GetValue(ViewportWidthProperty);
        }

        public static void SetViewportWidth(DependencyObject obj, double value)
        {
            obj.SetValue(ViewportWidthProperty, value);
        }

        public static readonly DependencyProperty ViewportWidthProperty =
            DependencyProperty.RegisterAttached("ViewportWidth", typeof(double), typeof(DeepzoomBehavior), null);


        public static TextBlock GetMoreInfoBlock(DependencyObject obj)
        {
            return (TextBlock)obj.GetValue(MoreInfoBlockProperty);
        }

        public static void SetMoreInfoBlock(DependencyObject obj, TextBlock value)
        {
            obj.SetValue(MoreInfoBlockProperty, value);
        }

        public static readonly DependencyProperty MoreInfoBlockProperty =
            DependencyProperty.RegisterAttached("MoreInfoBlock", typeof(TextBlock), typeof(DeepzoomBehavior), null);

        public static Point GetMousePosition(DependencyObject obj)
        {
            return (Point)obj.GetValue(MousePositionProperty);
        }

        public static void SetMousePosition(DependencyObject obj, Point value)
        {
            obj.SetValue(MousePositionProperty, value);
        }

        public static readonly DependencyProperty MousePositionProperty =
            DependencyProperty.RegisterAttached("MousePosition", typeof(Point), typeof(DeepzoomBehavior), null);

 

The Behavior class provides two virtual methods: OnAttached and OnDetaching, which gives an opportunity to wire up any custom events/logic. The AssociatedObject is the object to which the behavior is attached. So let us use these to wire up the MotionFinished event of MultiScaleImage which will be fired when the MultiScaleImage zooming/ panning is finished. Using this plus writing some logic to show the image specific information, our code should look like this.

  protected override void OnAttached()
        {
            base.OnAttached();
            AssociatedObject.MotionFinished += AssociatedObject_MotionFinished;
        }

        protected override void OnDetaching()
        {
            base.OnDetaching();
            AssociatedObject.MotionFinished -= AssociatedObject_MotionFinished;
        }

        private void AssociatedObject_MotionFinished(object sender, RoutedEventArgs e)
        {
            string moreInfo = "No Information at this stage";
            if (AssociatedObject.ViewportWidth <= GetViewportWidth(AssociatedObject))
            {
                int index = GetSubImageIndex(GetMousePosition(AssociatedObject));
               
                if (index == 0)
                {
                    moreInfo = "First image Information";
                }
                else if (index == 1)
                {
                    moreInfo = "Second image information";
                }
                else if (index == 2)
                {
                    moreInfo = "Third image information";
                }
            }
            GetMoreInfoBlock(AssociatedObject).Text = moreInfo;
        }

        public int GetSubImageIndex(Point logicalPos)
        {
            int maxzindex = int.MaxValue;
            int index = -1;
            for (int i = 0; i < AssociatedObject.SubImages.Count; i++)
            {
                if (GetSubImageLogicalRect(i).Contains(logicalPos) &&
                    maxzindex > AssociatedObject.SubImages[i].ZIndex)
                {
                    index = i;
                }
            }
            return index;
        }

        public Rect GetSubImageLogicalRect(int index)
        {
            var subimage = AssociatedObject.SubImages[index];
            Point subimage_viewportorigin = new Point(
                -subimage.ViewportOrigin.X / subimage.ViewportWidth,
                -subimage.ViewportOrigin.Y / subimage.ViewportWidth);
            double subimage_viewportwidth = subimage.ViewportWidth;
            return new Rect(subimage_viewportorigin, new Size(1 / subimage_viewportwidth, 1 / subimage_viewportwidth / subimage.AspectRatio));
        }

 

The MotionFinished event assumes that there are three images added and hardcoded the information needs to be shown.  The other helper functions provide the index of the image which is focused. Okay, now we have the behavior implemented, so how do we add this behavior to the component ?

 Attaching Behavior to Component

Navigate to Page.xaml and in XAML view and add the following namespace references as shown below.

  xmlns:behaviors="clr-namespace:DeepZoomProject.Behaviors"
  xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"

 

Delete everything with in the Grid element to avoid loads of visualstate information DeepZoom has created as we do not need that for this article. With in the MultiScaleImage, attach the properties which the behavior is expecting and then add the behavior as shown below.

 
 <Grid x:Name="LayoutRoot" Width="Auto" Height="Auto" MouseEnter="EnterMovie" MouseLeave="LeaveMovie" MinWidth="640" MinHeight="480">
         <MultiScaleImage x:Name="msi" Source="../GeneratedImages/dzc_output.xml"
                                  behaviors:DeepzoomBehavior.MousePosition="{Binding LogicalPoint}"
                                  behaviors:DeepzoomBehavior.ViewportWidth="0.5"
                                  behaviors:DeepzoomBehavior.MoreInfoBlock="{Binding ElementName=MoreInfoTextBlock}">
            <i:Interaction.Behaviors>
                <behaviors:DeepzoomBehavior />
            </i:Interaction.Behaviors>
        </MultiScaleImage>
        <Canvas>
            <TextBlock Canvas.Top="16" Canvas.Left="250" x:Name="MoreInfoTextBlock" Foreground="Red"/>
        </Canvas>
    </Grid>
 

Let us create a DataContext class which holds the LogicalPoint of the mouseposition to identify the subimage index. The code snippet is hereunder.


 
public class MultiScaleImageContext : INotifyPropertyChanged
    {
        private Point _logicalPoint;
        public Point LogicalPoint
        {
            get
            {
                return _logicalPoint;
            }
            set
            {
                if (_logicalPoint != value)
                {
                    _logicalPoint = value;
                    NotifyPropertyChanged("LogicalPoint");
                }
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;

        public void NotifyPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }
    }
 

Navigate to Page.xaml.cs and hook the DataContext just after InitializeComponent in the page constructor. Also on the mouse events set the LogicalPoint to our DataContext. The code snippet is here under.

public Page()
        {
            InitializeComponent();
            this.DataContext = new MultiScaleImageContext();
            //
            // Firing an event when the MultiScaleImage is Loaded
            //
            this.msi.Loaded += new RoutedEventHandler(msi_Loaded);

            //
            // Firing an event when all of the images have been Loaded
            //
            this.msi.ImageOpenSucceeded += new RoutedEventHandler(msi_ImageOpenSucceeded);

            //
            // Handling all of the mouse and keyboard functionality
            //
            this.MouseLeftButtonDown += delegate(object sender, MouseButtonEventArgs e)
            {
                lastMouseDownPos = e.GetPosition(msi);
                lastMouseViewPort = msi.ViewportOrigin;

                mouseDown = true;

                msi.CaptureMouse();
            };

            this.MouseLeftButtonUp += delegate(object sender, MouseButtonEventArgs e)
            {
                if (!duringDrag)
                {
                    bool shiftDown = (Keyboard.Modifiers & ModifierKeys.Shift) == ModifierKeys.Shift;
                    double newzoom = zoom;

                    if (shiftDown)
                    {
                        newzoom /= 2;
                    }
                    else
                    {
                        newzoom *= 2;
                    }
                    
                    Zoom(newzoom, msi.ElementToLogicalPoint(this.lastMousePos));
                }
                duringDrag = false;
                mouseDown = false;
                (this.DataContext as MultiScaleImageContext).LogicalPoint = msi.ElementToLogicalPoint(this.lastMousePos);
                msi.ReleaseMouseCapture();
            };

            this.MouseMove += delegate(object sender, MouseEventArgs e)
            {
                lastMousePos = e.GetPosition(msi);
                (this.DataContext as MultiScaleImageContext).LogicalPoint = msi.ElementToLogicalPoint(lastMousePos);
                if (mouseDown && !duringDrag)
                {
                    duringDrag = true;
                    double w = msi.ViewportWidth;
                    Point o = new Point(msi.ViewportOrigin.X, msi.ViewportOrigin.Y);
                    msi.UseSprings = false;
                    msi.ViewportOrigin = new Point(o.X, o.Y);
                    msi.ViewportWidth = w;
                    zoom = 1/w;
                    msi.UseSprings = true;
                }

                if (duringDrag)
                {
                    Point newPoint = lastMouseViewPort;
                    newPoint.X += (lastMouseDownPos.X - lastMousePos.X) / msi.ActualWidth * msi.ViewportWidth;
                    newPoint.Y += (lastMouseDownPos.Y - lastMousePos.Y) / msi.ActualWidth * msi.ViewportWidth;
                    msi.ViewportOrigin = newPoint;
                }
            };

            new MouseWheelHelper(this).Moved += delegate(object sender, MouseWheelEventArgs e)
            {
                e.Handled = true;

                double newzoom = zoom;

                if (e.Delta < 0)
                    newzoom /= 1.3;
                else
                    newzoom *= 1.3;
                (this.DataContext as MultiScaleImageContext).LogicalPoint = msi.ElementToLogicalPoint(this.lastMousePos);
                Zoom(newzoom, msi.ElementToLogicalPoint(this.lastMousePos));
                msi.CaptureMouse();
            };
        }
 

Now the behavior is attached to the MultiScaleImage and all the attached properties are set appropriately and you can run the application by setting DeepZoomProjectTestPage.html as startup. Fully zoom to an image and once the zoom stops the TextBlock shows the image specific information.

 
Published: 15 Mar 2010  03:02
0  Comments  |  Trackback Url  | 0  Links to this post | Bookmark this post with:        

Links to this post

No linkbacks added

Comments

No comments added yet

 
 
 
 

Post comment

Name *:
URL:
Email:
Comments:


CAPTCHA Image Validation