Implementing ScrollViewer and a multiline TextBox properly
15 June 2013
Would you like to create a scrollable multi-line TextBox like OneNote or Messages? This would sound like a reasonably straightforward task, but as it turns out it is a task fraught with peril.
This solution works for a TextBox sitting at the top or bottom of the page (e.g. messages), or a TextBox that need to fill the screen (e.g. comments/notes/an editor). It handles varying TextBox height and allows for proper scrolling at all times.
Here’s the steps required for this to work:
1. Simulate a popped up keyboard in the page
Create a placeholder UI element that simulates the space the keyboard takes up on the page, which effectively squishes the ScrollViewer into the available space.
Only display when the TextBox gets focus (i.e. only the keyboard is visible) and hide it when it doesn’t, using the GotFocus and LostFocus events.
Note the keyboard height differs between different resolutions, so some checking is required to find the correct keyboard height, which can be done on the page’s Loaded event.
2. Manually scroll the ScrollViewer while new lines of text are entered.
This is required to so that as text wraps down to new lines the caret is kept in view. We do this by manually scrolling the ScrollViewer as the TextBox size increases. This is done in the TextChanged event of the TextBox.
3. Prevent Windows Phone from natively scrolling up your page.
Naturally while you’re typing in a mult-line TextBox if the caret gets too close to the keyboard, Windows Phone automatically pushes the entire page upward so as to keep the text field from being hidden underneath the keyboard. This built-in feature is redundant where we have our own means of scrolling (the ScrollViewer). This implicit action needs to be manually reset. To fix this, keep resetting the ApplicationRootFrame’s RenderTransformation property whenever the TextBox gets Focus.
4. Manually scroll to intended caret position
On initially tapping a populated Textbox, scrolling to the point at which the user wants the cursor to be – specifically to an area that would be hidden after the keyboard is shown – requires some manual means to accomplish. This is handled on the Tap event.
What we get is the following behaviour, while retaining proper scrolling while typing in the TextBox, as well as when focus is outside of it.
XAML + C#
The only real shortcoming is that it doesn’t detect whether the Clipboard row is being displayed ontop of the keyboard, which may hide some of the TextBox. But since it’s of static height and can be easily checked via Clipboard.ContainsText() method, it should be easy enough to extend this solution to handle.
Support for panoramic mode will only require some more custom heights, and detection for orientation changes and states.
I’ve spent some time searching and trying to implement this properly, and the solution combines the ideas from a few sources on the internet. So thanks must go to them for their contributions.