tkinter update() and update_idletasks() - python-3.x

I 'm reading a lot of update() and update_idletasks().
I did a GUI with some widgets. One button is called Run and it runs a motor endless. Another button is called Stop, and as you can image, it is to stop the movement of the motor. All works properly but the print("update") is called more and more often. At the first cycle of the motor 2 time, cycle by cycle leak source from the OS.
How can I solve the issue? I tried with update_idletasks() as well. instead to self.update() i could insert a function just to read the Stop button. any idea?
class GUI(object):
def __init__(self, master):
self.globalvar()
self.bus = smbus.SMBus(1)
self.master = master
#----------Load the GUI
self.loadGUI()
self.up()
def Running(self):
while self.s:
movethemotor()
self.up()
def Stop(self):
self.s = False
print("Stop")
def up(self):
...
self.master.update()
self.master.after(100,self.up)
if __name__ == "__main__":
root = tk.Tk()
root.title("MLCT GUI")
rpi = GUI(root)
root.geometry('1050x530')
root.resizable(False, False)
root.mainloop()
Another things that get me cracy: if I replace the "up" function:
def up(self):
...
root.update()
root.after(100,self.up)
I can read the print update, one time the first cycle, 2 time the second, 3 times the third etc.

Related

Controlling another application from within tkinter

I don't know if what I'm seeking to achieve is even possible. I have written a tkinter app which imports a method from an external class. This method runs a hill-climbing algorithm which will run perpetually and try to improve upon the "score" that it has calculated. After each pass, it presents the current output and score to the user and asks (on the command line) if they wish to continue.
The first challenge in getting this working was to implement threading. I have this working, but I don't don't know if I have done it correctly.
The algorithm will continue until the user signals that they have got the answer they were looking for, or loses the will to live and presses CTRL-C.
In my tkinter main app, this presents me with two problems:
How to display the output from this external method. I tried to write a while loop that polled the output field periodically, (see commented out section of the "start_proc" method) but that was clearly never going to work, and besides, I would ideally like to see real-time output; and
How to interact with the algorithm to either continue or stop (see commented out section of the "my_long_procedure" method). As you can see, I can inject a "stopped" attribute, and that does indeed halt the algorithm, but I can't take my eyes off the output because the desired answer may have gone past before I can press stop.
Below is, I hope, a simplified bare-bones example of what I am trying to do.
This is a learning exercise for me and I would be grateful of any help.
import tkinter as tk
from threading import Thread
from random import randint
import time
class MyTestClass: # This would actually be imported from another module
def __init__(self):
self.stopped = False
def my_long_procedure(self):
# Fake method to simulate actual algorithm
count = 0
maxscore = 0
i = 0
while count < 1000 and not self.stopped:
i += 1
score = randint(1,10000)
if score > maxscore:
maxscore = score
self.message = f'This is iteration {i} and the best score is {maxscore}'
print(self.message)
# self.carry_on = input("Do you want to continue? ")
# if self.carry_on.upper() != "Y":
# return maxscore
time.sleep(2)
print('OK - You stopped me...')
class MyMainApp(tk.Tk):
def __init__(self, title="Sample App", *args, **kwargs):
super().__init__()
self.title(title)
self.test_run = MyTestClass()
self.frame1 = tk.LabelFrame(self, text="My Frame")
self.frame1.grid(row=0, column=0, columnspan=2, padx=5, pady=5, sticky=tk.NSEW)
self.frame1.columnconfigure(0, weight=1)
self.frame1.rowconfigure(0, weight=1)
start_button = tk.Button(self.frame1, text="Start!",
command=self.start_proc).grid(row=0, column=0, padx=5, pady=5, sticky=tk.W)
stop_button = tk.Button(self.frame1, text="Stop!",
command=self.stop_proc).grid(row=0, column=2, padx=5, pady=5, sticky=tk.E)
self.output_box = tk.Text(self.frame1, width=60, height=8, wrap=tk.WORD)
self.output_box.grid(row=1, column=0, columnspan=3, sticky=tk.NSEW)
def start_proc(self):
self.test_run.stopped = False
self.control_thread = Thread(target=self.test_run.my_long_procedure, daemon=True)
self.control_thread.start()
time.sleep(1)
self.output_box.delete(0.0, tk.END)
self.output_box.insert(0.0, self.test_run.message)
# self.control_thread.join()
# while not self.test_run.stopped:
# self.output_box.delete(0.0, tk.END)
# self.output_box.insert(0.0, self.test_run.message)
# time.sleep(0.5)
def stop_proc(self):
self.test_run.stopped = True
if __name__ == "__main__":
MyMainApp("My Test App").mainloop()
If you own the implementation of the algorithm you could pass a callback (a method of MyMainApp) so that the algorithm signals on his own whenever he has done some "work" worth notification to the user. This would look like:
def my_long_procedure(self,progress):
The callback prototype could be:
def progress(self,iteration,result):
and instead of print(self.message) you could do progress(i,maxscore)
starting the thread:
self.control_thread = Thread(target=self.test_run.my_long_procedure, daemon=True,args=(self.progress,))
Unfortunatly you need to be aware that you cannot refresh the GUI from another thread than the main thread. This is a much discussed tkinter limitation. So in a nutshell you cannot directly call any of your GUI widgets from progress function. The workaround to this issue is to store the progress in the progress function and register a function to be executed whenever the tkinter main loop will be idle. you can do somethink like self.after_idle(self.update_ui) from progress method. update_ui() would be a new methode updating eg a progress bar or your graph using data transmitted by the progress callback and saved as MyMainApp properties.
More on this "pattern" (using message queues instead of callback) here:
https://www.oreilly.com/library/view/python-cookbook/0596001673/ch09s07.html

