Setting the Focus to an Entry in Xamarin.Forms - c#

This is just a simplified example, but I'm trying to set this up so that when I open up this page in my Application, the first thing that happens is the keyboard pops up ready for the user to type in their response to an Entry field.
var namelabel = new Label { Text = "What is your name?" };
var nameentry = new Entry { Placeholder = "Type here..." };
var colorlabel = new Label { Text = "What's your fav color?" };
var colorentry = new Entry { Placeholder = "Type here..." };
Content = new StackLayout {
Spacing = 15,
Children = { namelabel, nameentry, colorlabel, colorentry }
};
How can I set the focus of the page to the first entry? And additionally, after the user has put in their first entry, how could I set this up so that the user could press a "Next" Button on the Keyboard (or something along those lines) and the app will take them to fill in the second entry?

Use the Focus method
nameentry.Focus();
If you want the focus to be set when your page appears, you should probably do this in the OnAppearing method
protected override void OnAppearing ()
{
base.OnAppearing ();
nameentry.Focus();
}

In one of my projects I did something like this. Please try the following example:
public class EntryFocusBehavior : Behavior<Entry>
{
public string NextFocusElementName { get; set; }
protected override void OnAttachedTo(Entry bindable)
{
base.OnAttachedTo(bindable);
bindable.Completed += Bindable_Completed;
}
protected override void OnDetachingFrom(Entry bindable)
{
bindable.Completed -= Bindable_Completed;
base.OnDetachingFrom(bindable);
}
private void Bindable_Completed(object sender, EventArgs e)
{
if (string.IsNullOrEmpty(NextFocusElementName))
return;
var parent = ((Entry)sender).Parent;
while (parent != null)
{
var nextFocusElement = parent.FindByName<Entry>(NextFocusElementName);
if (nextFocusElement != null)
{
nextFocusElement.Focus();
break;
}
else
{
parent = parent.Parent;
}
}
}
}
And then XAML:
!!! Please let me know if I made a mistake in the code.

Just inside OnAppearing(), add the following code,
protected async override void OnAppearing()
{
await Task.Run(async () =>
{
await Task.Delay(100);
Device.BeginInvokeOnMainThread(async () =>
{
txtName.Focus();
});
});
}
Note: txtName is the reference name of your Entry Field.

Focus() needs to have a visible page and visible control to focus on.
The issue in my case was that is is necessary that OnAppearing has to exit before a page is shown / visible. What helped in my case is to wait for page visibility in a different thread and then set the focus in the main (UI) thread:
protected override void OnAppearing()
{
base.OnAppearing();
Task.Run(() =>
{
while (!IsVisible)
{
Debug.WriteLine("Page not visible, waiting...");
Task.Delay(50).Wait();
}
Device.BeginInvokeOnMainThread(() =>
{
bool gotFocus = Entry.Focus();
if (!gotFocus)
Debug.WriteLine("Could not set focus.");
});
});
}

So long as the element to focus on is the topmost element, this should work. I place this in the OnAppearing method.
base.OnAppearing();
Entry entry = this.FindByName<Entry>("NameOfEntryElement");
entry.Focus();
The source of this info is here:
https://forums.xamarin.com/discussion/100354/entry-focus-not-working-for-android
There is further discussion in the article about timing issues.

I know this is an old thread but this might work for someone as it worked for me.
protected override async void OnAppearing()
{
base.OnAppearing();
await Task.Delay(500);
await Task.Run(() =>
{
entryname.Focus();
});
}

It's possible that Focus or RequestFocus because your control didn't load again. You can override onAppearing but even if you use that, it may possible that didn't work because the control is not set again.
So you can use onAppearing, but for the fist apparing you may use Xamarin community toolkit. And LifeCycleEffect
<Entry x:Name="myEntry">
<Entry.Effects>
<xct:LifecycleEffect Loaded="LifeCycleEffect_Loaded" />
</Entry.Effects>
</Entry>
Here in C#
void LifeCycleEffect_Loaded(object? sender, EventArgs e)
{
if ( sender is Entry && (sender as Entry ).Name != null && (sender as Entry).Name.Equals("myEntry") )
// myEntry.Focus() or myEntry.RequestFocus() or (sender as Entry).Focus()
}
I suggest you to take notes from this link
https://learn.microsoft.com/en-us/xamarin/community-toolkit/effects/lifecycleeffect

