'Why entry's Focus() method isn't working from page's constructor?
In Xamarin Forms, when I use the following code:
public SomePage()
{
InitializeComponent();
someEntry.Focus();
}
the code entry isn't focused by default, however, if I use the following code:
protected override void OnAppearing()
{
base.OnAppearing();
someEntry.Focus();
}
it works as needed (entry is focused). Why is that? Isn't codeEntry already existing and sitting at it's place, fully functional, after InitializeComponent()
call? I mean, I sure can change Text
property from page constructor.
Solution 1:[1]
In Xamarin, every control has equivalent view renderer, that is native UI element which will only be created when control is added to the native element hierarchy. In constructor, native element for entry is not yet created. However, in OnAppering, entry's corresponding native element is created so it can get the focus.
Also this seems like a bug as Xamarin is storing state and applying it when creating the native UI element. Its time to file a bug !!!
Solution 2:[2]
When I use Shell this don't work anymore.
protected override void OnAppearing()
{
base.OnAppearing();
someEntry.Focus();
}
But this does work:
protected async override void OnAppearing()
{
base.OnAppearing();
await Task.Delay(100);
someEntry.Focus();
}
Solution 3:[3]
File.xaml
<Entry x:Name="txtLPN" Placeholder="Scan LPN." Grid.Row="1" Grid.Column="0" FontSize="15" Focused="txtLPN_Focused" />
File.cs >>>>>
private void txtLPN_Focused(object sender, FocusEventArgs e)
{
txtLPN.CursorPosition = 0;
if (!string.IsNullOrEmpty(txtLPN.Text))
txtLPN.SelectionLength = txtLPN.Text.Length;
}
protected async override void OnAppearing()
{
base.OnAppearing();
await Task.Delay(600);
txtLPN.Focus();
}
Solution 4:[4]
I tried all of the above. I think because my page is a pop-up and has some animation none of the above worked. However this worked for me:
BackgroundWorker setFocus = new BackgroundWorker();
In constructor
setFocus.DoWork += SetFocus_DoWork;
private void SetFocus_DoWork(object sender, DoWorkEventArgs e)
{
bool worked = false;
while (!worked)//will keep trying until it can set focus (when MyEntry is rendered)
{
Thread.Sleep(1);
MainThread.InvokeOnMainThreadAsync(()=> worked = MyEntry.Focus());
}
}
protected override void OnAppearing()
{
base.OnAppearing();
if(!setFocus.IsBusy)
{
setFocus.RunWorkerAsync();
}
}
You may want to add something to handle if "worked" is never set to true like try this for a few seconds.
Solution 5:[5]
You can use the Xamarin Community Toolkit LifecycleEffect
to call some code when the renderer for the Entry is initialized/cleaned up. Combine this with OnAppearing
to reliably show the keyboard without using cheap DoEvents
hacks like await Task.Yield()
or await Task.Delay(100)
.
XAML:
<Entry x:Name="userName">
<Entry.Effects>
<xct:LifecycleEffect Loaded="LifecycleEffect_Loaded" Unloaded="LifecycleEffect_Unloaded" />
</Entry.Effects>
</Entry>
C#:
private bool userNameLoaded = false;
protected override void OnAppearing()
{
base.OnAppearing();
if (userNameLoaded)
{
userName.Focus();
}
}
private void LifecycleEffect_Loaded(object sender, System.EventArgs e)
{
if (sender == userName)
{
userNameLoaded = true;
userName.Focus();
}
}
private void LifecycleEffect_Unloaded(object sender, System.EventArgs e)
{
if (sender == userName)
{
userNameLoaded = false;
}
}
OnAppearing
won't be called after background + resume on iOS, so you'll need to hook into Application.OnResume
to show focus if the user backgrounds + restores the app on iOS:
protected override void OnResume()
{
if (Xamarin.Forms.Device.RuntimePlatform == Xamarin.Forms.Device.iOS)
{
// TODO: Use Messenger, check Shell.Current.CurrentPage, etc. to set focus.
}
}
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | |
Solution 2 | Wim Kuijpers |
Solution 3 | Yasin Patel |
Solution 4 | Sean McAvoy |
Solution 5 | Trevor Balcom |