Ebooks

Events in wxPython

Events are integral part of every GUI application. All GUI applications are event-driven. An application reacts to different event types which are generated during its life. Events are generated mainly by the user of an application. But they can be generated by other means as well; e.g. Internet connection, window manager, or timer. So when we call MainLoop() method, our application waits for events to be generated. The MainLoop() method ends when we exit the application.

Definitions

Event is a piece of application-level information from the underlying framework, typically the GUI toolkit. Event loop is a programming construct that waits for and dispatches events or messages in a program. The event loop repeatedly looks for events to process. A dispatcher is a process which maps events to event handlers. Event handlers are methods that react to events.

Event object is an object associated with the event. It is usually a window. Event type is a unique event that has been generated. Event binder is an object that binds an event type with an event handler.

wxPython wx.EVT_MOVE example

In the following example we react to a wx.MoveEvent event. The event is generated when we move a window to a new position. The event binder for this event is wx.EVT_MOVE.

simple_event.py
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
ZetCode wxPython tutorial

This is a wx.MoveEvent event demostration.

author: Jan Bodnar
website: www.zetcode.com
last modified: April 2018
"""

import wx

class Example(wx.Frame):

    def __init__(self, *args, **kw):
        super(Example, self).__init__(*args, **kw)

        self.InitUI()


    def InitUI(self):

        wx.StaticText(self, label='x:', pos=(10,10))
        wx.StaticText(self, label='y:', pos=(10,30))

        self.st1 = wx.StaticText(self, label='', pos=(30, 10))
        self.st2 = wx.StaticText(self, label='', pos=(30, 30))

        self.Bind(wx.EVT_MOVE, self.OnMove)

        self.SetSize((350, 250))
        self.SetTitle('Move event')
        self.Centre()

    def OnMove(self, e):

        x, y = e.GetPosition()
        self.st1.SetLabel(str(x))
        self.st2.SetLabel(str(y))


def main():

    app = wx.App()
    ex = Example(None)
    ex.Show()
    app.MainLoop()


if __name__ == '__main__':
    main() 

The example displays the current position of the window.

self.Bind(wx.EVT_MOVE, self.OnMove)

Here we bind the wx.EVT_MOVE event binder to the OnMove() method.

def OnMove(self, e):
    
    x, y = e.GetPosition()
    self.st1.SetLabel(str(x))
    self.st2.SetLabel(str(y))

The event parameter in the OnMove() method is an object specific to a particular event type. In our case it is the instance of a wx.MoveEvent class. This object holds information about the event. For example the event object or the position of the window. In our case the event object is the wx.Frame widget. We can find out the current position by calling the GetPosition() method of the event.

Move event
Figure: Move event

wxPython event binding

The three steps to work with events in wxPython are:

In wxPython we say to bind a method to an event. Sometimes a word hook is used. You bind an event by calling the Bind() method. The method has the following parameters:

Bind(event, handler, source=None, id=wx.ID_ANY, id2=wx.ID_ANY)

The event is one of EVT_* objects. It specifies the type of the event. The handler is an object to be called. In other words, it is a method that a programmer binds to an event. The source parameter is used when we want to differentiate between the same event type from different widgets. The id parameter is used when we have multiple buttons, menu items etc. The id is used to differentiate among them. The id2 is used when it is desirable to bind a handler to a range of ids, such as with EVT_MENU_RANGE.

Note that method Bind() is defined in class EvtHandler. It is a class from which wx.Window inherits. wx.Window is a base class for most widgets in wxPython. There is also a reverse process. If we want to unbind a method from an event, we call the Unbind() method. It has the same parameters as the above one.

Vetoing events

Sometimes we need to stop processing an event. To do this, we call the method Veto().

event_veto.py
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import wx

"""
ZetCode wxPython tutorial

In this example we veto an event.