Can anyone explain to me why this doesn't work
protected override void OnAppearing()
{
base.OnAppearing();
CustomerNameEntry.Focus();
}
But this does (adding async and Task.Delay(1))
protected override async void OnAppearing()
{
base.OnAppearing();
await Task.Delay(1);
CustomerNameEntry.Focus();
}
I'd rather not add this clutter, it seems hacky but it's the only way I've been able to get it to work (I've also tried invoking on main thread, to no avail).

The simple one did not work for me:
protected override void OnAppearing()
{
MyEntry.Focus();
}
I had to wait an unspecified amount of time, but did not want to wait longer than necessary. Also, I didn't want to pollute OnAppearing() with delay code, so implemented a helper with a Focus methdo that works:
using System.Threading.Tasks;
using Xamarin.Forms;
namespace MyApp.Xmrn.Views.Helpers
{
internal static class ViewHelper
{
// Disable the warning about a non-awaited async, as we use it as
// fire and forget and return from the method immediately.
#pragma warning disable 1998, 4014
/// <summary>
/// Entry.Focus replacement, that tries to focus a control
/// multiple times until it succeeds.
/// </summary>
/// <param name="entry">Entry control to be focused.</param>
internal static async Task Focus(Entry entry)
{
Task.Run(async () =>
{
int threshold = 20;
while (!entry.Focus() && threshold-- > 0)
await Task.Delay(50);
});
}
#pragma warning restore 1998, 4014
}
}
And then to use it
protected override async void OnAppearing()
{
base.OnAppearing();
await ViewHelper.Focus(MyEntry);
}

protected override void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
base.OnPropertyChanged(propertyName);
if (propertyName == "Renderer")
{
myEntry.Focus();
}
}

This worked perfectly for me :D
protected override async void OnAppearing() {
base.OnAppearing();
while(!passwordInput.Focus()) { await Task.Delay(50); }
}

Related

Xamarin forms ListView keep populating data from List<object> instead of simply display the objet once

I keep encountering this problem no matter what :
The List<Object> listArticles is simply keep adding data everytime the page appears ,it keep populating the data over and over again instead of simply display them once
I have tried to declare a tempList everytime the function is called , but no success
List<PlanDefinition> tempListArticles = new List<PlanDefinition>(listArticles)
How to encouter this problem?
async protected override void OnAppearing()
{
listArticles= articleView.getArticlesFromPlan();
PopulateOrderLists(listArticles);
base.OnAppearing();
}
protected override void OnDisappearing()
{
base.OnDisappearing();
}
private void PopulateOrderLists(List<PlanDefinition> listArticles)
{
List<PlanDefinition> tempListArticles = new List<PlanDefinition>(listArticles);
OrderTemplate prod = new OrderTemplate();
for (var i = 0; i < tempListArticles.Count; i++)
{
prod = new OrderTemplate();
prod.BindingContext = tempListArticles[i].Name;
OrderInformation.Children.Add(prod);
}
}
Only call your PopulateOrderLists once, assumably when the listArticles has not been populated, but adjust it to your requirements of when you want to replace the current listArticles or add to it...
Example:
async protected override void OnAppearing()
{
if (listArticles != null && listArticles.Count > 0)
{
listArticles= articleView.getArticlesFromPlan();
PopulateOrderLists(listArticles);
}
base.OnAppearing();
}

Prevent users from swiping further than expected using CarouselPage Xamarin.Forms

I am struggling to find a solution to this issue with CarouselPages:
Basically, users are able to swipe further than what I wish them to, and this shows a white page behind the current one being displayed. Everything works perfectly fine on Android, but not on iOS.
I have tried this: https://forums.xamarin.com/discussion/72031/is-there-a-way-to-disable-swipe-gesture-when-using-carouselpage but it completely disables the animation when changing. I still want the animation to be there, but not have the swipe enabled.
Thank you in advance.
Why not just remove all Pages but one as long as swiping should be disabled?
using System.Collections.Generic;
using Xamarin.Forms;
namespace MyNamespace
{
public class MyClass : CarouselPage
{
protected override void OnAppearing()
{
base.OnAppearing();
foreach (var child in Children)
{
if (child != CurrentPage)
Children.Remove(child);
}
}
public void DisplayPage(ContentPage page)
{
Children.Clear();
Children.Add(page);
}
}
}
From: Is there a way to disable swipe gesture when using Carouselpage
In response to #Héctor Manuel Martínez Durán's answer:
Foreach loop will throw exception Collection was modified; enumeration operation may not execute - to fix:
for (int i = 0; i < Children.Count; i++)
{
if (Children[i] != CurrentPage)
Children.Remove(Children[i]);
}
Original:
protected override void OnAppearing()
{
base.OnAppearing();
foreach (var child in Children)
{
if (child != CurrentPage)
Children.Remove(child);
}
}
Sample implementation:
public class MyPage : CarouselPage
{
ContentPage ChildPage1 = new ContentPage();
ContentPage ChildPage2 = new ContentPage();
public MyPage()
{
Padding = new Thickness(0, 30, 0, 0);
Children.Add(ChildPage1);
Children.Add(ChildPage2);
CurrentPage = ChildPage1;
}
protected override void OnAppearing()
{
base.OnAppearing();
for (int i = 0; i < Children.Count; i++)
{
if (Children[i] != CurrentPage)
Children.Remove(Children[i]);
}
}
public void DisplayPage(ContentPage page)
{
Children.Clear();
Children.Add(page);
}
}
NOTE: Attempted to submit as an edit, results below
Jo*** review: Reject
This edit does not make the post even a little bit easier to read, easier to find, more accurate or more accessible. Changes are either completely superfluous or actively harm readability.
Ho*** review: Reject
This edit was intended to address the author of the post and makes no sense as an edit. It should have been written as a comment or an answer.
List<ContentPage> pageList;
public MainPage ()
{
InitializeComponent ();
}
protected override void OnAppearing()
{
base.OnAppearing();
pageList = Children.ToList();
foreach (var child in Children.ToList())
{
if (child != CurrentPage)
{
Children.Remove(child);
}
}
}
private void Button_Clicked(object sender, System.EventArgs e)
{
Children.Add(pageList[1]);
CurrentPage = Children[1];
Children.RemoveAt(0);
}
I came across this self-contained custom solution. Single code file and simple usage.
Article here: https://www.roccacreative.co.uk/fyi/xamarin-forms-manual-carousel
Code: https://github.com/roccacreative/XamarinForms-Carousel

MvvmCross View Model Initialize Complete

