How to enable threading with tkinter? - python-3.x

When I want to build a program like a clock on Python3,there is a problem about threading & tkinter.
my_code :
#!/usr/bin/python3
#-*-coding:utf-8-*-
import tkinter as tk,time,threading,queue
def update_time(in_q):
while True:
in_q.put(time.strftime('%Y-%m-%d %H:%M:%S',time.localtime()))
class App_gui:
def __init__(self,parent):
self.top_frame = tk.Frame(parent)
self.clock = tk.Label(self.top_frame)
self.clock.pack()
self.top_frame.pack()
self.begin_thread()
def begin_thread(self):
self.clock_q = queue.Queue()
self.clock_thread = threading.Thread(target=update_time(self.clock_q))
self.clock_thread.start()
self.listen()
def listen(self):
gate_time = self.clock_q.get()
self.clock.config(text=gate_time)
self.clock.after(200,self.listen)
if __name__ == '__main__':
root = tk.Tk()
my_app = App_gui(root)
root.mainloop()
when I run this code,there's nothing happen.

Well threading is not all that difficult however in this case threading is overkill for a simple time loop.
We can use after() to manage a label for time without having to use threading.
import tkinter as tk
from time import strftime
class AppGUI(tk.Tk):
def __init__(self):
super().__init__()
self.time_label = tk.Label(self)
self.time_label.pack()
self.track_time()
def track_time(self):
self.time_label.config(text="{}".format(strftime('%Y-%m-%d %H:%M:%S')))
self.time_label.after(1000, self.track_time)
if __name__ == '__main__':
AppGUI().mainloop()

solved
#!/usr/bin/env python3
#-*-coding:utf-8-*-
import threading,time,tkinter as tk
def clock_task():
global clock_time
while True:
clock_time = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime())
time_label.config(text=clock_time)
time.sleep(1)
clock_t = threading.Thread(target=clock_task)
clock_t.setDaemon(True)
root = tk.Tk()
time_label = tk.Label(root)
time_label.pack()
# start thread before mainloop
clock_t.start()
root.mainloop()

Related

Python kivy and multiprocessing bug

I am stuck on writing a very simple kivy gui with 2 buttons.
button_1 launches the countdown in multiprocessing. It works.
button_2 is supposed to end the countdown in multiprocessing. That does not work...
Can anyone please point out what I am doing wrong and why?
Thank you kindly in advance.
from kivy.app import App
from kivy.uix.button import Button
from kivy.uix.boxlayout import BoxLayout
import multiprocessing
import time
class MyApp(App):
list_of_objects_in_class = []
def build(self):
layout = BoxLayout()
button_1 = Button(text='launch_countdown', on_press=self.launch_countdown)
button_2 = Button(text='end_the_countdown', on_press=self.end_the_countdown)
layout.add_widget(button_1)
layout.add_widget(button_2)
return layout
#staticmethod
def actual_countdown_function(*args):
print('I am launching the countdown!')
for count in range(5, 0, -1):
print(count)
time.sleep(1)
print('Countdown Finished!!!') # When I press end_the_countdown button, this is NOT supposed to be printed.
def launch_countdown(self, *args):
MyApp.list_of_objects_in_class.append(multiprocessing.Process(target=MyApp.actual_countdown_function, args=()))
MyApp.list_of_objects_in_class[0].start()
def end_the_countdown(self, *args):
print('I am terminating the countdown early!')
try:
MyApp.list_of_objects_in_class[0].terminate()
MyApp.list_of_objects_in_class.clear()
except:
pass
if __name__ == "__main__":
MyApp().run()
I tested to see if .terminate() works on my system, it works. The script below terminates successfully but the script above does NOT...
import multiprocessing
import time
def print_one():
time.sleep(3)
print('This does NOT get printed because proc.terminate() works on my Linux Mint OS')
def print_two():
print('This gets printed')
process_to_terminate = multiprocessing.Process(target=print_one, args=())
process_to_terminate.start()
process_to_keep_and_NOT_terminate = multiprocessing.Process(target=print_two, args=())
process_to_keep_and_NOT_terminate.start()
process_to_terminate.terminate() # Works and kills print_one function
It's better to use threads indeed to run such a small tasks.
Anyway, I think, the original problem could be solved by setting SIGTERM handler in a child process, because it seems to be ignoring SIGTERM by default:
def actual_countdown_function(self, *args):
+ signal.signal(signal.SIGTERM, lambda *_, **__: sys.exit(0))
print('I am launching the countdown!')
But, TBH, if you will really need a way to manage child process, I strongly recommend to use Locks/Conditions/Queues instead. They have almost the same API like all the threading primitives, but built to interact over pipes/shared-memory/sockets/etc.
after many different attempts, I was able to solve my own problem by using threading instead of multiprocessing.
Here is the code:
from kivy.app import App
from kivy.uix.button import Button
from kivy.uix.boxlayout import BoxLayout
import threading
import time
class MyApp(App):
my_var = True
list_of_objects_in_class = []
def build(self):
layout = BoxLayout(size =(300, 300))
button_1 = Button(text='launch_countdown', on_press=self.launch_countdown)
button_2 = Button(text='end_the_countdown', on_press=self.end_the_countdown, height = "100", width = 140)
layout.add_widget(button_1)
layout.add_widget(button_2)
return layout
def actual_countdown_function(self, *args):
print('I am launching the countdown!')
x = 5
while x != 0:
new_var = self.my_var
if new_var == False:
break
time.sleep(1)
print(x, new_var)
x -=1
del(new_var)
print('Countdown Finished!!!') # When I press end_the_countdown button, this is NOT supposed to be printed.
def launch_countdown(self, *args):
t1 = threading.Thread(target=self.actual_countdown_function)
t1.start()
def end_the_countdown(self, *args):
print('I am terminating the countdown early!')
self.my_var = not self.my_var
print(self.my_var)
self.list_of_objects_in_class.clear()
if __name__ == "__main__":
MyApp().run()

