Windows Phone 7.0 handling text entry screens and keyboard layouts

As it happens sometimes, I had to develop simple page with one textbox at the bottom of the page. Designer put attention on this screen – “page title should be visible even with opened keyboard”.

imageimage

Pretty easy piece of work. Let’s do that:

0. Understand how it works.

When SIP keyboard is rendered, PhoneApplicationFrame.TranslateTransform.Y is set to specific values (-259 in landscape orientation, -339 in portrait orientation). To update layout, we’ll just set top margin to the specified value(-s) and after that Silverlight layout system will fix the issue.

1. Create simple windows phone page

<phone:PhoneApplicationPage
    x:Class="Test.Keyboard.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
    FontFamily="{StaticResource PhoneFontFamilyNormal}"
    FontSize="{StaticResource PhoneFontSizeNormal}"
    Foreground="{StaticResource PhoneForegroundBrush}"
    SupportedOrientations="PortraitOrLandscape"
    >
    <Grid x:Name="LayoutRoot" >
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>
        <StackPanel Grid.Row="0" Margin="12,17,0,28">
            <TextBlock Text="WINDOWS PHONE" Style="{StaticResource PhoneTextNormalStyle}"/>
            <TextBlock Text="developer's ?" Margin="9,-7,0,0" Style="{StaticResource PhoneTextTitle1Style}"/>
        </StackPanel>
        <Grid Grid.Row="1" Margin="12,0,12,0"></Grid>
        <TextBox Grid.Row="2" LostFocus="TextBoxLostFocus"/>
    </Grid>
</phone:PhoneApplicationPage>

2. Add some nice magic inside the page

public partial class MainPage : PhoneApplicationPage
    {
        private const double LandscapeShift = -259d;
        private const double LandscapeShiftWithBar = -328d;
        private const double Epsilon = 0.00000001d;
        private const double PortraitShift = -339d;
        private const double PortraitShiftWithBar = -408d;

        public static readonly DependencyProperty TranslateYProperty = DependencyProperty.Register("TranslateY", typeof(double), typeof(MainPage), new PropertyMetadata(0d, OnRenderXPropertyChanged));

        public MainPage()
        {
            InitializeComponent();
            Loaded += MainPageLoaded;
        }

        public double TranslateY
        {
            get { return (double)GetValue(TranslateYProperty); }
            set { SetValue(TranslateYProperty, value); }
        }

        private static void OnRenderXPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            ((MainPage)d).UpdateTopMargin((double)e.NewValue);
        }

        private void MainPageLoaded(object sender, RoutedEventArgs e)
        {
            BindToKeyboardFocus();
        }

        private void BindToKeyboardFocus()
        {
            PhoneApplicationFrame frame = Application.Current.RootVisual as PhoneApplicationFrame;
            if (frame != null)
            {
                var group = frame.RenderTransform as TransformGroup;
                if (group != null)
                {
                    var translate = group.Children[0] as TranslateTransform;
                    var translateYBinding = new Binding("Y");
                    translateYBinding.Source = translate;
                    SetBinding(TranslateYProperty, translateYBinding);
                }
            }
        }

        private void UpdateTopMargin(double translateY)
        {
            if (IsClose(translateY, LandscapeShift) || IsClose(translateY, PortraitShift)
||IsClose(translateY, LandscapeShiftWithBar) || IsClose(translateY, PortraitShiftWithBar)
)
            {
                LayoutRoot.Margin = new Thickness(0, -translateY, 0, 0);
            }
        }

        private bool IsClose(double a, double b)
        {
            return Math.Abs(a - b) < Epsilon;
        }

        private void TextBoxLostFocus(object sender, RoutedEventArgs e)
        {
            LayoutRoot.Margin = new Thickness();
        }
    }

3. Have fun – example and sources

Here is the demo project with sources.
Test.Keyboard.zip

Tags: , , , , , , , ,

