none
LayoutTransform and ScaleTransform; CenterX/CenterY not working

    Question

  • I have an image I am scaling with a ViewBox, setting LayoutTransform to a ScaleTransform. My XAML looks like this:

                <ScrollViewer   

                        x:Name="m_SiteImageScroller"   

                        x:FieldModifier="private"   

                        Grid.Column="0"   

                        HorizontalScrollBarVisibility="Auto"   

                        VerticalScrollBarVisibility="Auto">  

     

                <Viewbox x:Name="m_SiteImageViewbox" x:FieldModifier="private">  

                    <Viewbox.LayoutTransform> 

                        <ScaleTransform x:Name="m_Scale" x:FieldModifier="private" /> 

                    </Viewbox.LayoutTransform> 

                    <InkCanvas x:Name="m_DrawingLayer" EditingMode="None">  

                        <Image x:Name="m_Image" x:FieldModifier="private" Source="Raw.jpg">  

                              

                        </Image> 

                    </InkCanvas> 

                </Viewbox> 

            </ScrollViewer> 

     

    I then have some C# code which reads a Slider and assigns to ScaleTransform.ScaleX/ScaleY:

            private void m_ScaleAdjuster_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)  
            {  
     
                m_Scale.ScaleX = m_ScaleAdjuster.Value;  
                m_Scale.ScaleY = m_ScaleAdjuster.Value;  
       
            }  
     

    Problem is, I can assign any values to the ScaleTransform's CenterX and CenterY properties (to scale from the middle rather than (0,0) ) and they have no effect whatsoever. NONE. I tried using the transform as a RenderTransform instead of a LayoutTransform and setting RenderTransformOrigin to (.5,.5). This works. The image scales from the center (but of course then I have the problems of doing the scaling behind the layout system's back). But I can't for the life of me get the LayoutTransform to work with the CenterX / CenterY properties.

    Any ideas?

    TIA,
    John
    Wednesday, January 14, 2009 7:13 PM

Answers

  • Hi,

    in fact your problem is not with the viewbox, it is with the ScrollViewer. What happens is that the size of the ViewBox changes, and that the ScrollViewer displays a bigger background, but the value of the scrollbars doesn't change. What you need is some logic to remember the relative setting of the scrollbars every time the user changes the scrollbars, and to change the value of the scrollbars to the same relative position if the size of the ViewBox changes.

    This does the job of keeping the same point in the middle of ScrollViewer's viewport when you are changing the scale factor:

     

    Private _RelScrollX As Double

    Private _RelScrollY As Double

        Private Sub Scroller_ScrollChanged(ByVal sender As System.ObjectByVal e As System.Windows.Controls.ScrollChangedEventArgs)  
            If e.ExtentHeightChange <> 0 Or e.ExtentWidthChange <> 0 Then 
                Dim scroll As ScrollViewer = DirectCast(sender, ScrollViewer)  
                scroll.ScrollToHorizontalOffset(Math.Max(_RelScrollX * e.ExtentWidth - 0.5 * e.ViewportWidth, 0))  
                scroll.ScrollToVerticalOffset(Math.Max(_RelScrollY * e.ExtentHeight - 0.5 * e.ViewportHeight, 0))  
            Else 
                If e.ExtentWidth > 0 Then 
                    _RelScrollX = (e.HorizontalOffset + 0.5 * e.ViewportWidth) / e.ExtentWidth  
                End If 
                If e.ExtentHeight > 0 Then 
                    _RelScrollY = (e.VerticalOffset + 0.5 * e.ViewportHeight) / e.ExtentHeight  
                End If 
     
            End If 
     
        End Sub 
     

    and you need to add this to your ScrollViewer element:

    ScrollChanged="Scroller_ScrollChanged"


     

    • Edited by hbarck Saturday, January 17, 2009 5:11 PM added code
    • Marked as answer by John_C Saturday, January 17, 2009 11:16 PM
    Saturday, January 17, 2009 8:23 AM

