1
votes

I am new to Gtk and this is my very first Gtk project. I am using C and Gtk+ 2 on Code::Blocks on Windows 7. In my project, I have a main window which initially shows a home screen, holding a button to "Run" the project and some other buttons ("Exit", "About" etc). When this "Run" button is clicked, the program has to read and write some large text files, and then show a new screen with some new data (I am creating this new screen by destroying all the previous children of the main window and putting in new stuff into it).

Now due to large sizes of those text files, the program lags for some time when Run is clicked, so I want to show an intermediate screen having some message like "Loading...". But I am unable to do it, because this intermediate screen is never shown.

Here is what I have; I hope the code makes it clear:

GtkWidget *windowMain = NULL;
GtkWidget *vboxMain = NULL;
//These 2 are global.

void home_screen()    //Works well
{
    //...Created new main window...
    //...Created new main vbox...
    //...Added vboxMain to windowMain...


    GtkWidget *menuButton = gtk_button_new_with_label("Run");
    g_signal_connect (menuButton, "clicked",  G_CALLBACK (intermediate_screen), NULL);
    gtk_box_pack_start (GTK_BOX (vboxMain), menuButton, TRUE, TRUE, 0); //Add button to vboxMain.

    gtk_widget_show_all (windowMain);
}


void intermediate_screen()    //Is not shown at correct time 
{
    // CLEAR MAIN WINDOW:
    GList *children, *iter;
    children = gtk_container_get_children(GTK_CONTAINER(windowMain));
    for(iter = children; iter != NULL; iter = g_list_next(iter))
    gtk_widget_destroy(iter->data);
    g_list_free(children);

    GtkWidget *label = gtk_label_new(NULL);
    gtk_label_set_markup(GTK_LABEL(label1), "<b>Loading...</b>");
    gtk_container_add (GTK_CONTAINER (windowMain), label);

    gtk_widget_show_all(windowMain);

    prepare_files();    //Function to work with the text files
}


void prepare_files()     //Starts working before "Loading..." is shown
{
    //Some file handling which takes some time to complete.

    next_screen();
}

void next_screen()
{
    // CLEAR MAIN WINDOW AGAIN TO CLEAR THE "Loading..." LABEL:
    GList *children, *iter;
    children = gtk_container_get_children(GTK_CONTAINER(windowMain));
    for(iter = children; iter != NULL; iter = g_list_next(iter))
    gtk_widget_destroy(iter->data);
    g_list_free(children);

    vboxMain = gtk_vbox_new (FALSE, 5);
    gtk_widget_set_size_request (vboxMain, 600, 600);
    gtk_container_add (GTK_CONTAINER (windowMain), vboxMain);

    //Add components to the vboxMain

    gtk_widget_show_all(windowMain);
}

Problem is that intermediate_screen() shows the "Loading" message only after the prepare_files() function has been completed - and hence it is of no use. During all that time, only homescreen is being shown... In fact, the next_screen() is shown immediately after, so "Loading" does not even show up. But shouldn't it show the loading message during all that lag, as the prepare_files() function is called later?

What am I doing wrong and how should I correctly do it?

Sorry if this was something obvious. As I said I am a beginner in Gtk+ .

1

1 Answers

2
votes

Gtk+ drawing happens on a timer that fires in the same thread as your code. In other words the drawing code can only run when your code is not running: when "Run" is clicked, the next draw can only happen after intermediate_screen() has returned (and "Loading..." screen has already been replaced).

You could add some hacks in intermediate_screen() that runs a few iterations of the main loop so at least one draw would happen but that would still be bad and unresponsive design. There are 2 possible better solutions:

  1. Use an asynchronous API like GIO to read and write to files: this means no function in your code ever runs long enough to disrupt drawing or interacting with the UI. Implementing this is a little more complex than synchronous reads (like the ones you probably use now), one possible short version is: Create a GFile, call g_file_read_async(), in the callback call g_file_read_finish(), create a GDataInputStream from the GFileInputStream you get, then use g_data_input_stream_read_*_async() functions to start reading lines or other chunks of the file, and in that callback get the data with g_data_input_stream_read_*_finish().

  2. Alternatively create a new thread and read the data in that thread using the same reading code you now use. The down side is that you now have to deal with thread-safety yourself -- this can be difficult, and threading bugs are the hardest to debug.

In my opinion option #1 is the best compromise in almost all situations where an asynchronous API is available. Note that if your own processing of the file contents takes a long time, you should do that in small chunks as well (usually it works out nicely so you can e.g. read one line or larger chunk asynchronously and process the line/chunk in the callback).