Implement a cancel button with Qt in python

自闭症网瘾萝莉.ら 提交于 2021-02-11 12:41:50

问题


This question is probably dumb, but to give some background: I have been given a bunch of code of a software whose GUI is written in Qt, and I have been told that I should improve it by implement multithreading so the interface doesn't freeze (I have a bunch of code written like that that I am using as a template). I am not familiar with Qt or GUIs in general.

ATM I am trying to implement a simple cancel button: as the process in the background might take long, I want to be able to stop it arbitrarily (for example because one of the input values was incorrect or misspelled). Surprisingly I couldn't find many detailed questions or tutorials on this.

That said I tried to implement a simple widget to figure out how this works:

import time
import sys
from PyQt5 import QtCore, QtGui, QtWidgets

class Calc(QtCore.QObject):
    new_val = QtCore.pyqtSignal(float)

    def __init__(self):
        super().__init__()
        self.a = 0

    def Stop(self, b=False):
        if b:
            QtCore.QThread.quit()   # This obviously doesn't work

    def upd(self, a):
        self.a = a
        a2 = a**2
        for i in range(10):  # To simulate a lengthy process
            time.sleep(1)

        self.new_val.emit(a2)

class CalcWidget(QtWidgets.QWidget):
   go_sig = QtCore.pyqtSignal(float)
   stop_sig = QtCore.pyqtSignal(bool)

    def __init__(self, arg):
        super().__init__()
        self.arg = arg
        self.arg_thread = QtCore.QThread()
        self.arg.moveToThread(self.arg_thread)

        self.stop = QtWidgets.QPushButton('Cancel')

        self.a = QtWidgets.QSpinBox()
        self.a2 = QtWidgets.QLabel('--')

        self.a.valueChanged.connect(self._go)
        self.go_sig.connect(self.arg.upd)

        self.stop.clicked.connect(self._Stop)
        self.stop_sig.connect(self.arg.Stop)

        self.arg.new_val.connect(self.myupd)
        
        # this is only the interface
        hbox = QtWidgets.QHBoxLayout()
        vbox1 = QtWidgets.QVBoxLayout()
        vbox1.setAlignment(QtCore.Qt.AlignLeft)
        vbox1.addWidget(self.a)

        hbox.addLayout(vbox1)

        vbox2 = QtWidgets.QVBoxLayout()
        vbox2.setAlignment(QtCore.Qt.AlignLeft)
        vbox2.addWidget(self.a2)
        vbox2.addWidget(self.stop)

        hbox.addLayout(vbox2)

        self.setLayout(hbox)

        self.arg_thread.start()

    def _go(self):
        self.go_sig.emit(self.a.value())

    def _Stop(self):
         self.stop_sig.emit(True)

    def myupd(self, a2):
        self.a2.setText(str(a2))



if __name__ == "__main__":
    app = QtWidgets.QApplication(sys.argv)
    calc = Calc()
    w = CalcWidget(calc)
    w.show()
    app.exec()

Now here the function Stop() obviously doesn't work, I have tried different implementations, as to quit the thread from the function CalcWidget._Stop() or define a variable in Calc() and add an conditional statement in the for loop in Calc.upd. However it seems to me I am missing something obvious or simple and I can't make it work. Any help or suggestion is welcome.

EDIT: By tweaking around the problem I rewrote the Stop function as follows:

def Stop():
    self.stop = True

where self.stop is a class variable, now I can add a condition in the loop:

for i in range(10):
    if self.stop:
        break
    time.sleep(1)

however the changes are queued up so the thread finishes the loop and then calls Stop. Making this useless. I wonder if there is a way to solve this. I'd like not to redefine a thread object as it seems to me it would require more work in re-implementing par of the code that is already written and working.


回答1:


This is a short example of how you can implement it. Please let me know if you want me to add notes.

from PyQt5 import QtWidgets, QtCore


class WorkerThread(QtCore.QThread):
    is_active = True
    
    def run(self):
        while self.is_active:
            print("Processing...")
            self.sleep(1)


class Window(QtWidgets.QWidget):
    def __init__(self, parent=None):
        super(Window, self).__init__(parent)
        
        self.start_button = QtWidgets.QPushButton("Start")
        self.cancel_button = QtWidgets.QPushButton("Cancel")
        
        self.start_button.clicked.connect(self.start)
        self.cancel_button.clicked.connect(self.stop)
        
        laytout = QtWidgets.QVBoxLayout()
        laytout.addWidget(self.start_button)
        laytout.addWidget(self.cancel_button)
        self.setLayout(laytout)
        
        self.worker = WorkerThread()
    
    def start(self):
        self.worker.is_active = True
        self.worker.start()
        
    def stop(self):
        self.worker.is_active = False

if __name__ == "__main__":
    import sys
    app = QtWidgets.QApplication(sys.argv)
    win = Window()
    win.show()
    sys.exit(app.exec_())


来源:https://stackoverflow.com/questions/64660647/implement-a-cancel-button-with-qt-in-python

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!