Reputation: 5682
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:
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>
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.
Upvotes: 4
Views: 1435
Reputation: 22251
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...
}
}
Upvotes: 0
Reputation: 31
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.
Upvotes: 3