471,349 Members | 1,217 Online
Bytes | Software Development & Data Engineering Community
Post +

Home Posts Topics Members FAQ

Join Bytes to post your question to a community of 471,349 software developers and data experts.

Showing a message dialog from a thread using GTK

I have a worker thread, and when the worker is done I want it to show a popup dialog. The sample application below demonstrates this. There are two buttons: a "Show dialog" button which immediately displays a dialog, and a "Do work" button which launches a worker thread. The worker thread simulates some work by sleeping a moment, and then attempts to display a dialog.

The problem is that when the worker thread attempts to display the popup the program freezes. What is the difference between showing a dialog from the event handler thread in the "connected" button2_click method, and showing a dialog in the run method of a custom thread?

Expand|Select|Wrap|Line Numbers
  1. import gobject
  2. import gtk
  3. import threading
  4. import time
  5.  
  6. class MessageBox(gtk.MessageDialog):
  7.     def __init__(self, parent, message):
  8.         gtk.MessageDialog.__init__(self, parent, gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
  9.                                 gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, message)
  10.         self.set_default_response(gtk.RESPONSE_OK)
  11.         self.connect('response', self._handle_clicked)
  12.  
  13.     def _handle_clicked(self, *args):
  14.         self.destroy()
  15.  
  16. class TestThread(threading.Thread):
  17.     def __init__(self, mainview):
  18.         threading.Thread.__init__(self)
  19.         self.mainview = mainview
  20.  
  21.     def run(self):
  22.         print "Some work is done here..."
  23.         time.sleep(3)
  24.         dialog = MessageBox(self.mainview, "Work completed")
  25.         dialog.show_all()
  26.         print "Work complete"
  27.  
  28. class MainView(gtk.Window):
  29.     def __init__(self):
  30.         gtk.Window.__init__(self)
  31.         self.connect('delete_event', self.handle_window_delete_event)
  32.         self.connect('destroy', self.quit)
  33.  
  34.         button1 = gtk.Button("Do work")
  35.         button1.connect('clicked', self.button1_click)
  36.         button2 = gtk.Button("Show dialog")
  37.         button2.connect('clicked', self.button2_click)
  38.         box = gtk.VBox()
  39.         box.pack_start(button1)
  40.         box.pack_start(button2)
  41.         self.add(box)
  42.  
  43.     def quit(self, *args):
  44.         gtk.main_quit()
  45.  
  46.     def handle_window_delete_event(self, *args):
  47.         return False
  48.  
  49.     def button1_click(self, *args):
  50.         worker = TestThread(self)
  51.         worker.start()
  52.  
  53.     def button2_click(self, *args):
  54.         dialog = MessageBox(self, "Just a message!")
  55.         dialog.show_all()
  56.  
  57. if __name__ == "__main__":
  58.     gobject.threads_init()
  59.     main = MainView()
  60.     main.show_all()
  61.     gtk.main()
  62.  
Oct 3 '07 #1
6 12865
ilikepython
844 Expert 512MB
I have a worker thread, and when the worker is done I want it to show a popup dialog. The sample application below demonstrates this. There are two buttons: a "Show dialog" button which immediately displays a dialog, and a "Do work" button which launches a worker thread. The worker thread simulates some work by sleeping a moment, and then attempts to display a dialog.

The problem is that when the worker thread attempts to display the popup the program freezes. What is the difference between showing a dialog from the event handler thread in the "connected" button2_click method, and showing a dialog in the run method of a custom thread?

