4

How is it possible to reach an input field within a ScrollViewer when the input field is overlapped by the soft keyboard?

This scenario is easily reproduced:

  1. Create a new page with a ScrollViewer containing some TextBoxes. Make as many TextBoxes as you need until you need to scroll the page to reach the last three TextBoxes.

    <ScrollViewer>
      <StackPanel Orientation="Vertical">
        <TextBox Margin="20" />
        <TextBox Margin="20" />
        <TextBox Margin="20" />
        ..
        <TextBox Margin="20" />
        <TextBox Margin="20" />
        <TextBox Margin="20" PlaceholderText="3" />
        <TextBox Margin="20" PlaceholderText="2" />
        <TextBox Margin="20" PlaceholderText="1" />
      </StackPanel>
    </ScrollViewer>
    
  2. Start the app and tap into "Placeholder 3". The keyboard pops up and overlaps "Paceholder 2" and "Placeholder 1".

How can I improve the layout so I can reach these TextBoxes ("1" and "2") without closing and re-opening the keyboard all the time?

An example that shows a working solution can be found on every WindowsPhone: Settings => VPN => Enable VPN => Add new profile => Click in any of the TextBoxes and you'll see that you can scroll to every part of the layout although the soft keyboard is up.

muetzenflo
  • 5,653
  • 4
  • 41
  • 82
  • possible duplicate of [Keyboard overlaps textbox](http://stackoverflow.com/questions/23342427/keyboard-overlaps-textbox) – Chris W. Dec 04 '14 at 16:07
  • somehow they are related, yes. But in my case I do not change the focus, but just want to scroll the complete layout around and above the keyboard. – muetzenflo Dec 04 '14 at 16:21
  • You're still going to have to scroll to the vertical offset to negate the height of the keyboard and provide the content into the available space of the viewport of the scrollviewer, the concept would essentially be the same for your question as is with that one. – Chris W. Dec 04 '14 at 16:43
  • Also the linked question is for WP8 and mine is for WP8.1. In my case there is no Application.Current.RootVisual available to translate. I googled a bit, but cannot find any replacement property. Could it work with Window.Current.CoreWindow? But how do I translate it? – muetzenflo Dec 09 '14 at 09:21
  • I found this to y-translate the offset: https://social.msdn.microsoft.com/Forums/windowsapps/en-US/99652e87-113c-47fa-a8e7-60f11fc9f160/virtual-keyboard-covering-textbox But I still cannot scroll whereever I want while the keyboard is open. The overscroll effect always kicks in before I am even close to the upper end of the layout. – muetzenflo Dec 09 '14 at 09:33

2 Answers2

3

Been awhile on this question but for others who may be looking for a good solution here is what I did.

Subscribe to the keyboard show and hide events and size the height of the scrollviewer based on when the keyboard is showing or hiding.

Xaml

<ScrollViewer x:Name="scrlvwrKBScroll" VerticalScrollMode="Enabled">
  <StackPanel Orientation="Vertical">
    <TextBox Margin="20" />
    <TextBox Margin="20" />
    <TextBox Margin="20" />
    ..
    <TextBox Margin="20" />
    <TextBox Margin="20" />
    <TextBox Margin="20" PlaceholderText="3" />
    <TextBox Margin="20" PlaceholderText="2" />
    <TextBox Margin="20" PlaceholderText="1" />
  </StackPanel>
</ScrollViewer>

C#

public Constructor()
{
  this.InitializeComponent()
  InputPane.GetForCurrentView().Showing += Keyboard_OnShow;
  InputPane.GetForCurrentView().Hiding += Keyboard_OnHide;
}

private void Keyboard_OnShow(InputPane sender, InputPaneVisibilityEventArgs args)
{
  this.scrllvwrKBScroll.Height = this.ActualHeight - args.OccludedRect.Height - 50;
}

private void Keyboard_OnHide(InputPane sender, InputPaneVisibilityEventArgs args)
{
  this.scrllvwrKBScroll.height = this.ActualHeight;
}

There may be a better way to adjust the height based on the heights of the containers you are using but this is what I used to get my application to work.

Markus Bruckner
  • 2,852
  • 2
  • 24
  • 29
Osolith
  • 31
  • 3
0

I also encountered this problem whenever a Page with a BottomAppBar is displaced in the layout from the root visual. This can be caused by a Margin or Padding on a wrapper element.

Broken visual tree:

  • Window.Current.Content Frame
    • Border with 1px Margin
      • ContentPresenter
        • Page with BottomAppBar

I could find no "non-disgusting" workaround, but adjusting the offset directly on the root ScrollViewer did work for me. See UWPMobileScrollIssue for a full repro and workaround.

// ...snip...
namespace UWPFocusTestApp
{
    sealed partial class App : Application
    {
        // ...snip...
        protected override void OnLaunched(LaunchActivatedEventArgs e)
        {
            // ...snip...
            if (rootFrame == null)
            {
                // ...snip...

                // Place the frame in the current Window
                Window.Current.Content = rootFrame;

                #region WORKAROUND
                if (AnalyticsInfo.VersionInfo.DeviceFamily == "Windows.Mobile")
                {
                    InputPane.GetForCurrentView().Showing += InputPane_Showing;
                }
                #endregion
            }

            // ...snip...
        }

        #region WORKAROUND
        private void InputPane_Showing(InputPane sender, InputPaneVisibilityEventArgs args)
        {
            // we only need to hook once
            InputPane.GetForCurrentView().Showing -= InputPane_Showing;

            var frame = (Frame)Window.Current.Content;

            // Find root ScrollViewer
            DependencyObject cNode = frame;
            while (true)
            {
                var parent = VisualTreeHelper.GetParent(cNode);
                if (parent == null)
                {
                    break;
                }
                cNode = parent;
            }
            var rootScrollViewer = (ScrollViewer)cNode;

            // Hook ViewChanged to update scroll offset
            bool hasBeenAdjusted = false;
            rootScrollViewer.ViewChanged += (_1, svargs) =>
            {
                // once the scroll is removed, clear flag
                if (rootScrollViewer.VerticalOffset == 0)
                {
                    hasBeenAdjusted = false;
                    return;
                }
                // if we've already adjusted, bail.
                else if (hasBeenAdjusted)
                {
                    return;
                }

                var appBar = ((Page)frame.Content)?.BottomAppBar;
                if (appBar == null)
                {
                    return;
                }

                hasBeenAdjusted = true;
                rootScrollViewer.ChangeView(null, rootScrollViewer.VerticalOffset + appBar.ActualHeight, null);
            };
        }
        #endregion

        // ...snip...
    }
}
Mitch
  • 21,223
  • 6
  • 63
  • 86