author: Jan Bodnar
website: www.zetcode.com
last modified: April 2018
"""

class Example(wx.Frame):

    def __init__(self, *args, **kw):
        super(Example, self).__init__(*args, **kw)

        self.InitUI()

    def InitUI(self):

        self.Bind(wx.EVT_CLOSE, self.OnCloseWindow)

        self.SetTitle('Event veto')
        self.Centre()

    def OnCloseWindow(self, e):

        dial = wx.MessageDialog(None, 'Are you sure to quit?', 'Question',
            wx.YES_NO | wx.NO_DEFAULT | wx.ICON_QUESTION)

        ret = dial.ShowModal()

        if ret == wx.ID_YES:
            self.Destroy()
        else:
            e.Veto()


def main():

    app = wx.App()
    ex = Example(None)
    ex.Show()
    app.MainLoop()


if __name__ == '__main__':
    main()

In our example, we process a wx.CloseEvent. This event is called, when we click the X button on the titlebar, press Alt+F4 or select close from the system menu. In many applications, we want to prevent from accidentally closing the window if we made some changes. To do this, we must bind the wx.EVT_CLOSE event binder.

dial = wx.MessageDialog(None, 'Are you sure to quit?', 'Question',
    wx.YES_NO | wx.NO_DEFAULT | wx.ICON_QUESTION)
    
ret = dial.ShowModal()

During the processing of the close event we show a message dialog.

if ret == wx.ID_YES:
    self.Destroy()
else:
    event.Veto()

Depending on the return value from the dialog, we destroy the window, or veto the event. Notice that to close the window, we must call the Destroy() method. By calling the Close() method, we would end up in an endless cycle.

wxPython event propagation

There are two types of events: basic events and command events. They differ in propagation. Event propagation is travelling of events from child widgets to parent widgets and grand parent widgets. Basic events do not propagate. Command events do propagate. For example wx.CloseEvent is a basic event. It does not make sense for this event to propagate to parent widgets.

By default, the event that is caught in a event handler stops propagating. To continue propagation, we call the Skip() method.

event_propagation.py
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
ZetCode wxPython tutorial

This example demonstrates event propagation.

author: Jan Bodnar
website: www.zetcode.com
last modified: April 2018
"""

import wx

class MyPanel(wx.Panel):

    def __init__(self, *args, **kw):
        super(MyPanel, self).__init__(*args, **kw)

        self.Bind(wx.EVT_BUTTON, self.OnButtonClicked)

    def OnButtonClicked(self, e):

        print('event reached panel class')
        e.Skip()


class MyButton(wx.Button):

    def __init__(self, *args, **kw):
        super(MyButton, self).__init__(*args, **kw)

        self.Bind(wx.EVT_BUTTON, self.OnButtonClicked)

    def OnButtonClicked(self, e):

        print('event reached button class')
        e.Skip()


class Example(wx.Frame):

    def __init__(self, *args, **kw):
        super(Example, self).__init__(*args, **kw)

        self.InitUI()


    def InitUI(self):

        mpnl = MyPanel(self)

        MyButton(mpnl, label='Ok', pos=(15, 15))

        self.Bind(wx.EVT_BUTTON, self.OnButtonClicked)

        self.SetTitle('Propagate event')
        self.Centre()

    def OnButtonClicked(self, e):

        print('event reached frame class')
        e.Skip()


def main():

    app = wx.App()
    ex = Example(None)
    ex.Show()
    app.MainLoop()


if __name__ == '__main__':
    main()

In our example, we have a button on a panel. The panel is placed in a frame widget. We define a handler for all widgets.

def OnButtonClicked(self, e):

    print('event reached button class')
    e.Skip()

We process the button click event in our custom button class. The Skip() method propagates the event further to to panel class.

Try to omit some Skip() methods and see what hapens.

Window identifiers

Window identifiers are integers that uniquely determine the window identity in the event system. There are three ways to create window ids.

wx.Button(parent, -1)
wx.Button(parent, wx.ID_ANY)

If we provide -1 or wx.ID_ANY for the id parameter, we let the wxPython automatically create an id for us. The automatically created id's are always negative, whereas user specified ids must always be positive. We usually use this option when we do not need to change the widget state. For example a static text that will never be changed during the life of the application. We can still get the id if we want. There is a method GetId() which determines the id.

default_ids.py
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
ZetCode wxPython tutorial

In this example we use automatic ids
with wx.ID_ANY.