Expand|Select|Wrap|Line Numbers
  1. import gobject
  2. import gtk
  3. import threading
  4. import time
  5.  
  6. class MessageBox(gtk.MessageDialog):
  7.     def __init__(self, parent, message):
  8.         gtk.MessageDialog.__init__(self, parent, gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
  9.                                 gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, message)
  10.         self.set_default_response(gtk.RESPONSE_OK)
  11.         self.connect('response', self._handle_clicked)
  12.  
  13.     def _handle_clicked(self, *args):
  14.         self.destroy()
  15.  
  16. class TestThread(threading.Thread):
  17.     def __init__(self, mainview):
  18.         threading.Thread.__init__(self)
  19.         self.mainview = mainview
  20.  
  21.     def run(self):
  22.         print "Some work is done here..."
  23.         time.sleep(3)
  24.         dialog = MessageBox(self.mainview, "Work completed")
  25.         dialog.show_all()
  26.         print "Work complete"
  27.  
  28. class MainView(gtk.Window):
  29.     def __init__(self):
  30.         gtk.Window.__init__(self)
  31.         self.connect('delete_event', self.handle_window_delete_event)
  32.         self.connect('destroy', self.quit)
  33.  
  34.         button1 = gtk.Button("Do work")
  35.         button1.connect('clicked', self.button1_click)
  36.         button2 = gtk.Button("Show dialog")
  37.         button2.connect('clicked', self.button2_click)
  38.         box = gtk.VBox()
  39.         box.pack_start(button1)
  40.         box.pack_start(button2)
  41.         self.add(box)
  42.  
  43.     def quit(self, *args):
  44.         gtk.main_quit()
  45.  
  46.     def handle_window_delete_event(self, *args):
  47.         return False
  48.  
  49.     def button1_click(self, *args):
  50.         worker = TestThread(self)
  51.         worker.start()
  52.  
  53.     def button2_click(self, *args):
  54.         dialog = MessageBox(self, "Just a message!")
  55.         dialog.show_all()
  56.  
  57. if __name__ == "__main__":
  58.     gobject.threads_init()
  59.     main = MainView()
  60.     main.show_all()
  61.     gtk.main()
  62.  
I'm not sure if this is the problem, but usually, child threads shouldn't do anything with the GUI in the maiin thread. Threads usually use the queue module to communicate between threads. What you need is a callback to listen from events from the queue in the main thread and the child thread to put some data on the queue.
Oct 3 '07 #2
bartonc
6,596 Expert 4TB
In some frameworks, threads are allowed to post events to the event queue.
Using this technique, your main thread (which is usually the only thread allow to use GUI elements) can pop up the dialog. I'm not sure if this is also true of GTK, though.
Oct 3 '07 #3
Thanks for the hints.I was expecting something like this, and it seems somewhat reasonable that the toolkit allows certain operatinons only from the main thread. I tested the callback approach, but i couldn't get it to work either. Here is some sample code for activating callbacks:

Expand|Select|Wrap|Line Numbers
  1. ...
  2. class TestThread(threading.Thread):
  3.     def __init__(self, mainview):
  4.         threading.Thread.__init__(self)
  5.         self.mainview = mainview
  6.  
  7.     def run(self):
  8.         print "Some work is done here..."
  9.         time.sleep(3)
  10.         print "worker " + str(threading.currentThread())
  11.         # Perform some action that will cause a 'notify' signal to be emitted
  12.         mainview.show_dialog = True
  13.         self.mainview.set_title("x")
  14.  
  15. ...
  16.  
  17. class MainView(gtk.Window):
  18.     def __init__(self):
  19.         gtk.Window.__init__(self)
  20.         self.connect('notify', self.do_notify)
  21.         self.show_dialog = False
  22. ...        
  23.     def do_notify(self, *args):
  24.         if self.show_dialog:
  25.             print "notify " + str(threading.currentThread())
  26.             dialog = MessageBox(self.mainview, "Work completed")
  27.             dialog.show_all()
  28. ...
  29.  
This did not work because the 'notify' signal is called in the same thread that performs the action that triggers the signal. The application will still freeze. Do you have any ideas for emitting a singnal in a way that will make it to be called in the main thread (running the gtk.main() loop)?
Oct 4 '07 #4
bartonc
6,596 Expert 4TB
Thanks for the hints.I was expecting something like this, and it seems somewhat reasonable that the toolkit allows certain operatinons only from the main thread. I tested the callback approach, but i couldn't get it to work either. Here is some sample code for activating callbacks:

