wx.Timer позволяет разработчикам программного обеспечения запускать определённые фрагменты кода через определённые интервалы. В данной статье я рассмотрю несколько разных способов создания таймеров. Объекта таймера запускает свой собственный цикл события, который им и контролируется, при этом не вмешивается в главный цикл wxPython.
Простой таймер
Мой первый пример невероятно прост. В нём лишь одна кнопка, которой можно останавливать и запускать таймер. Давайте взглянем на код.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
import time import wx class MyForm(wx.Frame): def __init__(self): wx.Frame.__init__(self, None, title="Timer Tutorial 1", size=(500,500)) 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: ", time.ctime()) # Запускает программу if __name__ == "__main__": app = wx.App(True) frame = MyForm().Show() app.MainLoop() |
Как вы видите, я импортирую только два модуля: wx и time. Я использую модуль time для того, чтобы задать время, в которое будет запущено событие wx.Timer. Две главных вещи, на которые стоит обратить внимания – это как привязывать таймер к событию и к самому хэндлеру события.
Есть вопросы по Python?
На нашем форуме вы можете задать любой вопрос и получить ответ от всего нашего сообщества!
Паблик VK
Одно из самых больших сообществ по Python в социальной сети ВК. Видео уроки и книги для вас!
Для того, чтобы данный пример заработал, вам нужно привязать фрейм к событию таймера. Я пытался привязать сам таймер (например, используя self.timer.Bind), но он не сработал. Логично, что я обратился к Робину Дану, чтобы выяснить, что происходит. Он сказал, что, если перентом таймера является фрейм, фрейм будет единственным объектом, который получит событие таймера, и так будет до тех пор, пока вы не извлечёте wx.Timer и не используете (отмените) его метод Notify. Как по мне, звучит логично.
В любом случае, давайте взглянем на мой хэндлер события. В нём я захватывают ярлык кнопки и использую условное выражение if, чтобы решить хочу я запускать или останавливать таймер, ведь ярлык будет отличатся в зависимости от текущего состояния таймера. В данном случае я могу использовать лишь одну функцию, которая будет переключать состояние таймера и самой кнопки. Стоит обратить внимание на методы Start и Stoр. Они контролируют сам таймер.
В одной из приложений, которое мне пришлось разрабатывать, я использовал таймер для запуска проверки состояния папки «Входящие» моей электронной почты. Я обратил внимание на то, что если выйду из приложения, при этом не остановив таймер, то моя программа просто превратится в процесс-зомби. Так что вам нужно убедиться в том, что вы остановить все таймеры перед тем, как закрыть программу или снять задачу.
Перед тем, как перейти к следующему примеру, давайте посмотрим, как можно переработать этот. У Робина Дана было несколько идей, так что я внедрил их в код, который вы можете найти ниже. Вы уже нашли отличия?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
import wx import time class MyForm(wx.Frame): def __init__(self): wx.Frame.__init__(self, None, title="Timer Tutorial 1", size=(500,500)) 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: ", time.ctime()) # Запускает программу if __name__ == "__main__": app = wx.App(True) frame = MyForm().Show() app.MainLoop() |
Как вы видите, я заменил хэндлер события, позволяющий проверить запущен таймер или нет вместо того, чтобы смотреть на ярлык кнопки. Он короче лишь на одну строку, но при этом является отличной демонстрацией того, что можно достичь выполнения одной задачи несколькими разными способами.
Использование нескольких таймеров
Часто бывает так, что вам нужно одновременно запустить сразу несколько таймеров. Например, чтобы проверять обновления одного или нескольких веб-API. Давайте рассмотрим простой пример, позволяющий создать несколько таймеров.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 |
import wx import time TIMER_ID1 = 2000 TIMER_ID2 = 2001 class MyForm(wx.Frame): def __init__(self): wx.Frame.__init__(self, None, title="Timer Tutorial 2") 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: ", time.ctime()) else: print("\ntimer 2 updated: ", time.ctime()) # Запускает программу if __name__ == "__main__": app = wx.App() frame = MyForm().Show() app.MainLoop() |
Если честно, то второй пример практически идентичен первому. Главным различием является то, что здесь у нас две кнопки и две разных инстанции. Я решил устроить немного «показухи» и привязал обе кнопки к одному хэндлеру события.
Это, наверное, один из лучших моих трюков. Чтобы увидеть, какая из кнопок запускает событие, вы можете использовать метод GetEventObject этого же события. Затем вы можете прикрепить к кнопке ярлык. Если вы совсем жёсткий, вы заметите, что я могу превратить строчки 30 и 31 в эту комбинацию:
1 |
1 btnLabel = event.GetEventObject().GetLabel() |
Я разбил его на две строки, чтобы вам было проще понять принцип работы. Я использовал что-то вроде деления стрингов, чтобы захватывать номер ярлыка кнопки, так что я смогу узнать какой таймер запустится или остановится. Затем моя программа вводит мои заготовленные IF-утверждения, в которых она проверяет номер ярлыка кнопки и номер таймера. Теперь вы также знаете, как запустить или остановить несколько таймеров одновременно.
И снова же, Робин Дан предложил мне лучший способ написания второго примера, и вот как это выглядело:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
import wx import time class MyForm(wx.Frame): def __init__(self): wx.Frame.__init__(self, None, title="Timer Tutorial 2") 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: ", time.ctime()) else: print ("\ntimer 2 updated: ", time.ctime()) # Запускает программу if __name__ == "__main__": app = wx.App() frame = MyForm().Show() app.MainLoop() |
В __init__ я добавил словарь, который привязан к объектам кнопок. Значениями словаря являются: номер таймера, объект таймера и количество секунд (технически – миллисекунд) между событиями таймера.
Затем я обновил хэндлер события кнопки таким образом, чтобы объект кнопки захватывался из метода GetEventObject, а затем извлекал соответствующие значения используя указанный ключ объекта для доступа к словарю. Затем я применяю тот же трюк, о котором я детально рассказывал во время переработки примера, расположенного выше, буквально проверяя запущен таймер или нет.
Итоги
На данный момент вы уже должны довольно уверенно справляться с wx.Timer, а также чётко понимать, в каком из ваших проектов он будет смотреться гармоничнее всего. Это отличный способ запустить определённое событие через определённый промежуток времени, а ещё он довольно надёжен.
Отличным примером использования является программа для электронной почты, в которой требовалось через определённый промежуток времени проверять наличие новых писем. Я настроил таймер на настолько короткие промежутки времени, что получал уведомление о получении письма в ту же секунду, когда оно приходило.
Являюсь администратором нескольких порталов по обучению языков программирования Python, Golang и Kotlin. В составе небольшой команды единомышленников, мы занимаемся популяризацией языков программирования на русскоязычную аудиторию. Большая часть статей была адаптирована нами на русский язык и распространяется бесплатно.
E-mail: vasile.buldumac@ati.utm.md
Образование
Universitatea Tehnică a Moldovei (utm.md)
- 2014 — 2018 Технический Университет Молдовы, ИТ-Инженер. Тема дипломной работы «Автоматизация покупки и продажи криптовалюты используя технический анализ»
- 2018 — 2020 Технический Университет Молдовы, Магистр, Магистерская диссертация «Идентификация человека в киберпространстве по фотографии лица»