Cannot reopen dialog after closed with "X" if using GtkBuilder for construction

I get strange opening behavior, when I show a dialog from gtk_builder_get_object and close it with “X” (Window manager “window close” button):

  1. Closing the dialog with “X” requires two clicks; One click will not close the dialog
  2. Reopening of the dialog is not possible.

I prepared a video showing what happens:

Q Can someone tell me what I am doing wrong?

This is my full example code:

/*
  Example program that shows problem with GtkBuilder:

  - run
  - click button
  - <dialog opens>
  - click "X" to close window
  - <nothing happens - already strange>
  - again: click "X" to close window
  - <dialog closes>
  - click button
  - ACTUAL: <dialog opens, but is empty>
  - EXPECTED: <dialog opens as defined>
*/
#include <gtk/gtk.h>

static GtkBuilder * builder;

// Imagine this is actually content a resource
// file containing other dialogs well.
const char * dialog = ""
"<interface>"
"  <object class=\"GtkDialog\" id=\"dialog1\">"
"    <child internal-child=\"vbox\">"
"      <object class=\"GtkBox\" id=\"vbox1\">"
"        <child internal-child=\"action_area\">"
"          <object class=\"GtkBox\" id=\"hbuttonbox1\">"
"            <child>"
"              <object class=\"GtkButton\" id=\"ok_button\">"
"                <property name=\"label\">gtk-ok</property>"
"                <signal name=\"clicked\" handler=\"ok_button_clicked\"/>"
"              </object>"
"            </child>"
"          </object>"
"        </child>"
"      </object>"
"    </child>"
"  </object>"
"</interface>";



void
on_button_clicked (GtkButton *button,
                   gpointer   nothing)
{
  GtkWidget * dialog = GTK_WIDGET (gtk_builder_get_object (builder, "dialog1"));
  gtk_widget_show_all (GTK_DIALOG (dialog));
  gtk_dialog_run (GTK_DIALOG (dialog));
}



GtkWindow*
create_window()
{
  GtkWindow *window = GTK_WINDOW (gtk_window_new (GTK_WINDOW_TOPLEVEL));

  // initialize builder
  builder = gtk_builder_new_from_string (dialog, g_utf8_strlen (dialog, 30000));

  // create means to open the dialog
  {
    GtkWidget *layout = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
    gtk_container_add (GTK_CONTAINER (window), layout);

    GtkWidget *button = gtk_button_new_with_label ("open dialog");
    gtk_container_add (GTK_CONTAINER (layout), button);

    g_signal_connect (button, "clicked", G_CALLBACK (on_button_clicked), NULL);
  }

  return window;
}



int
main(int   argc,
     char *argv[])
{
    gtk_init (&argc, &argv);

    GtkWindow *window = create_window ();

    gtk_widget_show_all (GTK_WIDGET (window));
    gtk_main();

    return 0;
}

The ownership of top level windows (GtkWindow, GtkDialog, etc) is transferred from GtkBuilder to GTK itself:

A GtkBuilder holds a reference to all objects that it has constructed and drops these references when it is finalized. This finalization can cause the destruction of non-widget objects or widgets which are not contained in a toplevel window. For toplevel windows constructed by a builder, it is the responsibility of the user to call gtk_widget_destroy() to get rid of them and all the widgets they contain.

Any GtkWindow instance (and its derived types, like GtkDialog) will call gtk_widget_destroy() as the default behaviour for the action of closing a window using the “close” button on the window decoration. If you want to avoid that, you have two options:

I also recommend you don’t use gtk_dialog_run(). Always connect to the “response” signal, instead. If you want to prevent user interaction with the rest of the application, you can make the dialog modal using GTK_DIALOG_MODAL.

This shouldn’t matter, because GtkBuilder does not get finalized in my code - if I’m not mistaken on the meaning of finalize.

How would I have to handle GTK_RESPONSE_DELETE_EVENT to prevent its effect?
I would expect the callback to return TRUE/FALSE depending on, if gtk should go on cleaning up, but the callback is void

Even doing this, will not prevent the window from closing:

void
on_dialog_response(GtkDialog *dialog,
                   int        response_id,
                   gpointer   user_data)
{
  return;
}