Expand|Select|Wrap|Line Numbers
  1. ...
  2. class TestThread(threading.Thread):
  3.     def __init__(self, mainview):
  4.         threading.Thread.__init__(self)
  5.         self.mainview = mainview
  6.  
  7.     def run(self):
  8.         print "Some work is done here..."
  9.         time.sleep(3)
  10.         print "worker " + str(threading.currentThread())
  11.         # Perform some action that will cause a 'notify' signal to be emitted
  12.         mainview.show_dialog = True
  13.         self.mainview.set_title("x")
  14.  
  15. ...
  16.  
  17. class MainView(gtk.Window):
  18.     def __init__(self):
  19.         gtk.Window.__init__(self)
  20.         self.connect('notify', self.do_notify)
  21.         self.show_dialog = False
  22. ...        
  23.     def do_notify(self, *args):
  24.         if self.show_dialog:
  25.             print "notify " + str(threading.currentThread())
  26.             dialog = MessageBox(self.mainview, "Work completed")
  27.             dialog.show_all()
  28. ...
  29.  
This did not work because the 'notify' signal is called in the same thread that performs the action that triggers the signal. The application will still freeze. Do you have any ideas for emitting a singnal in a way that will make it to be called in the main thread (running the gtk.main() loop)?
There is probably an event mechanism for this but in the mean time you could:
Use one of the "thread-safe" mechanisms provided by the threading module (like Event()) or a Queue.Queue() and poll the state of the thread from your main loop using a timer. That's what I do in wxPython, anyway. For that matter, you could just poll t.isAlive() where t is a reference to your thread.
Oct 4 '07 #5
There is probably an event mechanism for this but in the mean time you could:
Use one of the "thread-safe" mechanisms provided by the threading module (like Event()) or a Queue.Queue() and poll the state of the thread from your main loop using a timer. That's what I do in wxPython, anyway. For that matter, you could just poll t.isAlive() where t is a reference to your thread.
As I see it, the Queue would not provide any help here, as it is intended for exchanging data between threads. The problem is rather that the thing I want to do can not be done in the worker thread, and so far I did not find the correct way of scheduling my own events to be executed in the main loop. It's the Widget.show_all() call that jams the system if it is not called in the GTK main loop.

However, now I found a solution to the problem. A recent post in the GTK mailing list relates to the same problem: http://mail.gnome.org/archives/gtk-a.../msg00034.html There is no specific example in the message, but here is mine:

Expand|Select|Wrap|Line Numbers
  1. class MessageBox(gtk.MessageDialog):
  2.     def __init__(self, parent, message):
  3.         gtk.MessageDialog.__init__(self, parent, gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, message)
  4.         self.set_default_response(gtk.RESPONSE_OK)
  5.         self.connect('response', self._handle_clicked)
  6.  
  7.     def _handle_clicked(self, *args):
  8.         self.destroy()
  9.  
  10.     def show_dialog(self):
  11.         gobject.timeout_add(0, self._do_show_dialog)
  12.  
  13.     def _do_show_dialog(self):
  14.         self.show_all()
  15.         return False
  16.  
  17. ...
  18.  
  19. class TestThread(threading.Thread):
  20.     def __init__(self, mainview):
  21.         threading.Thread.__init__(self)
  22.         self.mainview = mainview
  23.  
  24.     def run(self):
  25.         time.sleep(3)
  26.         dialog = MessageBox(self.mainview, "Work completed")
  27.         dialog.show_dialog()
  28. ...
  29.  
The MessageBox.show_dialog() is now made thread safe from GTK point of view. The gobject.timeout_add() schedules a function to be called from the GTK main loop. The GTK main loop will call MessageBox._do_show_dialog() which will make the dialog visible. The False return value must be included, as GTK would otherwise call the function repeatedly. Now I can call dialog.show_dialog() directly from the worker thread.
Oct 4 '07 #6
With C, I use "g_idle_add()", passing a function pointer and parameters, because I hear that GTK is thread aware, not thread safe.

GLib is thread safe (with "if (!g_thread_supported ()) g_thread_init (NULL);" in init) and solve this problem for me.
May 28 '09 #7

Post your reply

Sign in to post your reply or Sign up for a free account.

Similar topics

8 posts views Thread by Raed Sawalha | last post: by
5 posts views Thread by John | last post: by
2 posts views Thread by Lars Netzel | last post: by
reply views Thread by Chukkalove | last post: by
1 post views Thread by Claire | last post: by

By using Bytes.com and it's services, you agree to our Privacy Policy and Terms of Use.

To disable or enable advertisements and analytics tracking please visit the manage ads & tracking page.