How to implement the abolition of actions? - python

I write some simple program with Python and PyGObject. I wanted her to inform the user about the progress through the ProgressBar. I googled that this process should be a separate thread, otherwise the interface hold on, and now have some like this for tests:
import time
import threading
from gi.repository import Gtk, GObject
GObject.threads_init()
def tread_function():
progress.set_fraction(0)
time.sleep(5)
progress.set_fraction(0.25)
time.sleep(5)
progress.set_fraction(0.5)
time.sleep(5)
progress.set_fraction(0.75)
time.sleep(5)
progress.set_fraction(1)
def clickOk(*args):
t = threading.Thread(target=tread_function)
t.start()
def clickCancel(*args):
pass
buttonOk = Gtk.Button("Start Count")
buttonOk.connect("clicked", clickOk)
buttonCancel = Gtk.Button("Cancel Count")
buttonCancel.connect("clicked", clickCancel)
progress = Gtk.ProgressBar()
progress.set_show_text(True)
vBox = Gtk.VBox()
vBox.pack_start(buttonOk, True, True, 10)
vBox.pack_start(buttonCancel, True, True, 10)
vBox.pack_start(progress, True, True, 10)
window = Gtk.Window()
window.connect('destroy', Gtk.main_quit)
window.add(vBox)
window.show_all()
Gtk.main()
Now when the interface does not hold on, I would like to give a chance user to stop the work to its full completion, if he made ​​a mistake in settings. But I can not googled, or find in documentation how to kill a thread before its complete execution.

There is no trivial way to kill a thread in python. To get around this you need to build in your own hooks that will trigger early thread exits. A good way to do this is to use Event, which is a thread-safe trigger that can be set.
Before you do that though, you may wish to wrap up your code in some classes. Writing GUIs without classes will only cause you pain in the long term.
from threading import Event,Thread
class FakeClass(object):
def __init__(self):
self.progress = Gtk.ProgressBar()
self.progress.set_show_text(True)
self.buttonOk = Gtk.Button("Start Count")
self.buttonOk.connect("clicked", self.clickOk)
self.buttonCancel = Gtk.Button("Cancel Count")
self.buttonCancel.connect("clicked", self.clickCancel)
#create an event to trigger in your thread
self.trigger = Event()
self.t = None
#Other GTK stuff here...
def tread_function(self):
progress_fraction = 0.0
#keep looping while the trigger is not set and the
#progress is not > 1.0
while not self.trigger.is_set() and progress <= 1.0:
progress.set_fraction(progress_fraction)
time.sleep(1)
progress_fraction += 0.1
def clickOk(self, *args):
# reset the trigger
self.trigger.clear()
#launch the thread
self.t = threading.Thread(target=self.tread_function)
self.t.start()
def clickCancel(self, *args):
# set the trigger (interrupting the thread)
self.trigger.set()
# join the thread so it is not left hanging
if not self.t is None:
self.t.join()
# More other GTK stuff here...

Related

Using a QTimer within a PyQt worker thread

I am working with serial device and set a flag (which is global variable) based on the received data. Now I want to reset the flag after a while (for example one second) by using a timer.
Here is the code:
class Inlet_Worker(QObject):
def __init__(self):
super(Inlet_Worker, self).__init__()
self.timer = QTimer(self)
self.timer.timeout.connect(self.Reset_Register_Barcode)
def run(self):
global Register_Barcode
while True :
if client.read_coils(address = 0x0802).bits[0]:
Register_Barcode = True
self.timer.start(1000)
def Reset_Register_Barcode(self):
global Register_Barcode
Register_Barcode = False
However the timer is not working.
I will assume from your example code that your are using a QThread and that you also use QObject.moveToThread on your worker object. This is the correct procedure, but there are some other things you must do to make your timer work.
Firstly, you should use a single-shot timer so as to avoid re-regsitration whilst the current one is active. Secondly, you must explicitly process any pending events, since your while-loop will block the thread's event-loop. Without this, the timer's timeout signal will never be emitted. Thirdly, you should ensure that the worker and thread shut down cleanly when the program exits (which will also prevent any Qt error messages). Finally, if possible, you should use signals to communicate registration changes to the main GUI thread, rather than global variables.
The demo script below (based on your example) implements all of that. After the Start button is clicked, the thread will start and periodically update the regsitration (indicated by the check-box). Hopefully you shoudld be able to see how to adapt it to your real application:
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
class Inlet_Worker(QObject):
barcodeRegistered = pyqtSignal(bool)
def __init__(self):
super().__init__()
self._stopped = False
self._registered = False
self.timer = QTimer(self)
self.timer.setSingleShot(True)
self.timer.timeout.connect(self.updateBarcodeRegistration)
def run(self):
count = 0
self._stopped = False
while not self._stopped:
#if client.read_coils(address = 0x0802).bits[0]:
count += 1
if count % 20 == 0 and not self._registered:
self.updateBarcodeRegistration(True)
self.timer.start(2000)
QCoreApplication.processEvents()
QThread.msleep(100)
self.updateBarcodeRegistration(False)
self.timer.stop()
print('Stopped')
def updateBarcodeRegistration(self, enable=False):
print('Register' if enable else 'Reset')
self._registered = enable
self.barcodeRegistered.emit(enable)
def stop(self):
self._stopped = True
class Window(QWidget):
def __init__(self):
super().__init__()
self.thread = QThread()
self.worker = Inlet_Worker()
self.worker.moveToThread(self.thread)
self.button = QPushButton('Start')
self.check = QCheckBox('Registered')
layout = QHBoxLayout(self)
layout.addWidget(self.button)
layout.addWidget(self.check)
self.thread.started.connect(self.worker.run)
self.button.clicked.connect(self.thread.start)
self.worker.barcodeRegistered.connect(self.check.setChecked)
def closeEvent(self, event):
self.worker.stop()
self.thread.quit()
self.thread.wait()
if __name__ == '__main__':
app = QApplication(['Test'])
window = Window()
window.setGeometry(600, 100, 200, 50)
window.show()
app.exec()

