LayoutTransform and ScaleTransform; CenterX/CenterY not working
-
Wednesday, January 14, 2009 7:13 PM
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
All Replies
-
Wednesday, January 14, 2009 7:31 PMThose 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 8:15 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.
-
Friday, January 16, 2009 2:04 PM*desperation bump*
-
Friday, January 16, 2009 8:00 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.- Proposed As Answer by Brendan Clark - MSFT Friday, January 16, 2009 9:22 PM
-
Saturday, January 17, 2009 3:32 AM
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 8:23 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 DoublePrivate Sub Scroller_ScrollChanged(ByVal sender As System.Object, ByVal 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"
-
Saturday, January 17, 2009 11:16 PM
Works like a charm (but then you already knew that 8-] ).
Thanks hbarck! Owe you one...
John
-
Thursday, November 12, 2009 2:34 PMHello, 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 5:11 PMHi,
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 -
Wednesday, November 18, 2009 9:01 AM
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 7:05 PMHi,
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 -
Thursday, November 19, 2009 1:32 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 3:34 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
-
Friday, November 20, 2009 7:11 AMHi, yes this is exactly it. Thank you very much for help.
-
Tuesday, December 22, 2009 6:12 AMHi,
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 -
Wednesday, December 15, 2010 5:45 AMThank you hbarck, your a life saver :)
-
Wednesday, March 23, 2011 3:22 PM
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
-
Thursday, March 24, 2011 5:26 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 -
Friday, May 17, 2013 9:50 AM
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!

