0

The following code freezes when a user is moving the main window, and a dialog box will pop up.

https://i.stack.imgur.com/Y1n87.jpg

Has anyone noticed this problem before?

I'm using idle_add to display the message dialog, but that doesn't solve the problem.

from time import sleep
import gtk
import pygtk
pygtk.require("2.0")
from threading import Thread
import gobject

class PyApp(gtk.Window): 
    def __init__(self):
        super(PyApp, self).__init__()
        self.connect("destroy", gtk.main_quit)       
        self.set_size_request(250, 100)
        self.set_position(gtk.WIN_POS_CENTER)
        self.set_title("Test")

        btn = gtk.Button("Click Here")
        btn.connect("clicked", self.on_click)
        self.add(btn)

        self.show_all()

    def decorator_threaded(func):
        def wrapper(*args, **kwargs):
            gtk.gdk.threads_enter()
            thread = Thread(target=func, args=args, kwargs=kwargs)
            thread.start()
            return thread
        return wrapper

    @decorator_threaded
    def running_on_another_thread(self):
        sleep(2) # Heavy task
        gobject.idle_add(self.error_message)

    def on_click(self, widget):
        self.running_on_another_thread()

    def error_message(self):
        md = gtk.MessageDialog(self, 
            gtk.DIALOG_DESTROY_WITH_PARENT, gtk.MESSAGE_ERROR, 
            gtk.BUTTONS_CLOSE, "Error")
        md.run()
        md.destroy()

PyApp()
gtk.gdk.threads_init()
gtk.main()

I also tried using Gtk 3.0 and noticed the same error.

import threading
import time
import gi
gi.require_version("Gtk", "3.0")
from gi.repository import GLib, Gtk


def app_main():
    win = Gtk.Window(default_height=100, default_width=250)
    win.connect("destroy", Gtk.main_quit)
    win.set_position(Gtk.WindowPosition.CENTER)

    def error_message():
        md = Gtk.MessageDialog(
            transient_for=win,
            flags=0,
            message_type=Gtk.MessageType.ERROR,
            buttons=Gtk.ButtonsType.CLOSE,
            text="Error",
        )
        md.run()
        md.destroy()

    def example_target():
        time.sleep(2) # Heavy task
        GLib.idle_add(error_message)

    win.show_all()

    thread = threading.Thread(target=example_target)
    thread.daemon = True
    thread.start()


if __name__ == "__main__":
    app_main()
    Gtk.main()

1 Answers1

0

The solution is to call timeout_add on the thread to schedule the MessageDialog to run on the main GUI thread.

The class MessageBox also worked in Gtk 2 using pygtk.

import threading
import time
import gi
gi.require_version("Gtk", "3.0")
from gi.repository import GLib, Gtk

class MessageBox(Gtk.MessageDialog):
    def __init__(self, parent, message):
        Gtk.MessageDialog.__init__(self, transient_for=parent,
                                   flags=0,
                                   message_type=Gtk.MessageType.ERROR,
                                   buttons=Gtk.ButtonsType.CLOSE,
                                   text=message)
        self.set_default_response(Gtk.ResponseType.OK)
        self.connect('response', self._handle_clicked)

    def _handle_clicked(self, *args):
        self.destroy()

    def show_dialog(self):
        GLib.timeout_add(0, self._do_show_dialog)

    def _do_show_dialog(self):
        self.show_all()
        return False

def app_main():
    win = Gtk.Window(default_height=100, default_width=250)
    win.connect("destroy", Gtk.main_quit)
    win.set_position(Gtk.WindowPosition.CENTER)

    def error_message():
        dialog = MessageBox(win, "Error")
        dialog.show_dialog()

    def example_target():
        time.sleep(2) # Heavy task
        error_message()

    win.show_all()

    thread = threading.Thread(target=example_target)
    thread.daemon = True
    thread.start()


if __name__ == "__main__":
    app_main()
    Gtk.main()

Note: The issue cannot be reproduced on Linux.

References: