Help I have a control that is automatically getting scrolled to.. Is my WPF app haunted ?

Long has it been since I wrote a blog post on WPF. <excited> Today I found something that I had to share with you guys. It’s not some big WPF feature, rather its very very small and probably it serves only a case or 2 however if you are unaware of it, you can spend hours scratching your head trying to figure out what is happening in your UI.

The problem

Let me start by giving you some context on what was my issue. I had a ScrollViewer, inside it I had a bunch of A4 paper like rectangles, which I use to render different pages of a book. Each “rectangle” had an InkCanvas so that the user can annotate the papers. The ink is only enabled when the user presses on a button. The issue was that the scrollviewer would automatically scroll as soon as there is a mouse down. This happened all the time causing things to flicker when I have multiple pages visible on screen. What made this even worse is the fact that this happened as soon as the user was pressing down with the pen thus ink would end up everywhere =) yes it was as if the app is haunted

I started wondering why would this happen. I prematurely blamed the InkCanvas for this however when I switch it with something else it was still happening. I started investigating and I came across this StackOverFlow thread (kudos Andrew Smith always great insights on WPF).

Let’s understand why this happens

What happens is that OnGotFocus of a control BringIntoView is called. BringIntoView is a method inside FrameworkElement which will raise an event called RequestBringIntoView. This is an event which gets bubbled up the visual tree. Anyone can handle this event. In the case where you have a ScrollViewer as one of your ancestors, ScollViewer will call the MakeVisible method which will basically ask the layout panel to render the item in view.

postimage1

This is all fine and dandy however sometimes (such as my case) you want to disable this behavior since it can irritate the user (or in my case make a mess with Ink)

Solution

The solution is easy, don’t let the event reach the ScrollViewer. To do this you simple need to handle the event like you would with any other RoutedEvent. Consider the following XAML

<ScrollViewer Grid.Row="2">
    <ItemsControl ItemsSource="{Binding}" x:Name="List">
        <ItemsControl.ItemTemplate>
            <DataTemplate>
                <Button Width="200" Height="150" Background="{Binding Item2}">
                    
                    <TextBlock Text="{Binding Item1}" Foreground="White" FontSize="25" FontWeight="Bold" VerticalAlignment="Center" HorizontalAlignment="Center"/>
                    
                </Button>        
            </DataTemplate>
        </ItemsControl.ItemTemplate>
    </ItemsControl>
</ScrollViewer>

What you would do in this case is that you would handle the RequestBringIntoView of the ItemsControl (or any element whose ancestor is the scrollviewer, in this case it must be the ItemsControl) and set is handled to true. And voila. no more scrolling.

This might seem very small and useless however by knowing how this works you will avoid being in a position where you see a behavior and cannot explain it. I have attached a small code sample if you want to play around a bit more with this.

Download Sample Code