我正在使用PyQt为项目制作GUI。
GUI
的屏幕截图
输入数字并提交后,我需要执行在后台运行的功能,否则应用会冻结,直到流程结束。
我还需要在暗盒中输出由函数生成的日志。
这是GUI代码:
import sys
from PyQt5.QtWidgets import (
QWidget,
QDesktopWidget,
QLineEdit,
QGridLayout,
QLabel,
QFrame,
QPushButton,
QApplication,
QTextEdit
)
from PyQt5.QtGui import (QTextCursor)
from bot.bot import (run, slack_notification)
from multiprocessing import Process, Pipe
class LogginOutput(QTextEdit):
def __init__(self, parent=None):
super(LogginOutput, self).__init__(parent)
self.setReadOnly(True)
self.setLineWrapMode(self.NoWrap)
self.insertPlainText("")
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):
p = Process(target=run, args=(user_input.displayText(), box))
p.start()
user_input.clear()
if __name__ == '__main__':
app = QApplication(sys.argv)
widget = App()
sys.exit(app.exec_())
run
函数:
def run(user_input, log):
if user_input == "":
log.append("Please enter a value\n")
else:
log.append("Test")
为了在后台运行该功能,我尝试使用Process
,但是当我执行追加功能时,GUI不会更新。
提前谢谢
答案 0 :(得分:2)
不应该从另一个线程更新GUI,因为Qt创建了一个应用程序所在的循环,尽管python为线程工作提供了许多替代方法,这些工具通常不处理Qt的逻辑,因此它们可能会产生问题。 Qt提供了使用QThread(低级)执行此类任务的类,但这次我将使用QRunnable和QThreadPool,我创建了一个与Process相同的类:
users--
使用:
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,解决方案是使用信号,或者在这种情况下,为简单起见,使用self.p = ProcessRunnable(target=run, args=(user_input.displayText(), box))
self.p.start()
:
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)
[...]
答案 1 :(得分:0)
一个gui应用程序总是需要它自己的阻塞循环,所以你是对的,你可以去任一线程或进程。但是我相信,一旦你进入Qt世界,你还必须使用提供的工具进行产卵。
尝试PyQt5.QtCore.QProcess
或PyQt5.QtCore.QThread
。
我相信你可以在野外找到一个适合你的例子。