0

I've got an application which makes something in background. To inform the user it update some widgets with its progress. That works.

But somethings there is an error or something else in this background operation so it has to display a dialog. This freeze my whole application although I handle everything with the Threading-Lock. An example of code with exactly my problem is this:

import threading, time
from gi.repository import Gtk, Gdk

def background(label, parent):
    for t in range(5):
        label.set_text(str(t))
        time.sleep(1)
    Gdk.threads_enter()
    dlg = Gtk.MessageDialog(
                type=Gtk.MessageType.INFO,
                buttons=Gtk.ButtonsType.OK,
                message_format="Time is gone.",
                title="Info")
    dlg.run()
    dlg.destroy()
    Gdk.threads_leave()

def main():
    window = Gtk.Window()
    window.connect("delete-event", Gtk.main_quit)
    label = Gtk.Label()
    window.add(label)
    window.show_all()
    thread = threading.Thread(target=background, args=(label, window))
    Gdk.threads_init()
    Gdk.threads_enter()
    thread.start()
    Gtk.main()
    Gdk.threads_leave()

if __name__=="__main__":
    main()
Chickenmarkus
  • 1,131
  • 11
  • 25

1 Answers1

3

In gtk3, all gtk functions like adding/removing/changing widgets must be executed by the gtk thread (the thread that's running Gtk.main()).

The fixed code:

import threading, time
from gi.repository import Gtk, Gdk, GLib # need GLib for GLib.PRIORITY_DEFAULT

# a short utility function that I like to use.
# makes the Gtk thread execute the given callback.
def add_mainloop_task(callback, *args):
    def cb(args):
        args[0](*args[1:])
        return False
    args= [callback]+list(args)
    Gdk.threads_add_idle(GLib.PRIORITY_DEFAULT, cb, args)

def background(label, parent):
    for t in range(5):
        #~ label.set_text(str(t)) # let the gtk thread do this.
        add_mainloop_task(label.set_text, str(t))
        time.sleep(1)
    #~ Gdk.threads_enter() # don't need this.
    dlg = Gtk.MessageDialog(
                type=Gtk.MessageType.INFO,
                buttons=Gtk.ButtonsType.OK,
                message_format="Time is gone.",
                title="Info")

    # put these two functions calls inside a little function, and let the gtk thread execute it.
    def run_dialog(dlg):
        dlg.run()
        dlg.destroy()
    add_mainloop_task(run_dialog, dlg)
    #~ Gdk.threads_leave() # don't need this.

def main():
    window = Gtk.Window()
    window.connect("delete-event", Gtk.main_quit)
    label = Gtk.Label()
    window.add(label)
    window.show_all()
    thread = threading.Thread(target=background, args=(label, window))
    Gdk.threads_init()
    #~ Gdk.threads_enter() # don't need this.
    thread.start()
    Gtk.main()
    #~ Gdk.threads_leave() # don't need this.

if __name__=="__main__":
    main()
Aran-Fey
  • 39,665
  • 11
  • 104
  • 149
  • Damn, my whole application is not constructed for commumication with main thread... Why there are still the Gdk.threads_enter/leave() methods if they don't work anymore? – Chickenmarkus Oct 15 '14 at 09:49
  • Without Gtk.main() wrapped in Gdk.threads_enter/leave() I get only a frozen empty window. With your code works. Thanks! But how can I receive the result of e. g. a question dialog? – Chickenmarkus Oct 15 '14 at 09:53
  • @Chickenmarkus: Well, if you aren't doing any expensive operations, you can just let the Gtk thread do it. Unfortunately I have no experience with the other scenario; I suppose you'll need to take a look at how to share data between threads - but it would probably be best to create another question about that here on SO, so you can get more useful answers from more experienced Gtk programmers. – Aran-Fey Oct 15 '14 at 11:26
  • 1
    Gdk.threads_enter/leave() are deprecated in GTK+3 and can be removed sometimes (https://wiki.gnome.org/Projects/PyGObject/Threading). Pity! – Chickenmarkus Oct 15 '14 at 13:57