author: Jan Bodnar
website: www.zetcode.com
last modified: April 2018
"""

import wx


class Example(wx.Frame):

    def __init__(self, *args, **kw):
        super(Example, self).__init__(*args, **kw)

        self.InitUI()

    def InitUI(self):

        pnl = wx.Panel(self)
        exitButton = wx.Button(pnl, wx.ID_ANY, 'Exit', (10, 10))

        self.Bind(wx.EVT_BUTTON,  self.OnExit, id=exitButton.GetId())

        self.SetTitle("Automatic ids")
        self.Centre()

    def OnExit(self, event):

        self.Close()


def main():

    app = wx.App()
    ex = Example(None)
    ex.Show()
    app.MainLoop()


if __name__ == '__main__':
    main()

In this example, we do not care about the actual id value.

self.Bind(wx.EVT_BUTTON,  self.OnExit, id=exitButton.GetId())

We get the automatically generated id by calling the GetId() method.

It is recommended to use standard identifiers. The identifiers can provide some standard graphics or behaviour on some platforms.

wxPython standard ids

wxPython contains some standard ids such as wx.ID_SAVE or wx.ID_NEW.

standard_ids.py
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
ZetCode wxPython tutorial

In this example we create buttons with standard ids.

author: Jan Bodnar
website: www.zetcode.com
last modified: April 2018
"""

import wx

class Example(wx.Frame):

    def __init__(self, *args, **kw):
        super(Example, self).__init__(*args, **kw)

        self.InitUI()

    def InitUI(self):

        pnl = wx.Panel(self)
        grid = wx.GridSizer(3, 2)

        grid.AddMany([(wx.Button(pnl, wx.ID_CANCEL), 0, wx.TOP | wx.LEFT, 9),
            (wx.Button(pnl, wx.ID_DELETE), 0, wx.TOP, 9),
            (wx.Button(pnl, wx.ID_SAVE), 0, wx.LEFT, 9),
            (wx.Button(pnl, wx.ID_EXIT)),
            (wx.Button(pnl, wx.ID_STOP), 0, wx.LEFT, 9),
            (wx.Button(pnl, wx.ID_NEW))])

        self.Bind(wx.EVT_BUTTON, self.OnQuitApp, id=wx.ID_EXIT)

        pnl.SetSizer(grid)

        self.SetTitle("Standard ids")
        self.Centre()

    def OnQuitApp(self, event):

        self.Close()


def main():

    app = wx.App()
    ex = Example(None)
    ex.Show()
    app.MainLoop()


if __name__ == '__main__':
    main()

In our example we use standard identifiers on buttons. On Linux, the buttons have icons.

grid.AddMany([(wx.Button(pnl, wx.ID_CANCEL), 0, wx.TOP | wx.LEFT, 9),
    (wx.Button(pnl, wx.ID_DELETE), 0, wx.TOP, 9),
    (wx.Button(pnl, wx.ID_SAVE), 0, wx.LEFT, 9),
    (wx.Button(pnl, wx.ID_EXIT)),
    (wx.Button(pnl, wx.ID_STOP), 0, wx.LEFT, 9),
    (wx.Button(pnl, wx.ID_NEW))])

We add six buttons to a grid sizer. The wx.ID_CANCEL, wx.ID_DELETE, wx.ID_SAVE, wx.ID_EXIT, wx.ID_STOP, and wx.ID_NEW are standard identifiers.

self.Bind(wx.EVT_BUTTON, self.OnQuitApp, id=wx.ID_EXIT)

We bind the button click event to the OnQuitApp() event handler. The id parameter is used to differantiate among the buttons. We uniquely identify the source of the event.

Standard identifiers
Figure: Standard identifiers

Custom event ids

The last option is to use own identifiers. We define our own global ids.

custom_ids.py
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
ZetCode wxPython tutorial

In this example we use custom event ids.

