Determining button up/down times in wxPython

I recently encountered a need to be able to determine how long a user held down a button on a GUI dialog in order to command a proportional amount of movement in a mechanism. In other words, the longer the button was down, the longer the mechanism would be commanded to move.

It turns out that this is relatively easy to do with wxPython. Basically one merely needs to bind the mouse events wx.EVT_LEFT_DOWN and wx.EVT_LEFT_UP events to the button of interest. When the left mouse button is pressed the wx.EVT_LEFT_DOWN event is generated, and when it is released a wx.EVT_LEFT_UP event occurs. By binding handlers to these events one can use them to determine when a button was selected and how long it was held down by the user.

There are two ways to bind the mouse button events. The first uses the “old” style and looks like this:

wx.EVT_LEFT_DOWN(self.button1, self.OnBtn1Down)

and the second form using the “new” style with the Bind method looks like this:

self.button1.Bind(wx.EVT_LEFT_DOWN, self.OnBtn1Down)

There are also corresponding handlers for the wx.EVT_LEFT_UP event.

In the button up/down event handlers the last statement must be event.Skip(), otherwise the button event will not be passed on to the “real” button event handler.

I’ve provided two examples below. The first is frame-based and uses a textbox widget to display the timing data for each button. The second use a modal dialog box and text control widgets to display the click data.

Both versions display a running average “down-time” for button 1, along with a click total. For buttons 2 and 3 just the down-time is displayed. The clear button, as one might surmise, clears out all previous data.

Frame and Panel Version

""" wxPython Button timing example

    Uses the wx.EVT_LEFT_DOWN and wx.EVT_LEFT_UP events to determine how long
    a button has been held down by the user.

    The up/down events are bound to the buttons, and the actual button event
    is processed after the button is released. It is important to note that
    the up/down handlers MUST call event.Skip() in order to pass the events
    along to the "real" button event handlers.

    Button 1 generates a cumulative click count and an average hold-down time.
    Multiple clicking has shown that the average down time is around 70 mS
    unless the user is really quick, in which case it can drop to around 55 mS.

    Buttons 2 and 3 simply display the hold-down time.

    The Clear button clears the text display and resets the count and average
    values for button 1.

    To execute:

        python buttontime.py

    29 June 2009 - jmh
"""

import wx
import time

