Search code examples
c#xamluwpscrollviewer

Scaling image from center orgin using ScrollViewer is not working in UWP


I am using scroll viewer to zoom the image which is set as background for Grid.When, I am setting the zoom level value on button click from code behind with scrollViewer.ChangeView(horizontalOffset,verticalOffset,zoomFactor) image is getting zoomed from Top left corner ,not from the center position.When increasing scale level as 0.1 every time in the button click.

Find the sample here:Sample

XAML:

        <UserControl  Grid.Row="2">
        <ScrollViewer x:Name="scrollViewer" HorizontalScrollBarVisibility="Visible" VerticalScrollBarVisibility="Visible"  VerticalScrollMode="Enabled" HorizontalScrollMode="Enabled"  ZoomMode="Enabled"  >

            <Grid x:Name="ImageGrid" VerticalAlignment="Center" HorizontalAlignment="Center" Width="1260" Height="350" >
                <Image x:Name="MyImage" Width="1260" Height="210" Source="Assets\EditedImage.jpg" RenderTransformOrigin="0.5, 0.5" />
            </Grid>

        </ScrollViewer>
    </UserControl>

C#:

    float count = 1;
    private void Btn_Click(object sender, RoutedEventArgs e)
    {
        count += 0.1f;
        var width = this.scrollViewer.ExtentWidth / 2;
        var height = this.scrollViewer.ExtentHeight  / 2;

        scrollViewer.ChangeView(width, height, count);
    }

Solution

  • You must calculate the offset relative to the scrollable area.

    XAML

    <StackPanel>
      <Button Click="ZoomIn_OnButtonClick" Content="Increase Zoom"/>
      <ScrollViewer x:Name="ScrollViewer" 
                    HorizontalScrollBarVisibility="Visible" 
                    HorizontalContentAlignment="Center" 
                    VerticalContentAlignment="Center">
        <Image Stretch="Uniform" Source="Assets\golden_eaglew.jpg" />
      </ScrollViewer>
    </StackPanel>
    

    Solution using ScrollViewer.ChangeView

    Since the scroll viewer's extent changes as a result of applying the zoom and the zoom is applied after the scroll offsets are applied, you have to defer the scrolling to the center by making use of the ScrollViewer.ViewChanged event. This is because the scrollable area is influenced by the extent's size (e.g. ScrollableWidth = ExtentWidth - ViewportWidth).

    public MainPage()
    {
      this.InitializeComponent(); 
    
      this.ScrollViewer.ViewChanged += ScrollToCenterOnScrollViewerChanged;
    }
    
    private void ScrollToCenterOnScrollViewerChanged(object sender, ScrollViewerViewChangedEventArgs e)
    {
      if (e.IsIntermediate)
      {
        return;
      }
    
      var scrollViewer = sender as ScrollViewer;
      scrollViewer.ChangeView(
        scrollViewer.ScrollableWidth / 2, 
        scrollViewer.ScrollableHeight / 2, 
        null);
    }
    
    private void ZoomIn_OnButtonClick(object sender, RoutedEventArgs e) 
      => this.ScrollViewer.ChangeView(null, null, this.ScrollViewer.ZoomFactor + 0.1f);
    

    Alternative Solution 1: use ScrollViewer.ZoomToFactor

    Note that this API is marked as deprecated and is not guaranteed to be available in future framework versions.

    private void ZoomIn_OnButtonClick(object sender, RoutedEventArgs e)
    {   
      this.ScrollViewer.ZoomToFactor(this.ScrollViewer.ZoomFactor + 0.1f);
      this.ScrollViewer.ScrollToHorizontalOffset(this.ScrollViewer.ScrollableWidth / 2);
      this.ScrollViewer.ScrollToVerticalOffset(this.ScrollViewer.ScrollableHeight / 2);
    }
    

    Alternative Solution 2: implement a custom ZoomToFactor method

    private void ZoomToFactor(double zoomFactor, ScrollViewer scrollViewer)
    {
      if (!(scrollViewer?.Content is FrameworkElement zoomTarget))
      {
        return;
      }
    
      // Apply the zoom to the scroll content
      zoomTarget.Width = zoomTarget.ActualWidth * zoomFactor;
      zoomTarget.Height = zoomTarget.ActualHeight * zoomFactor;
    
      // Scroll the zoomed scroll content to center
      var scaledScrollableWidth = scrollViewer.ExtentWidth * zoomFactor - scrollViewer.ViewportWidth);
      var scaledScrollableHeight = scrollViewer.ExtentHeight * zoomFactor - scrollViewer.ViewportHeight;
      double horizontalScrollCenterOffset = scaledScrollableWidth / 2;
      double verticalScrollCenterOffset = scaledScrollableHeight / 2;
    
      scrollViewer.ScrollToHorizontalOffset(horizontalScrollCenterOffset);
      scrollViewer.ScrollToVerticalOffset(verticalScrollCenterOffset);
    }
    
    // Usage example
    private void ZoomIn_OnButtonClick(object sender, RoutedEventArgs e)
    {   
      ZoomToFactor(1.1, this.ScrollViewer);
    }