Python. Doing some work on background with Gtk GUI - python

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()

Related

Tkinter close protocol when tkinter is threaded

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()

GTK freezes when opening dialog if user is moving the main window

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.

Getting mousewheel events in DrawArea with python GTK3

I'm trying to get a mousewheel event inside a Gtk.DrawArea.
Does someone know howto achieve this? The method
DrawTest.on_scroll() is never called currently:
#!/usr/bin/env python3
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
class DrawTest(Gtk.DrawingArea):
def __init__(self):
super(DrawTest, self).__init__()
self.connect("scroll-event", self.on_scroll)
def on_scroll(self, btn, event):
print("Scroll event")
return True
class MainWindow(Gtk.Window):
def __init__(self):
super(MainWindow, self).__init__()
self.connect("destroy", lambda x: Gtk.main_quit())
evtest = DrawTest()
self.add(evtest)
self.show_all()
def run(self):
Gtk.main()
def main(args):
mainwdw = MainWindow()
mainwdw.run()
return 0
if __name__ == '__main__':
import sys
sys.exit(main(sys.argv))
You need to set or add events that the Gtk.DrawingArea should handle.
Just add this line of code to your DrawTest class in the init method:
self.set_events (Gdk.EventMask.ALL_EVENTS_MASK)
It should look like this:
class DrawTest(Gtk.DrawingArea):
def __init__(self):
super(DrawTest, self).__init__()
self.set_events (Gdk.EventMask.ALL_EVENTS_MASK)
self.connect("scroll-event", self.on_scroll)
...
The set_events method comes from Gtk.Widget class and it says:
The event mask for a window determines which events will be reported
for that window from all master input devices. For example, an event
mask including Gdk.EventMask.BUTTON_PRESS_MASK means the window should
report button press events. The event mask is the bitwise OR of values
from the Gdk.EventMask enumeration.
See the ‘input handling overview [event-masks]’ for details.
For simplicity I've set the ALL_EVENTS_MASK, more on Gdk.EventMask.
PS: Notice that Gdk.Window is not the same as Gtk.Window as you will see if you read more on the subject.

Python Mouse Position send via OSC Slow

I'm using a raspberry pi, running debian to send mouse position data over OSC using Python. The information is sending correctly, but after a minute or two, the stream gets slower and slower... I can't figure why.
I combined these two sets of code.
Mouse
https://gist.github.com/anapsix/239439bc2ee25728785f
OSC
https://pypi.python.org/pypi/python-osc
I tried running just the mouse code, and it doesn't slow down, so I assume it has something to do with the OSC data, something is building in a cache or something.
I'm not very experienced with Python and have a lot to learn. I've tried searching for an answer here and other places, but haven't come across one when searching for OSC or UDP slow.
Any ideas?
#!/usr/bin/env python
"""
Corrected, the thread stops now.
"""
import sys
import os
from OSC import OSCClient, OSCMessage
from time import sleep
import gtk
gtk.gdk.threads_init()
import threading
# uses the package python-xlib
# from http://snipplr.com/view/19188/mouseposition-on-linux-via-xlib/
# or: sudo apt-get install python-xlib
from Xlib import display
old_stdout = sys.stdout
sys.stdout = open(os.devnull, 'w')
client = OSCClient()
client.connect( ("192.168.1.183", 7110) )
def mousepos():
"""mousepos() --> (x, y) get the mouse coordinates on the screen (linux, Xlib)."""
data = display.Display().screen().root.query_pointer()._data
return data["root_x"], data["root_y"]
class MouseThread(threading.Thread):
def __init__(self, parent, label):
threading.Thread.__init__(self)
self.label = label
self.killed = False
def run(self):
try:
while True:
if self.stopped():
break
text = "{0}".format(mousepos())
self.label.set_text(text)
sleep(0.01)
client.send( OSCMessage("/MousePos", ["{0}".format(mousepos())] ) )
except (KeyboardInterrupt, SystemExit):
sys.exit()
def kill(self):
self.killed = True
def stopped(self):
return self.killed
class PyApp(gtk.Window):
def __init__(self):
super(PyApp, self).__init__()
self.set_title("Mouse coordinates 0.1")
self.set_size_request(250, 50)
self.set_position(gtk.WIN_POS_CENTER)
self.connect("destroy", self.quit)
label = gtk.Label()
self.mouseThread = MouseThread(self, label)
self.mouseThread.start()
fixed = gtk.Fixed()
fixed.put(label, 10, 10)
self.add(fixed)
self.show_all()
def quit(self, widget):
self.mouseThread.kill()
gtk.main_quit()
if __name__ == '__main__':
app = PyApp()
gtk.main()

Dialog in thread freeze whole app despite Gdk.threads_enter/leave()

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()

Categories

Resources