class ButtonPanel(wx.Panel):

    def __init__(self, parent):
        wx.Panel.__init__(self, parent, -1)

        self.btn1downtime = 0
        self.btn2downtime = 0
        self.btn3downtime = 0

        self.btn1_time = 0
        self.btn2_time = 0
        self.btn3_time = 0

        self.btn1_clks = 0
        self.btn1_tot  = 0

        self.idBtn1 = wx.NewId()
        self.idBtn2 = wx.NewId()
        self.idBtn3 = wx.NewId()
        self.idBtn4 = wx.NewId()
        self.idBtn5 = wx.NewId()

        topsizer     = wx.BoxSizer( wx.HORIZONTAL )
        menusizer    = wx.BoxSizer( wx.VERTICAL )
        contentsizer = wx.BoxSizer( wx.VERTICAL )

        self.textbox = wx.TextCtrl(self, -1, "",
            wx.DefaultPosition, wx.DefaultSize,
            wx.TE_MULTILINE)

        self.textbox.SetFont(wx.Font(14, wx.SWISS, wx.NORMAL, wx.NORMAL))

        self.button1 = wx.Button(self, self.idBtn1, "Button 1")
        self.button2 = wx.Button(self, self.idBtn2, "Button 2")
        self.button3 = wx.Button(self, self.idBtn3, "Button 3")
        self.button4 = wx.Button(self, self.idBtn4, "Quit")
        self.button5 = wx.Button(self, self.idBtn5, "Clear")

        self.button1.SetFont(wx.Font(10, wx.SWISS, wx.NORMAL, wx.NORMAL))
        self.button2.SetFont(wx.Font(10, wx.SWISS, wx.NORMAL, wx.NORMAL))
        self.button3.SetFont(wx.Font(10, wx.SWISS, wx.NORMAL, wx.NORMAL))
        self.button4.SetFont(wx.Font(10, wx.SWISS, wx.NORMAL, wx.NORMAL))
        self.button5.SetFont(wx.Font(10, wx.SWISS, wx.NORMAL, wx.NORMAL))

        wx.EVT_LEFT_DOWN(self.button1, self.OnBtn1Down)
        wx.EVT_LEFT_UP(self.button1, self.OnBtn1Up)

        wx.EVT_LEFT_DOWN(self.button2, self.OnBtn2Down)
        wx.EVT_LEFT_UP(self.button2, self.OnBtn2Up)

        wx.EVT_LEFT_DOWN(self.button3, self.OnBtn3Down)
        wx.EVT_LEFT_UP(self.button3, self.OnBtn3Up)

        wx.EVT_BUTTON(self, self.idBtn1, self.OnBtn1)
        wx.EVT_BUTTON(self, self.idBtn2, self.OnBtn2)
        wx.EVT_BUTTON(self, self.idBtn3, self.OnBtn3)
        wx.EVT_BUTTON(self, self.idBtn4, self.OnBtn4)
        wx.EVT_BUTTON(self, self.idBtn5, self.OnBtn5)

        contentsizer.Add(self.textbox, 1, wx.EXPAND)

        menusizer.Add(self.button1, 1, wx.EXPAND)
        menusizer.Add(self.button2, 1, wx.EXPAND)
        menusizer.Add(self.button3, 1, wx.EXPAND)
        menusizer.Add(self.button4, 1, wx.EXPAND)
        menusizer.Add(self.button5, 1, wx.EXPAND)

        topsizer.Add(menusizer, 0)
        topsizer.Add(contentsizer, 1, wx.EXPAND)

        self.SetSizer(topsizer)

    #---------------------------------------------------------------------------
    # Mouse button event handlers
    #---------------------------------------------------------------------------
    def OnBtn1Down(self, event):
        self.btn1_downtime = time.time()
        event.Skip()    # REQUIRED!

    def OnBtn1Up(self, event):
        self.btn1_uptime = time.time()
        self.btn1_time = self.btn1_uptime - self.btn1_downtime
        self.btn1_clks += 1
        self.btn1_tot += self.btn1_time
        event.Skip()    # REQUIRED!

    def OnBtn2Down(self, event):
        self.btn2_downtime = time.time()
        event.Skip()    # REQUIRED!

    def OnBtn2Up(self, event):
        self.btn2_uptime = time.time()
        self.btn2_time = self.btn2_uptime - self.btn2_downtime
        event.Skip()    # REQUIRED!

    def OnBtn3Down(self, event):
        self.btn3_downtime = time.time()
        event.Skip()    # REQUIRED!

    def OnBtn3Up(self, event):
        self.btn3_uptime = time.time()
        self.btn3_time = self.btn3_uptime - self.btn3_downtime
        event.Skip()    # REQUIRED!

    #---------------------------------------------------------------------------
    # Button widget event handlers
    #---------------------------------------------------------------------------
    def OnBtn1(self, event):
        self.textbox.AppendText("Btn1: %3.3f, clicks: %d, avg: %3.3f\n" % \
                                (self.btn1_time,
                                 self.btn1_clks,
                                (self.btn1_tot/self.btn1_clks)))

    def OnBtn2(self, event):
        self.textbox.AppendText("Btn2: %3.3f\n" % self.btn2_time)

    def OnBtn3(self, event):
        self.textbox.AppendText("Btn3: %3.3f\n" % self.btn3_time)

    def OnBtn4(self, event):
        self.GetParent().Close(True)

    def OnBtn5(self, event):
        self.btn1_time = 0
        self.btn2_time = 0
        self.btn3_time = 0

        self.btn1_clks = 0
        self.btn1_tot  = 0

        self.textbox.Clear()

#-------------------------------------------------------------------------------
# Launch
#-------------------------------------------------------------------------------
app     = wx.PySimpleApp(0)
frame   = wx.Frame(None, -1, "Timed Buttons")

ButtonPanel(frame)

frame.Show(True)
frame.SetSize(wx.Size(800, 600))
frame.Move(wx.Point(50,50))

