Search code examples
c#xamlmicrosoft-metrowindows-store-apps.net-4.5

A Simple Photo Album with Pinch and Zoom using FlipView


I'm trying to create a simple photo album (Windows Store App) using Flip View.

I have the Image element embedded within a ScrollViewer. I'm able to browse through the photos, but I'm looking to do the following things.

  • The image should fill the height of the screen uniformly [when the image is not zoomed]. I get vertical scrollbars for few items. I dont have this problem when the height of all the images are the same.
  • When I change the orientation of the screen, a part of the image is clipped on the right side.
  • The scrollviewer should forget the zoom level (reset zoom factor to 1) when I move between pages.

This is the code I have right now. What Am I doing wrong? And what should I add in my EventHandler to reset my ScrollViewer's zoom factor.

<FlipView 
    Name="MainFlipView"
    Margin="0"
    Height="{Binding ActualHeight, ElementName=pageRoot, Mode=OneWay}"
    Width="{Binding ActualWidth, ElementName=pageRoot, Mode=OneWay}"
    Background="Black">
         <FlipView.ItemTemplate>
              <DataTemplate>
                  <ScrollViewer Name="myScrollViewer" ZoomMode="Enabled"
                                Height="{Binding ActualHeight, ElementName=pageRoot, Mode=OneWay}"
                                Width="{Binding ActualWidth, ElementName=pageRoot, Mode=OneWay}"
                                HorizontalAlignment="Center"
                                VerticalAlignment="Center"
                                HorizontalScrollBarVisibility="Auto"
                                VerticalScrollBarVisibility="Auto"
                                MinZoomFactor="0.5"
                                MaxZoomFactor="2.5"
                                Margin="0" >
                       <Image Source="{Binding Path=Image}"
                              Name="MainImage" Stretch="Uniform" />
                  </ScrollViewer>
            </DataTemplate>
        </FlipView.ItemTemplate>
    </FlipView>

Solution

  • What user2199147 said should solve your first bullet point, the other two I had to fix programmatically, though it should be noted that I also had to use the VisualTreeHelper class which you'll have to import, and an extension method to help me use the helper class.

    First of all, I had to a method from the VisualTreeHelper extension, which finds the first element in the FlipView that is of any type:

    private T FindFirstElementInVisualTree<T>(DependencyObject parentElement) where T : DependencyObject
    {
        if (parentElement != null)
        {
            var count = VisualTreeHelper.GetChildrenCount(parentElement);
            if (count == 0)
                return null;
    
            for (int i = 0; i < count; i++)
            {
                var child = VisualTreeHelper.GetChild(parentElement, i);
    
                if (child != null && child is T)
                    return (T)child;
                else
                {
                    var result = FindFirstElementInVisualTree<T>(child);
                    if (result != null)
                    {
                        return result;
                    }
                }
            }
        }
        return null;
    }
    

    For going into portrait mode, I added a callback handler for WindowSizeChanged, and simply reset all the ScrollViewers in the flip view back to their default

    private void WindowSizeChanged(object sender, Windows.UI.Core.WindowSizeChangedEventArgs e)
    {
        //Reset scroll view size
        int count = MainFlipView.Items.Count;
        for(int i = 0; i < count; i++)
        {
            var flipViewItem = MainFlipView.ItemContainerGenerator.ContainerFromIndex((i));
            var scrollViewItem = FindFirstElementInVisualTree<ScrollViewer>(flipViewItem);
            if (scrollViewItem is ScrollViewer)
            {
                ScrollViewer scroll = (ScrollViewer)scrollViewItem;
                scroll.Height = e.Size.Height; //Reset width and height to match the new size
                scroll.Width = e.Size.Width;
                scroll.ZoomToFactor(1.0f);//Zoom to default factor
            }
        }
    }
    

    And then in your constructor you need Window.Current.SizeChanged += WindowSizeChanged; in order for the callback to ever be called.

    Now, for setting each ScrollViewer back to their default positions, we do a similar process, only whenever the FlipView selection is changed, we reset the ScrollViewer back to its default zoom factor

    private void FlipViewSelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        if (sender is FlipView)
        {
            FlipView item = (FlipView)sender;
            var flipViewItem = ((FlipView)sender).ItemContainerGenerator.ContainerFromIndex(((FlipView)sender).SelectedIndex);
            var scrollViewItem = FindFirstElementInVisualTree<ScrollViewer>(flipViewItem);
            if (scrollViewItem is ScrollViewer)
            {
                ScrollViewer scroll = (ScrollViewer)scrollViewItem;
                scroll.ScrollToHorizontalOffset(0);
                scroll.ScrollToVerticalOffset(0);
                scroll.ZoomToFactor(1.0f);
            }
        }
    }
    

    And again, we have to have a call in the constructor that looks like MainFlipView.SelectionChanged += FlipViewSelectionChanged;

    I know these methods seem really hackish and roundabout, because they are, but it's what worked for me, and I hope this helps.