8
votes

I'm using Xamarin Forms to develop a Cross-Platform app.

I want to change the Text of the button "Do Something" to something like "Wait", while the code is processing the routine, and back to "Do Something" after the code finish to run.

The problem is: The button text only change after the code is completed.

Simple Exemple:

private void Button_Clicked(object sender, EventArgs e)
{
    var btn = (Button)sender;
    btn.Text = "Wait";

    ...some code..

    btn.Text = "Do Something";
}

There's some way to "force" the update of Text to "Wait" before the code finish?

3
The UI thread is being blocked by your code execution. You need to modify the Button text in a separate threadSandeep Bansal
Thanks for the info Sandeep!Leonardo Lima Almeida

3 Answers

5
votes
    private async void Btn1_Clicked(object sender, EventArgs e)
    {
       btn1.Text = "Wait";

        await Task.Run(async () =>
        {
            for (int i = 1; i <= 5; i++)
            {
                //if your code requires UI then wrap it in BeginInvokeOnMainThread
                //otherwise just run your code
                Device.BeginInvokeOnMainThread(() =>
                {
                    btn1.Text = $"Wait {i}";
                });
                await Task.Delay(1000);
            }
        });

        btn1.Text = "Done";


    }
2
votes

The main point is that you want to have the async operations you need to wait for to not happen on the UI-thread. Yuri's solution does this, but you could also do your async operations and then if you need to make any updates to the UI, you just need that to happen on the UI-Thread to prevent the UI from locking up.

The real problem comes from how your threads are working. If you want to see this real time, pull open the Threads debug pad in Xamarin Stud and watch the threads as you step through the code.

Let's take your code as an example:

private void Button_Clicked(object sender, EventArgs e)
{
    var btn = (Button)sender;
    btn.Text = "Wait";
    ...

As we enter the "Button_Clicked" event handler, we are now on the UI-Thread. How do we know? because the user clicked the button and we are here. Everything has run on the main UI thread. Whatever we do next in our code will continue to run on that thread unless the Operating System determines that it needs to manage the threads for some reason (performance, that's how the OS works etc).

If your some code does some asynchronous Task, like sending a web request call to get some data from a REST API, you need to be aware of what Thread you are on. We cannot guarantee that our web request call will run on the UI-Thread or any Thread without manually handling that. That's the benefit of our advanced Operating Systems, it handles the multi-core multi-thread processing for us.

For this reason, we want to make sure that any updates to the UI happen on the UI-Thread. The correct solution to your code would be below:

private async void Btn1_Clicked(object sender, EventArgs e)
{
   var btn = (Button)sender;
   btn.Text = "Wait";

   ...some code...maybe async or not (take out async above if not)

   Device.BeginInvokeOnMainThread(() =>
   {
        btn.Text = "Do Something";
   });
}    

This works for us because we are telling Xamarin.Forms to run that command on the Main Thread. Xamarin.Forms exposes the main UI-Thread this way. Another option would be to take the context of our UI-Thread and caching it to be used when making updates. This is something that is typically done in Xamarin.Android and Xamarin.iOS applications.

The real way to do all of this is through Data-Bindings. Xamarin.Forms was built with the intention of being used with a Model-View-ViewModel(MVVM) pattern. Xamarin.Forms Bindings handle all of the context switches for you when you call OnPropertyChanged. That way you could just update the property in your ViewModel bound to your buttons text.

I hope this helps provide a better understanding of what is happening on the Thread level!

Disclosure: I work for Xamarin/Microsoft

1
votes

In Xamarin.forms use this way

  Device.BeginInvokeOnMainThread(() =>
       {
      Task.Delay(4000).Wait();
            btn.Text = "Do Something";
       });