app.MainLoop()

wx.Dialog Version

Note that this version is in two parts: buttondlg.py and dlgTimedButtons.py. The Boa Constructor tool was used to create the dialog skeleton, and the rest was filled in afterwards. Use the “Evts” tab in Boa’s Inspector panel to add the mouse events to a particular button.

# buttondlg.py
#
# Creates frame with menu and status bar for use with dlgTimedButtons
#
# 3 July 2009 - jmh

import wx

import dlgTimedButtons

idShowDlg = wx.NewId()
idQuit    = wx.NewId()

class DlgTimedButton(wx.Frame):
    def __init__(self, parent, id, title):
        wx.Frame.__init__(self, parent, id, title, size=(250, 150))

        menubar = wx.MenuBar()
        mainmenu = wx.Menu()
        mainmenu.Append(idShowDlg, 'Dialog', 'Timed buttons dialog')
        mainmenu.Append(idQuit, 'Quit', 'Quit application')

        self.Bind(wx.EVT_MENU, self.OnShowDlg, id=idShowDlg)
        self.Bind(wx.EVT_MENU, self.OnQuit, id=idQuit)

        menubar.Append(mainmenu, 'Main')
        self.SetMenuBar(menubar)

        self.statusbar = self.CreateStatusBar()

        self.Centre()
        self.Show(True)

    def OnShowDlg(self, event):
        dlg = dlgTimedButtons.dlgTimedButtons(self)
        dlg.ShowModal()

    def OnQuit(self, event):
        self.Close()

#-------------------------------------------------------------------------------
# Launch
#-------------------------------------------------------------------------------
app = wx.App()
DlgTimedButton(None, -1, 'Timed Button Dialog Example')
app.MainLoop()

and here is the actual dialog with the timed button goodness:

#Boa:Dialog:dlgTimedButtons
#
# Timed button modal dialog example
# 3 July 2009 - jmh
# initially created with Boa Constructor

import wx
import time

def create(parent):
    return dlgTimedButtons(parent)

[wxID_DLGTIMEDBUTTONS, wxID_DLGTIMEDBUTTONSBTNCLEAR,
 wxID_DLGTIMEDBUTTONSBTNQUIT, wxID_DLGTIMEDBUTTONSBUTTON1,
 wxID_DLGTIMEDBUTTONSBUTTON2, wxID_DLGTIMEDBUTTONSBUTTON3,
 wxID_DLGTIMEDBUTTONSSTATICTEXT1, wxID_DLGTIMEDBUTTONSSTATICTEXT2,
 wxID_DLGTIMEDBUTTONSSTATICTEXT3, wxID_DLGTIMEDBUTTONSTXTBTN1CLICKS,
 wxID_DLGTIMEDBUTTONSTXTBTNTIME1, wxID_DLGTIMEDBUTTONSTXTBTNTIME1AVG,
 wxID_DLGTIMEDBUTTONSTXTBTNTIME2, wxID_DLGTIMEDBUTTONSTXTBTNTIME3,
] = [wx.NewId() for _init_ctrls in range(14)]

