I want to create a Gtk program that requires to continuously update a ListBox, i.e. add and remove rows. I am trying this approach. It could be cleaner but here it is just long enough to explain the problem.
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
from time import sleep
from threading import Thread
def add_widgets(listbox):
# add 5 rows
for _ in range(5):
for _ in range(5):
add_row(listbox)
window.show_all() # make the changes visible
sleep(1) # to make the change stay
# remove all the rows
for row in listbox.get_children():
listbox.remove(row)
window.show_all()
sleep(1)
# all the addition and removal done
print('finished')
def add_row(listbox):
listboxrow = Gtk.ListBoxRow()
label = Gtk.Label()
label.set_text("Hey There")
listboxrow.add(label)
listbox.add(listboxrow)
window = Gtk.Window()
window.connect('destroy', Gtk.main_quit)
listbox = Gtk.ListBox()
window.add(listbox)
window.show_all()
# Thread to add and remove rows
update_thread = Thread(target=add_widgets, args=(listbox, ))
update_thread.start()
Gtk.main()
This code runs fine like 50% of the time. For the rest, it gives me 3 types of errors, all of them randomly.
The good old SegFault after like 2 or 3 iteration of the parent loop in add_widgets
The following:
**
Gtk:ERROR:../gtk/gtk/gtkcssnode.c:319:lookup_in_global_parent_cache: assertion failed: (node->cache == NULL)
Bail out! Gtk:ERROR:../gtk/gtk/gtkcssnode.c:319:lookup_in_global_parent_cache: assertion failed: (node->cache == NULL)
Aborted
The last one doesn't crash the program but it adds random number of rows instead. i.e. it adds 3(random) rows instead of 5, as specified or maybe no rows at all.
I've tried adding more sleep statements at appropriate places because initially, I thought, it could be because the windows are not ready when updating widgets.
I wanna know why its happening and how can I fix it.
EDIT: It's probably something to do with the UI and the thread synchronization because, it(the crash) happens more frequently when interacting with the window. For instance, when you are dragging it, it has more chances of crashing.
You must not call GTK+ code from other threads. All interactions must be done in the main thread. This is documented in https://developer.gnome.org/gdk3/stable/gdk3-Threads.html.
If you want to do some "background" updates of your widgets, you can simulate that with functions such as g_idle_add() (https://developer.gnome.org/glib/stable/glib-The-Main-Event-Loop.html#g-idle-add), g_timeout_add() (https://developer.gnome.org/glib/stable/glib-The-Main-Event-Loop.html#g-timeout-add) and other related functions.
Related
In a separate thread, I check for information in the pySerial Buffer (Infinite Loop). If new information is available, I'd like to show that Input in a Gtk.TextView. After Googling on the subject it turns out doing Gtk -Stuff inside threads is a killer. Random errors will appear and so on, which was also a problem I ran into.
I decided to use a queue to synchronize the thread with the GUI. Putting information into a queue is pretty simple, but how do I suppose to check in the mainloop if there are any entries in the queue?
Some kind of event would be nice, which triggers if any new information is available.
Is there anything of that kind? Maybe a function exists to implement custom python code into the GTK3+ mainloop?
To successfully periodically update the GUI from events in a thread, we cannot simply use threading to start a second process. Like you mention, it will lead to conflicts.
Here is where GObject comes in, to, as it is put in this (slightly outdated) link :
call gobject.threads_init() at applicaiton initialization. Then you launch your threads normally, but make sure the threads never do any GUI tasks directly. Instead, you use gobject.idle_add to schedule GUI task to executed in the main thread
When we replace gobject.threads_init() by GObject.threads_init() and gobject.idle_add by GObject.idle_add(), we pretty much have the updated version of how to run threads in a Gtk application. A simplified example, showing an increasing number of Monkeys in your textfield. See the comments in the code:
Example, counting an updated number of monkeys in your TextView:
......
#!/usr/bin/env python3
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, GObject
import time
from threading import Thread
class InterFace(Gtk.Window):
def __init__(self):
Gtk.Window.__init__(self, title="Test 123")
maingrid = Gtk.Grid()
maingrid.set_border_width(10)
self.add(maingrid)
scrolledwindow = Gtk.ScrolledWindow()
scrolledwindow.set_hexpand(True)
scrolledwindow.set_vexpand(True)
scrolledwindow.set_min_content_height(50)
scrolledwindow.set_min_content_width(150)
maingrid.attach(scrolledwindow, 0,0,1,1)
self.textfield = Gtk.TextView()
self.textbuffer = self.textfield.get_buffer()
self.textbuffer.set_text("Let's count monkeys")
self.textfield.set_wrap_mode(Gtk.WrapMode.WORD)
scrolledwindow.add(self.textfield)
# 1. define the tread, updating your text
self.update = Thread(target=self.counting_monkeys)
# 2. Deamonize the thread to make it stop with the GUI
self.update.setDaemon(True)
# 3. Start the thread
self.update.start()
def counting_monkeys(self):
# replace this with your thread to update the text
n = 1
while True:
time.sleep(2)
newtext = str(n)+" monkey" if n == 1 else str(n)+" monkeys"
GObject.idle_add(
self.textbuffer.set_text, newtext,
priority=GObject.PRIORITY_DEFAULT
)
n += 1
def run_gui():
window = InterFace()
# 4. this is where we call GObject.threads_init()
GObject.threads_init()
window.show_all()
Gtk.main()
run_gui()
Found a Solution:
GObject.timeout_add
http://www.pygtk.org/pygtk2reference/gobject-functions.html#function-gobject--timeout-add
This GObject Function implements a Callback to a own Function every X Milliseconds. Found a pritty simple Explanation here:
https://gist.github.com/jampola/473e963cff3d4ae96707
So i can work down all Informations gathered from the Queue every X Milliseconds. Nice way for dealing with Threads!
I have been trying to set up a progress bar in a python tkinter gui that shows that a process is running. The process is long and I have no way to really measure the progress, so I need to use an indeterminate progress bar. However, I really dislike the style of the ttk indeterminate progress bar that bounces back and forth. I want one that scrolls across the bar over and over again, kind of like this image
Is this possible with tkinter?
have you tried ttk's determinate Progressbar? You can make the progress just continuously scroll across the bar.
for example:
#!/usr/bin/env python3
import tkinter
import tkinter.ttk as ttk
root = tkinter.Tk()
frame = ttk.Frame()
pb = ttk.Progressbar(frame, length=300, mode='determinate')
frame.pack()
pb.pack()
pb.start(25)
root.mainloop()
I know its an old question, but I have found a way to do this for anyone else writing tkinter.
I've been working on a tkinter app for a bit now and have determined that to handle tkinter objects, you absolutely need a separate thread. Although it is apparently frowned upon to handle tkinter objects via something else than the mainloop() method, it has been working well for me. I've never had a main thread is not in main loop error and never experienced objects that didn't update correctly.
I edited Corey Goldberg's code a bit and got it working. Here's what I got (some explanations in the comments).
import tkinter
import tkinter.ttk as ttk
import threading
def mainProgram(): # secure the main program initialization in its own def
root = tkinter.Tk()
frame = ttk.Frame()
# You need to use indeterminate mode to achieve this
pb = ttk.Progressbar(frame, length=300, mode='indeterminate')
frame.pack()
pb.pack()
# Create a thread for monitoring loading bar
# Note the passing of the loading bar as an argument
barThread = threading.Thread(target=keepLooping, args=(pb,))
# set thread as daemon (thread will die if parent is killed)
barThread.daemon=True
# Start thread, could also use root.after(50, barThread.start()) if desired
barThread.start()
pb.start(25)
root.mainloop()
def keepLooping(bar):
# Runs thread continuously (till parent dies due to daemon or is killed manually)
while 1:
"""
Here's the tricky part.
The loading bar's position (for any length) is between 0 and 100.
Its position is calculated as position = value % 100.
Resetting bar['value'] to 0 causes it to return to position 0,
but naturally the bar would keep incrementing forever till it dies.
It works, but is a bit unnatural.
"""
if bar['value']==100:
bar.config(value=0) # could also set it as bar['value']=0
if __name__=='__main__':
mainProgram()
I've added if __name__=='__main__': because I feel it defines the scope a bit better.
As a side note I've found that running threads with while 1: will crank my CPU at about 20-30% usage for that one thread in particular. It's easily solvable by importing time and using time.sleep(0.05) thereafter significantly lowering the CPU usage.
Tested on Win8.1, Python 3.5.0.
For my class, I am creating a "Mandelbrot Explorer" program. There is one main issue: I lose control of the GUI (all written in Tkinter/Ttk, in Python 2.7) when actually drawing to the Canvas.
Here is my code:
# There is some code above and below, but only this is relevant
for real, imag in graph.PlaneIteration(self.graph.xMin, self.graph.xMax, resolution, self.graph.yMin, self.graph.yMax, resolution, master = self.graph, buffer_action = self.graph.flush):
# the above line iterates on the complex plane, updating the Canvas for every x value
c = complex(real, imag)
function, draw, z, current_iter = lambda z: z**2 + c, True, 0, 1
while current_iter <= iterations:
z = function(z)
if abs(z) > limit:
draw = False
break
current_iter += 1
self.progressbar.setValue(100 * (real + self.graph.xMax) / total)
color = self.scheme(c, current_iter, iterations, draw)
# returns a hex color value
self.graph.plot(c, color)
# self.graph is an instance of my custom class (ComplexGraph) which is a wrapper
# around the Canvas widget
# self.graph.plot just creates a line on the Canvas:
# self.create_line(xs,ys,xs+1,ys+1, fill=color)
My issue is that when run, the graphing takes a while - about 30 seconds. In this time, I cannot use the GUI. If I try to, the window freezes and only unfreezes once the drawing is done.
I tried using threading (I enclosed the entirety of the upper code in a function, thread_process):
thread.start_new_thread(thread_process, ())
However, the problem remains.
Is there a way to fix this? Thanks!
You can execute your loop "threaded" with Tkinter by implicitly returning to Tkinter's main loop execution after every point your draw. Do this by using widget.after to register the next function call:
plane = graph.PlaneIteration(...)
def plotNextPoint():
try:
real, imag = plane.next()
except StopIteration:
return
c = complex(real, imag)
...
self.graph.plot(c, color)
self.graph.after(0, plotNextPoint)
plotNextPoint()
This way, after each point you draw, the Tkinter mainloop will run again and update the display before calling your plotNextPoint function again. If this is too slow, try wrapping the body of plotNextPoint in a for _ in xrange(n) loop to draw n points between redraws.
You're right about the cause of the problem—the GUI event loop is not running while you're busy running this code.
And you're right about threading being a good solution. (The other major solution is to break the job up into smaller subtasks and have each one schedule the next. For a more detailed overview of the options and all of the wrinkles, see Why your GUI app freezes.)
But it's not quite as simple as putting the whole thing on a thread.
Unfortunately, Tkinter (like many GUI frameworks) is not free-threaded. You cannot call methods on any GUI objects from a background thread. If you do, different things happen on different platforms and versions, ranging from blocking the main thread to crashing the program to raising exceptions.
Also, remember that, even without Tkinter, you can't safely share mutable objects between threads without some kind of synchronization. And you're doing exactly that with the Tkinter objects, right?
The Tkinter wiki explains one way to get around both of these problems at once in Tkinter and Threads: Create a Queue, have the background thread put messages on it, and have the main thread check it every so often (e.g., by using after to schedule a nonblocking get every 100ms until the background thread is done).
If you don't want to come up with a "protocol" for passing data from the background thread to the main thread, remember that in Python, a bound method, or a tuple of a bound method and some arguments, it perfectly good, passable data. So, instead of calling self.graph.plot(c, color), you can just self.q.put((self.graph.plot, c, color)).
The library mtTkinter wraps this all up for you, making it look like Tkinter is free-threaded by using a Queue in the background. It isn't highly tested or frequently maintained, but even if it doesn't work in the future it still makes great sample code.
I'm trying to learn how to use the thread module. I followed along with the instructions here: http://effbot.org/zone/tkinter-threads.htm
My hope is the test script will:
Print out the "count" every two seconds
Show a pop-up dialog window (also every 2 seconds)
The pop-ups should be allowed to accumulate (if I don't click "OK" for a while, there should be
multiple pop-ups)
However, when I run this script it will freeze the main window and after a while crash. I think I'm not implementing the thread module correctly.
Could someone please have a look and point out what I'm doing wrong?
Here is what I've tried so far:
from Tkinter import *
import thread
import Queue
import time
class TestApp:
def __init__(self, parent):
self.super_Parent = parent
self.main_container = Frame(parent)
self.main_container.pack()
self.top_frame = Frame(self.main_container)
self.top_frame.pack(side=TOP)
self.bottom_frame = Frame(self.main_container)
self.bottom_frame.pack(side=TOP)
self.text_box = Text(self.top_frame)
self.text_box.config(height=20, width=20)
self.text_box.pack()
self.queue = Queue.Queue()
self.update_me()
def show_popup(self):
self.my_popup = Toplevel(self.main_container)
self.my_popup.geometry('100x100')
self.popup_label = Label(self.my_popup, text="Hello!")
self.popup_label.pack(side=TOP)
self.pop_button = Button(self.my_popup, text="OK", command=self.my_popup.destroy)
self.pop_button.pack(side=TOP)
def write(self, line):
self.queue.put(line)
def update_me(self):
try:
while 1:
line = self.queue.get_nowait()
if line is None:
self.text_box.delete(1.0, END)
else:
self.text_box.insert(END, str(line))
self.text_box.see(END)
self.text_box.update_idletasks()
except Queue.Empty:
pass
self.text_box.after(100, self.update_me)
def pipeToWidget(input, widget):
widget.write(input)
def start_thread():
thread.start_new(start_test, (widget,))
def start_test(widget):
count = 0
while True:
pipeToWidget(str(count) + "\n", widget)
count += 1
time.sleep(2)
widget.show_popup()
root = Tk()
widget = TestApp(root)
start_button = Button(widget.bottom_frame, command=start_thread)
start_button.configure(text="Start Test")
start_button.pack(side=LEFT)
root.title("Testing Thread Module")
root.mainloop()
I can't reproduce your problem, but I can see why it would happen.
You're using the queue to pass messages from the background thread to the main thread for updating text_box, which is correct. But you're also calling widget.show_popup() from the background thread, which means it creates and displays a new Toplevel in the background thread. That's not correct.
All UI code must run in the same thread—not all UI code for each top-level window, all UI code period. On some platforms, you may get away with running each window in its own thread (or even free-threading everything), but that isn't supposed to work, and definitely will crash or do improper things on some platforms. (Also, that single UI thread has to be the initial thread on some platforms, but that isn't relevant here.)
So, to fix this, you need to do the same dance for creating the popups that you do for updating the textbox.
The obvious way to do that is to move the widget.show_popup() to the loop in update_me(). If you want it to happen 2 seconds after the textbox updates, just add self.top_frame.after(2000, self.show_popup) to the method.
But I'm guessing you're trying to teach yourself how to have multiple independent updating mechanisms, so telling you "just use a single update queue for everything" may not be a good answer. In that case, just create two queues, and a separate update method servicing each queue. Then, do your pipeToWidget, sleep 2 seconds, then pipeToPopup.
Another way around this is to use mtTkinter. It basically does exactly what you're doing, but makes it automatic, pushing each actual Tk GUI call onto a queue to be run later by the main loop. Of course your objects themselves have to be thread-safe, and this also means that you have to deal with the GUI calls from one thread getting interleaved with calls from another thread. But as long as neither of those is a problem (and they don't seem to be in your case), it's like magic.
If you want to know why this is freezing and/or crashing for you on Win7 and not for me on OS X 10.8… well, you really need to look into a mess of Tcl, C, and Python code, and also at how each thing is built. And, unless it's something simple (like your Tk build isn't free-threaded), it wouldn't tell you much anyway. The code isn't supposed to work, and if it seems to work for me… that probably just means it would work every time until the most important demo of my career, at which point it would fail.
I'm working on my first Python and GTK script. I'm trying to do a counter/timer. The problem I have is that, while the logging function returns the proper values every second, the gtk.label is not updated. What am I doing wrong?
def startTimer(self, buttonStart):
self.imgTimer.set_from_stock(Gtk.STOCK_YES, 2)
self.runTimer(120)
def runTimer(self, timeout):
for i in reversed(range(0,timeout)):
logging.debug(i) #returns values
self.labelTimer.set_text(i) #doesn't do anything
time.sleep(1)
You don't give GTK+ a chance to draw the updated the label. You should either use threads (see PyGTK FAQ), or something like
while gtk.events_pending ():
gtk.main_iteration ()
after updating the label.