如何使用 cmd2 和 wxPython 创建非阻塞 GUI?
How can I create a non-blocking GUI with cmd2 and wxPython?
如何从 cmd2
解释器命令创建非阻塞 wxPython
GUI windows,即使解释器和 wx.App
都必须是 运行在主线程上?
我正在使用 cmd2
模块在 Python 3.x 中创建一个命令行解释器。我的解释器中的一个命令将允许我创建一个“非阻塞”wxPython
GUI window,带有倒数计时器,运行 在单独的线程中。它需要 运行 与主解释器线程分开,否则它将阻止在计时器 运行s.
时输入其他命令
当我尝试使用 wxTimer
对象允许我的 GUI 更新其进度条并检查剩余时间时,我收到以下错误:
wx._core.wxAssertionError: C++ assertion "wxThread::IsMain()" failed at ..\..\src\common\timerimpl.cpp(60) in wxTimerImpl::Start(): timer can only be started from the main thread
任何启动计时器的尝试都会破坏密码。例如,下面的简单代码不起作用:
import wx
import threading
import cmd2
class TimerFrame(wx.Frame):
def __init__(self, time):
super().__init__(None, title=str(time), size=(300, 300))
self.InitUI()
def InitUI(self):
self.timer = wx.Timer(self, 1)
self.timer.Start(100)
self.Bind(wx.EVT_TIMER, self.OnTimer, id=1)
self.Show()
def OnTimer(self, event):
print("Updating")
class Interpreter(cmd2.Cmd):
def __init__(self):
super().__init__()
self.app = wx.App()
def do_timer(self, _):
threading.Thread(target=self.createTimer, args=(self.app,)).start()
def createTimer(self, app):
TimerFrame("Window Title")
app.MainLoop()
if __name__ == "__main__":
Interpreter().cmdloop()
请注意,注释掉包含 timer.Start(100)
的行允许在单独的线程中成功创建 windows,但它们缺少必要的 Timer
功能。
其他我试过但不起作用的东西:
- 在新线程中创建
wx.App
而不是传递它(导致相同的错误,但 MainLoop
而不是 Timer
)
- 在主线程中创建
wx.App
并在其中 运行 设置其 MainLoop
(阻止命令行接受更多命令)
- 运行 解释器命令循环在一个单独的线程中(抱怨
cmdloop
必须来自主线程 运行)
文档中有一节关于 Integrating cmd2 with event loops:
Many Python concurrency libraries involve or require an event loop which they are in control of such as asyncio
, gevent
, Twisted
, etc.
虽然这是专门讨论以网络为中心的事件循环,但它实际上与 wx 等 GUI 事件循环存在相同的问题。
tl;dr 不是调用 cmdloop()
,阻塞整个主线程直到解释器退出,而是调用 preloop()
,然后重复调用 onecmd()
(或 onecmd_plus_hooks()
或 runcmd_plus_hooks()
,视情况而定)来自 wx
回调。
换句话说,您从 wx
事件循环驱动 cmd2
事件循环,因此 wx
循环可以接管线程。
wx
也有一种手动驱动事件循环的方法。参见 wx.EventLoopBase
和相关的 类。 (可能有一些很好的示例代码类似于 cmd2
文档中的代码,但您必须搜索它。)想法几乎相同:代替 运行 wx
循环并永远阻塞线程,您手动创建 wx.EventLoop
和 wx.EventLoopActivator
,然后从 cmd2
事件循环。
换句话说,您从 cmd2
事件循环驱动 wx
事件循环,因此 cmd2
循环可以接管线程。
如果这些都不适合您,您可以使用 multiprocessing
而不是 threading
。这样,两个事件循环都在主线程中 运行,但其中一个只是 运行 在子进程的主线程中。
这可能是 GUI 应用程序的问题,因此将 wx
放入子进程可能不起作用(或者,更糟的是,可能对某些 platforms/setups 起作用但对其他不起作用,或者可能起作用但经常神秘地失败......),但 cmd2
大概只需要看到 stdin/stdout/tty,所以它可能会起作用。
如何从 cmd2
解释器命令创建非阻塞 wxPython
GUI windows,即使解释器和 wx.App
都必须是 运行在主线程上?
我正在使用 cmd2
模块在 Python 3.x 中创建一个命令行解释器。我的解释器中的一个命令将允许我创建一个“非阻塞”wxPython
GUI window,带有倒数计时器,运行 在单独的线程中。它需要 运行 与主解释器线程分开,否则它将阻止在计时器 运行s.
当我尝试使用 wxTimer
对象允许我的 GUI 更新其进度条并检查剩余时间时,我收到以下错误:
wx._core.wxAssertionError: C++ assertion "wxThread::IsMain()" failed at ..\..\src\common\timerimpl.cpp(60) in wxTimerImpl::Start(): timer can only be started from the main thread
任何启动计时器的尝试都会破坏密码。例如,下面的简单代码不起作用:
import wx
import threading
import cmd2
class TimerFrame(wx.Frame):
def __init__(self, time):
super().__init__(None, title=str(time), size=(300, 300))
self.InitUI()
def InitUI(self):
self.timer = wx.Timer(self, 1)
self.timer.Start(100)
self.Bind(wx.EVT_TIMER, self.OnTimer, id=1)
self.Show()
def OnTimer(self, event):
print("Updating")
class Interpreter(cmd2.Cmd):
def __init__(self):
super().__init__()
self.app = wx.App()
def do_timer(self, _):
threading.Thread(target=self.createTimer, args=(self.app,)).start()
def createTimer(self, app):
TimerFrame("Window Title")
app.MainLoop()
if __name__ == "__main__":
Interpreter().cmdloop()
请注意,注释掉包含 timer.Start(100)
的行允许在单独的线程中成功创建 windows,但它们缺少必要的 Timer
功能。
其他我试过但不起作用的东西:
- 在新线程中创建
wx.App
而不是传递它(导致相同的错误,但MainLoop
而不是Timer
) - 在主线程中创建
wx.App
并在其中 运行 设置其MainLoop
(阻止命令行接受更多命令) - 运行 解释器命令循环在一个单独的线程中(抱怨
cmdloop
必须来自主线程 运行)
文档中有一节关于 Integrating cmd2 with event loops:
Many Python concurrency libraries involve or require an event loop which they are in control of such as
asyncio
,gevent
,Twisted
, etc.
虽然这是专门讨论以网络为中心的事件循环,但它实际上与 wx 等 GUI 事件循环存在相同的问题。
tl;dr 不是调用 cmdloop()
,阻塞整个主线程直到解释器退出,而是调用 preloop()
,然后重复调用 onecmd()
(或 onecmd_plus_hooks()
或 runcmd_plus_hooks()
,视情况而定)来自 wx
回调。
换句话说,您从 wx
事件循环驱动 cmd2
事件循环,因此 wx
循环可以接管线程。
wx
也有一种手动驱动事件循环的方法。参见 wx.EventLoopBase
和相关的 类。 (可能有一些很好的示例代码类似于 cmd2
文档中的代码,但您必须搜索它。)想法几乎相同:代替 运行 wx
循环并永远阻塞线程,您手动创建 wx.EventLoop
和 wx.EventLoopActivator
,然后从 cmd2
事件循环。
换句话说,您从 cmd2
事件循环驱动 wx
事件循环,因此 cmd2
循环可以接管线程。
如果这些都不适合您,您可以使用 multiprocessing
而不是 threading
。这样,两个事件循环都在主线程中 运行,但其中一个只是 运行 在子进程的主线程中。
这可能是 GUI 应用程序的问题,因此将 wx
放入子进程可能不起作用(或者,更糟的是,可能对某些 platforms/setups 起作用但对其他不起作用,或者可能起作用但经常神秘地失败......),但 cmd2
大概只需要看到 stdin/stdout/tty,所以它可能会起作用。