В этой статье я расскажу, как обнаружить особые нажатия горячих клавиш и почему это может быть полезным. В самом деле, обнаруживать такие клавиши достаточно просто, но иногда удивляет то, что один виджет ведёт себя совершенно отлично от другого. Самое сложное наступает, когда нужно захватить EVT_CHAR.
Примечание: примеры, которые я рассматриваю в данной статье, не работают на Mac OSX El Capitan.
Сначала я расскажу о событиях, вызываемых горячими клавишами, wx.EVT_KEY_DOWN и wx.EVT_KEY_UP, а потом уже погружусь в сложности wx.EVT_CHAR.
Обнаружение событий, связанных с горячими клавишами
Обнаружение событий в wxPython, связанных с горячими клавишами, в wxPython – процесс достаточно простой. Всё, что нужно, это выяснить код какой клавиши вы хотите обнаружить в документации 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 | import wx class MyForm(wx.Frame):     def __init__(self):         wx.Frame.__init__(self, None, title="Key Press Tutorial")         panel = wx.Panel(self, wx.ID_ANY)         btn = wx.Button(panel, label="OK")         btn.Bind(wx.EVT_KEY_DOWN, self.onKeyPress)     def onKeyPress(self, event):         keycode = event.GetKeyCode()         print(keycode)         if keycode == wx.WXK_SPACE:             print("you pressed the spacebar!")         event.Skip() if __name__ == "__main__":     app = wx.App(True)     frame = MyForm()     frame.Show()     app.MainLoop() | 
Вы заметите, что в данном фрагменте кода виджетами последствий являются только панель и кнопка. Я привязываю клавишу к EVT_KEY_DOWN и проверяю в хэндлере, нажимал ли пользователь пробел. Событие запускается только если клавиша была нажата. Вы также заметите, что я вызывал event.Skip в конце. Если вы не вызовете event.Skip, то клавиша будет поглощена и не будет запущено соответствующее событие горячей клавиши. Это не повлияет на кнопку, но в командной строке события горячих клавиш подходят для обнаружения верхнего и нижнего регистров, ударений, умляутов и им подобных.
Я использовал подобный метод для отслеживания нажатий клавиш-стрелок в моём приложении, которое являлось таблицей. Я хотел быть способным обнаруживать данные клавиши во время редактирования ячейки, чтобы нажатие стрелки делало активно другую ячейку. Это не поведение по умолчанию. В сетке, каждая ячейка имеет собственный редактор, а нажатие клавиш-стрелок перемещало курсор внутри ячейки.
Когда вы запускаете код, написанный выше, и нажимаете пробел, то должны увидеть что-то похожее на этот скриншот:

Просто эксперимента ради, я создал пример, похожий на тот, что вы видите выше, только теперь я привязал события, связанные с нажатием клавиш вверх и вниз к двум разным виджетам. Для этого понадобиться следующий код:
| 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 | import wx class MyForm(wx.Frame):     def __init__(self):         wx.Frame.__init__(self, None, title="Key Press Tutorial 2")         panel = wx.Panel(self, wx.ID_ANY)         sizer = wx.BoxSizer(wx.VERTICAL)         btn = self.onWidgetSetup(wx.Button(panel, label="OK"),                                   wx.EVT_KEY_UP,                                  self.onButtonKeyEvent, sizer)         txt = self.onWidgetSetup(wx.TextCtrl(panel, value=""),                                  wx.EVT_KEY_DOWN, self.onTextKeyEvent,                                  sizer)         panel.SetSizer(sizer)     def onWidgetSetup(self, widget, event, handler, sizer):         widget.Bind(event, handler)         sizer.Add(widget, 0, wx.ALL, 5)         return widget     def onButtonKeyEvent(self, event):         keycode = event.GetKeyCode()         print(keycode)         if keycode == wx.WXK_SPACE:             print("you pressed the spacebar!")         event.Skip()     def onTextKeyEvent(self, event):         keycode = event.GetKeyCode()         print(keycode)         if keycode == wx.WXK_DELETE:             print("you pressed the delete key!")         event.Skip() if __name__ == "__main__":     app = wx.App(True)     frame = MyForm()     frame.Show()     app.MainLoop() | 
Когда запустите код, написанный выше, попробуйте нажать на кнопку и затем на пробел. Затем переместите фокус с текстового поля и нажмите клавишу «Delete». Когда вы закончите, то должны увидеть что-то вроде этого скриншота:

