Programmatically scroll controls into view when vi

2020-03-06 03:58发布

问题:

I have a page with a vertical set of textboxes. If one of them is focused, all of them should be visible, even if the onscreen keyboard is displayed. There are just enough of them that all of them fit in the available space above the keyboard. When the bottom textbox is focused, the page gets automatically scrolled up so that all of them are visible, but if the top textbox is focused, the onscreen keyboard covers the bottom one.

This is a simplified example of my page:

<Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
    <ItemsControl ItemsSource="{Binding List}" Margin="120 140 0 0">
        <ItemsControl.ItemTemplate>
            <DataTemplate>
                <StackPanel Orientation="Horizontal" Margin="0 10 0 0">
                    <TextBox Text="{Binding Text, Mode=TwoWay}" />
                </StackPanel>
            </DataTemplate>
        </ItemsControl.ItemTemplate>
    </ItemsControl>
</Grid>

DataContext contains a list of 10 items:

public class Item
{
    public string Text { get; set; }
}

public class ViewModel
{
    public List<Item> List { get; set; }
}

public MainPage()
{
    this.InitializeComponent();
    DataContext = new ViewModel
    {
        List = Enumerable.Range(0, 10).Select(i => new Item { Text = i.ToString() }).ToList()
    };
}

I've already tried a couple of approaches, all without success:

  1. In TextBox.GotFocus event programmatically changed focus to the bottom textbox and back.
  2. In TextBox.GotFocus event and InputPane.Showing event tried setting the vertical offset of a ScrollViewer: (a) the one I included in the page around the Grid (b) the one in the visual tree above the Page that Windows uses to automatically bring the focused control in view. In both cases the ScrollViewer doesn't react to ScrollToVerticalOffset calls.

I've also looked at the sample suggested in this question but it reacts to onscreen keyboard differently, not by scrolling the page.

回答1:

Thanks to Cyprient's answer I finally managed to get this to work. I pursued option 2.a from my question.

Adding UpdateLayout() call was required, but when I put it in the GotFocus event handler it only worked after the virtual keyboard was already opened. To make it work the first time when the keyboard was still opening, I had to make two changes:

  1. I had to put the code in the Showing event of InputPane.
  2. I had to put it in a callback for the dispatcher so that it was called only after the Showing event handler had already returned.

Here's the final code:

public MainPage()
{
    this.InitializeComponent();
    DataContext = new ViewModel
    {
        List = Enumerable.Range(0, 10).Select(i => new Item { Text = i.ToString() }).ToList()
    };

    var inputPane = InputPane.GetForCurrentView();
    inputPane.Showing += InputPane_Showing;
}

private async void InputPane_Showing(InputPane sender, InputPaneVisibilityEventArgs args)
{
    await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
        {
            var parentScrollViewer = FindParent<ScrollViewer>(this.pageRoot);
            parentScrollViewer.VerticalScrollMode = ScrollMode.Enabled;
            parentScrollViewer.ScrollToVerticalOffset(65);
            parentScrollViewer.UpdateLayout();
        });
}

Here's the helper function I'm using to get the reference to the same ScrollViewer which is used when the page contents gets scrolled automatically because the focused control would not be shown otherwise:

public static T FindParent<T>(FrameworkElement reference)
    where T : FrameworkElement
{
    FrameworkElement parent = reference;
    while (parent != null)
    {
        parent = parent.Parent as FrameworkElement;

        var rc = parent as T;
        if (rc != null)
        {
            return rc;
        }
    }

    return null;
}


回答2:

Sometimes the ScrollViewer does not refresh itself when you are using ScrollToVerticalOffset. The workaround consists in calling scrollviewer.UpdateLayout() after scrolling. It worked for me in several cases.