author: Jan Bodnar
website: www.zetcode.com
last modified: April 2018
"""

import wx

ID_MENU_NEW = wx.NewId()
ID_MENU_OPEN = wx.NewId()
ID_MENU_SAVE = wx.NewId()


class Example(wx.Frame):

    def __init__(self, *args, **kw):
        super(Example, self).__init__(*args, **kw)

        self.InitUI()

    def InitUI(self):

        self.CreateMenuBar()
        self.CreateStatusBar()

        self.SetSize((350, 250))
        self.SetTitle('Custom ids')
        self.Centre()

    def CreateMenuBar(self):

        mb = wx.MenuBar()

        fMenu = wx.Menu()
        fMenu.Append(ID_MENU_NEW, 'New')
        fMenu.Append(ID_MENU_OPEN, 'Open')
        fMenu.Append(ID_MENU_SAVE, 'Save')

        mb.Append(fMenu, '&File')
        self.SetMenuBar(mb)

        self.Bind(wx.EVT_MENU, self.DisplayMessage, id=ID_MENU_NEW)
        self.Bind(wx.EVT_MENU, self.DisplayMessage, id=ID_MENU_OPEN)
        self.Bind(wx.EVT_MENU, self.DisplayMessage, id=ID_MENU_SAVE)

    def DisplayMessage(self, e):

        sb = self.GetStatusBar()

        eid = e.GetId()

        if eid == ID_MENU_NEW:
            msg = 'New menu item selected'
        elif eid == ID_MENU_OPEN:
            msg = 'Open menu item selected'
        elif eid == ID_MENU_SAVE:
            msg = 'Save menu item selected'

        sb.SetStatusText(msg)


def main():

    app = wx.App()
    ex = Example(None)
    ex.Show()
    app.MainLoop()


if __name__ == '__main__':
    main() 

In the code example, we create a menu with three menu items. The ids for this menu items are created globally.

ID_MENU_NEW = wx.NewId()
ID_MENU_OPEN = wx.NewId()
ID_MENU_SAVE = wx.NewId()

The wx.NewId() method creates a new unique id.

self.Bind(wx.EVT_MENU, self.DisplayMessage, id=ID_MENU_NEW)
self.Bind(wx.EVT_MENU, self.DisplayMessage, id=ID_MENU_OPEN)
self.Bind(wx.EVT_MENU, self.DisplayMessage, id=ID_MENU_SAVE) 

All three menu items are identified by their unique id.

eid = e.GetId()

if eid == ID_MENU_NEW:
    msg = 'New menu item selected'
elif eid == ID_MENU_OPEN:
    msg = 'Open menu item selected'
elif eid == ID_MENU_SAVE:
    msg = 'Save menu item selected'

From the event object we retrieve the id. Depending on the id value, we prepare the message, which is displayed in the statusbar of the application.

wx.PaintEvent

A paint event is generated when a window is redrawn. This happens when we resize a window or when we maximize it. A paint event can be generated programatically as well. For example, when we call SetLabel() method to change a wx.StaticText widget. Note that when we minimize a window, no paint event is generated.

paint_event.py
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
ZetCode wxPython tutorial

In this example we count paint events.

author: Jan Bodnar
website: www.zetcode.com
last modified: April 2018
"""

import wx


class Example(wx.Frame):

    def __init__(self, *args, **kw):
        super(Example, self).__init__(*args, **kw)

        self.InitUI()

    def InitUI(self):

        self.count = 0
        self.Bind(wx.EVT_PAINT, self.OnPaint)

        self.SetTitle('Paint events')
        self.SetSize((350, 250))
        self.Centre()

    def OnPaint(self, e):

        self.count += 1
        dc = wx.PaintDC(self)
        text = "Number of paint events: {0}".format(self.count)
        dc.DrawText(text, 20, 20)


def main():

    app = wx.App()
    ex  = Example(None)
    ex.Show()
    app.MainLoop()


if __name__ == '__main__':
    main()

In our example we count the number of paint events and draw the current number of generated events on the window.

self.Bind(wx.EVT_PAINT, self.OnPaint)

We bind the EVT_PAINT event to the OnPaint() method.

def OnPaint(self, e):

    self.count += 1
    dc = wx.PaintDC(self)
    text = "Number of paint events: {0}".format(self.count)
    dc.DrawText(text, 20, 20)

Inside the OnPaint() event, we increase the counter draw the number of paint events on the window with DrawText() method.

wx.FocusEvent

A focus indicates the currently selected widget in application. The text entered from the keyboard or pasted from the clipboard is sent to the widget, which has the focus. There are two event types concerning focus. The wx.EVT_SET_FOCUS event, which is generated when a widget receives focus. The wx.EVT_KILL_FOCUS is generated, when the widget looses focus. The focus is changed by clicking or by a keybord key, esually Tab or Shift+Tab.

focus_event.py
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
ZetCode wxPython tutorial

In this example we work with wx.FocusEvent.

