The following code freezes when a user is moving the main window, and a dialog box will pop up.
https://imgur.com/a/aAh6Xee
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()
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:
https://bytes.com/topic/python/answers/717463-showing-message-dialog-thread-using-gtk#post2858146
https://mail.gnome.org/archives/gtk-app-devel-list/2007-October/msg00034.html.
Related
I have this small code that execute a function and in the meanwhile shows an indeterminate tkinter progressbar. When you let the program execute everything works fine while if you try to stop the process by closing the tkinter window you get the RuntimeError: main thread is not in main loop. I understand that the solution could be to bring the bar mainloop in the main thread or alternatively use queue but it is not clear to me how to do this. Here you can find the code with a simple mock function (addit) that executes. Thank you everyone in advance!
import threading
importing tkinter module
import tkinter
import tkinter.ttk as ttk
from tkinter import messagebox
from tkinter import Button
from tkinter import Label
import sys
import time
class tkwindow(threading.Thread):
def __init__(self):
threading.Thread.__init__(self, daemon=True)
def run(self):
self.wbar=tkinter.Tk()
self.wbar.attributes("-topmost", True)
self.wbar.title('Tool')
lb=Label(self.wbar,text='Check in progress')
lb.pack()
pbar = ttk.Progressbar(self.wbar,orient='horizontal', length=500, mode='indeterminate')
pbar.pack(pady=25)
pbar.start(50)
self.wbar.protocol("WM_DELETE_WINDOW", self.on_closing)
self.loopmain()
def loopmain(self):
self.wbar.mainloop()
def quitall(self):
self.wbar.quit()
sys.exit()
def on_closing(self):
if messagebox.askokcancel("Quit", "Do you want to quit?"):
self.wbar.quit()
sys.exit()
def main():
mygui=tkwindow()
mygui.start()
addit(2,3)
mygui.quitall()
def addit(a,b):
time.sleep(3)
print(a+b)
return
if __name__=='__main__':
main()
The structure of the code is only wrong. You should inherit tk.Tk and not threading.Thread. Instead of creating a function for the mainloop, just insert it at the bottom of the run() function (use self instead of self.wbar). Initialize the thread at the start of the run() function. In the main() function you have called start but in the class you defined it as run(). Here is your code if you added the above changes and fixed all errors:
import threading
import tkinter as tk
import tkinter.ttk as ttk
from tkinter import messagebox
from tkinter import Button
from tkinter import Label
import sys
import time
class tkwindow(tk.Tk):
def __init__(self):
super().__init__()
def run(self):
threading.Thread.__init__(self, daemon=True)
self.attributes("-topmost", True)
self.title('Tool')
lb=Label(self, text='Check in progress')
lb.pack()
pbar = ttk.Progressbar(self, orient='horizontal', length=500, mode='indeterminate')
pbar.pack(pady=25)
pbar.start(50)
self.protocol("WM_DELETE_WINDOW", self.on_closing)
self.mainloop()
def quitall(self):
self.quit()
sys.exit()
def on_closing(self):
if messagebox.askokcancel("Quit", "Do you want to quit?"):
self.quitall()
def main():
mygui=tkwindow()
mygui.run()
addit(2,3)
mygui.quitall()
def addit(a,b):
time.sleep(3)
print(a+b)
return
if __name__=='__main__':
main()
There is no way to put the progress bar into the thread. Even if you could, it wouldn't be of any use since it would make the code very complex. So just use this as an alternative.
Updated Code
import threading
import tkinter as tk
import tkinter.ttk as ttk
from tkinter import messagebox
from tkinter import Button
from tkinter import Label
import sys
import time
class tkwindow(tk.Tk):
def __init__(self):
super().__init__()
def run(self):
threading.Thread.__init__(self, daemon=True)
self.attributes("-topmost", True)
self.title('Tool')
lb=Label(self, text='Check in progress')
lb.pack()
pbar = ttk.Progressbar(self, orient='horizontal', length=500, mode='indeterminate')
pbar.pack(pady=25)
pbar.start(50)
self.protocol("WM_DELETE_WINDOW", self.on_closing)
def quitall(self):
self.quit()
sys.exit()
def on_closing(self):
if messagebox.askokcancel("Quit", "Do you want to quit?"):
self.quitall()
def main():
mygui=tkwindow()
mygui.run()
addit(2,3)
mygui.mainloop()
def addit(a,b):
print("ADDIT")
threading.Timer(3, lambda: print(a+b)).start()
if __name__=='__main__':
main()
Probably I have found a way to do just what I wanted using queue. Unfortunately the code kill the main thread abruptly with interrupt_main and not in a nice way as sys.exit() could do but is the only solution I have found for now. The updated code is the following:
import threading
import tkinter
import tkinter.ttk as ttk
from tkinter import messagebox
from tkinter import Button
from tkinter import Label
import sys
import time
from queue import Queue
import _thread
import os
class tkwindow(threading.Thread):
def __init__(self,dataq):
threading.Thread.__init__(self, daemon=True)
self.dataq=dataq
def run(self):
self.wbar=tkinter.Tk()
self.wbar.attributes("-topmost", True)
self.wbar.title('Tool')
lb=Label(self.wbar,text='Check in progress')
lb.pack()
pbar = ttk.Progressbar(self.wbar,orient='horizontal', length=500, mode='indeterminate')
pbar.pack(pady=25)
pbar.start(50)
self.dataq.put(0)
self.loopmain()
def loopmain(self):
self.wbar.protocol("WM_DELETE_WINDOW", self.on_closing)
self.wbar.after(100,self.checkq)
self.wbar.mainloop()
def checkq(self):
v=self.dataq.get()
if v:
if messagebox.askokcancel("Quit", "Do you want to quit?"):
self.wbar.quit()
os._exit(0) #or alternatively _thread.interrupt_main()
def quitall(self):
self.wbar.quit()
sys.exit()
def on_closing(self):
self.dataq.put(1)
self.checkq()
def main():
dataq=Queue()
mygui=tkwindow(dataq)
mygui.start()
addit(2,3)
mygui.quitall()
def addit(a,b):
time.sleep(3)
print(a+b)
return
if __name__=='__main__':
main()
I would like to show notifications using my Gtk app, but when I run my code below, I everything works but the notification doesn't show, even when I click on the button. I tried running it using a desktop file like the one suggested in this answer, but it still didn't work. Here's my code:
import gi
import sys
gi.require_version("Gtk", "3.0")
from gi.repository import Gio, Gtk
class App(Gtk.Application):
def __init__(self, *args, **kwargs):
Gtk.Application.__init__(self, *args, application_id="org.example.myapp", **kwargs)
self.window = None
def do_startup(self):
Gtk.Application.do_startup(self)
def do_activate(self):
if not self.window:
self.button = Gtk.Button(label="send notification")
self.button.connect("clicked", self.notnotnot)
self.window = Gtk.ApplicationWindow(application=self)
self.window.add(self.button)
self.window.show_all()
self.window.present()
def notnotnot(self, *args):
notification = Gio.Notification()
notification.set_body("Hello!")
self.send_notification(None, notification)
if __name__ == "__main__":
app = App()
app.run(sys.argv)
and here's the desktop file org.example.myapp.desktop:
[Desktop Entry]
Type=Application
Name=My Application
Exec=python3 /home/user/programs/python/testing/SO/problem_why_is_gtk....py
Terminal=true
X-GNOME-UsesNotifications=true
I figured out that just setting the priority to high made the notification appear. Note that Gio.Notification.set_urgent() is deprecated. You need to use Gio.Notification.set_priority(). Here is the code with the added line marked accordingly:
import gi
import sys
gi.require_version("Gtk", "3.0")
from gi.repository import Gio, Gtk
class App(Gtk.Application):
def __init__(self, *args, **kwargs):
Gtk.Application.__init__(self, *args, application_id="org.example.myapp", **kwargs)
self.window = None
def do_startup(self):
Gtk.Application.do_startup(self)
def do_activate(self):
if not self.window:
self.button = Gtk.Button(label="send notification")
self.button.connect("clicked", self.notnotnot)
self.window = Gtk.ApplicationWindow(application=self)
self.window.add(self.button)
self.window.show_all()
self.window.present()
def notnotnot(self, *args):
notification = Gio.Notification()
notification.set_body("Hello!")
notification.set_priority(Gio.NotificationPriority.HIGH) ### ADDED LINE
self.send_notification(None, notification)
if __name__ == "__main__":
app = App()
app.run(sys.argv)
If you specify a string id instead of None when sending the notification (self.send_notification("my_notif_id", notification)), you can later withdraw the notification using self.withdraw_notification("my_notif_id").
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()
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()
python 3.2.2
gtk3 3.2.2
python-gobject 3.0.2
I'm trying to display a GUI and do some work in the background. As I understand it should look something like this:
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import time
from threading import Thread
from gi.repository import Gtk, Gdk
class Gui():
def run(self):
self.Window = Gtk.Window()
self.Window.set_border_width(8)
self.Window.set_title("Некий GUI")
self.Window.connect('destroy', lambda x: self.stop())
self.outBut = Gtk.Button.new_from_stock(Gtk.STOCK_OK)
self.outBut.set_size_request(150, 35)
self.outBut.connect('clicked', lambda x: self.passfun)
self.Window.add(self.outBut)
self.Window.show_all()
def stop(self):
Gtk.main_quit()
def passfun(self):
pass
class LoopSleep(Thread):
def run(self):
i = 1
while True:
print(i)
i = i + 1
#time.sleep(1)
gui = Gui()
gui.run()
loopSleep = LoopSleep()
loopSleep.start()
Gdk.threads_init()
Gdk.threads_enter()
Gtk.main()
Gdk.threads_leave()
But it does not work. Several cycles occurs when you press the button. And the cycle runs after the window is closed. But not together.
What I do wrong?
Can't claim to be any expert on python threading nor gtk3 but after playing around a little with your example I found something that appears to work the way you want it. Instead of sub classing Thread i use threading.start(target=loop_sleep), and placed that inside Gui.
Glib.threads_init() also seem to be needed.
#!/usr/bin/env python3
from gi.repository import Gtk,Gdk, GLib
import threading
import time
class Gui(Gtk.Window):
def __init__(self):
self.Window = Gtk.Window()
self.Window.set_border_width(8)
self.Window.set_title("Некий GUI")
self.Window.connect('destroy', lambda x: self.stop())
self.outBut = Gtk.Button.new_from_stock(Gtk.STOCK_OK)
self.outBut.set_size_request(150, 35)
self.Window.connect('destroy', lambda x: self.stop())
self.Window.add(self.outBut)
self.Window.show_all()
threading.Thread(target=loop_sleep).start()
def stop(self):
Gtk.main_quit()
def passfun(self):
pass
def loop_sleep():
i = 1
while True:
print(i)
i = i + 1
#time.sleep(1)
app = Gui()
GLib.threads_init()
Gdk.threads_init()
Gdk.threads_enter()
Gtk.main()
Gdk.threads_leave()
Does somebody knows what this error means?
Fatal Python error: PyEval_RestoreThread: NULL tstate
In my application when I destroy the main window this error is printed. I am using multiples threads to run differents jobs in the same time.
I really dont have any ideia what is this..
If someone ever lived the same problem please help me..
Below is a code to show how to reproduce this error. (I tried to make smallest code that I could)
#!/usr/bin/env python
import gtk
import threading
import sys
class Test(threading.Thread):
"""A subclass of threading.Thread, with a kill() method."""
def __init__(self, *args, **keywords):
threading.Thread.__init__(self, *args, **keywords)
gtk.gdk.threads_init()
self.killed = False
def start(self):
"""Start the thread."""
self.__run_backup = self.run
self.run = self.__run # Force the Thread to install our trace.
threading.Thread.start(self)
def __run(self):
"""Hacked run function, which installs the trace."""
sys.settrace(self.globaltrace)
self.__run_backup()
self.run = self.__run_backup
def globaltrace(self, frame, why, arg):
if why == 'call':
return self.localtrace
else:
return None
def localtrace(self, frame, why, arg):
if self.killed:
if why == 'line':
raise SystemExit()
return self.localtrace
def kill(self):
self.killed = True
class Window(gtk.Window):
"""Main window"""
def __init__(self):
"""Create a main window and all your children"""
super(Window, self).__init__()
self.connect('destroy', gtk.main_quit)
button = gtk.Button("Click and after, close window")
button.connect("clicked", self.on_item_run)
self.add(button)
self.show_all()
def on_item_run(self, widget):
t = Test()
t.start()
if __name__ == "__main__":
window = Window()
gtk.gdk.threads_enter()
gtk.main()
gtk.gdk.threads_leave()
Thanks a lot..
What version of gtk are you using? This link seems to indicate it's a threading bug that was fixed in 2.0.1.