In this post, I’ll detail how to catch specific key presses and why this can be useful. This is another in my series of “requested” tutorials. There really isn’t much to catching key presses, but it can be a little confusing when one widget behaves slightly differently from another. The really complicated stuff comes in when you need to capture the EVT_CHAR.
First I’ll cover the key events, wx.EVT_KEY_DOWN and wx.EVT_KEY_UP and then I’ll go over the intricacies of wx.EVT_CHAR. It is my belief that programming is easiest to understand if you see some sample code, so I’ll start off with simple example:
import wx class MyForm(wx.Frame): def __init__(self): wx.Frame.__init__(self, None, wx.ID_ANY, "Key Press Tutorial") # Add a panel so it looks the correct on all platforms 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() # Run the program if __name__ == "__main__": app = wx.PySimpleApp() frame = MyForm() frame.Show() app.MainLoop()
You will notice that the only widgets of consequence in this piece of code are a panel and a button. I bind the button to the EVT_KEY_DOWN and in the handler I check if the user has pressed the spacebar. The event only fires if the button has focus. You’ll notice that I also call “event.Skip” at the end. Iif you don’t call Skip, then the key will “eaten” and there won’t be a corresponding char event. This won’t matter on a button, but you might care in a text control as char events are the proper way of catching upper and lower case, accents, umlauts and the like.
I’ve used a similar method to catch arrow key presses in a spreadsheet-type application of mine. I wanted to be able to detect these keys so that if I was editing a cell, an arrow key press would make the selection change to a different cell. That is not the default behavior. In a grid, each cell has its own editor and pressing the arrow keys just moves the cursor around within the cell.
Just for fun, I created a similar example to the one above where I bound to the key and the key down events, but with two different widgets. Check it out below:
import wx class MyForm(wx.Frame): def __init__(self): wx.Frame.__init__(self, None, wx.ID_ANY, "Key Press Tutorial 2") # Add a panel so it looks the correct on all platforms 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() # Run the program if __name__ == "__main__": app = wx.PySimpleApp() frame = MyForm() frame.Show() app.MainLoop()
Admittedly, this is mostly for illustration. The main thing to know is that you really don’t use EVT_KEY_UP unless you need to keep track of multi-key combinations, like CTRL+K+Y or something (on a semi-related note, see the wx.AcceleratorTable). While I’m not doing this in my example, it is important to note that if you are checking for the CTRL key, then it’s best to use event.CmdDown() rather than event.ControlDown. The reason being that CmdDown is the equivalent of ControlDown on Windows and Linux, but on Mac it simulates the Command key. Thus, CmdDown is the best cross-platform way of checking if the CTRL key has been pressed.
And that’s really all you need to know about key events. Let’s go on and see what we can learn about char events. Here’s a simple example:
import wx class MyForm(wx.Frame): def __init__(self): wx.Frame.__init__(self, None, wx.ID_ANY, "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() # Run the program if __name__ == "__main__": app = wx.PySimpleApp() frame = MyForm() frame.Show() app.MainLoop()
I think the main thing that is different is that you want to check for accents or international characters. Thus, you’ll have complex conditionals that check if certain keys are pressed and in what order. Robin Dunn (creator of wxPython) said that wxSTC checks for both key and char events. If you plan on supporting users outside the USA, you’ll probably want to learn how this all works.
Robin Dunn went on to say that if you want to get the key events in order to handle “commands” within the application, then using the raw values in a EVT_KEY_DOWN handler is appropriate. However if the intent is to handle the entry of “text” then the app should use the cooked values in an EVT_CHAR event handler in order to get the proper handling for non US keyboards and input method editors. (Note: key up and key down events are considered “raw” whereas char events have been “cooked” for you.) As Robin Dunn explained it to me, on non-US keyboards then part of cooking the key events into char events is mapping the physical keys to the national keyboard map, to produce characters with accents, umlauts, and such.
I apologize that this tutorial doesn’t cover more on char events, but I just couldn’t find much for examples.
These code samples were tested on the following
- Windows Vista SP2, wxPython 2.8.9.2 (unicode), Python 2.5.2