In version 5 of MvvmCross, there has been added an asynchronous Initialize override where you can do you heavy data loading.
public override async Task Initialize()
{
MyObject = await GetObject();
}
Is there a way to determine in the View that the Initialize has completed? Say in the View I want to set the Toolbar Title to a display a field in MyObject
MyViewModel vm;
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
// Create your application here
this.SetContentView(Resource.Layout.MyView);
var toolbar = (Toolbar)FindViewById(Resource.Id.toolbar);
SetSupportActionBar(toolbar);
vm = (MyViewModel)this.ViewModel;
SupportActionBar.Title = vm.MyObject.Name;
}
On the line that sets the SupportActionBar.Title, is there a way to know for sure whether the Initialize task has completed and if not, get notified when it does?
UPDATE:
I tried set two correct answers because #nmilcoff answered my actual question and #Trevor Balcom showed me a better way to do what I wanted.
Yes, you can subscribe to InitializeTask's property changes.
Something like this will work:
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
// your code
ViewModel.PropertyChanged += MyViewModel_PropertyChanged;
}
private void MyViewModel_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if(e.PropertyName == nameof(ViewModel.InitializeTask) && ViewModel.InitializeTask != null)
{
ViewModel.InitializeTask.PropertyChanged += ViewModel_InitializeTask_PropertyChanged;
}
}
private void ViewModel_InitializeTask_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if(e.PropertyName == nameof(ViewModel.InitializeTask.IsSuccessfullyCompleted))
SupportActionBar.Title = ViewModel.MyObject.Name;
}
Of course, it could be the case that it might be easier to just listen to ViewModel.MyObject.Name property changes. But the above is a generic way to listen to InitializeTask property changes.
You can learn more about InitializeTask and MvxNotifyTask in the official documentation.
On Xamarin Forms:
I wanted to add Property Changed event login in the VM to be able to test it, so:
View.xaml.cs
protected override void OnViewModelSet()
{
base.OnViewModelSet();
var vm = this.DataContext as SearchMovieViewModel;
if (vm is null)
{
return;
}
vm.OnViewModelSet();
}
On your ViewModel:
/// <summary>
/// This method should be called in every View Code Behind when you
/// need to subscribe to InitializeTask changes.
/// </summary>
public void OnViewModelSet()
{
if (this.InitializeTask is null)
{
return;
}
this.InitializeTask.PropertyChanged += this.InitializeTask_PropertyChanged;
}
Finally on your View Model implement whatever check you need to do for MvvmCross InitializeTask, in my case I used IsCompleted Property, but you can use whichever you need:
private void InitializeTask_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(this.InitializeTask.IsCompleted))
{
// do something
}
}
Don´t forget to unsubscribe, for example when the view is destroyed. You can override this method in your View Model:
public override void ViewDestroy(bool viewFinishing = true)
{
base.ViewDestroy(viewFinishing);
this.InitializeTask.PropertyChanged -= this.InitializeTask_PropertyChanged;
}
The Toolbar also supports data binding the Title property like so:
<android.support.v7.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:popupTheme="#style/AppTheme.PopupOverlay"
app:MvxBind="Title MyObject.Name" />

Xamarin Forms ListView Programatic Refresh Not Stopping on Android When Page Loaded

