python - 当控制台输出重新路由到 GUI 时暂停 Python 代码
问题描述
我借用了在 stackoverflow 上找到的设计,将控制台输出重定向到 PyQt5 GUI textEdit 小部件。这工作正常,但文本不会“实时”显示。一旦进程完成,它似乎会将文本输出到 GUI。这一直不是问题,直到我尝试使用time.sleep(secs)
打印一些东西,暂停,然后打印其他东西。最终发生的是程序暂停 for secs
,然后一次打印所有语句。
此类位于 GUI 的 mainWindow 文件中:
class EmittingStream(QtCore.QObject):
textWritten = QtCore.pyqtSignal(str)
def write(self, text):
self.textWritten.emit(str(text))
这是__init__
我的事件处理文件的方法:
sys.stdout = EmittingStream(textWritten=self.normalOutputWritten)
self.case_setup_console.setReadOnly(True)
self.main_console.setReadOnly(True)
此函数在事件处理文件的主类中(外部__init__
):
def normalOutputWritten(self, text):
"""Append text to the QTextEdit."""
# Maybe QTextEdit.append() works as well, but this is how I do it:
cursor = self.case_setup_console.textCursor()
cursor.movePosition(QtGui.QTextCursor.End)
cursor.insertText(text)
self.case_setup_console.setTextCursor(cursor)
self.case_setup_console.ensureCursorVisible()
这可以按预期将输出重新路由到文本编辑小部件self.case_setup_console
。但是,当我尝试运行如下代码时:
print('This is the first message')
time.sleep(5)
print('This should print 5 seconds later')
发生的情况是程序等待 5 秒,然后将两个语句一起打印。
解决方案
在为 GUI 代码编程时,程序的设计方式发生了根本性的转变。简而言之:在构建和初始化之后,程序一直在 GUI 框架提供的“事件循环”中运行,并且只有在发生特定事件时才会调用您的代码。
这与您的代码一直在运行的终端应用程序形成对比,您可以通过“time.sleep”告诉何时执行“打印”、“输入”和暂停。
GUI 代码负责记录事件(键盘、UI、网络等)、重绘窗口内容并调用您的代码以响应事件,或者仅在需要重绘您的代码(例如更新背景图像)。
因此,当控件传递回其事件循环时,它只能使用重定向的“打印”呈现应该显示在窗口中的文本。当你time.sleep
暂停返回时 - 事件循环中没有代码运行,当然它不能做任何屏幕绘图。
需要的是,您在程序中编写暂停的方式是在暂停期间,GUI 事件循环正在运行 - 而不是“time.sleep”,它只会暂停整个线程。
在 Qt 中,这样做的方法是创建一个 QTimer 对象来调用您要用于在特定时刻打印文本的代码,然后通过从您的函数返回将执行交给 QtMainloop。
由于 Python 对嵌套函数的支持,这可以轻松完成,甚至lambda
在设置计时器时使用函数。
...
print('This is the first message')
timer = QtCore.QTimer
timer.singleShot(5000, lambda *_: print('This should print 5 seconds later'))
应该适用于给定的示例。(与 UI 一样,该调用以毫秒而不是秒为单位占用暂停时间)。
如果您需要在每个短语输出后安排要打印的更多文本,则需要在回调本身内部调用调度,并且需要更复杂一些,但它仍然可以像这样简单:
phrases = iter(("hello", "world", "I", "am", "talking", "slowly!"))
timer = QtCore.QTimer()
def talker(*_):
phrase = next(phrases, None)
if not phrase:
return
print(phrase)
timer.singleShot(1000, talker)
timer.singleShot(1000, talker)
(请注意,参数名称也没有什么特别之处*_
:我只是表示回调可能有任意数量的位置参数-(“*”部分,即 Python 语法)-我不在乎大约那时(我将参数序列称为“_”以表示我不在乎它是如何被调用的,因为它无论如何都不会被使用 - 这是一个编码约定))
iter
andnext
调用是比一个可能使用的更多的“Python 方言”,但是可以只使用一个列表和一个直到列表长度的计数器。
推荐阅读
- asp.net - Rjs calander 跳过月份变化
- google-chrome - Chrome 应用 OAuth 2.0 客户端说“抱歉,那里出了点问题,再试一次。”
- javascript - 如何只显示时间而不是时间和日期?
- c++ - 模板的内部枚举类不被视为常量表达式?
- json - joi validationerror 'value' 必须是一个数组
- javascript - 执行操作时如何让 javascript 保存输入字段?
- spring-webflux - 反应堆重试:reactor.retry.Retry 与 reactor.util.retry.Retry
- java - thymeleaf 和 maven web 项目无法识别 css 文件
- typescript - Typescript - 引擎如何推断以及如何应用推断背后是否有一致的逻辑?
- android - 添加片段后导航抽屉崩溃