четверг, 26 июля 2012 г.

wxPython: Использование wx.Timer (Перевод)


Последние несколько недель я видел большое количество вопросов по поводу таймеров в wxPython. Так что я решил, что пришло время написать пару скриптов чтобы показать, как они работают. Я разберу два примера: в первом у меня будет один таймер, во втором - два. Robin Dunn связался со мной и несколько улучшил мои примеры, так что к этой статье приложены и переработанные варианты моих скриптов.

Мой первый пример очень прост. Есть всего одна кнопка, которая запускает и останавливает таймер. Давайте посмотрим на код:
import wx
import time
 
class MyForm(wx.Frame):
 
    def __init__(self):
        wx.Frame.__init__(self, None, wx.ID_ANY, "Timer Tutorial 1", 
                                   size=(500,500))
 
        # Add a panel so it looks the correct on all platforms
        panel = wx.Panel(self, wx.ID_ANY)
 
        self.timer = wx.Timer(self)
        self.Bind(wx.EVT_TIMER, self.update, self.timer)
 
        self.toggleBtn = wx.Button(panel, wx.ID_ANY, "Start")
        self.toggleBtn.Bind(wx.EVT_BUTTON, self.onToggle)
 
    def onToggle(self, event):
        btnLabel = self.toggleBtn.GetLabel()
        if btnLabel == "Start":
            print "starting timer..."
            self.timer.Start(1000)
            self.toggleBtn.SetLabel("Stop")
        else:
            print "timer stopped!"
            self.timer.Stop()
            self.toggleBtn.SetLabel("Start")
 
    def update(self, event):
        print "\nupdated: ",
        print time.ctime()
 
# Run the program
if __name__ == "__main__":
    app = wx.PySimpleApp()
    frame = MyForm().Show()
    app.MainLoop()
Как можно видеть, я импортирую два модуля: wx и time. Я использую модуль time для отображения времени срабатывания wx.Timer. Две вещи на которые надо обратить внимание - это связывание таймера с обработчиком события и сам обработчик события. Для того, чтобы этот пример работал, Вам надо связать фрейм с событием таймера. Я пробовал связывать событие с самим таймером (т.е. self.timer.Bind), но это не сработало. Я спросил об этом Robin Dunn, который сказал, что если родителем таймера является фрейм, то тогда единственным объектом, который будет получать событие таймера будет этот фрейм, пока Вы не  переопределите метод Notify класса wx.Timer.
В любом случае, давайте посмотрим на обработчик события. В нём я получаю текст с кнопки и использую оператор выбора для того, чтобы понять, активировать или деактивировать таймер  в соответствии с написанным на кнопке. В таком случае, мне достаточно всего одного обработчика нажатия кнопки. Обратить внимание надо ещё на методы Start и Stop. Они и контролируют работу таймера.
В моих собственных приложениях я использую таймер для проверки новых сообщений на почте. При этом я обнаружил, что если я закрываю мою программу не останавливая таймер, программа становится "зомби". Поэтому Вам необходимо быть уверенным, что перед закрытием программы все таймеры остановлены.
Перед тем как мы перейдём к следующему примеру давайте посмотрим на переработанный вариант первого скрипта. Можете понять в чём разница?
import wx
import time
 
class MyForm(wx.Frame):
 
    def __init__(self):
        wx.Frame.__init__(self, None, wx.ID_ANY, "Timer Tutorial 1", size=(500,500))
 
        # Add a panel so it looks the correct on all platforms
        panel = wx.Panel(self, wx.ID_ANY)
 
        self.timer = wx.Timer(self)
        self.Bind(wx.EVT_TIMER, self.update, self.timer)
 
        self.toggleBtn = wx.Button(panel, wx.ID_ANY, "Start")
        self.toggleBtn.Bind(wx.EVT_BUTTON, self.onToggle)
 
    def onToggle(self, event):        
        if self.timer.IsRunning():
            self.timer.Stop()
            self.toggleBtn.SetLabel("Start")
            print "timer stopped!"
        else:
            print "starting timer..."
            self.timer.Start(1000)
            self.toggleBtn.SetLabel("Stop")
 
    def update(self, event):
        print "\nupdated: ",
        print time.ctime()
 
# Run the program
if __name__ == "__main__":
    app = wx.PySimpleApp()
    frame = MyForm().Show()
    app.MainLoop()
Как можете заметить, я изменил обработчик события для проверки запущен таймер или нет вместо того, чтобы смотреть на надпись на кнопке. Это сделало код более понятным и ещё раз показывает, как можно несколькими различными способами достичь одного и того же.
Теперь давайте посмотрим на мой второй пример:
import wx
import time
 
TIMER_ID1 = 2000
TIMER_ID2 = 2001
 
