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

Viewed 396

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);
    }
1 Answers

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);
}
Related