wxPython class doesnt show in window

So i created a class that runs a big calculation in a seperet thread.
This is what i expected to see:
And this is what i do see:
This is the frame class
class ThreadFrame(wx.Frame):
def __init__(self,parent=None):
wx.Frame.__init__(self,parent=parent)
self.frame = wx.Frame(None, title='Bitte Warten',style=wx.FRAME_NO_TASKBAR)
self.frame.SetSize(500,100)
self.panel=wx.Panel(self.frame)
self.parent=parent
self.WaitLbl=wx.StaticText(self.panel,-1)
self.WaitLbl.SetLabel('Geotags werden ausgelesen und abgerufen.')
self.progress = wx.Gauge(self.panel,size=(500,30), range=self.parent.list_ctrl.GetItemCount())
self.btn = wx.Button(self.panel,label='Abbrechen')
self.btn.Bind(wx.EVT_BUTTON, self.OnExit)
self.Sizer=wx.BoxSizer(wx.VERTICAL)
#Add Widgets LeftSizer
self.Sizer.Add(self.WaitLbl,0,wx.ALL|wx.CENTER,5)
self.Sizer.Add(self.progress,0,wx.ALL,5)
self.Sizer.Add(self.btn,0,wx.ALL|wx.CENTER,5)
self.panel.SetSizer(self.Sizer)
self.Sizer.Fit(self.panel)
self.panel.Layout()
self.Centre()
#Bind to the progress event issued by the thread
self.Bind(EVT_PROGRESS_EVENT, self.OnProgress)
#Bind to Exit on frame close
#self.Bind(wx.EVT_CLOSE, self.OnExit)
self.Show()
self.mythread = TestThread(self.frame, self.parent)
#Enable the GUI to be responsive by briefly returning control to the main App
while self.mythread.isAlive():
time.sleep(0.1)
wx.GetApp().Yield()
continue
try:
self.OnExit(None)
except:
pass
def OnProgress(self, event):
self.progress.SetValue(event.count)
#or for indeterminate progress
#self.progress.Pulse()
def OnExit(self, event):
if self.mythread.isAlive():
print('Thread lebt noch')
self.mythread.terminate() # Shutdown the thread
print('Thread wird beendet')
self.mythread.join() # Wait for it to finish
self.Close()
And this is the thread where the calculation is running
class TestThread(Thread):
def __init__(self,parent_target,toplevel):
Thread.__init__(self)
self.parent = toplevel
self.ownparent=parent_target
self.stopthread = False
self.start() # start the thread
def run(self):
print('Thread gestartet')
i=0
while self.stopthread == False:
#if calculation is not finished:
#do calculation and count i one up
evt = progress_event(count=i)
#Send back current count for the progress bar
try:
wx.PostEvent(self.ownparent, evt)
except: # The parent frame has probably been destroyed
self.terminate()
i=i+1
else:
print('Thread Terminated')
self.terminate()
def terminate(self):
self.stopthread = True
And this is how i call the class from my main programm:
frame=ThreadFrame(self)
The main program also has a frame open. So this is a frame which is opend and then starts a thread which does calculation and then stops.
I think thats all that is to know. I replaced the caluclation with speudo code because my brain hurts and i cant come up with a plachold right now. But i feel like between all the fiting and sizers and panels and frames i went somewhere wrong. And i totaly dont look through all of this stuff at the moment.
The code you show doesn't seem to correspond to the screenshots. Whatever problem with the layout you might have, you should still have "Bitte warten" in the frame title, but your screenshot doesn't even show this. Either you're not executing the code you show at all, or you create some other frame elsewhere in your code which you see here. Or, of course, you've uploaded a wrong screenshot or code version. But something just doesn't fit here.
So im not sure what caused the problem. I started from scratch and now it works. This time i didnt use a new frame because the class itself is allready a frame.
class GeoThreadFrame(wx.Frame):
def __init__(self, radsteuer):
wx.Frame.__init__(self,parent=radsteuer.frame,style=wx.DEFAULT_FRAME_STYLE | wx.STAY_ON_TOP|wx.FRAME_NO_TASKBAR)
panel = wx.Panel(self)
self.SetWindowStyle(wx.FRAME_NO_TASKBAR|wx.STAY_ON_TOP)
self.progress = wx.Gauge(panel,size=(300,30), pos=(10,50), range=radsteuer.list_ctrl.GetItemCount())
self.btn = wx.Button(panel,label='Abbrechen', size=(200,30), pos=(10,10))
self.btn.Bind(wx.EVT_BUTTON, self.OnExit)
panel.Center()
#Bind to the progress event issued by the thread
self.Bind(EVT_PROGRESS_EVENT, self.OnProgress)
#Bind to Exit on frame close
self.Bind(wx.EVT_CLOSE, self.OnExit)
self.Center()
self.Show()
self.mythread = GeoLocationThread(self, radsteuer)
#Enable the GUI to be responsive by briefly returning control to the main App
while self.mythread.isAlive():
#time.sleep(0.1)
wx.GetApp().Yield()
continue
try:
self.OnExit(None)
except:
pass
def OnProgress(self, event):
self.progress.SetValue(event.count)
#or for indeterminate progress
#self.progress.Pulse()
def OnExit(self, event):
if self.mythread.isAlive():
self.mythread.terminate() # Shutdown the thread
#self.mythread.join(3) # Wait for it to finish
self.Destroy()
class GeoLocationThread(Thread):
def __init__(self,parent_target,mainparent):
Thread.__init__(self)
self.parent = parent_target
self.mainparent=mainparent
self.stopthread = False
self.start() # start the thread
def run(self):
# A loop that will run for 5 minutes then terminate
i=0
while self.stopthread == False:
if i < self.mainparent.list_ctrl.GetItemCount():
self.calculation(i)
evt = progress_event(count=i)
i=i+1
#Send back current count for the progress bar
try:
wx.PostEvent(self.parent, evt)
except: # The parent frame has probably been destroyed
self.terminate()
else:
self.terminate()
def terminate(self):
self.stopthread = True
def calculate(self,i):
#your calculation here

PyQt, QThread: GUI freeze with large amount of SIGNAL from another thread (QListWidget massive update)

I have an app that starts the GUI and performs "the heavy part" of the code in another thread using QThread. In this thread I emit a SIGNAL that is connected to the GUI Class and that performs addItem on QListWidget.
There are a massive "signaling" from this thread to the GUI and it "freeze".
Is there a way to avoid this? Have I to use another mini GUI in different thread only for QListWidget?
Thanks
EDIT:
This is the thread that execute the heavy logic
class YourThreadName(QThread):
def __init__(self, some variables):
QThread.__init__(self)
def __del__(self):
self.wait()
def run(self):
# Here there is a for cycle that emits a SIGNAL
for ... :
...
self.emit(SIGNAL("needed_variable"), needed_variable)
...
In the GUI Class there are some methods, particularly:
class GUI(QtGui.QMainWindow, GUI.Ui_MainWindow):
def __init__(self, parent=None):
super(GUI, self).__init__(parent)
self.setupUi(self)
def ... (self):
...
def start_main_code(self):
self.new_thread = YourThreadName(some variables)
self.connect(self.new_thread, SIGNAL("finished()"), self.done)
self.connect(self.new_thread, SIGNAL("needed_variable"), self.show_variable)
self.new_thread.start()
def show_variable(self, data):
self.QListWidget_object.addItem(data)
def ... (self):
...
The script below is a Minimal, Complete, and Verifiable Example based on the information currently given in your question and comments. It emits data from a worker thread every 10ms and updates a list-widget in the GUI. On my Linux system (using Python-3.6.3, Qt-4.8.7 and PyQt-4.12.1) it does not block or freeze the GUI. There is obviously some flickering whilst the list-widget is being updated, but I am able to select items, scroll up and down, click the button, etc. And if I increase the sleep to 25ms, I don't even get any flickering.
UPDATE:
The performance can be improved by using setUniformItemSizes and sending the messages in batches. On my system, after a slight initial delay, the list populates with fifty thousand items almost instantly.
import sys
from PyQt4 import QtCore, QtGui
class Worker(QtCore.QThread):
message = QtCore.pyqtSignal(object)
def run(self):
batch = []
for index in range(50000):
if len(batch) < 200:
batch.append(index)
continue
self.message.emit(batch)
batch = []
class Window(QtGui.QWidget):
def __init__(self):
super(Window, self).__init__()
self.listWidget = QtGui.QListWidget()
self.listWidget.setUniformItemSizes(True)
self.button = QtGui.QPushButton('Start')
self.button.clicked.connect(self.handleButton)
layout = QtGui.QVBoxLayout(self)
layout.addWidget(self.listWidget)
layout.addWidget(self.button)
self.worker = Worker()
self.worker.message.connect(self.handleMessages)
def handleMessages(self, batch):
for message in batch:
self.listWidget.addItem('Item (%s)' % message)
def handleButton(self):
if not self.worker.isRunning():
self.worker.start()
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
window = Window()
window.setGeometry(600, 50, 200, 400)
window.show()
sys.exit(app.exec_())

