Switching focus is broken in Entry.Completed event handler

PavelMatushkinPavelMatushkin USMember ✭✭
edited January 2017 in Xamarin.Forms

I have a simple login form with the XAML layout: two entries for username and password and a login button. My application runs on an Android device equipped with a hardware keyboard. The main functionality of the form is to allow the user to key in the username, press Enter key on the hardware keyboard, key in the password, press Enter again, and then finish the login process.

To achieve that, the form must be capable of the following:

  • setting focus to the Username entry upon appearing on the screen;
  • responding to the hardware Enter key by switching focus to the next control.

The first part works fine: the form sets the focus to the UserName entry upon appearing on the screen. However, when I press the hardware Enter key, the focus switches to the button, instead of switching to the next entry. This is the sequence of events occurring on the form:

txtUserName: 'focused' // the form sets the initial focus in OnAppearing()
txtUserName: 'unfocused' // these two lines is the response to the hardware Enter key pressed while focus is in txtUserName
txtUserName: 'completed'
txtPassword: 'focused' // the forms switches the focus to txtPassword following the code in txtUserName_Completed()
txtPassword: 'unfocused' // txtPassword immediately loses focus; this is an unexpected behavior
btnLogin: 'focused' // focus is now on the button

What am I doing wrong? What is the correct way of manipulating focus in the scenario I described?

This is my form.

XAML layout:

    <?xml version="1.0" encoding="utf-8" ?>
    <ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
                 xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
                 x:Class="TestFocus.LoginPage">

      <StackLayout>
        <Entry x:Name="txtUserName" />

        <Entry x:Name="txtPassword" />

        <Button x:Name="btnLogin" Text="Login" />
      </StackLayout>

    </ContentPage>

... and the code-behind file:

using System;
using Xamarin.Forms;

namespace TestFocus
{
    public partial class LoginPage : ContentPage
    {
        public LoginPage()
        {
            InitializeComponent();

            txtUserName.Completed += txtUserName_Completed;
        }

        protected override void OnAppearing()
        {
            base.OnAppearing();

            txtUserName.Focus();
        }

        private void txtUserName_Completed(object sender, EventArgs e)
        {
            txtPassword.Focus();
        }
    }
}

Answers

  • JohnHardmanJohnHardman GBUniversity mod

    @PavelMatushkin - the below is how I do this. It works for me.

    Two things to highlight:

    (1) explicitly unfocussing before focussing
    (2) scrolling to ensure the second Entry is visible, but working around a XF bug on UWP

        private async void UserEmailEntry_Completed(object sender, System.EventArgs e)
        {
            _userEmailEntry?.Unfocus();
            if ((_svPage != null) && (ApplicationType.ScrollToAsyncExpectedToWork()))
                await _svPage.ScrollToAsync(_userPasswordEntry, ScrollToPosition.MakeVisible, false); // UWP never returns from ScrollToAsync
            _userPasswordEntry?.Focus();
        }
    
  • PavelMatushkinPavelMatushkin USMember ✭✭

    Thanks, I just tried it, and I don't think it works in Android.

  • PavelMatushkinPavelMatushkin USMember ✭✭

    bump

  • JohnHardmanJohnHardman GBUniversity mod
    edited February 2017

    @PavelMatushkin - You are right - although the code above works with a physical keyboard on Windows platforms, and works with a soft keyboard on Android, it fails with a physical keyboard on Android.

    As a nasty workaround, this does work (but I don't like it):

            Device.BeginInvokeOnMainThread(async () =>
            {
                await Task.Delay(100);
                _userPasswordEntry?.Focus();
            });
    

    Whether having to do this is a Xamarin.Forms bug, or an Android "feature", I don't know. If you have time (I don't right now), it would be worth investigating which it is, and depending on what you find, reporting a bug.

  • PavelMatushkinPavelMatushkin USMember ✭✭

    Thanks for this idea of a workaround. It does indeed affect the issue, but not in a consistent way.

    I added this snippet to my original project and did a series of 20 tests of clicking on a first entry and pressing Enter on a physical keyboard, and 3 times out of 20 the focus still went past the second entry. Increasing the delay did provide additional reliability, so it's probably some sort of a synchronization issue on Android. Unfortunately I, too, lack the resources to investigate it further.

  • JohnHardmanJohnHardman GBUniversity mod

    Yes, the moment a delay is introduced, there will be a race condition (that's why I called it nasty). The greater the delay, the less likely it is to fail, but the more sluggish the app becomes.

    I work on the basis that (with very, very, very few exceptions) putting a delay in code, rather than having event-triggered code, shows that there is bad design or implementation in an API that is being utilised. That even goes for automated UI tests. In this particular case, I don't know whether it is Xamarin or Android causing the problem here.

  • PavelMatushkinPavelMatushkin USMember ✭✭

    I think we fixed it by adding a handler for KeyPress event in an existing custom Entry renderer for Android. Inside the handler code we simply set e.Handled to false.

Sign In or Register to comment.