All replies

  • Those coordinates are supposed to be in pixels. You're specifying 0.5 presumably because you want the center; you actually need to specify whatever is half the width of the object.
    Controls for WPF, Windows Forms and Silverlight at http://www.divelements.co.uk
    Wednesday, January 14, 2009 7:31 PM
  • ScaleTransform.CenterX/CenterY are in pixels. RenderTransformOrigin.CenterX / CenterY is a proportion.

    I've assigned all kinds of things to ScaleTransform.CenterX/Y: I've tried (Width * .5, Height * .5); (.5, .5); (Int32.MaxValue, Int32.MaxValue) (yes, I actually did this!). No effect.

    Wednesday, January 14, 2009 8:15 PM
  • *desperation bump*
    Friday, January 16, 2009 2:04 PM
  • This won't work because you're using a layout transform.  Layout transforms only affect size, not position.  If you set a TranslateTransform as a LayoutTransform, it is ignored, because the layout pass itself determines where to position your content.  I believe the same thing applies here - CenterX and CenterY have no meaning during layout since the position has yet to be determined, so only ScaleX and ScaleY, which affect the size of your content, are respected.  If you want to move your content relative to its position (as determined by layout) you need to set a render transform on it instead. 

    To accomplish what I believe you want, where layout respects the scale size increase and you scale from a center point, you could derive your own Viewbox, override its MeasureOverride and scale its size up there for the layout bounds.  Then set your ScaleTransform as the RenderTransform.
    Friday, January 16, 2009 8:00 PM
  • Thanks for the help Brendan. It's surprising something like this is so frustrating in WPF. Anyway, ....

    I'm still not totally clear how I would calculate the size of my Viewbox subclass.

     

    Saturday, January 17, 2009 3:32 AM
  • Hi,

    in fact your problem is not with the viewbox, it is with the ScrollViewer. What happens is that the size of the ViewBox changes, and that the ScrollViewer displays a bigger background, but the value of the scrollbars doesn't change. What you need is some logic to remember the relative setting of the scrollbars every time the user changes the scrollbars, and to change the value of the scrollbars to the same relative position if the size of the ViewBox changes.

    This does the job of keeping the same point in the middle of ScrollViewer's viewport when you are changing the scale factor:

     

    Private _RelScrollX As Double

    Private _RelScrollY As Double

        Private Sub Scroller_ScrollChanged(ByVal sender As System.ObjectByVal e As System.Windows.Controls.ScrollChangedEventArgs)  
            If e.ExtentHeightChange <> 0 Or e.ExtentWidthChange <> 0 Then 
                Dim scroll As ScrollViewer = DirectCast(sender, ScrollViewer)  
                scroll.ScrollToHorizontalOffset(Math.Max(_RelScrollX * e.ExtentWidth - 0.5 * e.ViewportWidth, 0))  
                scroll.ScrollToVerticalOffset(Math.Max(_RelScrollY * e.ExtentHeight - 0.5 * e.ViewportHeight, 0))  
            Else 
                If e.ExtentWidth > 0 Then 
                    _RelScrollX = (e.HorizontalOffset + 0.5 * e.ViewportWidth) / e.ExtentWidth  
                End If 
                If e.ExtentHeight > 0 Then 
                    _RelScrollY = (e.VerticalOffset + 0.5 * e.ViewportHeight) / e.ExtentHeight  
                End If 
     
            End If 
     
        End Sub 
     

    and you need to add this to your ScrollViewer element:

    ScrollChanged="Scroller_ScrollChanged"


     

    • Edited by hbarck Saturday, January 17, 2009 5:11 PM added code
    • Marked as answer by John_C Saturday, January 17, 2009 11:16 PM
    Saturday, January 17, 2009 8:23 AM
  • Works like a charm (but then you already knew that 8-]  ).

     Thanks hbarck! Owe you one...

    John

    Saturday, January 17, 2009 11:16 PM
  • Hello, I have a little bit different problem with zooming. I have Canvas with childrens. This Canvas is placed in ScrollViewer. Functionality what I want is: zoom Canvas with its children with regards to actual position of mouse cursor in Canvas and when futhermost children of Canvas is not visible then show Scrollviewer scrollbars.

    I have working on it with this result:
    If I use LayoutTransform to scale Canvas I cant scale with regards to actual mouse position. Canvas size is same as Scrollviewer size.
    If I use RenderTransform to scale Canvas I can scale with regards to actual mouse position, but the Canvas size is bigger or smaller than Scrollviewer size.

    Please can help me?
    Thanks a lot. Petr.
    Thursday, November 12, 2009 2:34 PM
  • Hi,

    you will have to use the LayoutTransform, and you will have to change the scrollbar values in order to keep the same point under the mouse cursor while you zoom. In the example above, I fix the middle of the viewport (0.5 * e.ViewportWidth) , so you should replace this with the mouse position as counted from the left upper corner of the ViewPort.

    However, there might be some additional issue of keeping track of the position of the mouse inside the ViewPort.

    http://wpfglue.wordpress.com
    Thursday, November 12, 2009 5:11 PM
  • Hi, thanks for advice. I think it is a good way but I dont understand this behaviour. I use this implementation of scrollchange event.

    private void ZoomSCV_ScrollChanged(object sender, System.Windows.Controls.ScrollChangedEventArgs e)
            {
                Point actualCursorPosition = System.Windows.Input.Mouse.GetPosition(ZoomSCV);                       
    
                relScrollX = (e.HorizontalOffset + actualCursorPosition.X) / e.ExtentWidth;
                relScrollY = (e.VerticalOffset + actualCursorPosition.Y) / e.ExtentHeight;
    
                if (e.ExtentHeightChange != 0 || e.ExtentWidthChange != 0)
                {
                    ScrollViewer scv = sender as ScrollViewer;
                    scv.ScrollToHorizontalOffset(Math.Max(relScrollX * (e.ExtentWidth - e.ViewportWidth), 0));
                    scv.ScrollToVerticalOffset(Math.Max(relScrollY * (e.ExtentHeight - e.ViewportHeight), 0));
                }            
            } 


    Where ZoomSCV is scrollviewer which I want to zoom. This scrollviewer has as a children canvas object. My problem is if I zoom by mouse wheel at point dedicated by mouse cursor, cursor not stayed at the same point during zooming (of course I dont move mouse). Simply if I want to zoom to specified point in canvas the positon of this point is changing during zooming. Maybe I have some mistakes in implementation?
    Wednesday, November 18, 2009 9:01 AM
  • Hi,

    I think the formula for the offset is wrong. Let's calculate again:

    offset can go from 0 to extent - viewport. The absolute mouse position is offset + mouse. We want the same relative point to stay under the mouse. This point has the relative coordinate (offset + mouse) / extent, and offset + mouse will always be < extent as long as extent > viewport, because always mouse < viewport.

    so, if extent changes and mouse remains constant, the formula should be:

    relative = (offset + mouse) / extent => offset = extent * relative - mouse

    This can become negative if extent < viewport, so one should either stop zooming then, or wrap it into the Max function as I did, knowing that as soon as extent gets < viewport the mouse will not remain over the same point of the background.
    http://wpfglue.wordpress.com
    Wednesday, November 18, 2009 7:05 PM
  • Hi, if I use this implementation with changed offset values, zoomin dont work at all.

    private void ZoomSCV_ScrollChanged(object sender, System.Windows.Controls.ScrollChangedEventArgs e)
            {
                Point actualCursorPosition = System.Windows.Input.Mouse.GetPosition(ZoomSCV);                       
    
                relScrollX = (e.HorizontalOffset + actualCursorPosition.X) / e.ExtentWidth;
                relScrollY = (e.VerticalOffset + actualCursorPosition.Y) / e.ExtentHeight;
    
                if (e.ExtentHeightChange != 0 || e.ExtentWidthChange != 0)
                {
                    ScrollViewer scv = sender as ScrollViewer;
                    scv.ScrollToHorizontalOffset(Math.Max(e.ExtentWidth * relScrollX - actualCursorPosition.X, 0));
                    scv.ScrollToVerticalOffset(Math.Max(e.ExtentHeight * relScrollY - actualCursorPosition.Y, 0));
                }
            }
    I think, maybe is error in mouse position. The value of actualMousePositon is changing at every time, because I catch mouse position of whole scrollview named ZoomSCV and not for his viewport. In earlier discussion was written: position as counted from the left upper corner of the ViewPort. Maybe this is wrong, but I dont know how get mouse position of viewport?
    Thursday, November 19, 2009 1:32 PM
  • Hi,

    I figured it out, the problem was that we did not consider the old and new extent correctly...

    This is VB, but you can surely translate it.

            If e.ExtentHeightChange <> 0 Or e.ExtentWidthChange <> 0 Then
                Dim mousePos As Point = Mouse.GetPosition(Viewer)
                Debug.Print("MousePosBefore: {0}, {1}", mousePos.X, mousePos.Y)
                Dim offsetx As Double = e.HorizontalOffset + mousePos.X
                Dim offsety As Double = e.VerticalOffset + mousePos.Y
                Dim oldExtentWidth = e.ExtentWidth - e.ExtentWidthChange
                Dim oldExtentHeight = e.ExtentHeight - e.ExtentHeightChange
                Debug.Print("Offset: {0},{1}", offsetx, offsety)
                Dim relx As Double = offsetx / oldExtentWidth
                Dim rely As Double = offsety / oldExtentHeight
                Debug.Print("rel. Offset: {0},{1}", relx, rely)
                Debug.Print("Extent: {0},{1}", oldExtentWidth, oldExtentHeight)
                Debug.Print("New Extent: {0},{1}", e.ExtentWidth, e.ExtentHeight)
                offsetx = Math.Max(relx * e.ExtentWidth - mousePos.X, 0)
                offsety = Math.Max(rely * e.ExtentHeight - mousePos.Y, 0)
                Debug.Print("new offset: {0},{1}", offsetx, offsety)
                Viewer.ScrollToHorizontalOffset(offsetx)
                Viewer.ScrollToVerticalOffset(offsety)
    
            End If
    

    http://wpfglue.wordpress.com
    • Proposed as answer by Petr Šebesta Friday, November 20, 2009 7:09 AM
    Thursday, November 19, 2009 3:34 PM
  • Hi, yes this is exactly it. Thank you very much for help.
    Friday, November 20, 2009 7:11 AM
  • Hi,

    I tried your code, for me ScrollViewer change event is not fired when I zoom in on Canvas(rather than Viewer). First time while loading the screen, scrollviewer change event is fired but not when I zoom in.

    Here is my code :

    TransformGroup group = new TransformGroup();
    ScaleTransform scale = new LayoutTransform(1, 1, 0, 0);
    private Point mousePosition;

    public double ZoomFactor
    {
     get { return zoomFactor; }
     set { zoomFactor = value; }
    }

    private void uxRootCanvas_MouseRightButtonUp(object sender, MouseButtonEventArgs e)
    {
     if (mousePosition == null)
      mousePosition =new Point();
     mousePosition = e.GetPosition(uxRootCanvas);
     ShowActionBar(e);
    }

    private void uxScrollViewer_ScrollChanged(object sender, ScrollChangedEventArgs e)
    {
     if (e.ExtentHeightChange != 0 | e.ExtentWidthChange != 0)
     { 
      mousePosition = Mouse.GetPosition(uxRootCanvas);
                    Double offsetx = e.HorizontalOffset + mousePosition.X;
                    Double offsety = e.VerticalOffset + mousePosition.Y;
                    Double oldExtentWidth = e.ExtentWidth - e.ExtentWidthChange;

                    Double oldExtentHeight = e.ExtentHeight - e.ExtentHeightChange;

                    Double relx = offsetx / oldExtentWidth;
                    Double rely = offsety / oldExtentHeight;

                    offsetx = Math.Max(relx * e.ExtentWidth - mousePosition.X, 0);
                    offsety = Math.Max(rely * e.ExtentHeight - mousePosition.Y, 0);
                    uxScrollViewer.ScrollToHorizontalOffset(offsetx);
                    uxScrollViewer.ScrollToVerticalOffset(offsety);
     }
     else
     {
      if (e.ExtentWidth > 0)
      {
       _RelScrollX = (e.HorizontalOffset + 0.5 * mousePosition.X) / e.ExtentWidth;
      
      }
      if (e.ExtentHeight > 0)
      {
       _RelScrollY = (e.VerticalOffset + 0.5 * mousePosition.Y) / e.ExtentHeight;
      }
     }

     
    }

    void ZoomInMenuItem_Click(object sender, RoutedEventArgs e)
    {

    ZoomFactor += 0.1;

    group.Inverse.Transform(mousePosition);

    if (ZoomFactor <= 2)
    {

    scale.ScaleX = ZoomFactor;

    scale.ScaleY = ZoomFactor;

    scale.CenterX = mousePosition.X;

    scale.CenterY = mousePosition.Y;

    }

    }

     

     

     

    Am I missing something?


    Deeps
    Tuesday, December 22, 2009 6:12 AM
  • Thank you hbarck, your a life saver :)
    Wednesday, December 15, 2010 5:45 AM
  • hbarck,

    I just stumbled across this thread, which provides an answer for me that I've been looking for for hours.  However, your code works fantastic for me until I try to zoom out past 100%.  In other words, I have a slider that goes from 5% to 500%.  From 100% to 500%, everything works great.  The instant I get below 100%, it starts zooming from the upper-left corner rather than zooming out from the center point.  I have an image that's about 14,400 x 10,800 pixels, and a scrollviewer that's about 550 x 350 pixels.

    Any ideas?

    -Eric

    Wednesday, March 23, 2011 3:22 PM
  • Hi,

    I'm sure I've posted this before, but I can't find it now... here is a ZoomingScrollViewer class I ended up with, after almost losing my mind over it :)

    Imports System.Windows.Controls.Primitives
    
    
    Public Class ZoomingScrollViewer
      Inherits System.Windows.Controls.ScrollViewer
    
      Shared Sub New()
        DefaultStyleKeyProperty.OverrideMetadata(GetType(ZoomingScrollViewer), New FrameworkPropertyMetadata(GetType(ZoomingScrollViewer)))
      End Sub
    
      Public Sub New()
        MyBase.New()
        AddHandler Me.SizeChanged, AddressOf ZoomingScroller_SizeChanged
      End Sub
    
      Public Shared ReadOnly ZoomFactorProperty As DependencyProperty = DependencyProperty.Register("ZoomFactor", GetType(Double), GetType(ZoomingScrollViewer), New FrameworkPropertyMetadata(1.0, AddressOf OnZoomFactorChanged, AddressOf CoerceZoomFactor))
      Public Property ZoomFactor() As Double
        Get
          Return GetValue(ZoomFactorProperty)
        End Get
        Set(ByVal value As Double)
          SetValue(ZoomFactorProperty, value)
        End Set
      End Property
    
      Private Shared Sub OnZoomFactorChanged(ByVal d As ZoomingScrollViewer, ByVal e As DependencyPropertyChangedEventArgs)
        Dim p1 As Point = New Point(d.HorizontalOffset, d.VerticalOffset) + d.ZoomCenter - New Point(d.Adjustment.Left, d.Adjustment.Top)
        Dim p2 As Point = d.ZoomTransform.Inverse.Transform(p1)
        d.ZoomTransform = New ScaleTransform(e.NewValue, e.NewValue)
        p2 = d.ZoomTransform.Transform(p2)
        Dim delta As Point = p2 - p1
        If delta.X <> 0 Then
          d.ScrollToHorizontalOffset(d.HorizontalOffset + delta.X)
        End If
        If delta.Y <> 0 Then
          d.ScrollToVerticalOffset(d.VerticalOffset + delta.Y)
        End If
      End Sub
    
      Private Shared Function CoerceZoomFactor(ByVal d As ZoomingScrollViewer, ByVal baseValue As Double) As Double
        Dim limit As Double = 0.001
        If d.ContentHeight > 0 Then
          limit = 2 * SystemParameters.HorizontalScrollBarHeight / d.ContentHeight
        End If
        If d.ContentWidth > 0 Then
          limit = Math.Max(limit, 2 * SystemParameters.VerticalScrollBarWidth / d.ContentWidth)
        End If
        baseValue = Math.Max(baseValue, limit)
        Return baseValue
      End Function
    
      Public Shared ReadOnly AdjustmentProperty As DependencyProperty = DependencyProperty.Register("Adjustment", GetType(Thickness), GetType(ZoomingScrollViewer), New PropertyMetadata(New Thickness(0)))
      Public Property Adjustment() As Thickness
        Get
          Return GetValue(AdjustmentProperty)
        End Get
        Set(ByVal value As Thickness)
          SetValue(AdjustmentProperty, value)
        End Set
      End Property
    
      Public Shared ReadOnly ContentWidthProperty As DependencyProperty = DependencyProperty.Register("ContentWidth", GetType(Double), GetType(ZoomingScrollViewer))
      Public Property ContentWidth() As Double
        Get
          Return GetValue(ContentWidthProperty)
        End Get
        Set(ByVal value As Double)
          SetValue(ContentWidthProperty, value)
        End Set
      End Property
    
      Public Shared ReadOnly ContentHeightProperty As DependencyProperty = DependencyProperty.Register("ContentHeight", GetType(Double), GetType(ZoomingScrollViewer))
      Public Property ContentHeight() As Double
        Get
          Return GetValue(ContentHeightProperty)
        End Get
        Set(ByVal value As Double)
          SetValue(ContentHeightProperty, value)
        End Set
      End Property
    
      Private Shared ReadOnly ZoomTransformPropertyKey As DependencyPropertyKey = DependencyProperty.RegisterReadOnly("ZoomTransform", GetType(ScaleTransform), GetType(ZoomingScrollViewer), New PropertyMetadata(New ScaleTransform(1, 1)))
      Public Shared ReadOnly ZoomTransformProperty As DependencyProperty = ZoomTransformPropertyKey.DependencyProperty
      Public Property ZoomTransform() As ScaleTransform
        Get
          Return GetValue(ZoomTransformProperty)
        End Get
        Private Set(ByVal value As ScaleTransform)
          SetValue(ZoomTransformPropertyKey, value)
        End Set
      End Property
    
    
      Private Shared ReadOnly ContentRectPropertyKey As DependencyPropertyKey = DependencyProperty.RegisterReadOnly("ContentRect", GetType(Rect), GetType(ZoomingScrollViewer), New PropertyMetadata(Rect.Empty))
      Public Shared ReadOnly ContentRectProperty As DependencyProperty = ContentRectPropertyKey.DependencyProperty
      Public Property ContentRect() As Rect
        Get
          Return GetValue(ContentRectProperty)
        End Get
        Private Set(ByVal value As Rect)
          SetValue(ContentRectPropertyKey, value)
        End Set
      End Property
    
      Public Shared ReadOnly ZoomCenterProperty As DependencyProperty = DependencyProperty.Register("ZoomCenter", GetType(Point), GetType(ZoomingScrollViewer), New FrameworkPropertyMetadata(New Point(0, 0), Nothing, AddressOf CoerceZoomCenter))
      Public Property ZoomCenter() As Point
        Get
          Return GetValue(ZoomCenterProperty)
        End Get
        Set(ByVal value As Point)
          SetValue(ZoomCenterProperty, value)
        End Set
      End Property
    
      Private Shared Function CoerceZoomCenter(ByVal d As ZoomingScrollViewer, ByVal baseValue As Point) As Point
        Dim viewport As Rect = New Rect(0, 0, d.ViewportWidth, d.ViewportHeight)
        Dim content As Rect = GetContentRect(d)
        baseValue = CoerceToRect(baseValue, content)
        baseValue = CoerceToRect(baseValue, viewport)
        Return baseValue
      End Function
    
      Private Shared Function GetContentRect(ByVal d As ZoomingScrollViewer) As Rect
        Dim contentLocation As Point = New Point(d.Adjustment.Left - d.HorizontalOffset, d.Adjustment.Top - d.VerticalOffset)
        Dim contentSize As Size = d.ZoomTransform.Transform(New Point(d.ContentWidth, d.ContentHeight))
        Return New Rect(contentLocation, contentSize)
      End Function
    
      Private Shared Function CoerceToRect(ByVal value As Point, ByVal rect As Rect) As Point
        value = New Point(Math.Max(value.X, rect.X), Math.Max(value.Y, rect.Y))
        value = New Point(Math.Min(value.X, rect.Right), Math.Min(value.Y, rect.Bottom))
        Return value
      End Function
    
      Private Sub ZoomingScroller_SizeChanged(ByVal sender As Object, ByVal e As System.Windows.SizeChangedEventArgs)
        Dim padding As Size = New Size(Math.Max(0, e.NewSize.Width - SystemParameters.VerticalScrollBarWidth), Math.Max(0, e.NewSize.Height - SystemParameters.HorizontalScrollBarHeight))
        Dim adjustment As Thickness = New Thickness(padding.Width, padding.Height, padding.Width, padding.Height)
        Dim offset As Point = New Point(HorizontalOffset - Me.Adjustment.Left + adjustment.Left, VerticalOffset - Me.Adjustment.Top + adjustment.Top)
        Me.Adjustment = adjustment
        ScrollToHorizontalOffset(offset.X)
        ScrollToVerticalOffset(offset.Y)
      End Sub
    
      Private Sub SetupContentBindings()
        Dim c As FrameworkElement = TryCast(Me.Template.FindName("PART_ContentHost", Me), FrameworkElement)
        If c IsNot Nothing Then
          'These bindings should be in the control template
          'c.SetBinding(FrameworkElement.MarginProperty, New Binding("Adjustment") With {.Source = Me})
          'c.SetBinding(FrameworkElement.LayoutTransformProperty, New Binding("ZoomTransform") With {.Source = Me})
          SetBinding(ContentWidthProperty, New Binding("ActualWidth") With {.Source = c})
          SetBinding(ContentHeightProperty, New Binding("ActualHeight") With {.Source = c})
        End If
      End Sub
    
      Protected Overrides Sub OnScrollChanged(ByVal e As System.Windows.Controls.ScrollChangedEventArgs)
        CoerceValue(ZoomCenterProperty)
        ContentRect = GetContentRect(Me)
        MyBase.OnScrollChanged(e)
      End Sub
    
      Public Overrides Sub OnApplyTemplate()
        MyBase.OnApplyTemplate()
        SetupContentBindings()
      End Sub
    End Class
    
    

    This is the custom control's default style, which should go into themes/generic.xaml:

      <Style TargetType="{x:Type local:ZoomingScrollViewer}">
        <Setter Property="Template">
          <Setter.Value>
            <ControlTemplate TargetType="{x:Type local:ZoomingScrollViewer}">
              <Grid>
                <Grid.ColumnDefinitions>
                  <ColumnDefinition/>
                  <ColumnDefinition Width="Auto"/>
                </Grid.ColumnDefinitions>
                <Grid.RowDefinitions>
                  <RowDefinition/>
                  <RowDefinition Height="Auto"/>
                </Grid.RowDefinitions>
    
                <ScrollContentPresenter>
                  <ScrollContentPresenter.Content>
                    <ContentControl x:Name="PART_ContentHost"
                     LayoutTransform="{TemplateBinding ZoomTransform}"
                     Margin="{TemplateBinding Adjustment}"      
                     Content="{TemplateBinding Content}"
                     ContentStringFormat="{TemplateBinding ContentStringFormat}"
                     ContentTemplate="{TemplateBinding ContentTemplate}"/>
                  </ScrollContentPresenter.Content>
                </ScrollContentPresenter>
    
                <ScrollBar Name="PART_VerticalScrollBar"
                  Grid.Column="1"
                  Value="{TemplateBinding VerticalOffset}"
                  Maximum="{TemplateBinding ScrollableHeight}"
                  ViewportSize="{TemplateBinding ViewportHeight}"
                  Visibility="{TemplateBinding ComputedVerticalScrollBarVisibility}"/>
                <ScrollBar Name="PART_HorizontalScrollBar"
                  Orientation="Horizontal"
                  Grid.Row="1"
                  Value="{TemplateBinding HorizontalOffset}"
                  Maximum="{TemplateBinding ScrollableWidth}"
                  ViewportSize="{TemplateBinding ViewportWidth}"
                  Visibility="{TemplateBinding ComputedHorizontalScrollBarVisibility}"/>
    
              </Grid>
            </ControlTemplate>
          </Setter.Value>
        </Setter>
      </Style>
    
    

    You can set ZoomCenter to a point inside your ViewPort (default is the left upper corner, so you won't see anything in the beginning), and the content will zoom in a way that this point remains in the same relative position in the picture, also adjusting the margins when the picture gets smaller than the ViewPort.

    Good luck...


    http://wpfglue.wordpress.com
    Thursday, March 24, 2011 5:26 PM
  • I know this is an old thread but since it's the only one I found regarding this problem I'll post my solution.

    In my example I'm using a ScrollViewer and an Image control with LayoutTransform. I am scrolling using the MouseWheel event so my CenterX / CenterY properties should be the mouse position relative to the Image. The properties have no effect on the ScrollViewer so we'll have to scroll by hand. Here is how I did it and it works.

    mousePositionImage = e.MouseDevice.GetPosition(imageViewer);
    double zoom = e.Delta > 0 ? .2 : -.2;
    
    scaleTransform.ScaleX += zoom;
    scaleTransform.ScaleY += zoom;
    
    //now to scroll
    scrollViewer.ScrollToHorizontalOffset(scrollViewer.HorizontalOffset + (mousePositionImage.X) * zoom);
    scrollViewer.ScrollToVerticalOffset(scrollViewer.VerticalOffset + (mousePositionImage.Y) * zoom);

    If you want to zoom from the center just get the center of your image instead of mousePositionImage (the actual center, not (0,0)).

    Note that when using ScrollViewer the MouseWheel event scrolls down by default and you don't want that when using it for zoom. If you set e.Handled = true it will stop the routing event from reaching the image and you won't be able to catch it. Here is what I did (you can scroll when pressing the shift for X and Ctrl for Y):

    private void scrollViewer_PreviewMouseWheel(object sender, MouseWheelEventArgs e)
    {
                if (Keyboard.IsKeyDown(Key.LeftShift))
                {
                    //automatically scrolls
                }
                else if (Keyboard.IsKeyDown(Key.LeftCtrl))
                {
                    e.Handled = true; //stop scrolling
                    scrollViewer.ScrollToHorizontalOffset(scrollViewer.HorizontalOffset - e.Delta); //scroll horizontally
                }
                else
                {
                    e.Handled = true; //stop scrolling
                    imageViewer_MouseWheel(sender, e); //calling event programmatically
                }
    }
    
    
    private void imageViewer_MouseWheel(object sender, MouseWheelEventArgs e)
    {
                mousePositionImage = e.MouseDevice.GetPosition(imageViewer);
    	    double zoom = e.Delta > 0 ? .2 : -.2;
    
    	    scaleTransform.ScaleX += zoom;
    	    scaleTransform.ScaleY += zoom;
    
    	    //now to scroll
    	    scrollViewer.ScrollToHorizontalOffset(scrollViewer.HorizontalOffset + (mousePositionImage.X) * zoom);
    	    scrollViewer.ScrollToVerticalOffset(scrollViewer.VerticalOffset + (mousePositionImage.Y) * zoom);
    }

    Note that the imageViewer_MouseWheel event is no longer hooked up to the Image control MouseWheel event. You don't need it to be since you stop the routing event and by calling it programmatically when you need to, it does the same thing.

    Hope it helps! If any question please reply!

    Friday, May 17, 2013 9:50 AM
  • Works perfect! Thanks
    Friday, July 26, 2013 2:51 PM