class MyForm(wx.Frame):
 
    def __init__(self):
        wx.Frame.__init__(self, None, wx.ID_ANY, "Timer Tutorial 2")
 
        # Add a panel so it looks the correct on all platforms
        panel = wx.Panel(self, wx.ID_ANY)
 
        self.timer = wx.Timer(self, id=TIMER_ID1)
        self.Bind(wx.EVT_TIMER, self.update, self.timer)
        self.timer2 = wx.Timer(self, id=TIMER_ID2)
        self.Bind(wx.EVT_TIMER, self.update, self.timer2)
 
        self.toggleBtn = wx.Button(panel, wx.ID_ANY, "Start Timer 1")
        self.toggleBtn.Bind(wx.EVT_BUTTON, self.onStartTimerOne)
        self.toggleBtn2 = wx.Button(panel, wx.ID_ANY, "Start Timer 2")
        self.toggleBtn2.Bind(wx.EVT_BUTTON, self.onStartTimerOne)
 
        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(self.toggleBtn, 0, wx.ALL|wx.CENTER, 5)
        sizer.Add(self.toggleBtn2, 0, wx.ALL|wx.CENTER, 5)
        panel.SetSizer(sizer)        
 
    def onStartTimerOne(self, event):
        buttonObj = event.GetEventObject()
        btnLabel = buttonObj.GetLabel()
        timerNum = int(btnLabel[-1:])
        print timerNum
 
        if btnLabel == "Start Timer %s" % timerNum:
            if timerNum == 1:
                print "starting timer 1..."
                self.timer.Start(1000)
            else:
                print "starting timer 2..."
                self.timer2.Start(3000)
            buttonObj.SetLabel("Stop Timer %s" % timerNum)
        else:
            if timerNum == 1:
                self.timer.Stop()
                print "timer 1 stopped!"
            else:
                self.timer2.Stop()
                print "timer 2 stopped!"
            buttonObj.SetLabel("Start Timer %s" % timerNum)
 
    def update(self, event):
        timerId = event.GetId()
        if timerId == TIMER_ID1:
            print "\ntimer 1 updated: ",
        else:
            print "\ntimer 2 updated: ", 
        print time.ctime()
 
# Run the program
if __name__ == "__main__":
    app = wx.PySimpleApp()
    frame = MyForm().Show()
    app.MainLoop()
По правде, второй пример ни чем по сути не отличается от первого. Главное отличие состоит в том, что теперь у нас две кнопки и два таймера. Я решил выпендриться и использовать один обработчик для обоих кнопок. Это, возможно, один из моих лучших трюков. Для того же, чтобы понять, какая кнопка была нажата, я использую метод GetEventObject события. Затем мы получаем надпись с кнопки. Если же Вы полный фанат программирования, то можете заметить, что строки 32 и 33 можно объединить:
btnLabel = event.GetEventObject().GetLabel()
Я разбил их на две строки для простоты. Затем я использую срез строки для получения номера кнопки и могу запустить / остановить соответствующий таймер. Запуск или остановка проверяются во вложенном операторе IF.
Вот переработанная версия этого примера от Robin Dunn:
import wx
import time
 
class MyForm(wx.Frame):
 
    def __init__(self):
        wx.Frame.__init__(self, None, wx.ID_ANY, "Timer Tutorial 2")
 
        # Add a panel so it looks the correct on all platforms
        panel = wx.Panel(self, wx.ID_ANY)
 
        self.timer = wx.Timer(self, wx.ID_ANY)
        self.Bind(wx.EVT_TIMER, self.update, self.timer)
        self.timer2 = wx.Timer(self, wx.ID_ANY)
        self.Bind(wx.EVT_TIMER, self.update, self.timer2)
 
        self.toggleBtn = wx.Button(panel, wx.ID_ANY, "Start Timer 1")
        self.toggleBtn.Bind(wx.EVT_BUTTON, self.onStartTimer)
        self.toggleBtn2 = wx.Button(panel, wx.ID_ANY, "Start Timer 2")
        self.toggleBtn2.Bind(wx.EVT_BUTTON, self.onStartTimer)
 
        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(self.toggleBtn, 0, wx.ALL|wx.CENTER, 5)
        sizer.Add(self.toggleBtn2, 0, wx.ALL|wx.CENTER, 5)
        panel.SetSizer(sizer)
 
        # Формат данных в словаре:
        # (timerNum, timerObj, секунды_между_событиями_таймера)
        self.objDict = {self.toggleBtn: (1, self.timer, 1000),
                        self.toggleBtn2: (2, self.timer2, 3000)}
 
    def onStartTimer(self, event):
        btn = event.GetEventObject()
        timerNum, timer, secs = self.objDict[btn]
        if timer.IsRunning():
            timer.Stop()
            btn.SetLabel("Start Timer %s" % timerNum)
            print "timer %s stopped!" % timerNum
        else:
            print "starting timer %s..." % timerNum
            timer.Start(secs)
            btn.SetLabel("Stop Timer %s" % timerNum)
 
    def update(self, event):
        timerId = event.GetId()
        if timerId == self.timer.GetId():
            print "\ntimer 1 updated: ",
        else:
            print "\ntimer 2 updated: ", 
        print time.ctime()
 
# Run the program
if __name__ == "__main__":
    app = wx.PySimpleApp()
    frame = MyForm().Show()
    app.MainLoop()
В “__init__” я добавил словарь, где ключами являются объекты кнопок. Значениями словаря являются номер таймера, объект таймера и количество секунд на события между таймерами. Затем я обновил обработчик нажатия кнопки для того, чтобы он получал объект кнопки из объекта события при помощи метода GetEventObject и извлекал из словаря соответствующие значения. После чего я могу использовать тот же трюк, что и в предыдущем переработанном примере для работы с таймером и кнопкой.
Примеры тестировались на этих платформах:
  • Windows Vista with wxPython 2.8.9.2 (msw-unicode) и Python 2.5.2.
  • Windows XP with wxPython 2.8.10.1 (msw-unicode) и Python 2.5.2
Вы можете скачать мой код по ссылкам ниже. Сообщите мне, если Вы тестировали его на других ОС или если у Вас есть какие-то вопросы. Огромное спасибо за помощь в работе над примерами Robin Dunn и, самое главное, за wxPython.
Домашнее чтение
Загрузки

Комментариев нет:

Отправить комментарий