Python 在后台运行函数并更新UI

Python 在后台运行函数并更新UI,python,pyqt,pyqt5,Python,Pyqt,Pyqt5,我正在使用PyQt为一个项目制作GUI 图形用户界面截图 输入一个数字并提交后,我需要执行将在后台运行的功能,否则应用程序将冻结,直到过程完成 我还需要在暗箱中输出由函数生成的日志 这是GUI代码: 导入系统 从PyQt5.QtWidgets导入( QWidget, QDesktopWidget, QLineEdit, QGridLayout, QLabel, QFrame, QPushButton, QApplication, QTextEdit ) 从PyQt5.QtGui导入(QText

我正在使用PyQt为一个项目制作GUI

图形用户界面截图

输入一个数字并提交后,我需要执行将在后台运行的功能,否则应用程序将冻结,直到过程完成

我还需要在暗箱中输出由函数生成的日志

这是GUI代码:

导入系统 从PyQt5.QtWidgets导入( QWidget, QDesktopWidget, QLineEdit, QGridLayout, QLabel, QFrame, QPushButton, QApplication, QTextEdit ) 从PyQt5.QtGui导入(QTextCursor) 从bot.bot导入(运行、松弛通知) 从多处理导入进程,管道 类LogginOutput(QTextEdit): def uuu init uuu(self,parent=None): 超级(LogginOutput,self)。\uuuu init\uuuu(父级) self.setReadOnly(True) self.setLineWrapMode(self.NoWrap) self.insertPlainText(“”) def append(自我,文本): self.moveCursor(QTextCursor.End) 当前=self.toPlainText() 如果当前==“”: self.insertPlainText(文本) 其他: self.insertPlainText(“\n”+文本) sb=自垂直滚动条() sb.setValue(sb.max()) 类应用程序(QWidget): 定义初始化(自): super()。\uuuu init\uuuuu() self.init_ui() def初始用户界面(自身): label=QLabel('Amount') 金额\u输入=QLineEdit() submit=QPushButton('submit',self) 框=日志输出(自身) submit.clicked.connect(lambda:self.changelab(框,金额\输入)) grid=QGridLayout() addWidget(标签,0,0) grid.addWidget(金额输入,1,0) grid.addWidget(提交,1,1) addWidget(框,2,0,5,2) self.setLayout(网格) 自我调整大小(350250) self.setWindowTitle('GetMeStuff Bot v0.1') self.show() def中心(自我): qr=self.frameGeometry() cp=QDesktopWidget().availableGeometry().center() qr.移动中心(cp) self.move(qr.topLeft()) def changeLabel(自身、框、用户输入): p=Process(target=run,args=(user\u input.displayText(),box)) p、 开始() 用户输入。清除() 如果uuuu name uuuuuu='\uuuuuuu main\uuuuuuu': app=QApplication(sys.argv) widget=App() sys.exit(app.exec_()) 以及
运行
功能:

def运行(用户输入,日志):
如果用户输入=“”:
log.append(“请输入一个值\n”)
其他:
log.append(“测试”)

为了在后台运行该函数,我尝试使用
Process
,但当我执行append函数时,GUI不会更新。

GUI应用程序将始终需要自己的阻塞循环,因此您可以转到线程或进程。然而,我相信,一旦你进入了Qt世界,你也必须使用提供的工具进行繁殖

尝试
PyQt5.QtCore.QProcess
PyQt5.QtCore.QThread


我相信您可以在野外找到适合您的示例。

GUI不应该从另一个线程更新,因为Qt会在应用程序所在的位置创建一个循环,尽管python提供了许多与线程一起工作的替代方案,但这些工具通常不会处理Qt的逻辑,因此它们可能会产生问题。Qt提供了使用QThread(低级)执行此类任务的类,但这次我将使用QRunnable和QThreadPool,我创建了一个行为与Process相同的类:

class ProcessRunnable(QRunnable):
    def __init__(self, target, args):
        QRunnable.__init__(self)
        self.t = target
        self.args = args

    def run(self):
        self.t(*self.args)

    def start(self):
        QThreadPool.globalInstance().start(self)
使用:

正如我之前所说,您不应该直接从另一个线程更新GUI,解决方案是使用信号,或者在本例中,为了简单起见,使用
QMetaObject.invokeMethod

def run(user_input, log):
    text = ""
    if user_input == "":
        text = "Please enter a value\n"
    else:
        text = "Test"

    QMetaObject.invokeMethod(log,
                "append", Qt.QueuedConnection, 
                Q_ARG(str, text))
要正确调用,这必须是一个插槽,为此,我们使用装饰器:

class LogginOutput(QTextEdit):
    # ...
    @pyqtSlot(str)
    def append(self, text):
        self.moveCursor(QTextCursor.End)
        # ...
完整且可行的示例在以下代码中

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

class ProcessRunnable(QRunnable):
    def __init__(self, target, args):
        QRunnable.__init__(self)
        self.t = target
        self.args = args

    def run(self):
        self.t(*self.args)

    def start(self):
        QThreadPool.globalInstance().start(self)

def run(user_input, log):
    text = ""
    if user_input == "":
        text = "Please enter a value\n"
    else:
        text = "Test"

    QMetaObject.invokeMethod(log,
                "append", Qt.QueuedConnection, 
                Q_ARG(str, text))

class LogginOutput(QTextEdit):
    def __init__(self, parent=None):
        super(LogginOutput, self).__init__(parent)

        self.setReadOnly(True)
        self.setLineWrapMode(self.NoWrap)
        self.insertPlainText("")

    @pyqtSlot(str)
    def append(self, text):
        self.moveCursor(QTextCursor.End)
        current = self.toPlainText()

        if current == "":
            self.insertPlainText(text)
        else:
            self.insertPlainText("\n" + text)

        sb = self.verticalScrollBar()
        sb.setValue(sb.maximum())

class App(QWidget):
    def __init__(self):
        super().__init__()

        self.init_ui()

    def init_ui(self):
        label = QLabel('Amount')
        amount_input = QLineEdit()
        submit = QPushButton('Submit', self)
        box = LogginOutput(self)

        submit.clicked.connect(lambda: self.changeLabel(box, amount_input))

        grid = QGridLayout()
        grid.addWidget(label, 0, 0)
        grid.addWidget(amount_input, 1, 0)
        grid.addWidget(submit, 1, 1)
        grid.addWidget(box, 2, 0, 5, 2)

        self.setLayout(grid)
        self.resize(350, 250)
        self.setWindowTitle('GetMeStuff Bot v0.1')
        self.show()

    def center(self):
        qr = self.frameGeometry()
        cp = QDesktopWidget().availableGeometry().center()
        qr.moveCenter(cp)
        self.move(qr.topLeft())

    def changeLabel(self, box, user_input):
        self.p = ProcessRunnable(target=run, args=(user_input.displayText(), box))
        self.p.start()
        user_input.clear()


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

您的代码只显示您是如何构建GUI的,它没有显示您提到的主要问题,您谈论的是流程,而您没有显示您是如何使用它的,如果您希望我们帮助您,您必须提供[mvce]。抱歉,我想解释就足够了。添加了任务代码。我测试了它,但它对我无效(?)。我将'import time while True:time.sleep(1)print(1)`放在slot函数中,它仍然阻塞gui。
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from PyQt5.QtCore import *

class ProcessRunnable(QRunnable):
    def __init__(self, target, args):
        QRunnable.__init__(self)
        self.t = target
        self.args = args

    def run(self):
        self.t(*self.args)

    def start(self):
        QThreadPool.globalInstance().start(self)

def run(user_input, log):
    text = ""
    if user_input == "":
        text = "Please enter a value\n"
    else:
        text = "Test"

    QMetaObject.invokeMethod(log,
                "append", Qt.QueuedConnection, 
                Q_ARG(str, text))

class LogginOutput(QTextEdit):
    def __init__(self, parent=None):
        super(LogginOutput, self).__init__(parent)

        self.setReadOnly(True)
        self.setLineWrapMode(self.NoWrap)
        self.insertPlainText("")

    @pyqtSlot(str)
    def append(self, text):
        self.moveCursor(QTextCursor.End)
        current = self.toPlainText()

        if current == "":
            self.insertPlainText(text)
        else:
            self.insertPlainText("\n" + text)

        sb = self.verticalScrollBar()
        sb.setValue(sb.maximum())

class App(QWidget):
    def __init__(self):
        super().__init__()

        self.init_ui()

    def init_ui(self):
        label = QLabel('Amount')
        amount_input = QLineEdit()
        submit = QPushButton('Submit', self)
        box = LogginOutput(self)

        submit.clicked.connect(lambda: self.changeLabel(box, amount_input))

        grid = QGridLayout()
        grid.addWidget(label, 0, 0)
        grid.addWidget(amount_input, 1, 0)
        grid.addWidget(submit, 1, 1)
        grid.addWidget(box, 2, 0, 5, 2)

        self.setLayout(grid)
        self.resize(350, 250)
        self.setWindowTitle('GetMeStuff Bot v0.1')
        self.show()

    def center(self):
        qr = self.frameGeometry()
        cp = QDesktopWidget().availableGeometry().center()
        qr.moveCenter(cp)
        self.move(qr.topLeft())

    def changeLabel(self, box, user_input):
        self.p = ProcessRunnable(target=run, args=(user_input.displayText(), box))
        self.p.start()
        user_input.clear()


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