author: Jan Bodnar
website: www.zetcode.com
last modified: April 2018
"""

import wx

class MyWindow(wx.Panel):

    def __init__(self, parent):
        super(MyWindow, self).__init__(parent)

        self.color = '#b3b3b3'

        self.Bind(wx.EVT_PAINT, self.OnPaint)
        self.Bind(wx.EVT_SIZE, self.OnSize)
        self.Bind(wx.EVT_SET_FOCUS, self.OnSetFocus)
        self.Bind(wx.EVT_KILL_FOCUS, self.OnKillFocus)

    def OnPaint(self, e):

        dc = wx.PaintDC(self)

        dc.SetPen(wx.Pen(self.color))
        x, y = self.GetSize()
        dc.DrawRectangle(0, 0, x, y)

    def OnSize(self, e):

        self.Refresh()

    def OnSetFocus(self, e):

        self.color = '#ff0000'
        self.Refresh()

    def OnKillFocus(self, e):

        self.color = '#b3b3b3'
        self.Refresh()


class Example(wx.Frame):

    def __init__(self, *args, **kw):
        super(Example, self).__init__(*args, **kw)

        self.InitUI()


    def InitUI(self):

        grid = wx.GridSizer(2, 2, 10, 10)
        grid.AddMany([(MyWindow(self), 0, wx.EXPAND|wx.TOP|wx.LEFT, 9),
            (MyWindow(self), 0, wx.EXPAND|wx.TOP|wx.RIGHT, 9),
            (MyWindow(self), 0, wx.EXPAND|wx.BOTTOM|wx.LEFT, 9),
            (MyWindow(self), 0, wx.EXPAND|wx.BOTTOM|wx.RIGHT, 9)])


        self.SetSizer(grid)

        self.SetSize((350, 250))
        self.SetTitle('Focus event')
        self.Centre()


    def OnMove(self, e):

        print(e.GetEventObject())
        x, y = e.GetPosition()
        self.st1.SetLabel(str(x))
        self.st2.SetLabel(str(y))


def main():

    app = wx.App()
    ex = Example(None)
    ex.Show()
    app.MainLoop()


if __name__ == '__main__':
    main()

In our example, we have four panels. The panel with focus is highlighted.

self.Bind(wx.EVT_SET_FOCUS, self.OnSetFocus)
self.Bind(wx.EVT_KILL_FOCUS, self.OnKillFocus)

We bind two focus events to event handlers.

def OnPaint(self, e):
    
    dc = wx.PaintDC(self)

    dc.SetPen(wx.Pen(self.color))
    x, y = self.GetSize()
    dc.DrawRectangle(0, 0, x, y)

In the OnPaint() method, we draw on the windows. The colour of the outline depends on whether the window has focus or not. The outline of the focused window is drawn in red colour.

def OnSetFocus(self, e):

    self.color = '#ff0000'
    self.Refresh()

In the OnSetFocus() method, we set the self.color variable to red colour. We refresh the frame window which will generate a paint event for all its child widgets. The windows are redrawn and the one with the focus has a new colour for its outline.

def OnKillFocus(self, e):

    self.color = '#b3b3b3'
    self.Refresh()

The OnKillFocus() method is called when the window looses focus. We change the colour value and refresh.

Focus event
Figure: Focus event

wx.KeyEvent

When we press a key on our keyboard, a wx.KeyEvent is generated. This event is sent to the widget that has currently focus. There are three different key handlers:

A common request is to close application when the Esc key is pressed.

key_event.py
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
ZetCode wxPython tutorial

In this example we work with wx.KeyEvent.

author: Jan Bodnar
website: www.zetcode.com
last modified: April 2018
"""

import wx

class Example(wx.Frame):

    def __init__(self, *args, **kw):
        super(Example, self).__init__(*args, **kw)

        self.InitUI()

    def InitUI(self):

        pnl = wx.Panel(self)
        pnl.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
        pnl.SetFocus()

        self.SetSize((350, 250))
        self.SetTitle('Key event')
        self.Centre()

    def OnKeyDown(self, e):

        key = e.GetKeyCode()

        if key == wx.WXK_ESCAPE:

            ret  = wx.MessageBox('Are you sure to quit?', 'Question',
                wx.YES_NO | wx.NO_DEFAULT, self)

            if ret == wx.YES:
                self.Close()


def main():

    app = wx.App()
    ex = Example(None)
    ex.Show()
    app.MainLoop()


if __name__ == '__main__':
    main()

In this example, we process the Esc key press. A message box is shown to confirm the termination of the application.

pnl.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)

We bind an event handler to the wx.EVT_KEY_DOWN event.

key = e.GetKeyCode()

Here we get the key code of the pressed key.

if key == wx.WXK_ESCAPE:

We check the key code. The Esc key has wx.WXK_ESCAPE code.

In this chapter, we talked about events in wxPython.