class dlgTimedButtons(wx.Dialog):
    def __init__(self, parent):
        self.btn1downtime = 0
        self.btn2downtime = 0
        self.btn3downtime = 0

        self.btn1_time = 0
        self.btn2_time = 0
        self.btn3_time = 0

        self.btn1_clks = 0
        self.btn1_tot  = 0

        self._init_ctrls(parent)
        self.ClrDisplay()

    def _init_ctrls(self, prnt):
        # generated method, don't edit
        wx.Dialog.__init__(self, id=wxID_DLGTIMEDBUTTONS,
              name='dlgTimedButtons', parent=prnt, pos=wx.Point(477, 295),
              size=wx.Size(353, 201), style=wx.DEFAULT_DIALOG_STYLE,
              title='Timed Buttons')
        self.SetClientSize(wx.Size(345, 174))

        self.button1 = wx.Button(id=wxID_DLGTIMEDBUTTONSBUTTON1,
              label='Button 1', name='button1', parent=self, pos=wx.Point(16,
              24), size=wx.Size(75, 23), style=0)
        self.button1.Bind(wx.EVT_BUTTON, self.OnButton1Button,
              id=wxID_DLGTIMEDBUTTONSBUTTON1)
        self.button1.Bind(wx.EVT_LEFT_UP, self.OnButton1LeftUp)
        self.button1.Bind(wx.EVT_LEFT_DOWN, self.OnButton1LeftDown)

        self.button2 = wx.Button(id=wxID_DLGTIMEDBUTTONSBUTTON2,
              label='Button 2', name='button2', parent=self, pos=wx.Point(16,
              72), size=wx.Size(75, 23), style=0)
        self.button2.Bind(wx.EVT_BUTTON, self.OnButton2Button,
              id=wxID_DLGTIMEDBUTTONSBUTTON2)
        self.button2.Bind(wx.EVT_LEFT_UP, self.OnButton2LeftUp)
        self.button2.Bind(wx.EVT_LEFT_DOWN, self.OnButton2LeftDown)

        self.button3 = wx.Button(id=wxID_DLGTIMEDBUTTONSBUTTON3,
              label='Button 3', name='button3', parent=self, pos=wx.Point(16,
              96), size=wx.Size(75, 23), style=0)
        self.button3.Bind(wx.EVT_BUTTON, self.OnButton3Button,
              id=wxID_DLGTIMEDBUTTONSBUTTON3)
        self.button3.Bind(wx.EVT_LEFT_UP, self.OnButton3LeftUp)
        self.button3.Bind(wx.EVT_LEFT_DOWN, self.OnButton3LeftDown)

        self.btnQuit = wx.Button(id=wxID_DLGTIMEDBUTTONSBTNQUIT, label='Close',
              name='btnQuit', parent=self, pos=wx.Point(16, 144),
              size=wx.Size(75, 23), style=0)
        self.btnQuit.Bind(wx.EVT_BUTTON, self.OnBtnQuitButton,
              id=wxID_DLGTIMEDBUTTONSBTNQUIT)

        self.txtBtnTime1 = wx.TextCtrl(id=wxID_DLGTIMEDBUTTONSTXTBTNTIME1,
              name='txtBtnTime1', parent=self, pos=wx.Point(112, 24),
              size=wx.Size(100, 21), style=0, value='')
        self.txtBtnTime1.SetEditable(False)

        self.txtBtnTime2 = wx.TextCtrl(id=wxID_DLGTIMEDBUTTONSTXTBTNTIME2,
              name='txtBtnTime2', parent=self, pos=wx.Point(112, 72),
              size=wx.Size(100, 21), style=0, value='')
        self.txtBtnTime2.SetEditable(False)

        self.txtBtnTime3 = wx.TextCtrl(id=wxID_DLGTIMEDBUTTONSTXTBTNTIME3,
              name='txtBtnTime3', parent=self, pos=wx.Point(112, 96),
              size=wx.Size(100, 21), style=0, value='')
        self.txtBtnTime3.SetEditable(False)

        self.txtBtnTime1Avg = wx.TextCtrl(id=wxID_DLGTIMEDBUTTONSTXTBTNTIME1AVG,
              name='txtBtnTime1Avg', parent=self, pos=wx.Point(272, 24),
              size=wx.Size(64, 21), style=0, value='')
        self.txtBtnTime1Avg.SetEditable(False)

        self.staticText1 = wx.StaticText(id=wxID_DLGTIMEDBUTTONSSTATICTEXT1,
              label='Times', name='staticText1', parent=self, pos=wx.Point(144,
              48), size=wx.Size(40, 16), style=0)

        self.staticText2 = wx.StaticText(id=wxID_DLGTIMEDBUTTONSSTATICTEXT2,
              label='Average', name='staticText2', parent=self,
              pos=wx.Point(224, 32), size=wx.Size(41, 13), style=0)

        self.txtBtn1Clicks = wx.TextCtrl(id=wxID_DLGTIMEDBUTTONSTXTBTN1CLICKS,
              name='txtBtn1Clicks', parent=self, pos=wx.Point(272, 48),
              size=wx.Size(64, 21), style=0, value='')
        self.txtBtn1Clicks.SetEditable(False)

        self.staticText3 = wx.StaticText(id=wxID_DLGTIMEDBUTTONSSTATICTEXT3,
              label='Clicks', name='staticText3', parent=self, pos=wx.Point(240,
              56), size=wx.Size(26, 13), style=0)

        self.btnClear = wx.Button(id=wxID_DLGTIMEDBUTTONSBTNCLEAR,
              label='Clear', name='btnClear', parent=self, pos=wx.Point(160,
              144), size=wx.Size(51, 23), style=0)
        self.btnClear.Bind(wx.EVT_BUTTON, self.OnBtnClearButton,
              id=wxID_DLGTIMEDBUTTONSBTNCLEAR)

        self.txtBtnTime1.SetFont(wx.Font(8, wx.SWISS, wx.NORMAL, wx.NORMAL, False, 'Arial'))
        self.txtBtnTime2.SetFont(wx.Font(8, wx.SWISS, wx.NORMAL, wx.NORMAL, False, 'Arial'))
        self.txtBtnTime3.SetFont(wx.Font(8, wx.SWISS, wx.NORMAL, wx.NORMAL, False, 'Arial'))
        self.txtBtnTime1Avg.SetFont(wx.Font(8, wx.SWISS, wx.NORMAL, wx.NORMAL, False, 'Arial'))
        self.txtBtn1Clicks.SetFont(wx.Font(8, wx.SWISS, wx.NORMAL, wx.NORMAL, False, 'Arial'))

    def ClrDisplay(self):
        self.txtBtnTime1.SetValue("0.0")
        self.txtBtnTime2.SetValue("0.0")
        self.txtBtnTime3.SetValue("0.0")

        self.txtBtnTime1Avg.SetValue("0")
        self.txtBtn1Clicks.SetValue("0.0")

    def OnButton1LeftUp(self, event):
        self.btn1_uptime = time.time()
        self.btn1_time = self.btn1_uptime - self.btn1_downtime
        self.btn1_clks += 1
        self.btn1_tot += self.btn1_time
        event.Skip()    # REQUIRED!

    def OnButton1LeftDown(self, event):
        self.btn1_downtime = time.time()
        event.Skip()    # REQUIRED!

    def OnButton2LeftUp(self, event):
        self.btn2_uptime = time.time()
        self.btn2_time = self.btn2_uptime - self.btn2_downtime
        event.Skip()    # REQUIRED!

    def OnButton2LeftDown(self, event):
        self.btn2_downtime = time.time()
        event.Skip()    # REQUIRED!

    def OnButton3LeftUp(self, event):
        self.btn3_uptime = time.time()
        self.btn3_time = self.btn3_uptime - self.btn3_downtime
        event.Skip()    # REQUIRED!

    def OnButton3LeftDown(self, event):
        self.btn3_downtime = time.time()
        event.Skip()    # REQUIRED!

    def OnButton1Button(self, event):
        self.txtBtnTime1.SetValue("%3.3f" % self.btn1_time)
        self.txtBtnTime1Avg.SetValue("%3.3f" % (self.btn1_tot/self.btn1_clks))
        self.txtBtn1Clicks.SetValue(str(self.btn1_clks))

    def OnButton2Button(self, event):
        self.txtBtnTime2.SetValue("%3.3f" % self.btn2_time)

    def OnButton3Button(self, event):
        self.txtBtnTime3.SetValue("%3.3f" % self.btn3_time)

    def OnBtnQuitButton(self, event):
        self.Destroy()

    def OnBtnClearButton(self, event):
        self.btn1_time = 0
        self.btn2_time = 0
        self.btn3_time = 0
        self.btn1_clks = 0
        self.btn1_tot  = 0
        self.ClrDisplay()

And that’s it. It’s just that easy. Once the time interval between the down and up events has been captured it can be put to use in various interesting ways.

Advertisements

0 Responses to “Determining button up/down times in wxPython”



  1. Leave a Comment

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s




Follow Crankycode on WordPress.com

Little Buddy

An awesome little friend

Jordi the Sheltie passed away in 2008 at the ripe old age of 14. He was the most awesome dog I've ever known.


%d bloggers like this: