通过GUI在模拟中读取用户输入
我正在把一个基于模拟的应用程序用 simpy 转换成图形用户界面(GUI)。
现在这个程序是在控制台里运行的,simpy 负责模拟,它默认是以循环的方式运行的。这就是我遇到的问题所在。
在控制台版本的代码中,我通过 raw_input() 函数获取用户输入,这样可以打断代码,让用户输入模拟所需的值。不过,尽管我查了很多资料,似乎在我正在构建的 GUI 中,使用 pyqt 没有类似的简单方法。
那我唯一的办法就是把这些过程放在不同的线程里吗?如果我选择这个方法,具体应该怎么做,才能让它正常工作呢?
1 个回答
PyQt是基于事件的。它会不断运行一个循环,等待各种事件的发生,当有你关心的事件出现时,它会调用你的回调函数(或者说信号和槽)。所以,你不能直接说“等我输入完再继续”。
不过,在你遇到这个问题之前,如果你的模拟程序在主线程中不断循环,PyQt也不能在主线程中持续运行它的循环。这样一来,它就无法响应操作系统的事件,比如“更新你的窗口”或“退出”。对于用户来说,应用程序就像是卡住了一样;她只会看到那个常见的转圈圈(或者其他平台的类似图标)。
而你选择解决第一个问题的方法,几乎可以免费解决大部分第二个问题。
为什么你的GUI应用程序会卡住试图用一般的术语解释整个问题,以及所有可能的解决方案,以Tkinter作为GUI库的例子。如果你想要更具体的Qt相关内容,我相信Qt教程中有一整部分讲这个,虽然我不太确定具体在哪,你可能需要把一些C++的内容在脑海中转成Python。
不过,有两个主要的选择:回调函数或者线程。
首先,你可以把你的循环分成小块,每块只需要几毫秒。与其一次性运行整个循环,不如先运行第一块,然后在最后一行请求PyQt尽快安排下一块(例如,使用一个QTimer
,超时时间设为0)。这样,Qt就能每隔几毫秒检查一次事件,如果没有事情要做,它会立即开始你的下一步工作。
如果你的控制流程已经围绕一个迭代器(或者推送协程)构建,并且能适当地分块,这样做会很简单。如果不是,这可能意味着你需要把外部循环的控制流程反过来,这可能会让人很难理解。
那么,完成这个后,如何获取用户输入呢?很简单:
- 在你原本会调用
raw_input
的地方,不是安排下一块代码,而是做一些合适的GUI操作——比如创建一个弹出消息框,显示一个文本输入框和按钮,等等。 - 把下一块代码连接为按钮点击或消息框确认等信号的处理函数。
另外,你也可以在后台线程中运行你的工作。这不需要你重新组织任何东西,但你需要小心不要在不同线程之间共享任何东西。不幸的是,这包括在后台线程中调用GUI控件的方法,这听起来似乎让你无法做任何有用的事情。幸运的是,PyQt有机制可以很容易地处理这个问题:信号会在必要时自动在线程之间传递。
那么,在这种情况下,如何请求用户输入呢?
- 把
raw_input
之后的所有内容分离到一个单独的函数中,并把它连接为got_input
信号的处理函数。 - 在原来的函数中,原本调用
raw_input
的地方,改为发出gimme_input
信号。 - 为
gimme_input
信号写一个处理函数,让它在主线程中运行,这样就可以显示GUI控件(就像上面单线程的例子一样)。 - 为OK按钮写一个处理函数,发出
got_input
信号回到工作线程。