Попробуйте нажать другие клавиши, и вы увидите, что коды горячих клавиш распечатались для каждой клавиши.
Соглашусь, этот код скорее для иллюстрации. Главное, что вам нужно знать это то, что вы действительно не используете EVT_KEY_UP до тех пор, пока вам не понадобится отслеживать несколько комбинаций из двух-трёх клавиш вроде CTRL+K+Y, или что-то подобное (в качестве дополнительного замечания: ознакомитесь с возможностями wx.AcceleratorTable). Так как я не делаю этого в своём примере, стоит отметить, что если вы проверяете клавишу CTRL, то лучше будет использовать event.CmdDown(), чем event.ControlDown. Причина в том, что CmdDown является эквивалентом для ControlDown на Windows и Linux, но в Mac он симулирует клавишу Command. Таким образом, CmdDown лучший кроссплатформенный способ проверки нажатия клавиши CTRL.
И это, пожалуй, всё, что вам нужно знать о событиях, связанных с горячими клавишами. Теперь давайте разберёмся с char-событиями.
Обнаружение с char-событий
Обнаружение с char-событий немного сложнее, но не слишком. Давайте взглянем на пример, чтобы понять какие существуют отличия.
| 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 | import wx class MyForm(wx.Frame):     def __init__(self):         wx.Frame.__init__(self, None, title="Char Event Tutorial")         # Add a panel so it looks the correct on all platforms         panel = wx.Panel(self, wx.ID_ANY)         btn = wx.TextCtrl(panel, value="")         btn.Bind(wx.EVT_CHAR, self.onCharEvent)     def onCharEvent(self, event):         keycode = event.GetKeyCode()         controlDown = event.CmdDown()         altDown = event.AltDown()         shiftDown = event.ShiftDown()         print(keycode)         if keycode == wx.WXK_SPACE:             print("you pressed the spacebar!")         elif controlDown and altDown:             print(keycode)         event.Skip() if __name__ == "__main__":     app = wx.App(True)     frame = MyForm()     frame.Show()     app.MainLoop() | 
Я думаю, что главное отличие в том, что вы захотите проверять ударения или международные знаки. Таким образом, у вас будут сложные условия, которые проверяют были ли нажаты определённые клавиши и в каком порядке. Робин Данн (создатель wxPython) сказал, что wxSTC проверять и события, связанные с горячими клавишами, и char-события. Если в ваши планы входит поддержка пользователей за пределами англоязычных стран, вы, скорее всего, захотите узнать, как это работает.
Цитата Робина Дана: «Если вы хотите, чтобы события, связанные с горячими клавишами, выполняли «команды» внутри приложения, используйте сырые значения в хэндлере EVT_KEY_DOWN. Как бы то ни было, если в перечень возможностей приложения входит ввод текста, тогда оно должно использовать готовые значения в хэндлере события EVT_CHAR, чтобы нормально обрабатывать не английскую раскладку клавиатуры и другие редакторы методов ввода. (Примечание: события keydown/keyup считаются сырыми, в то время как char-события считаются готовыми).
Как мне объяснил Робин Дан, во всех раскладках клавиатуры, кроме английской, заготовка клавиш для char-событий является мэппингом физических клавиш к национальной раскладке клавиатуры, чтобы появлялись символы с ударениями, умляутами и прочие.
Когда вы будете тестировать мою демо-версию, вы заметите, что в нём коды, привязанные к горячим клавишам, активируются, когда вы их нажимаете. Если вы хотите увидеть коды для особых комбинаций клавиш, попробуйте нажать CTRL или SHIFT одновременно с другой клавишей. Вам также нужно иметь возможность обнаруживать ALT, но я заметил, что эта опция не работает ни на моём компьютере с Windows 7, ни на моём макбуке.
Итоги
Теперь вы можете написать своё собственное приложение, использующее захват клавиш и char-события. Вам также может пригодится другая статья на похожую тему, которая называется AcceleratorTable. Этот класс позволяет разработчикам захватывать комбинации клавиш, которые пользователь нажимает в то время, как использует программу. Так что прочтите её, когда будет возможность.

Являюсь администратором нескольких порталов по обучению языков программирования Python, Golang и Kotlin. В составе небольшой команды единомышленников, мы занимаемся популяризацией языков программирования на русскоязычную аудиторию. Большая часть статей была адаптирована нами на русский язык и распространяется бесплатно.
E-mail: vasile.buldumac@ati.utm.md
Образование
Universitatea Tehnică a Moldovei (utm.md)
- 2014 — 2018 Технический Университет Молдовы, ИТ-Инженер. Тема дипломной работы «Автоматизация покупки и продажи криптовалюты используя технический анализ»
- 2018 — 2020 Технический Университет Молдовы, Магистр, Магистерская диссертация «Идентификация человека в киберпространстве по фотографии лица»