locked
ScrollViewer Bug? RRS feed

  • General discussion

  • I have ScrollViewer with Image as a content. I set up DoubleTapped event on ScrollViewer which calls ChangeView method with null as horizontal/vertical offsets and some zoom factor (it is proper zoom factor). In pointer(mouse) mode it works correctly but in Touch mode event is fired, ChangeView returns true(!) and nothing happens(i checked in debug ZoomFactor property isn't changed). I tested it on simulator, I have Windows 8.1 final version from msdnaa. Is it a bug?

    EDIT:

    Ok, I found solution needed to run ChangeView with Dispatcher, seems like it run before layout validation but why does it worked every time in mouse mode? Because touch mode needs more layout processing ? Btw. should be some adnotation in documentation imho.

    • Edited by fex_ Sunday, September 29, 2013 8:52 PM
    Sunday, September 29, 2013 8:24 PM

All replies

  • Hi fex_,

    Thanks for posting your solution. If you need more help with this can you please provide a minimal sample demonstrating what your code is doing? I'm not quite envisioning what is called when from the text description.

    --Rob

    Monday, September 30, 2013 11:36 PM
    Moderator
  • Hi Fex,

    I once worked on a problem where I wanted a double tap to change the zoom level in a scroll viewer. I found that while your finger is touching the screen, it is not safe to edit the state of the scroll-viewer at all (this was on 8.0, but I wouldn't be surprised if this is still the case). I ended up setting a flag on double tap, which I then checked for in the PointerReleased event. If true, I would execute the view change.

    I think this is likely related to how events are routed when touching a scroll viewer. While touching the scroll viewer, evens are routed through a background thread and the ScrollViewer updates its view state in a worker thread. So, if you use the DoubleTapped event, then while you're changing the view of the ScrollViewer in the UI thread, there's a background thread that's keeping track of your finger and also tries to update the view state at the same time. Since doubleTapped is fired on your second Tap's down action, this is a problem. Hence, when I wanted to work my way around it, I had to make the "doube tap" happen when releasing the finger after tap number 2.

    The good thing is that once you start manipulating the ScrollViewer though moving your finger around the screen, the events will be moved to the background thread, and you won't get the PointerReleased event at all, so you won't treat a Tap->Press-Move-Release as a double tap this way.

    Even if this isn't the solution for you, I hope it might help you figure out what's going on. One test you might want to do, even with the dispatcher, is to keep holding your finger down on tap number 2 of your double tap sequence (i.e. Tap->long press) to make sure it still works.

    Tomas

    Tuesday, October 1, 2013 5:17 PM
  • @Rob Caplan

    Just add DoubleTapped event handler on ScrollViewer and:

    private async void ScrollViewer_DoubleTapped(object sender, DoubleTappedRoutedEventArgs e)
    {
         var sv = sender as ScrollViewer;

         sv.ChangeView(null, null, someZoomScale);

    }

    (won't work in touch mode) but this will work:

    private async void ScrollViewer_DoubleTapped(object sender, DoubleTappedRoutedEventArgs e)
    {
         var sv = sender as ScrollViewer;

          var dispatcher = Window.Current.CoreWindow.Dispatcher;
           await dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.High, () => {

                  sv.ChangeView(null, null, someZoomScale);});

    }


    • Edited by fex_ Wednesday, October 2, 2013 12:23 PM
    Wednesday, October 2, 2013 11:52 AM
  • @T Hoffman

    Well I can't handle PointerReleased event in my case because I have ScrollViewer inside FlipView DataTemplate. (handling such event will prevent to fire FlipView swipe gesture). Btw. could you share some part of your code with DoubleTap zoom to point or give some advices how to do that properly?

    Wednesday, October 2, 2013 11:58 AM
  • Hi fex,

    Sorry about the delay. Here's a sample which does rudimentary double tap zooming.

    It's from a simple Blank app template.

    MainPage.xaml:

    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
            <FlipView>
                <ScrollViewer x:Name="MyScroller">
                    <Canvas Width="700" Height="700">
                        <Canvas.Background>
                            <LinearGradientBrush StartPoint="0 0" EndPoint="1 1">
                                <GradientStop Offset="0" Color="Red"/>
                                <GradientStop Offset="0.2" Color="Green"/>
                                <GradientStop Offset="0.4" Color="blue"/>
                                <GradientStop Offset="0.6" Color="Orange"/>
                                <GradientStop Offset="0.8" Color="Black"/>
                                <GradientStop Offset="1" Color="Yellow"/>
                            </LinearGradientBrush>
                        </Canvas.Background>
                    </Canvas>
                </ScrollViewer>
                <Rectangle Width="300" Height="300" Fill="Red"/>
                <Rectangle Width="300" Height="300" Fill="Green"/>
                <Rectangle Width="300" Height="300" Fill="Blue"/>
            </FlipView>
        </Grid>

    MainPage.xaml.cs:

    public sealed partial class MainPage : Page
        {
            bool _ShouldDoubleTapOnRelease;
    
            public MainPage()
            {
                this.InitializeComponent();
                MyScroller.PointerPressed += MyScroller_PointerPressed;
                MyScroller.PointerReleased += MyScroller_PointerReleased;
                MyScroller.DoubleTapped += MyScroller_DoubleTapped;
            }
    
            void MyScroller_DoubleTapped(object sender, DoubleTappedRoutedEventArgs e)
            {
                _ShouldDoubleTapOnRelease = true;
            }
    
            void MyScroller_PointerReleased(object sender, PointerRoutedEventArgs e)
            {
                if (_ShouldDoubleTapOnRelease)
                {
                    MyScroller.ChangeView(null, null, 5);
                }
            }
    
            void MyScroller_PointerPressed(object sender, PointerRoutedEventArgs e)
            {
                _ShouldDoubleTapOnRelease = false;
            }
        }

    As you can see, there's just a simple flag there to check whether a double tap happened or not. If it did, I take action when the touch is released. If you start a swiping gesture, you won't get the released event at all, since it's swallowed by the ScrollViewer. Therefore, this is safe, as far as I know. Might be worth checking out PointerCancelled, PointerCaptureLost, and also test what happens if you use a second finger to the screen after DoubleTap has executed but before PointerReleased happens (either way, you should be able to abort gracefully in this case).

    Also, you might want to check the time between the DoubleTapped event and PointerReleased, in case you don't want someone to hold forever and then still have the double tap effect.

    Let me know if you would like some more clarifications.

    Cheers,

    Tomas

    Saturday, October 5, 2013 1:09 AM