void
on_button_clicked(GtkButton *button,
                  gpointer   nothing)
{
  GtkWidget * dialog = GTK_WIDGET (gtk_builder_get_object (builder, "dialog1"));
  g_signal_connect (dialog, "response", G_CALLBACK (on_dialog_response), NULL);
  gtk_widget_show_all (GTK_DIALOG (dialog));
  gtk_dialog_run (GTK_DIALOG (dialog));
}

…And maybe an idea what’s the deal with double clicking the close button?

You missed the rest of the quote, regarding the top level windows.

You can stop the signal emission by using g_signal_stop_emission_by_name(dialog, "response") from within the signal handler—but I’d like to point out that you’re really doing something wrong, here. Dialogs should not be kept around and recycled; if you find yourself doing this, then you should really use a GtkWindow and use the delete-event signal instead.

Alternatively, you could create your own GtkDialog subclass, and use a composite widget template to load the UI from an XML definition. Once you do that, you can then just create a new instance of your subclass every time you need it.

Solution

The immediate solution for me will be using the delete-event as you suggested. Thank you!

Fundamental handling of GtkBuilder

Something might be fundamentally wrong with my code. So I’d like to add a follow-up question.

  1. If I don’t do that, I would have to gtk_builder_new_from_xxx everytime the dialog opens. I.e. GtkBuilder would everytime have to parse the string/file/resource. Is it supposed to be like that? This feels like a lot of parsing overhead for the same object again and again…

  2. In my (real) case (not the example), there are several dialogs inside one UI definition file. Could it be, that usually only one window goes into one definition file?

The cost of using GtkBuilder is not in the parsing, but in the object instantiation—and you still need to pay that when building objects by hand. The XML parser is really fast, unless you have a multi megabytes UI definition file, in which case you should split it out into multiple files anyway, or it’s going to be unmaintainable.

Summarize

The problem was, that the code tried to “recycle” the object from GtkBuilder, but the window-close-button destroyed the dialog.

The Solution is to prevent the dialog to be destroyed:


gboolean
prevent_destruction(GtkWidget *dialog,
                    GdkEvent  *event,
                    gpointer   unused)
{
  // prevent other handlers from destroying:
  return TRUE; 
}


void
on_button_clicked(GtkButton *button,
                  gpointer   window)
{
 // [...]

  g_signal_connect (dialog, "delete-event", G_CALLBACK (prevent_destruction), NULL);


 // [...]
}

Note however that this recycling of windows is not the recommended way.

The recommended way is to create a new GtkBuilder and reparse the resource:

void
on_button_clicked(GtkButton *button,
                  gpointer   window)
{
  GtkBuilder * builder = gtk_builder_new_from_string (dialog, g_utf8_strlen (dialog, 30000));
  GtkWidget * dialog = GTK_WIDGET (gtk_builder_get_object (builder, "dialog1"));

  g_signal_connect (dialog, "response", G_CALLBACK (on_dialog_response), NULL);

 
  gtk_widget_show_all (GTK_WIDGET(dialog));
}

Gained insights

  1. GtkBuilder is not a factory, it will create every object only once.
    Maybe subclassing a widget and use composite widget template might be like a factory (unfinished investigation).
  2. Objects from GtkBuilder are not meant to be reused/recycled. You should destroy closed windows and create a new GtkBuilder for everytime the window should be opened again.
  3. Don’t use gtk_dialog_run for making dialogs modal. They are only modal until a (any) response happened… and the function is removed in GTK4.
  4. Signal emission can generally be stopped with g_signal_stop_emission_by_name.

That post makes less sense for me now. Apparently GtkBuilder instantiates all objects it parses. That means it doesn’t matter if parsing or instantiation is slow, because both is happening on gtk_builder_new.

If I don’t recycle dialogs and instead create a new GtkBuilder each time the dialog is opened, I have to pay the cost of instantiation of dialogs that I don’t need. I feel there’s a lot of unnecessary work going on, then.

Shouldn’t then be the suggested way to have only one dialog per file/ string / resource?

You have to pay the cost of instantiating an object regardless of whether you do it through GtkBuilder or the object constructor; my point is that the XML parsing cost is minimal compared to the actual instantiation.

Yes: you should break up your UI definition file to one top level per file, so you only parse the UI definition for the dialog only when you need to create it. That will lead you towards using templates and composite widgets with your own dialog subclass, so you automatically parse the UI definition when instantiating that class.

1 Like