Search code examples
c#monoxamarin-studiogtk#

How to Show a MessageDialog while executing a process in background and close it after finished


I'm creating a simple desktop application using Gtk#, When the user clicks a button I want to show a "loading indicator" MessageDialog and make some processing in background, when the process is finished close the dialog and update some controls from the UI.

I'm very new to Gtk# and Mono, so my code looks like this:

protected void OnBtnClicked(object sender, EventArgs e)
{
    try
    {
        Task.Factory.StartNew(() =>
        {
            var dlg = new MessageDialog(this, DialogFlags.Modal, MessageType.Info, ButtonsType.None, "Processing...");
            dlg.Run();          

            //Some sync calls to remote services
            //...

            //The process finished so close the Dialog
            dlg.Destroy();

            //Here: Update the UI with remote service's response
            //txtResult.Buffer.Text = result.Message;
        });
    }
    catch (Exception ex)
    {
        var dlg = new MessageDialog(this, DialogFlags.Modal, MessageType.Error, ButtonsType.Close, ex.Message);
        dlg.Title = "Error";
        dlg.Run();
        dlg.Destroy();
    }
}

This code is showing the MessageDialog but it never closes.

Mono version: 4.4.2

IDE: Xamarin Studio Community Edition 6.0.2

Gtk# version: 2.12.38


Solution

  • After reading a guide for responsive Mono applications and asking to Miguel de Icaza through Twitter I've found the way of doing this.

    Things to take into account:

    1) Never create or try to modify UI elements from another thread.

    2) If you need to modify UI controls from another thread use the Application.Invoke() method inside that thread.

    3) The Run() method from MessageDialog class waits for user interaction to get closed i.e Click the close button or something that calls a Close/Destroy event. The use of that method is wrong in this scenario because I will close the MessageDialog from my code, so the right method to show the dialog is Show().

    With that in my mind, my final code looks like this:

    protected void OnBtnClicked(object sender, EventArgs e)
    {
        try
        {
            var mdCalculate = new MessageDialog(this, DialogFlags.Modal, MessageType.Info, ButtonsType.None, "Processing...");
            mdCalculate.Title = "Calculate";
            mdCalculate.Show();
    
            Task.Factory.StartNew(() =>
            {
                //Some sync calls to remote services
                //...
    
                //returns the data I will show in the UI, lets say it's a string
                return someData;
            }).ContinueWith((prevTask) =>
            {
                Application.Invoke((send, evnt) =>
                {
                    txtResult.Buffer.Text = prevTask.Result; //this is the string I returned before (someData)
                    mdCalculate.Hide();
                    mdCalculate.Destroy();
                });
            });
        }
        catch (Exception ex)
        {
            var dlg = new MessageDialog(this, DialogFlags.Modal, MessageType.Error, ButtonsType.Close, ex.Message);
            dlg.Title = "Error";
            dlg.Run();
            dlg.Destroy();
        }
    }
    

    Demo:

    enter image description here