Python Tkinter Threading - How to end / kill / stop thread when user closes GUI

If I have a non for loop threaded task being run alongside tkinter such as time.sleep(seconds) how can I safely end the task before it has concluded, or, before the time.sleep() has ended.
If you run the program and click the button and then close the GUI window then finished will still print in the interpreter window a few seconds later.
Please note that in this case time.sleep() is just a placeholder for a long running non for loop function.
# python 3 imports
import tkinter as tk
import threading
import queue
import time
def center_app(toplevel):
toplevel.update_idletasks()
w = toplevel.winfo_screenwidth() - 20
h = toplevel.winfo_screenheight() - 100
size = tuple(int(Q) for Q in toplevel.geometry().split("+")[0].split("x"))
toplevel.geometry("%dx%d+%d+%d" % (size + (w / 2 - size[0] / 2, h / 2 - size[1] / 2)))
class app(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self.protocol("WM_DELETE_WINDOW", self.USER_HAS_CLOSED_WINDOW)
self.b1 = tk.Button(self, text = "*** Start Thread ***", font = ("Arial",25,"bold"), command = self.func_1)
self.b1.pack(side = "top", fill = "both", expand = True)
center_app(self)
self.queue = queue.Queue()
def USER_HAS_CLOSED_WINDOW(self, callback = None):
print ("attempting to end thread")
# end the running thread
self.destroy()
def func_1(self):
self.b1.config(text = " Thread Running ")
self.b1.update_idletasks()
t = ThreadedFunction(self.queue).start()
self.after(50, self.check_queue)
def check_queue(self):
try:
x = self.queue.get(0) # check if threaded function is done
self.b1.config(text = "*** Start Thread ***")
self.b1.update_idletasks()
except:
self.after(50, self.check_queue)
class ThreadedFunction(threading.Thread):
def __init__(self, queue):
threading.Thread.__init__(self, daemon = True)
self.queue = queue
def run(self):
time.sleep(10) # how to end this if user closes GUI
print ("finished")
self.queue.put("result")
a = app()
a.mainloop()
I have made a function USER_HAS_CLOSED_WINDOW to catch the user closing the window, where I was thinking I would put something to end the running thread, but I'm not sure how.
I guess the other option is to put an appropriate timeout on the thread and use .join() somehow. But I'm not sure how to do that either
It appears that you should use Event object and use wait() method and not time.sleep when you use thread that you want to interrupt.
Probably this can help you:
https://stackoverflow.com/a/5205180
https://stackoverflow.com/a/38828735

PyQt5: Update labels inrun time

I have a problem updating labels in a loop during run time. I think I need to use signals and so on, but I've tried everything I can think of now. What I want my program to do:
When I click the button a loop should start that run some function that take some time. While the function is running the corresponding label should update its text to say "running" and when its done it should say "done" and continue to the next function. I've created some dummy code that represent what I'm after!
I have represented my function with a dummy function that just take some time.
import sys
from PyQt5.QtWidgets import *
import time
class Example(QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
# Set up an example ui
qbtn = QPushButton('Click', self)
qbtn.clicked.connect(self.changeLabels)
qbtn.resize(qbtn.sizeHint())
qbtn.move(100, 50)
# Add labels
self.labels = list()
self.labels.append(QLabel("Lbl1", self))
self.labels[-1].setFixedSize(50, 20)
self.labels.append(QLabel("Lbl2", self))
self.labels[-1].setFixedSize(50, 20)
self.labels[-1].move(0, 20)
self.labels.append(QLabel("Lbl3", self))
self.labels[-1].setFixedSize(50, 20)
self.labels[-1].move(0, 40)
self.setGeometry(300, 300, 250, 150)
self.setWindowTitle('Test')
self.show()
def changeLabels(self):
# Loop over all labels. For each label a function will be executed that will take some time. In this case
# I represent that with a dummy function to just take time. While the function is running the label should say
# "running" and when its finished it should say "done".
for lbl in self.labels:
orgTxt = lbl.text()
lbl.setText("%s Running" % orgTxt)
self.dummyFunction()
lbl.setText("%s Done" % orgTxt)
def dummyFunction(self):
time.sleep(1)
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
The GUI does not support blocking tasks because the GUI needs some time to update some attributes of the window, a possible solution is to use a new thread and implement the dummy function, in the following example the implementation is shown with the use of signals. If your real function updates any GUI view you should not do it directly in the thread, you should do it through signals.
class DummyThread(QThread):
finished = pyqtSignal()
def run(self):
time.sleep(1)
self.finished.emit()
class Example(QWidget):
[...]
def changeLabels(self):
for lbl in self.labels:
orgTxt = lbl.text()
lbl.setText("%s Running" % orgTxt)
thread = DummyThread(self)
thread.start()
thread.finished.connect(lambda txt=orgTxt, lbl=lbl : lbl.setText("%s Done" % txt))
An easier way would be to call QApplication.processEvents() to force QT to process all the events present on the queue. But the UI won't be as responsive as it would be with another thread doing the work and sending signals to the main thread as stated in other answers.

python UI freezing

I'm trying to make basic functionality
after pressing "start" button start counter , after pressing stop button stop counter,
but after I start process, it looks like only counting thread is working and it's not possible to press stop button
#!/usr/bin/python
# -*- coding: utf-8 -*-
import sys
from PyQt4 import QtGui, QtCore
from test.test_sax import start
import time
from threading import Thread
import threading
class Example(QtGui.QWidget):
x = 1
bol = True
def __init__(self):
super(Example, self).__init__()
self.qbtn = QtGui.QPushButton('Quit', self)
self.qbtn.resize(self.qbtn.sizeHint())
self.qbtn.move(50, 50)
self.qbtn2 = QtGui.QPushButton('Start', self)
self.qbtn2.resize(self.qbtn2.sizeHint())
self.qbtn2.move(150, 50)
self.qbtn.clicked.connect(self.stopCounter)
self.qbtn2.clicked.connect(self.startUI)
self.setGeometry(300, 300, 250, 150)
self.setWindowTitle('Quit button')
self.show()
def stopCounter(self):
Example.bol = False
def startUI(self):
Example.bol = True
thread = Thread(self.counterr())
def counterr(self):
x = 0
while Example.bol:
print x
x += 1
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
a = Example()
sys.exit(app.exec_())
thx
Now you call the slow function before you even create the thread. Try this instead:
thread = Thread(target=self.counterr)
thread.start()
In a Qt application you might also consider the QThread class that can run its own event loop and communicate with your main thread using signals and slots.
You are using the Thread class completely incorrectly, I'm afraid. You are passing it the result of the counterr method, which never returns.
Pass counterr (without calling it) to the Thread class as the target, then start it explicitly:
def startUI(self):
self.bol = True
thread = Thread(target=self.counterr)
thread.start()
Also, just access bol as an instance variable, not a class variable.

nonblocking timer in python

I am developing an app in python with pyGtk+Glade.
I want to execute a function every N seconds (just like in javascript the function setTimeout()). If the user perform a click, some action must be done, and the timer must be reset.
I was trying with threading.Timer, something like this:
def callback():
print "callback executed"
t = threading.Timer(10.0, callback)
t.start()
but it doesn't work for me because it blocks everything for the N secs and doesn't capture the user click.
Any other option?
Since you're using PyGTK, your program should probably be using the g_main_loop, in which case you can call glib.timeout_add (interval, callback) to add a callback that gets called (roughly) every X seconds.
Here's an example:
import glib
import gtk
def yo ():
print "yo"
return True
glib.timeout_add (1000, yo)
gtk.main ()
Unfortunately, to reset the timeout I couldn't come up with an elegant solution. But you can create your own main loop so you have control over when the timeout resets, sorta like this:
import glib
import gtk
import time
timeout = 1;
timer = time.time() + timeout
while (True):
gtk.main_iteration (False)
if (timer <= time.time()):
print "Time up!"
timer = time.time() + timeout
This creates a timer which calls MainWin.update() every second. When the button is pressed, the current timer is killed and a new timer is started.
import pygtk
pygtk.require('2.0')
import gtk
import gobject
import time
class MainWin(object):
def destroy(self, widget, data=None):
gtk.main_quit()
def __init__(self):
self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
self.window.connect("destroy", self.destroy)
self.window.set_border_width(10)
self.button = gtk.Button("Reset")
self.button.connect("clicked", self.onclick)
self.window.add(self.button)
self.button.show()
self.window.show()
self.reset_timer = False
self.count = 0
self.duration = 1000
self.timer = gobject.timeout_add(self.duration, self.update)
def reset(self):
print('Resetting timer')
gobject.source_remove(self.timer)
# start a new period call to update
self.count = 0
self.timer = gobject.timeout_add(self.duration, self.update)
def onclick(self, widget):
# Every action which resets the timer should call self.reset_timer().
self.reset()
def update(self):
print('{t:.1f}: count = {i}'.format(t=time.time() % 100, i=self.count))
self.count += 1
return True
def main(self):
gtk.main()
if __name__=='__main__':
MainWin().main()

Categories