Let user force stop a loop

I am making an app that uses tkinter for interface
the interface has two buttons, one that says 'calculate' and one that says 'stop'.
the Calculate button fires a calculate() which has a recursive call to itself, which makes it either an infinite loop, or a very deep loop. I want the user to be able to be able to make the calculation stop throw the button 'stop'.
def init():
btnCalculate = Button(myframe, text="Caculate", command= Calculate, width=10)
btnStop = Button(myframe, text="Stop", command= Stop, width=10)
btnCalculate.place(x=0, y=0)
btnStop.place(x=100, y=0)
def Calculate():
Calculate(para)
def Calculate(para):
# do some stuff
# check condition
if condition:
Calculate(para)
def Stop():
return
Recursion blocks event loops that a GUI must use to do its work. So here is a modification of what you have that uses event scheduling to do what you want. To simulate recursion, I repeatedly put a call to the Calculate method on the event stack for processing. You can change the frequency from 1000 (milliseconds) to whatever you need.
from tkinter import *
stop = False
def init():
btnCalculate = Button(myframe, text="Calculate", command=Calculate, width=10)
btnStop = Button(myframe, text="Stop", command= Stop, width=10)
btnCalculate.pack()
btnStop.pack()
#def Calculate():
# Calculate(para)
def Calculate(*args):
global stop
# do some stuff
# check condition
if not stop:
print("Calculating...")
root.after(1000, lambda a=args: Calculate(a))
def Stop():
global stop
print('Stopping')
stop = True
root = Tk()
myframe = Frame(root)
myframe.pack()
init()
root.mainloop()

