# Simple PyQt5 counting GUI

I just began learning how to use signals and slots in PyQt5, and so I made a cute (pun intended) little program to display just one button. When the button is pressed a thread is spawned which will count from 0 to 99 and display the current count onto the button. However, when the button is counting, the program will not allow the user to spawn another thread.

I am about to actually use my knowledge for a much heavier task, and I wanted to know if there was an easier way to do what I did. Or, perhaps my way is not very efficient? Thanks in advance!

import threading
import sys
import time

from PyQt5.QtWidgets import *
from PyQt5.QtCore import *

class Application(QMainWindow):
counter = pyqtSignal(str)
counting = False

def __init__(self):
super(Application, self).__init__()

self.button = QPushButton()
self.button.setText('99')
self.button.clicked.connect(self.startCounting)
self.counter.connect(self.button.setText)

self.layout = QVBoxLayout()
self.frame = QFrame()
self.frame.setLayout(self.layout)
self.setCentralWidget(self.frame)

def startCounting(self):
if not self.counting:
self.counting = True

def something(self):
for x in range(100):
self.counter.emit(str(x))
time.sleep(.01)
self.counting = False

if __name__ == '__main__':
app = QApplication(sys.argv)
window = Application()
window.show()
sys.exit(app.exec_())

• I have never used PyQt, so I am asking for my own edification: why do you use a pyqtSignal instead of just setting the text directly? – zondo Aug 18 '16 at 0:49
• @zondo because its 'good practice' to let the main thread handle anything GUI related – Nick Pandolfi Aug 18 '16 at 1:03

Looks good to me!

super minor nits:

        thread = threading.Thread(target=self.something)


only uses the thread variable once, so you might as well do:

        threading.Thread(target=self.something).start()


Also, the only thing you use from threading is Thread so you might as well change your import to:

from threading import Thread


and then it can be just:

Thread(target=self.something).start()


...but again, these are super minor things! It looks good to me; I may have to look at pyQT again :)

since you already used signals/slots mechanism in your program you can easily replace python thread mechanism with QThread() and make it use separate Counter() object to divide program into separate logical blocks:

# Similar to threading.thread(target=self.counter.start)
self.counter = Counter()


Where the Counter() class (often named as Worker() class) is:

class Counter(QObject):
'''
Class intended to be used in a separate thread to generate numbers and send
'''

newValue = pyqtSignal(str)
stopped = pyqtSignal()

def __init__(self):
QObject.__init__(self)

def start(self):
'''
Count from 0 to 99 and emit each value to the GUI thread to display.
'''

for x in range(100):
self.newValue.emit(str(x))
time.sleep(.01)
self.stopped.emit()


By using this approach you can even modify Counter() object to receive some data from the GUI on-the-fly and react accordingly.

Another minor thing is that "from package import *" was used. Usually it is considered a bad practice since you import all the contents of the package. In this case all QtCore and QtWidgets modules were imported which is ~2/3 of the PyQt5 package itself I believe))) It is more verbose but much better to use:

from PyQt5.QtWidgets import QMainWindow, QPushButton, QVBoxLayout, QFrame, QApplication
from PyQt5.QtCore import pyqtSignal, QObject, QThread


or:

from PyQt5 import QtWidgets, QtCore
...
self.button = QtWidgets.QPushButton()


That way you and other code readers always know which object belongs to which package as well.

Here is the complete code rewritten according to those notes:

'''
https://codereview.stackexchange.com/questions/138992/simple-pyqt5-counting-gui
'''

import sys
import time

from PyQt5.QtWidgets import QMainWindow, QPushButton, QVBoxLayout, QFrame, QApplication
from PyQt5.QtCore import pyqtSignal, QObject, QThread

class Counter(QObject):
'''
Class intended to be used in a separate thread to generate numbers and send
'''

newValue = pyqtSignal(str)
stopped = pyqtSignal()

def __init__(self):
QObject.__init__(self)

def start(self):
'''
Count from 0 to 99 and emit each value to the GUI thread to display.
'''

for x in range(100):
self.newValue.emit(str(x))
time.sleep(.01)
self.stopped.emit()

class Application(QMainWindow):
def __init__(self):
QMainWindow.__init__(self)

# Configuring widgets
self.button = QPushButton()
self.button.setText('99')
self.layout = QVBoxLayout()
self.frame = QFrame()
self.frame.setLayout(self.layout)
self.setCentralWidget(self.frame)

self.counter = Counter()

# Connecting signals
self.button.clicked.connect(self.startCounting)
self.counter.newValue.connect(self.button.setText)

def startCounting(self):
'''
Start counting if no other counting is done.
'''

if __name__ == '__main__':
app = QApplication(sys.argv)
window = Application()
window.show()
sys.exit(app.exec_())


Keep up the good work and best wishes.

Yesterday I spent the day researching how threading should be done in PyQt5 for one of my own projects and found that a lot of the documentation was incorrect, including official documentation.

This was the best documentation we were able to find: Here
Here is a proof of concept example I came away with and later implemented.

Basically you have a Thread class where the code to be executed is in the run function:

class Thread(QRunnable):

def __init__(self):
self.signal = Signals()

@pyqtSlot()
def run(self):
time.sleep(5)
result = "Some String"
self.signal.return_signal.emit(result)


This makes a call to a Signal class that will store the signal as a subclass of PyQt5.QtCore.QObject:

class Signals(QObject):
return_signal = pyqtSignal(str)


Then the basic Application class that creates an instance of a PyQt5.QtCore.QThreadPool in the __init__ function and creates and calls the thread in clickCheckbox which sends the return signal to function_thread:

class App(QWidget):
def __init__(self):
super().__init__()
self.title='Hello, world!'
self.left=2100
self.top=500
self.width=640
self.height=480
self.initUI()

def initUI(self):
self.setWindowTitle(self.title)
self.setGeometry(self.left,self.top,self.width,self.height)
checkbox = QCheckBox('Check Box', self)
checkbox.stateChanged.connect(self.clickCheckbox)
self.show()

def clickCheckbox(self):

print(signal)


Initialising Application Window:

if __name__=='__main__':
app=QApplication(sys.argv)
ex=App()
sys.exit(app.exec_())


Applying all this, here is what I would suggest you do to your code:

import sys
import time
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *

class Signal(QObject):
signal = pyqtSignal(str)

def __init__(self, counting):
self.counting = counting
self.signal = Signal()

def run(self):
for x in range(100):
self.signal.signal.emit(str(x))
time.sleep(.01)
counting = False
self.signal.signal.emit(str(counting))

class Application(QMainWindow):
counting = False

def __init__(self):
super(Application, self).__init__()

self.button = QPushButton()
self.button.setText('99')
self.button.clicked.connect(self.startCounting)

self.layout = QVBoxLayout()
self.frame = QFrame()
self.frame.setLayout(self.layout)
self.setCentralWidget(self.frame)

def startCounting(self):

def something(self, signal):
if signal == "False":
print("Counting is Complete")
else:
print(signal)

if __name__ == '__main__':
app = QApplication(sys.argv)
window = Application()
window.show()
sys.exit(app.exec_())