wxPython:线程GUI --> 使用自定义事件处理器

3 投票
3 回答
8655 浏览
提问于 2025-04-15 19:47

我正在学习如何在主界面应用程序之外运行一个线程,以便进行串口的发送和接收,同时保持我的界面正常工作。我通过搜索找到了一些资料,最终来到了wxpython的维基页面,地址是:http://wiki.wxpython.org/LongRunningTasks,上面提供了几个例子。我决定先学习第一个例子,这个例子是当点击特定按钮时启动一个工作线程。

我在理解自定义事件定义时遇到了一些困难:

def EVT_RESULT(win, func):
    """Define Result Event."""
    win.Connect(-1, -1, EVT_RESULT_ID, func)

class ResultEvent(wx.PyEvent):
    """Simple event to carry arbitrary result data."""
    def __init__(self, data):
        """Init Result Event."""
        wx.PyEvent.__init__(self)
        self.SetEventType(EVT_RESULT_ID)
        self.data = data

主要是

def EVT_RESULT(win, func):
    """Define Result Event."""
    win.Connect(-1, -1, EVT_RESULT_ID, func)

我觉得EVT_RESULT放在类外面是为了让两个类都能调用它(是不是说它变成全局的了?)

而且……主界面应用程序通过以下方式来监控线程的进度:

# Set up event handler for any worker thread results
EVT_RESULT(self,self.OnResult)

我还注意到在很多例子中,当作者使用

from wx import *

时,他们只是通过

EVT_SOME_NEW_EVENT(self, self.handler)

来绑定,而不是

wx.Bind(EVT_SOME_NEW_EVENT, self.handler)

这让我更难理解。谢谢,

3 个回答

0

你可能想用Python的线程和队列,而不是自定义事件。我有一个wxPython的程序(OpenSTV),它会加载很大的文件,这会导致界面在加载时卡住。为了防止界面卡住,我启动了一个线程来加载文件,并使用队列来在界面和线程之间进行沟通(比如,把错误信息传递给界面)。

  def loadBallots(self):
    self.dirtyBallots = Ballots()
    self.dirtyBallots.exceptionQueue = Queue(1)
    loadThread = Thread(target=self.dirtyBallots.loadUnknown, args=(self.filename,))
    loadThread.start()

    # Display a progress dialog
    dlg = wx.ProgressDialog(\
      "Loading ballots",
      "Loading ballots from %s\nNumber of ballots: %d" % 
      (os.path.basename(self.filename), self.dirtyBallots.numBallots),
      parent=self.frame, style = wx.PD_APP_MODAL | wx.PD_ELAPSED_TIME
    )
    while loadThread.isAlive():
      sleep(0.1)
      dlg.Pulse("Loading ballots from %s\nNumber of ballots: %d" %
                (os.path.basename(self.filename), self.dirtyBallots.numBallots))
    dlg.Destroy()

if not self.dirtyBallots.exceptionQueue.empty():
  raise RuntimeError(self.dirtyBallots.exceptionQueue.get())
4

这是定义自定义事件的旧方法。想了解更多信息,可以查看迁移指南

摘自迁移指南:

如果你创建了自己的自定义事件类型和EVT_*函数,并且想要用上面的Bind方法来使用它们,那么你应该把你的EVT_*改成的实例,而不是一个函数。例如,如果你以前有这样的代码:

myCustomEventType = wxNewEventType()
def EVT_MY_CUSTOM_EVENT(win, id, func):
    win.Connect(id, -1, myCustomEventType, func)

那么你应该改成这样:

myCustomEventType = wx.NewEventType()
EVT_MY_CUSTOM_EVENT = wx.PyEventBinder(myCustomEventType, 1)

这里有我另外一篇帖子,里面有几个示例程序,正好符合你的需求。

2

你可以这样定义事件:

from wx.lib.newevent import NewEvent

ResultEvent, EVT_RESULT = NewEvent()

你可以这样发布这个事件:

wx.PostEvent(handler, ResultEvent(data=data))

这样来绑定事件:

def OnResult(event):
    event.data

handler.Bind(EVT_RESULT, OnResult)

不过,如果你只是想在主线程中从一个非主线程调用,可以使用 wx.CallAfter这里有个例子

自定义事件很有用,特别是当你不想硬性规定谁负责什么的时候(可以参考观察者设计模式)。举个例子,假设你有一个主窗口和几个子窗口。如果主窗口发生某些变化,某些子窗口需要刷新。主窗口可以直接刷新这些子窗口,但更优雅的做法是定义一个自定义事件,然后让主窗口自己发布这个事件(这样就不需要关心谁需要对此做出反应)。然后需要对这个事件做出反应的子窗口可以通过绑定这个事件来处理(如果有多个子窗口需要反应,记得调用 event.Skip(),这样所有绑定的方法都会被调用)。

撰写回答