25 Responses to “Windows Phone 7.0 handling text entry screens and keyboard layouts”

  1. Windows Phone 7.0 handling text entry screens and keyboard layouts – www.nalli.net Says:
    August 15th, 2011 at 3:13 pm

    [...] Read original post at Alex Sorokoletov’s Blog [...]

  2. Grigory Says:
    August 17th, 2011 at 8:30 am

    Unfortunately Test.Keyboard.zip project does not work.

  3. Alex Says:
    August 17th, 2011 at 8:39 am

    What’s wrong with the project? It’s working locally on my PC.

  4. Grigory Says:
    August 18th, 2011 at 1:17 am

    Sorry for misleading, i mean that the approach does not work for me. The header moves above the upper bound of screen.

    Im using SDK 7.1 beta 2.
    Here is the SS of the problem: http://db.tt/Zc257g8

    Also there are some details about the TranslateTransform.
    It is changed only when SIP is to hide a big piece of content. I mean if your page is filled with some content only for a half of its height, Render transform will not be changed.
    Actually, it even does not change when Hardware keyboard is present and only clipboard bar appeals above your content.

  5. Alex Says:
    August 18th, 2011 at 1:30 am

    Oh, I see.
    I need to adjust those constants.
    private const double LandscapeShift = -259d;
    private const double PortraitShift = -339d;

    Actually, these numbers are used to fire layout change only when animation completed. If I just bind to Y property of transform, then screen will be jumping all the time animation on opening keyboard runs.

    And in case with B2 seems that bar with autoreplace words causes PortraitShift to be larger. You can write Debug.WriteLine(translateY) in UpdateTopMargin(double translateY)
    and see what values you need

  6. Alex Says:
    August 18th, 2011 at 2:17 am

    I’ve uploaded new version of sample, it should work correctly now.
    Will update the post now

  7. Grigory Says:
    August 18th, 2011 at 6:28 am

    Great. Current solution works :)
    I found one minor issue. In portrait mode with hardware keyboard turned on(DeviceStatus.IsKeyboardDeployed) header jumps over the top a little bit. But i thinks this is nonessential.

    Also look at Japanese 12key layout. It has a bit bigger SIP than others in Portrait mode. I think it makes sense to add a special margin for it

    Great job Alex!

  8. Alex Says:
    August 18th, 2011 at 6:32 am

    Actually, until Mango is released, I’m trying to do solutions on 7.0.
    I was thinking about margin and hardware keyboard and you are right – there should special margin.
    Again, I use these coded margins because if you bind directly transform.Y to margin.Top it will work flawlessly but with small jumps I don’t like.
    If you like that solution, all you need is just removing that condition in UpdateTopMargin method.

    Could you please check margins for Japan kb and for HW keyboard margin and post them as a comment?

  9. Grigory Says:
    August 18th, 2011 at 8:33 am

    About HW keyboard – it does not show SIP but show clipboard panel which is 69 pixels height.

    And Japanese 12key KB seems to be 49pixels higher than normal layout.

  10. Grigory Says:
    August 18th, 2011 at 8:34 am

    Sorry, 39 pixels higher than normal

  11. Alex Says:
    August 19th, 2011 at 8:44 am

    Thank you so much!

  12. vikram Says:
    March 1st, 2012 at 11:05 pm

    please anybody post same code in vb.net…I converted above code to vb.net but it shows an error at OnRenderXPropertyChanged..thanks..

  13. udi zohar Says:
    April 23rd, 2012 at 2:31 pm

    Why do I get the error “the type or namespace ‘binding’ could not be found…” ?
    I added system.windows as a reference.

  14. udi zohar Says:
    April 23rd, 2012 at 2:37 pm

    Got it! never mind.
    You missed using System.Windows.Data;

  15. Chandra Says:
    August 29th, 2012 at 7:43 am

    When I am using the code without ApplicationBar its working fine, but in case of ApplicationBar I am getting gap between textbox and keyboard and not getting titles.

    Please suggest me for the above issue

  16. Alex Says:
    August 29th, 2012 at 9:57 am

    height of appbar is 72 or something like this. You need to adjust the numbers used in TranslateTransform

  17. latole Says:
    September 27th, 2012 at 7:47 pm

    Why do I get the error “the type or namespace ‘binding’ could not be found…” ?
    I added system.windows as a reference.

  18. Purushotham Says:
    November 9th, 2012 at 6:39 am

    Hi Alex,

    I went through you sample code and it works fine. I need to place more text boxes (Registration page). I placed the Scroll viewer in between the pagetitle and textbox you used, if i focus on the text box you used, im getting the SIP and the behaviour is expected one, im able to scroll the scrollviewer and the page title is there., but if i focus on the text box that i placed in the scroll viewer its not working. I need to replicate the behaviour you did for the text box in your sample. Can you give me some tips to do this.

    Thanks.

  19. Alex Says:
    November 9th, 2012 at 6:47 am

    Hi, Purushotham!
    I’d suggest you not to use scrollviewer and textboxes. Better is to split the registration to 2 or more pages and make it like a wizard (see how WP7 works when starting first time or after settings reset).
    This way 1) user will understand the information and fields clearly 2) you will not spend your time on fighting with strange OS behaviors.

    If you still want to continue using that way (scrollviewer, textboxes and so on), you should look at vertical offset property and maybe work also with this specific property.
    When opening KB, you will need to adjust it (decrease/increase by specific number).

    Again, I strongly suggest splitting your form into several steps. This will give you more space which you can also use as a description or guidance or tip on filling in this form.

    Alex.

  20. Alex Says:
    November 9th, 2012 at 6:49 am
  21. Jon Says:
    February 7th, 2013 at 10:43 am

    Hi Alex,

    For some reason when I tried to implement the code on my page it does not work. When I went through the debugger I realized that this statement never gets fired >> LayoutRoot.Margin = new Thickness(0, -translateY, 0, 0); << thus the layout never gets updated on my page. Any idea why?

  22. Alex Says:
    February 13th, 2013 at 2:21 am

    Hi, Jon! Thanks for the comment, let’s see.

    So is UpdateTopMargin get called? What WP version you are running this code on?
    If UpdateTopMargin is not called, then it’s something wrong with the binding. Otherwise, check condition inside UpdateTopMargin method.

  23. NeO Says:
    April 25th, 2013 at 7:40 pm

    if have muti textbox and put in scrollview, it doesn`t work fine.

  24. NeO Says:
    April 25th, 2013 at 8:03 pm

    the case is , if you tap first textbox, when keyboard show, you cant scroll to the last textbox in the bottom.

  25. How to implement ScrollViewer and multiline TextBox properly (Windows Phone) | KLING DIGITAL Says:
    June 16th, 2013 at 12:11 am

    [...] Alex Sorokoletov – Windows Phone 7 handling text entry screens and keyboard layouts [...]

Leave a Reply