【问题标题】:best way to bind timer event created within a for loop绑定在 for 循环中创建的计时器事件的最佳方法
【发布时间】:2025-12-24 09:15:11
【问题描述】:

我正在尝试编写一个 python 代码,它在 for 循环中创建和初始化 n 计时器并将它们绑定到一些事件。下面的代码是我这样做的一个例子:

import wx

#dm1 = {a dewell times values dictionary}
#screens = [a list of dm1 keys]
trials = range(1, 3)
timers = range(0, 4)


class DM1(wx.Frame):
    def __init__(self, *args, **kwargs):
        wx.Frame.__init__(self, *args, **kwargs)
        panel = wx.Panel(self)

        self.Go = wx.Button(panel, label = 'Go!', pos = (600, 450))
        self.Bind(wx.EVT_BUTTON, self.BuildTimers, self.Go)

    def BuildTimers(self, event):
        self.timers = {} 
        cum_timer = 0

        for trial in trials:
            for timer in timers:
                key = (timer, trial)
                new_timer = wx.Timer(self)
                cum_timer += dm1[screens[timer]]
                new_timer.Start(cum_timer * 1000, False)

                new_timer.mykey = key 
                self.timers[key] = new_timer

        self.Bind(wx.EVT_TIMER, self.Screens)

    def Screens(self, event):
        if event.GetEventObject() == self.timers[event.GetEventObject().mykey]:
            print event.GetEventObject().mykey
            if event.GetEventObject().mykey[0] == 0:
                print 'You bastard!'

            if event.GetEventObject().mykey[0] == 1:
                print 'you vicious...'                

            if event.GetEventObject().mykey[0] == 2:
                print 'heartless bastard!'

            if event.GetEventObject().mykey[0] == 3:
                print 'Oh It makes me mad!'

app = wx.App()
frame = DM1(None, title = 'IG', size = (wx.DisplaySize()))
frame.Show()
app.MainLoop()

计时器在我指定的时间没有启动:trial 的第二个循环似乎覆盖了第一个循环。例如,语句print event.GetEventObject().mykey,在完整的代码中,打印出

(0, 1) (1, 1) (1, 2) (2, 1) (3, 1) (0, 2) (3, 1) (2, 1)

而不是

(0, 1) (1, 1) (2, 1) (3, 1) (0, 2) (1, 2) (2, 2) (3, 2)

我猜问题出在GetEventObject,但我不知道将计时器绑定到事件的更好方法。有人有想法吗?

非常感谢!

【问题讨论】:

    标签: python events timer wxpython bind


    【解决方案1】:

    在使用 wxPython 时,我发现让小部件使用默认参数生成自己的 id 更为明智。然后,您只需向小部件询问其小部件 ID。这是一种危险的做法,意味着调试非常棘手,使用手动幻数 id 值并将小部件的 id 值发送到该值,特别是对于 4000 以下的 id,因为在低范围内定义了一堆区域。例如,一些内部方法会要求“给我 id 为 1 的小部件”,而不是获取一些内部默认小部件,该方法将获取您的计时器小部件。

    使用 TIMER_ID = 4000 之类的预定义,甚至更难以捉摸的错误 TIMER_ID = 4000 + someoffset,给代码增加额外的维护成本。

    我发现最灵活、最可靠的方法是使用 id=-1 或 id=wx.NewId()。然后说

    ident = myTimer.GetId()
    self.timers[ ident ] = myTimer
    

    self.timers[ myTimer ] = myTimer
    

    self.timers.append( myTimer ) 
    

    在哪里维护顺序并且索引的行为类似于您在代码中使用 ID 字段的方式。

    另一个好的做法是避免使用“id”作为临时变量,因为有 Python 内置函数 id() 并且在 wx 中,小部件也有一个 id 属性:

    w=Widget(..., id=int)
    

    【讨论】:

      【解决方案2】:

      您做错了,根据this thread,您应该将id参数传递给计时器。对于你的情况,这是我能想到的:

      def BuildTimers(self, event):
          self.timers = {} 
          cum_timer = 0
      
          i = 0
          for trial in trials:
              for timer in timers:
                  i += 1
                  key = (timer, trial)
                  new_timer = wx.Timer(self, id=i)
                  cum_timer += dm1[screens[timer]]
                  new_timer.Start(cum_timer * 1000, False)
      
                  self.timers[i] = key
      
                  self.Bind(wx.EVT_TIMER, lambda event, i=i: self.Screens(event, the_id=i), id=i)
      
      def Screens(self, event, the_id=None):
          print "In the timer id =", the_id, "which is", self.timers[the_id]
      

      为什么你所做的不起作用

      您正在设置 Timer 实例的属性,然后在您的函数中使用GetEventObject()。显然, Timer 不是一个事件对象......你这样做的方式,所有的定时器都会调用同一个函数,而它却无法知道哪个函数正在调用它。所以我传递了一个唯一的参数id,我认为它需要是一个int。为此,我使用了 lambda。它应该可以工作,但我没有测试它。

      另外,正如我看到的here,您甚至可能不需要 id,只需将其绑定到计时器,但您可能需要直接将密钥作为参数传递。类似的东西(没有i):

      self.Bind(wx.EVT_TIMER, lambda event, k=key: self.Screens(event, the_key=k), new_timer)

      【讨论】:

      • GetEventObject 返回调用处理程序的小部件或对象,所以我真的不认为这有什么问题。正如 DevPlayer 所提到的,在大多数情况下使用静态 ID 是不行的。您不必向 Timer(或任何小部件)传递 ID,因为 wxPython 会为您分配一个。
      • 我从来没有真正使用过计时器,但我搜索了一下,找到了我链接的主题,然后我在你的博客上找到了这篇文章,也提到了。