Maurice Bekambo
Maurice Bekambo

Reputation: 325

How to stop thread using QPushButton(Python)?

I'm currently developing an application that needs to continuously send a stream of my screen to a window in it. However, I would like a way to be able to stop this and restart it using a stop and start button which I've added too the grid using layout.addWidget(QtWidgets.QPushButton('Start'),0,0), layout.addWidget(QtWidgets.QPushButton('Stop'),0,1). However, I don't know how to properly connect these buttons to an inactive/active thread starting and stopping a thread respectively using those two buttons.

This is my code:

from PyQt5 import QtCore, QtGui, QtWidgets 
import cv2
import numpy as np
from mss import mss


class Thread(QtCore.QThread):
   changePixmap = QtCore.pyqtSignal(QtGui.QImage)
   scaled_size = QtCore.QSize(1500, 1000)
   


   def run(self):
       mon = {'top': 0, 'left': 0, 'width': 1920, 'height': 1080}
       with mss() as sct:

        while True:
           ret = True
           if ret:
               img = sct.grab(mon)
            #    cv2.imshow('test', np.array(img))
               rgbImage = cv2.cvtColor(np.array(img), cv2.COLOR_BGR2RGB)
               convertToQtFormat = QtGui.QImage(rgbImage.data, rgbImage.shape[1], rgbImage.shape[0], QtGui.QImage.Format_RGB888)
               p = convertToQtFormat.scaled(self.scaled_size, QtCore.Qt.KeepAspectRatio)
               self.changePixmap.emit(p)
def scaled(self, scaled_size):
        self.scaled_size = scaled_size


class PlayStreaming(QtWidgets.QWidget):
    def __init__(self):
        super(PlayStreaming,self).__init__()
        self.initUI()

    @QtCore.pyqtSlot(QtGui.QImage)
    def setImage(self, image):
        self.label.setPixmap(QtGui.QPixmap.fromImage(image))

    def initUI(self):
        self.setWindowTitle("Image")
        # create a label
        self.label = QtWidgets.QLabel(self)
        th = Thread(self)
        th.changePixmap.connect(self.setImage)
        th.start()
        lay = QtWidgets.QVBoxLayout(self)
        lay.addWidget(self.label, alignment=QtCore.Qt.AlignCenter)


class UIWidget(QtWidgets.QWidget):
    def __init__(self, parent=None):
        super(UIWidget, self).__init__(parent)
        # Initialize tab screen
        self.tabs = QtWidgets.QTabWidget()
        self.tab1 = QtWidgets.QWidget()   
        self.tab2 = QtWidgets.QWidget()
        self.tab3 = QtWidgets.QWidget()


        # Add tabs
        self.tabs.addTab(self.tab1,"Face")

        # Create first tab
        self.createGridLayout()
        self.tab1.layout = QtWidgets.QVBoxLayout()
        self.display = PlayStreaming()
        self.tab1.layout.addWidget(self.display, stretch=1)
        self.tab1.layout.addWidget(self.horizontalGroupBox)
        self.tab1.setLayout(self.tab1.layout)

        # Add tabs to widget        
        layout = QtWidgets.QVBoxLayout(self)
        layout.addWidget(self.tabs)

    def createGridLayout(self):
        self.horizontalGroupBox = QtWidgets.QGroupBox("Control")
        self.horizontalGroupBox.setStyleSheet("QGroupBox { background-color: red}");
        layout = QtWidgets.QGridLayout()
        layout.addWidget(QtWidgets.QPushButton('Start'),0,0) 
        layout.addWidget(QtWidgets.QPushButton('Stop'),0,1) 
        self.horizontalGroupBox.setLayout(layout)


if __name__ == '__main__':
    import sys
    app = QtWidgets.QApplication(sys.argv)
    w = UIWidget()
    w.resize(1000, 800)
    w.show()
    sys.exit(app.exec_())

Thanks in advance

To note I used the code at: How to change display size changing with window size in PyQT5? and Screen Capture with OpenCV and Python-2.7 as a template for my code.

Upvotes: 0

Views: 1301

Answers (1)

alec
alec

Reputation: 6112

You need a way to break the while True loop in the thread's run method in order to stop the thread. You could instead keep a boolean variable that will be set to False when Thread.stop() is called.

def run(self):
    self._go = True
    
    mon = {'top': 0, 'left': 0, 'width': 1920, 'height': 1080}
    with mss() as sct:

        while self._go:
            ret = True
            if ret:
                img = sct.grab(mon)
                #    cv2.imshow('test', np.array(img))
                rgbImage = cv2.cvtColor(np.array(img), cv2.COLOR_BGR2RGB)
                convertToQtFormat = QtGui.QImage(rgbImage.data, rgbImage.shape[1], rgbImage.shape[0], QtGui.QImage.Format_RGB888)
                p = convertToQtFormat.scaled(self.scaled_size, QtCore.Qt.KeepAspectRatio)
                self.changePixmap.emit(p)
        
def stop(self):
    self._go = False

I would recommend constructing the thread in the UIWidget class and connecting its signals from there.

class UIWidget(QtWidgets.QWidget):
    def __init__(self, parent=None):
        super(UIWidget, self).__init__(parent)
        # Initialize tab screen
        self.tabs = QtWidgets.QTabWidget()
        self.tab1 = QtWidgets.QWidget()   
        self.tab2 = QtWidgets.QWidget()
        self.tab3 = QtWidgets.QWidget()

        # Add tabs
        self.tabs.addTab(self.tab1,"Face")

        self.display = PlayStreaming()
        self.th = Thread(self)
        self.th.changePixmap.connect(self.display.setImage)

        # Create first tab
        self.createGridLayout()
        self.tab1.layout = QtWidgets.QVBoxLayout()
        self.tab1.layout.addWidget(self.display, stretch=1)
        self.tab1.layout.addWidget(self.horizontalGroupBox)
        self.tab1.setLayout(self.tab1.layout)

        # Add tabs to widget        
        layout = QtWidgets.QVBoxLayout(self)
        layout.addWidget(self.tabs)

    def createGridLayout(self):
        self.horizontalGroupBox = QtWidgets.QGroupBox("Control")
        self.horizontalGroupBox.setStyleSheet("QGroupBox { background-color: red}");
        layout = QtWidgets.QGridLayout()
        layout.addWidget(QtWidgets.QPushButton('Start', clicked=self.th.start), 0, 0) 
        layout.addWidget(QtWidgets.QPushButton('Stop', clicked=self.th.stop), 0, 1)
        self.horizontalGroupBox.setLayout(layout)

Now when you click the stop button, the current iteration will complete and then it will break out of the loop and stop. If you want the thread to stop immediately no matter where it is in the loop, then include self.th.setTerminationEnabled(True) and connect the stop button signal to self.th.terminate. But note the documentation warns against using QThread.terminate.

Upvotes: 1

Related Questions