Python 3.5 tkinter confirmation box created progress bar and reads in csv not quite working

I'm realtively new to python and am making a GUI app that does a lot of file i/o and processing. To complete this i would like to get a confirmation box to pop-up when the user commits and actions. From this when clicking 'yes' the app then runs the i/o and displays a progress bar.
From other threads on here I have gotten as far as reading about the requirement to create an addtional thread to take on one of these processes (for example Tkinter: ProgressBar with indeterminate duration and Python Tkinter indeterminate progress bar not running have been very helpful).
However, I'm getting a little lost because I'm not activating the threaded process from the Main() function. So I'm still getting lost in how, and where, I should be creating the progress bar and passing of the i/o process to another thread (reading in a csv file here).
Here is my code and I would be very grateful for any help anyone can give me:
import tkinter as tk
import tkinter.messagebox as messagebox
import csv
import tkinter.ttk as ttk
import threading
class ReadIn(tk.Frame):
def __init__(self, parent, *args, **kwargs):
tk.Frame.__init__(self, parent, *args, **kwargs)
self.parent = parent
self.initUI()
def initUI(self):
self.parent.title("Read in file and display progress")
self.pack(fill=tk.BOTH, expand=True)
self.TestBtn = tk.Button(self.parent, text="Do Something", command=lambda: self.confirm_pb())
self.TestBtn.pack()
def confirm_pb(self):
result = messagebox.askyesno("Confirm Action", "Are you sure you want to?")
if result:
self.handle_stuff()
def handle_stuff(self):
nf = threading.Thread(target=self.import_csv)
nf.start()
self.Pbar()
nf.join()
def Pbar(self):
self.popup = tk.Tk()
self.popup.title('Loading file')
self.label = tk.Label(self.popup, text="Please wait until the file is created")
self.progressbar = ttk.Progressbar(self.popup, orient=tk.HORIZONTAL, length=200,
mode='indeterminate')
self.progressbar.pack(padx=10, pady=10)
self.label.pack()
self.progressbar.start(50)
def import_csv(self):
print("Opening File")
with open('csv.csv', newline='') as inp_csv:
reader = csv.reader(inp_csv)
for i, row in enumerate(reader):
# write something to check it reading
print("Reading Row " + str(i))
def main():
root = tk.Tk() # create a Tk root window
App = ReadIn(root)
root.geometry('400x300+760+450')
App.mainloop() # starts the mainloop
if __name__ == '__main__':
main()
The statement nf.join() in function handle_stuff() will block tkinter's main loop to show the progress bar window. Try modify handle_stuff() as below:
def handle_stuff(self):
nf = threading.Thread(target=self.import_csv)
nf.start()
self.Pbar()
#nf.join() # don't call join() as it will block tkinter's mainloop()
while nf.is_alive():
self.update() # update the progress bar window
self.popup.destroy()

Resources