PyQt5 add second function to thread but not work

Previously I have tried to use Flask for doing the followings simultaneously:
Display live video streaming
Display real-time data streaming
Control the robot car
As the above is just for demonstration, with the video streaming performance not good enough, I decided to change the whole application to PyQt5 for further development and production. Now I can create the GUI for displaying live video streaming well, while the real-time data streaming cannot be done well. The error is
QObject::startTimer: Timers can only be used with threads started with QThread
The following is the whole program. Please help to see what's wrong in the adding thread issue. Thanks!
import sys
from PyQt5 import QtWidgets, QtCore, QtGui
import cv2
from vidgear.gears import CamGear
from random import random
data_list=[]
fps=60
options_cam={"CAP_PROP_FRAME_WIDTH":640,"CAP_PROP_FRAME_HEIGHT":480,"CAP_PROP_FPS":fps}
stream=CamGear(source=0,logging=False,**options_cam).start()
class MainWindow(QtWidgets.QWidget):
def __init__(self,*args):
super(MainWindow, self).__init__()
self.setWindowTitle('Vehicle control')
self.grid_layout=QtWidgets.QGridLayout()
self.video_label = QtWidgets.QLabel('Video streaming',self)
self.video_frame = QtWidgets.QLabel(self)
self.grid_layout.addWidget(self.video_label,0,0)
self.grid_layout.addWidget(self.video_frame,1,0)
self.data_label = QtWidgets.QLabel('Data streaming',self)
self.data_frame = QtWidgets.QListWidget(self)
self.grid_layout.addWidget(self.data_label,0,1)
self.grid_layout.addWidget(self.data_frame,1,1)
self.setLayout(self.grid_layout)
#self.thread=QtCore.QThread()
#self.thread.started.connect(self.nextFrameSlot)
#self.thread.start()
self.timer=QtCore.QTimer()
self.timer.timeout.connect(self.video_stream)
self.timer.start(0)
self.thread=QtCore.QThread()
self.thread.start()
self.timer2=QtCore.QTimer()
self.timer2.moveToThread(self.thread)
self.timer2.timeout.connect(self.data_stream)
self.timer2.start(0)
def video_stream(self):
frame = stream.read()
# My webcam yields frames in BGR format
frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
img = QtGui.QImage(frame, frame.shape[1], frame.shape[0], QtGui.QImage.Format_RGB888)
pix = QtGui.QPixmap.fromImage(img)
self.video_frame.setPixmap(pix)
QtCore.QThread.sleep(0)
def data_stream(self):
print("data stream")
stream_data=round(random()*10,3)
data_list.insert(0,str(stream_data)+'\n')
if len(data_list)>10:
del data_list[-1]
for i in range(len(data_list)):
self.data_frame.addItem(data_list[i])
self.data_frame.show()
QtCore.QThread.sleep(1000)
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec_())
Edit:
Thanks #musicamante's answer. I have updated the code as follows but still have the error "segmentation fault" for the video streaming, while if I run for data stream only, the updated list can be shown. So what's wrong with the setPixmap function? Thanks again!
import sys
from PyQt5 import QtWidgets, QtCore, QtGui
import cv2
from vidgear.gears import CamGear
from random import random
fps=60
options_cam={"CAP_PROP_FRAME_WIDTH":480,"CAP_PROP_FRAME_HEIGHT":480,"CAP_PROP_FPS":fps}
stream=CamGear(source=0,logging=False,**options_cam).start()
class CamGrabber(QtCore.QThread):
frame = QtCore.pyqtSignal(QtGui.QImage)
def run(self):
while True:
new_frame = stream.read()
new_frame = cv2.cvtColor(new_frame, cv2.COLOR_BGR2RGB)
img = QtGui.QImage(new_frame, new_frame.shape[1], new_frame.shape[0], QtGui.QImage.Format_RGB888)
self.frame.emit(img)
class DataProvider(QtCore.QThread):
data = QtCore.pyqtSignal(object)
def run(self):
while True:
newData = round(random()*10,3)
self.data.emit(newData)
QtCore.QThread.sleep(1)
class MainWindow(QtWidgets.QWidget):
def __init__(self,*args):
super(MainWindow, self).__init__()
self.setWindowTitle('Vehicle control')
self.grid_layout=QtWidgets.QGridLayout()
self.video_label = QtWidgets.QLabel('Video streaming',self)
self.video_frame = QtWidgets.QLabel(self)
self.grid_layout.addWidget(self.video_label,0,0)
self.grid_layout.addWidget(self.video_frame,1,0)
self.data_label = QtWidgets.QLabel('Data streaming',self)
self.data_frame = QtWidgets.QListWidget(self)
self.grid_layout.addWidget(self.data_label,0,1)
self.grid_layout.addWidget(self.data_frame,1,1)
self.setLayout(self.grid_layout)
self.camObject = CamGrabber()
self.camObject.frame.connect(self.newFrame)
self.camObject.start()
self.dataProvider = DataProvider()
self.dataProvider.data.connect(self.newData)
self.dataProvider.start()
def newFrame(self, img):
self.video_frame.setPixmap(QtGui.QPixmap.fromImage(img))
def newData(self, data):
self.data_frame.insertItem(0,str(data))
if self.data_frame.count() > 10:
self.data_frame.takeItem(9)
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec_())```
The QTimer error basically means that the a QTimer can only be started from the thread it exists.
Besides that, GUI element should always be directly accessed or modified from the main thread, not from another one.
In order to accomplish that, you'll need to create a separate "worker" thread, and communicate with the main one by taking advantage of the signal/slot mechanism.
class CamGrabber(QtCore.QThread):
frame = QtCore.pyqtSignal(QtGui.QImage)
def run(self):
while True:
frame = stream.read()
frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
img = QtGui.QImage(frame, frame.shape[1], frame.shape[0], QtGui.QImage.Format_RGB888)
self.frame.emit(img)
class DataProvider(QtCore.QThread):
data = QtCore.pyqtSignal(object)
def run(self):
while True:
newData = round(random()*10,3)
self.data.emit(newData)
# note that QThread.sleep unit is seconds, not milliseconds
QtCore.QThread.sleep(1)
class MainWindow(QtWidgets.QWidget):
def __init__(self,*args):
super(MainWindow, self).__init__()
self.setWindowTitle('Vehicle control')
self.grid_layout=QtWidgets.QGridLayout()
self.video_label = QtWidgets.QLabel('Video streaming',self)
self.video_frame = QtWidgets.QLabel(self)
self.grid_layout.addWidget(self.video_label,0,0)
self.grid_layout.addWidget(self.video_frame,1,0)
self.data_label = QtWidgets.QLabel('Data streaming',self)
self.data_frame = QtWidgets.QListWidget(self)
self.grid_layout.addWidget(self.data_label,0,1)
self.grid_layout.addWidget(self.data_frame,1,1)
self.setLayout(self.grid_layout)
self.camObject = CamGrabber()
self.camObject.frame.connect(self.newFrame)
self.camObject.start()
self.dataProvider = DataProvider()
self.dataProvider.data.connect(self.newData)
self.dataProvider.start()
def newFrame(self, img):
self.video_frame.setPixmap(QtGui.QPixmap.fromImage(img))
def newData(self, data):
self.data_frame.addItem(str(data))
if self.data_frame.count() > 10:
self.data_frame.takeItem(0)
If, for any reason, you want to control the data fetching from the main thread via a QTimer, you could use a Queue:
from queue import Queue
class DataProvider(QtCore.QObject):
data = QtCore.pyqtSignal(object)
def __init__(self):
super().__init__()
self.queue = Queue()
def run(self):
while True:
multi = self.queue.get()
# simulate a time consuming process
QtCore.QThread.sleep(5)
newData = round(multi * 10, 3)
self.data.emit(newData)
def pushData(self, data):
self.queue.put(data)
class MainWindow(QtWidgets.QWidget):
def __init__(self,*args):
# ...
self.requestTimer = QtCore.QTimer()
self.requestTimer.setInterval(1000)
self.requestTimer.timeout.connect(self.requestData)
# this will cause the timer to be executed only once after each time
# start() is called, so no new requests will overlap
self.requestTimer.setSingleShot(True)
self.requestTimer.start()
def requestData(self):
value = random()
print('requesting data with value {}'.format(value))
self.dataProvider.pushData(value)
print('waiting for result')
def newFrame(self, img):
self.video_frame.setPixmap(QtGui.QPixmap.fromImage(img))
def newData(self, data):
print('data received')
self.data_frame.addItem(str(data))
if self.data_frame.count() > 10:
self.data_frame.takeItem(0)
# restart the timer
self.requestTimer.start()

Unable to executing multiple functions using multiprocessing module from a TKinter GUI

Hi I have a small GUI which has a button using which two functions must be executed in different processors. In reality these two functions are heavy calculations. I do not want to use multi threading. I want them to run on 2 different processors. When I try to execute the button, another instance of the GUI gets created and it says
File "C:\Python3.7\lib\multiprocessing\reduction.py", line 60, in dump.
ForkingPickler(file, protocol).dump(obj)
TypeError: can't pickle _tkinter.tkapp objects
My code is as follows.
from multiprocessing import Process
from tkinter import Button, Tk, Frame
class GUI(Frame):
def __init__(self):
super().__init__()
self.button = Button(self, text="Start", command=self.execute)
self.button.pack()
self.pack()
def F1(self):
print("Hello")
def F2(self):
print("World")
def execute(self):
self.P1 = Process(target = self.F1)
self.P2 = Process(target = self.F2)
self.P1.start()
self.P2.start()
self.P1.join()
self.P2.join()
Root = Tk()
Software = GUI()
Root.mainloop()
Please click here
The problem lies with pickling tkinter widgets. You simply cannot do it, as the Tcl interpreter does not understand the python pickle format.
Coming to your code, I tried the following and it prints as expected:
from multiprocessing import Process
from tkinter import Button, Tk, Frame
class GUI(Frame):
def __init__(self):
super().__init__()
self.button = Button(self, text="Start", command=self.execute)
self.button.pack()
self.pack()
#staticmethod
def F1():
print("Hello")
#staticmethod
def F2():
print("World")
def execute(self):
self.P1 = Process(target = GUI.F1())
self.P2 = Process(target = GUI.F2())
self.P1.start()
self.P2.start()
self.P1.join()
self.P2.join()
Root = Tk()
Software = GUI()
Root.mainloop()

Update a progressbar in a thread

I have a Python code where I create a progressbar. The Tkinter environment is created in the Gui function with the progressbar and it is launched as a thread. Then in an other thread I calculate the value that the progressbar must have, but the problem is that I dont know how to update the Gui thread with the new value of the progressbar. Here is my code:
import tkinter as tk
from tkinter import ttk
import thread
def Gui():
root = tk.Tk()
root.geometry('450x450')
root.title('Hanix Downloader')
button1 = tk.Button(root, text='Salir', width=25,command=root.destroy)
button1.pack()
s = ttk.Style()
s.theme_use('clam')
s.configure("green.Horizontal.TProgressbar", foreground='green', background='green')
mpb = ttk.Progressbar(root,style="green.Horizontal.TProgressbar",orient ="horizontal",length = 200, mode ="determinate")
mpb.pack()
mpb["maximum"] = 3620
mpb["value"] = 1000
root.mainloop()
def main():
while True:
#Calculate the new value of the progress bar.
mpb["value"] = 100 #Does not work
root.update_idletasks()#Does not work
#Do some other tasks.
if __name__ == '__main__':
thread.start_new_thread( Gui,() )
thread.start_new_thread( main,() )
The error I get is that mpb and root do no exist. Thanks in advance.
You should get error because mpb and root are local variables which exist only in Gui but not in main. You have to use global to inform both functions to use global variables - and then main will have access to mpb created in Gui
I also add time.sleep(1) before while True: because sometimes main may start faster then Gui and it may not find mpb (because Gui had no time to create progressbar)
import tkinter as tk
from tkinter import ttk
import _thread
import time
def Gui():
global root, mpb
root = tk.Tk()
button1 = tk.Button(root, text='Exit', command=root.destroy)
button1.pack()
mpb = ttk.Progressbar(root, mode="determinate")
mpb.pack()
mpb["maximum"] = 3000
mpb["value"] = 1000
root.mainloop()
def main():
global root, mpb
time.sleep(1)
while True:
mpb["value"] += 100
#root.update_idletasks() # works without it
#Do some other tasks.
time.sleep(0.2)
if __name__ == '__main__':
_thread.start_new_thread(Gui, ())
_thread.start_new_thread(main, ())
Tested on Python 3.6.2, Linux Mint 18.2
EDIT: more precisely: you need global only in Gui because it assigns values to variables
root = ..., mpb = ....

How to start tkmessagebox from different class than main GUI?

I have a class GUI, where I set up all my widgets etc. for my GUI. I use threading to start a process from another class. This works fine, as long that other process just runs through. In some cases, I need to wait for a user input to proceed. I used tkmessagebox for this, but the messagebox doesn't appear and blocks the GUI without any error message. (It works when not started through the GUI).
Here's part of my code:
GUI part
from Tkinter import *
import Tkinter as ttk
import ttk
from tkMessageBox import *
from GUI_reader import Commandline_Reader
import threading
import Queue
class GUI:
def __init__(self,master):
self.master = master
self.argString='ds'
## self.workerThread()
button = ttk.Button(text='start', command=self.go).grid()
...
def go(self):
self.thread = threading.Thread(target=self.workerThread)
self.thread.daemon = True
self.thread.start()
...
def workerThread(self):
...
reader = Commandline_Reader(master, self.argString)
if reader.connect():
print 'success'
reader.run()
print 'success'
if __name__ == '__main__':
root = Tk()
client = GUI(root)
root.mainloop()
class commandline_reader:
import tkMessageBox
...
class Commandline_Reader:
def __init__(self, master, argString='')
self.master = master
...
def connect(self)
...
def run(self):
...
tkMessageBox.askokcancel('Calibration', 'Hit ok to start calibration', parent= self.master)
...
if __name__ == '__main__':
reader = Commandline_Reader(self,master)
if not reader.connect():
exit(-1)
if not reader.run():
exit(-2)

Resources