I have Portable project in Visual Studio 2015 with a ListView that gets populated with some data via an API call through the following function refreshData:
async Task refreshData()
{
myListView.BeginRefresh();
var apiCallResult = await App.Api.myApiGetCall();
myListView.ItemsSource = apiCallResult;
myListView.EndRefresh();
}
refreshData() is called in
protected override void OnAppearing()
{
base.OnAppearing();
refreshData();
}
Everything is working fine except on Android where the refresh indicator is not stopping or disappearing on EndRefresh() when the page is initially loaded. The page is in a TabbedPage so I can go to a different tab and then return to this page and the refresh indicator properly starts and stops with completion of my API call.
Why is refresh is not stopping when the page initially loads on Android? Any help would be appreciated.
Note: This works perfectly fine when I run on iOS.
So far I've tried:
replacing myListView.BeginRefresh() with myListView.IsRefreshing = true and myListView.EndRefresh() with myListView.IsRefreshing = false
Using Device.BeginInvokeOnMainThread(() => {//update list and endRefresh}).
Using async void refreshData() instead of async Task refreshData().
Personally I can get this problem when I start ListView refreshing in the Page Contructor and stop it after the data is loaded. Sometimes (quite often) Xamarin.Forms ListView doesn't cancel refreshing animation.
I believe you faced with a quite common issue with Android SwipeRefreshLayout: it may not stop refreshing animation after setRefreshing(false) called. Native Android developers use the following approach:
swipeRefreshLayout.post(new Runnable() {
#Override
public void run() {
mSwipeRefreshLayout.setRefreshing(refreshing);
}
});
Interestingly, Xamarin.Forms uses this approach when it sets initial refreshing status (code); however, it is not enough. You need a custom renderer:
public class ExtendedListViewRenderer : ListViewRenderer
{
/// <summary>
/// The refresh layout that wraps the native ListView.
/// </summary>
private SwipeRefreshLayout _refreshLayout;
public ExtendedListViewRenderer(Android.Content.Context context) : base(context)
{
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
_refreshLayout = null;
}
base.Dispose(disposing);
}
protected override void OnElementChanged(ElementChangedEventArgs<ListView> e)
{
base.OnElementChanged(e);
_refreshLayout = (SwipeRefreshLayout)Control.Parent;
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == ListView.IsRefreshingProperty.PropertyName)
{
// Do not call base method: we are handling it manually
UpdateIsRefreshing();
return;
}
base.OnElementPropertyChanged(sender, e);
}
/// <summary>
/// Updates SwipeRefreshLayout animation status depending on the IsRefreshing Element
/// property.
/// </summary>
protected void UpdateIsRefreshing()
{
// I'm afraid this method can be called after the ListViewRenderer is disposed
// So let's create a new reference to the SwipeRefreshLayout instance
SwipeRefreshLayout refreshLayoutInstance = _refreshLayout;
if (refreshLayoutInstance == null)
{
return;
}
bool isRefreshing = Element.IsRefreshing;
refreshLayoutInstance.Post(() =>
{
refreshLayoutInstance.Refreshing = isRefreshing;
});
}
}
Try this:
async void refreshData()
{
Device.BeginInvokeOnMainThread(() => {
myListView.BeginRefresh();
var apiCallResult = await App.Api.myApiGetCall();
myListView.ItemsSource = apiCallResult;
myListView.EndRefresh();
});
}
Apparently, there is no need for "Task" anymore.
If the error occurs only in Android, it's possible that it's just the way Android handles threads. It does not allow threads to change visual elements directly. Sometimes when we try to do that it throws a silent exception and the code action have no practical effect.
You need to follow MVVM Pattern
On your ViewModel you need to:
Implement INotifyPropertyChanged
Define properties like:
private bool _IsRefreshing;
public bool IsRefreshing
{
get { return _IsRefreshing; }
set { SetProperty(ref _IsRefreshing, value; }
/*So every time the property changes the view is notified*/
}
Define the method that fetch your data, in your case refreshData()
Toggle the IsRefreshing true/false when needed
On your Page you need to:
Bind the listview itemSource to a VM property with the SetPropertyValue
Bind ListView.IsRefreshing to ViewModel's IsRefreshing:
MyListView.SetBinding<MyViewModel>(ListView.IsRefreshing, vm => vm.IsRefreshing);
Here is a great article talking about INotifyPropertyChanged

Is there a way to clear the Navigation Service or not register a frame?

I'm developing an aplication for Windows 7, and I need to block a page.. my solutions are not register that frame or clear all the frames in back.
Guessing that you mean you have a page on the Back-stack that you want to remove --
In the new Mango SDK, there's a Method you can try NavigationService.RemoveBackEntry
However it might be easier to just use a boolean and check in OnNavigatedTo:
At page where you need to go back > 1 pages:
in App.xaml.cs:
public static bool IsBackwardNavigation = false;
public static string PageContext = string.Empty;
Page2.xaml.cs :
public void YourFunction()
{
App.PageContext = "MainPage";
App.IsBackwardNavigation = true;
if (NavigationService.CanGoBack)
NavigationService.GoBack();
}
And in each page's OnNavigatedTo:
Page1.xaml.cs:
string Page1 = "Page1";
protected override void OnNavigatedTo(object sender, NavigationEventArgs e)
{
if (App.IsBackwardNavigation)
{
if (!Page1.Equals(App.NavigationContext)
{
//since this page's name is not the correct page, the page will go back again.
if (NavigationService.CanGoBack)
NavigationService.GoBack();
}
else
{
//this is the page we're trying to get to
App.IsBackwardNavigation = false;
App.NavigationContext = string.Empty;
}
}
}
}

Categories