wxPython: Learning to Focus

I had a request this week to write up a quick tutorial on focus events in wxPython. Fortunately, there's not a lot of material to cover on this subject, so this should be a pretty quick and dirty little post. I'll see you after the jump!

There are really only two focus events that I've ever seen used: wx.EVT_SET_FOCUS and wx.EVT_KILL_FOCUS. The event, EVT_SET_FOCUS, is fired when a widget receives focus, such as when you click on a blank panel or put your cursor inside a TextCtrl widget. When you tab or click out of a widget that has focus, then the EVT_KILL_FOCUS is fired.

One of the few "gotchas" that I've seen mentioned on the wxPython mailing list is that the wx.Panel only accepts focus when it does not have a child widget that can accept focus. The best way to explain this is with a series of examples.

import wx
 
class MyForm(wx.Frame):
 
    def __init__(self):
        wx.Frame.__init__(self, None, wx.ID_ANY, "Focus Tutorial 1")
 
        # Add a panel so it looks the correct on all platforms
        panel = wx.Panel(self, wx.ID_ANY)
        panel.Bind(wx.EVT_SET_FOCUS, self.onFocus)        
        
    def onFocus(self, event):
        print "panel received focus!"
 
# Run the program
if __name__ == "__main__":
    app = wx.PySimpleApp()
    frame = MyForm().Show()
    app.MainLoop()

Now this code will display a blank panel with nothing on it. You'll notice that stdout immediately gets "panel received focus!" printed to it. Now if we add a TextCtrl or a Button, then they will receive focus and the OnFocus event handler will not get fired. Try running the code below to see this in action:

import wx
 
class MyForm(wx.Frame):
 
    def __init__(self):
        wx.Frame.__init__(self, None, wx.ID_ANY, "Focus Tutorial 1a")
 
        # Add a panel so it looks the correct on all platforms
        panel = wx.Panel(self, wx.ID_ANY)
        panel.Bind(wx.EVT_SET_FOCUS, self.onFocus)
        txt = wx.TextCtrl(panel, wx.ID_ANY, "")
        
    def onFocus(self, event):
        print "panel received focus!"
 
# Run the program
if __name__ == "__main__":
    app = wx.PySimpleApp()
    frame = MyForm().Show()
    app.MainLoop()

Just for fun, try putting a StaticText control in there instead of a TextCtrl. What do you expect will get the focus? If you guessed the StaticText control or the panel, you'd be wrong! In fact, it's the frame that receives the focus! Robin Dunn illustrated this to me by adding a timer to my code. Check it out:

import wx
 
class MyForm(wx.Frame):
 
    def __init__(self):
        wx.Frame.__init__(self, None, wx.ID_ANY, "Focus Finder")
 
        # Add a panel so it looks the correct on all platforms
        panel = wx.Panel(self, wx.ID_ANY)
        panel.Bind(wx.EVT_SET_FOCUS, self.onFocus)
        txt = wx.StaticText(panel, wx.ID_ANY, 
                   "This label cannot receive focus")

        self.timer = wx.Timer(self)
        self.Bind(wx.EVT_TIMER, self.onTimer)
        self.timer.Start(1000)
                
    def onFocus(self, event):
        print "panel received focus!"

    def onTimer(self, evt):
        print 'Focused window:', wx.Window.FindFocus()
 
# Run the program
if __name__ == "__main__":
    app = wx.PySimpleApp()
    frame = MyForm().Show()
    app.MainLoop()

You may be wondering why you'd want to know when the frame was in focus. This can be helpful if you need to know when someone has clicked on your application or brought a certain frame to the foreground. Of course, some people would rather know when the mouse enters the frame and that information can be had with EVT_ENTER_WINDOW (which I won't be covering here).

Now let's take a quick look at wx.EVT_KILL_FOCUS. I've created a simple example with just two controls. Try to guess what will happen if you tab between them.

import wx
 
class MyForm(wx.Frame):
 
    def __init__(self):
        wx.Frame.__init__(self, None, wx.ID_ANY, "Focus Tutorial 1a")
 
        # Add a panel so it looks the correct on all platforms
        panel = wx.Panel(self, wx.ID_ANY)
        
        txt = wx.TextCtrl(panel, wx.ID_ANY, "")
        txt.Bind(wx.EVT_SET_FOCUS, self.onFocus)
        txt.Bind(wx.EVT_KILL_FOCUS, self.onKillFocus)
        btn = wx.Button(panel, wx.ID_ANY, "Test")
        
        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(txt, 0, wx.ALL, 5)
        sizer.Add(btn, 0, wx.ALL, 5)
        panel.SetSizer(sizer)
        
    def onFocus(self, event):
        print "widget received focus!"
        
    def onKillFocus(self, event):
        print "widget lost focus!"
 
# Run the program
if __name__ == "__main__":
    app = wx.PySimpleApp()
    frame = MyForm().Show()
    app.MainLoop()

As you've probably surmised, as you tab between them, the TextCtrl is either firing a kill focus or a set focus event. How do you know which widget is firing those events? Look at my Bind methods. Only the text control is bound to focus events. As an exercise, try binding the button to those handlers too and print out which widget is firing what.

The last focus event I'm going to mention is wx.EVT_CHILD_FOCUS, something that I've never used. This event is used to determine when a child widget has received focus and to figure out which child it is. According to Robin Dunn, wx.lib.scrolledpanel uses this event. One of my readers told me of about a handy use-case for wx.EVT_CHILD_FOCUS: you can use it on the frame to simply clear the Statusbar when you click any other child widget. This
way you don't have an old "Error" message or sum such text in the statusbar when you click a different child widget.
(hattip @devplayer)

Also note that some of the more complex widgets have their own focus hokus pokus. See the following thread on how to get focus in a wx.grid.Grid cell: http://www.velocityreviews.com/forums/t352017-wxgrid-and-focus-event.html

I hope this tutorial has helped you better understand the way focus events work in wxPython. If you have questions or other feedback, feel free to drop me a line via the comments or ask the other wxPython developers on the mailing list.

Additional Information

Downloads

Copyright © 2024 Mouse Vs Python | Powered by Pythonlibrary