0

Here is a mocked up version of what I'm trying to do in my GUI. I have a MessageDialog which is created somewhere during the execution of a callback method. My problem is the MessageDialog won't close until the callback method finishes its execution.

I have a "dialog.destroy()" which I would expect to destroy the dialog. I click on "Yes/No" and the button depresses, but the dialog doesn't go away until "_go" finishes.

The "time.sleep(4)" is in there to simulate other stuff happening in my "_go" method after my MessageDialog interaction is over.

from gi.repository import Gtk, GObject
import time

class Gui(Gtk.Window):
    def __init__(self):
        Gtk.Window.__init__(self)
        self.connect("delete_event", Gtk.main_quit)
        self.set_size_request(700, 600)
        notebook = Gtk.Notebook()
        notebook.set_tab_pos(Gtk.PositionType.TOP)
        notebook.append_page(MyTab(), Gtk.Label("A tab"))
        self.add(notebook)
        notebook.show_all()
        self.show()

class MyTab(Gtk.VBox):
    def __init__(self):
        super(MyTab, self).__init__()
        self.go_button = Gtk.Button()
        self.go_button.add(Gtk.Image().new_from_stock(Gtk.STOCK_APPLY,
                                                 Gtk.IconSize.BUTTON))
        top_box = Gtk.HBox()
        top_box.pack_start(self.go_button, False, True, 5)
        self.pack_start(top_box, False, True, 5)

        # setup callbacks
        self.go_button.connect("clicked", self._go)

    def _go(self, _):
        dialog = Gtk.MessageDialog(Gtk.Window(), 
                                   Gtk.DialogFlags.MODAL,
                                   Gtk.MessageType.QUESTION,
                                   Gtk.ButtonsType.YES_NO,
                                   "RESPONSE REQUIRED")
        dialog.format_secondary_text("are you having fun?")
        response = dialog.run()
        dialog.destroy()
        print "your response is: " + str(response)
        time.sleep(4)
        print "left _go"

def main():
    """
    Main entry point.
    """
    Gui()
    Gtk.main()

if __name__ == "__main__":
    main()
Joshua Taylor
  • 84,998
  • 9
  • 154
  • 353
rikityplikity
  • 33
  • 1
  • 5

3 Answers3

3

This problem is not specific to dialogs. Any GUI change is invisible until you return to the main loop and give the system a chance to process the events accumulated by modifying the widgets.

If you really want to update the GUI immediately in the callback, you can manually spin the accumulated events with a loop like this after the call to dialog.destroy():

while Gtk.events_pending():
    Gtk.main_iteration()

However, be aware that this will not only update the screen, but also run other accumulated events, including idle and timeout handlers and button click callbacks (if any are pending). That can have unexpected consequences.

user4815162342
  • 141,790
  • 18
  • 296
  • 355
  • 1
    yes, spinning the main loop by hand is usually a bad thing to do unless you really know what you're doing. A better way would be to do whatever is taking a long time in an idle function. – iain Feb 17 '13 at 13:34
  • What about nesting another mainloop inside of the 'main' mainloop? It would be started just to launch dialogs and handle their delete events, and would quit when the dialog closes (the quit event being triggered by the dialog delete event). Would this provide me the 'close right away' behaviour I'm after? (I actually thought this was how Gtk Dialogs worked after doing a little reading about them.) – rikityplikity Feb 18 '13 at 22:30
  • @rikityplikity Modal dialogs do work like that, but you call `destroy` *after* exiting the inner loop. The `while` loop I posted is a mini-main-loop of sorts, that only runs for long enough to pick up the pending events (in your case only those that will show the dialog close). – user4815162342 Feb 18 '13 at 22:42
  • Ok that makes sense, I think the concept of a nested loops just clicked from your answer (pretty new to GUI programming). I've added a solution I came up with which uses a nested loop to achieve the behaviour I'm after. – rikityplikity Feb 19 '13 at 00:32
1

This is the correct behaviour. The window only disappears when control is given back to Gtk's main loop which only happens at the end of your _go callback.

iain
  • 5,660
  • 1
  • 30
  • 51
1

As per the comments on user4815162342's answer I came up with a solution that uses a nested main loop. This class takes in a dialog and provides a run method.

class NestedDialog(object):
    def __init__(self, dialog):
        self.dialog = dialog
        self.response_var = None

    def run(self):
        self._run()
        return self.response_var

    def _run(self):
        self.dialog.show()
        self.dialog.connect("response", self._response)
        Gtk.main()

    def _response(self, dialog, response):
        self.response_var = response
        self.dialog.destroy()
        Gtk.main_quit()

The dialog is then run as follows:

def _go(self, _):
    dialog = Gtk.MessageDialog(Gtk.Window(), 
               Gtk.DialogFlags.MODAL,
               Gtk.MessageType.QUESTION,
               Gtk.ButtonsType.YES_NO,
               "RESPONSE REQUIRED")
    dialog.format_secondary_text("are you having fun?")
    nested_dialog = NestedDialog(dialog)
    response = nested_dialog.run()
    print "your response is: " + str(response)
    time.sleep(4)
    print "left _go"
Community
  • 1
  • 1
rikityplikity
  • 33
  • 1
  • 5
  • Nested main loops are bad. They make it far too easy to heavily couple the context in which a widget is created to the context in which events arising from it are processed. They make it far too easy to become re-entrant when you really shouldn't. They can block other systems' own loops. And, ultimately, they subvert the event-driven philosophy of GLib, GTK+, and all other such frameworks. You should use the pattern of 'show widget, set handler, return, wait for callback'. And that's not just an idle philosophical recommendation: `gtk_dialog_run()` is on the cards to be deprecated, for example – underscore_d Jun 03 '17 at 19:49