Processing key events from a frame - a solution

In a previous post, somebody (zoro) was asking: "I'd like to make
a frame to process all key event before widgets in it get the event."

Robin replied with: "Key events are only sent to the widget that
has the focus. One way to steal them is to use an accelerator table,
but that doesn't sound like what you want to do. ..."

I do not know what zoro exactly wanted to do. The goal of this
post is to show, that it is possible to process key events, - "the
pressed keys" - from a frame, and this independently of the widget
that has the focus. In other words, I create "frame shortcuts",
understand key shortcuts at the frame level and not at the widget
level, therefore the focus independency.

Basic idea:
1) Create an instance of a wx.menu, menuA, and menuA entries with
their own ids, menu items are not necessary. Bind these menus to
the appropriate methods.
2) Create a wx.AcceleratorTable and bind every entry of this table
to the menuA ids defined in 1). This accelerator table is defined in
the main frame.
3) Do not show the menuA in the menubar. The purpose of this menu
is just to have some defined ids used by the accelerator table.

Demo application (code below)
The demo application is a frame, containing (via a panel) various
widgets. The frame has an invisible menu and an accelerator table.
Every accelerator table entry can be used independently of the widget
focus.

Comments:
- It is still possible to traverse the frame/window with the tab key.
- Be sure, your shortcuts do not interfere with the own widget
shortcuts.
- It seems, frame shortcuts have higher precedence over the "real" menu
shortcuts.
- The frame may have or not a "real" menu.
- If your frame/window does not use any text entry controls (TextCtrl,
STC), it is possible to use shortcuts as simple as "a", "b" or "+".
(I'm thinking about applications, like application desplaying graphics
or pdf documents. Note, in that case, a frame.OnKeyDown() can be used).
- Via the menu method, you can "bind" shortcuts to widgets like
ToggleButtons or OptionBoxes (see the demo)

Comments and improvments are wellcome.

Hope that may help.

Jean-Michel Fauth, Switzerland

···

#-------------------------------------------------------------------
#-*- coding: iso-8859-1 -*-
#-------------------------------------------------------------------
# KeyPreview.py
# win98, Py233, wxPy2515
# Jean-Michel Fauth, Switzerland
# 18 April 2004
#-------------------------------------------------------------------

import wx
from time import strftime

#-------------------------------------------------------------------

def jmtime():
    return strftime('[%X]')

#-------------------------------------------------------------------

class MyPanel(wx.Panel):
    
    def __init__(self, parent, id):
        wx.Panel.__init__(self, parent, id, wx.DefaultPosition, wx.DefaultSize)

        self.but1 = wx.Button(self, 1001, 'but1 F1', (10, 10), wx.DefaultSize)
        self.but1.Bind(wx.EVT_BUTTON, self.OnClick1)
        
        self.but2 = wx.Button(self, 1002, 'but2 F2', (10, 50), wx.DefaultSize)
        self.but2.Bind(wx.EVT_BUTTON, self.OnClick2)
        
        self.txt1 = wx.TextCtrl(self, 2001, 'text control1', (10, 100), (200, 30), wx.ST_NO_AUTORESIZE)
        
        self.txt2 = wx.TextCtrl(self, 2002, 'text control2', (310, 100), (200, 30), wx.ST_NO_AUTORESIZE)

        self.chbox1 = wx.CheckBox(self, 3001, 'chbox1 F3', (10, 150), wx.DefaultSize)
        self.chbox1.Bind(wx.EVT_CHECKBOX, self.EvtCheckBox1)

        self.chbox2 = wx.CheckBox(self, 3002, 'chbox2 F4 or Ctrl+Backspace', (310, 150), wx.DefaultSize)
        self.chbox2.Bind(wx.EVT_CHECKBOX, self.EvtCheckBox2)

        self.togbut1 = wx.ToggleButton(self, 4001, 'togbut1 F6', (10, 200), wx.DefaultSize)
        self.togbut1.Bind(wx.EVT_TOGGLEBUTTON, self.OnToggle)

        s = 'Try to press F5 , Alt+a, Ctrl+Alt+z or Shift+Backspace'
        statxt1 = wx.StaticText(self, -1, s, (10, 250), wx.DefaultSize)
        statxt1.SetBackgroundColour(wx.CYAN)

    def EvtCheckBox1(self, event):
        print jmtime(), 'OnEvtCheckBox1'

    def EvtCheckBox2(self, event):
        print jmtime(), 'OnEvtCheckBox2'

    def OnClick1(self, event):
        print jmtime(), 'OnClick1'

    def OnClick2(self, event):
        print jmtime(), 'OnClick2'

    def OnToggle(self, event):
        print jmtime(), 'OnToggle'
        print self.togbut1.GetValue()

#-------------------------------------------------------------------

class MyFrame(wx.Frame):

    def __init__(self, parent, id):
        sty = wx.DEFAULT_FRAME_STYLE
        s = __file__
        wx.Frame.__init__(self, parent, id, s, (10, 10), (600, 350), style=sty)

        menu100 = wx.Menu()
        menu100.Append(101, 'aaa\tCtrl+a')
        menu100.Append(102, 'bbb\tCtrl+b')
        menu100.Append(103, 'Quit')
        menuBar = wx.MenuBar()
        menuBar.Append(menu100, '&Menu1')
        self.SetMenuBar(menuBar)
        self.Bind(wx.EVT_MENU, self.OnMenu101, id=101)
        self.Bind(wx.EVT_MENU, self.OnMenu102, id=102)
        self.Bind(wx.EVT_MENU, self.OnCloseWindow, id=103)

        InvisibleMenu = wx.Menu()
        InvisibleMenu.Append(901, 'JobF1') # or (901, '')
        InvisibleMenu.Append(902, 'JobF2')
        InvisibleMenu.Append(903, 'JobF3')
        InvisibleMenu.Append(904, 'JobF4')
        InvisibleMenu.Append(905, 'JobF5')
        InvisibleMenu.Append(906, 'JobF6')
        self.Bind(wx.EVT_MENU, self.OnJobF1, id=901)
        self.Bind(wx.EVT_MENU, self.OnJobF2, id=902)
        self.Bind(wx.EVT_MENU, self.OnJobF3, id=903)
        self.Bind(wx.EVT_MENU, self.OnJobF4, id=904)
        self.Bind(wx.EVT_MENU, self.OnJobF5, id=905)
        self.Bind(wx.EVT_MENU, self.OnJobF6, id=906)

        self.panel = MyPanel(self, -1)

        #accelerator table
        x = [(wx.ACCEL_NORMAL, wx.WXK_F1, 901),
            (wx.ACCEL_NORMAL, wx.WXK_F2, 902),
            (wx.ACCEL_NORMAL, wx.WXK_F3, 903),
            (wx.ACCEL_NORMAL, wx.WXK_F4, 904),
            (wx.ACCEL_CTRL, wx.WXK_BACK, 904),
            (wx.ACCEL_NORMAL, wx.WXK_F5, 905),
            (wx.ACCEL_ALT, ord('A'), 905),
            (wx.ACCEL_CTRL | wx.ACCEL_ALT, ord('Z'), 905),
            (wx.ACCEL_SHIFT, wx.WXK_BACK, 905),
            (wx.ACCEL_NORMAL, wx.WXK_F6, 906),
            (wx.ACCEL_CTRL, ord('B'), 905), #same as menu shortcut
            ]
        atable = wx.AcceleratorTable(x)
        self.SetAcceleratorTable(atable)

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

    def OnJobF1(self, event):
        print jmtime(), 'F1 has been pressed'
        self.panel.OnClick1(None)

    def OnJobF2(self, event):
        print jmtime(), 'F2 has been pressed'

    def OnJobF3(self, event):
        print jmtime(), 'F3 has been pressed'
        if self.panel.chbox1.GetValue():
            self.panel.chbox1.SetValue(False)
        else:
            self.panel.chbox1.SetValue(True)

    def OnJobF4(self, event):
        print jmtime(), 'F4 has been pressed'
        if self.panel.chbox2.GetValue():
            self.panel.chbox2.SetValue(False)
        else:
            self.panel.chbox2.SetValue(True)

    def OnJobF5(self, event):
        print jmtime(), 'F5, Alt+a, Ctrl+Alt+z or Ctrl+Backspace has been pressed'
        s = 'You pressed F5, Alt+a, Ctrl+Alt+z or Ctrl+Backspace'
        wx.MessageBox(s, 'Info', style=wx.OK)

    def OnJobF6(self, event):
        print jmtime(), 'F6 has been pressed'
        if self.panel.togbut1.GetValue():
            self.panel.togbut1.SetValue(False)
        else:
            self.panel.togbut1.SetValue(True)

    def OnMenu101(self, event):
        print jmtime(), 'Menu101'

    def OnMenu102(self, event):
        print jmtime(), 'Menu102'

    def OnMenu103(self, event):
        print jmtime(), 'Menu103'

    def OnCloseWindow(self, event):
        print 'OnCloseWindow'
        self.Destroy()

#-------------------------------------------------------------------

class MyApp(wx.App):
    
    def OnInit(self):
        frame = MyFrame(None, -1)
        frame.Show(True)
        self.SetTopWindow(frame)
        return True

#-------------------------------------------------------------------

def main():
    print 'main is running...'
    app = MyApp(0)
    app.MainLoop()

#-------------------------------------------------------------------

if __name__ == '__main__' :
    main()

#eof-------------------------------------------------------------------

Jean-Michel Fauth wrote:

Basic idea:
1) Create an instance of a wx.menu, menuA, and menuA entries with
their own ids, menu items are not necessary. Bind these menus to the appropriate methods.
2) Create a wx.AcceleratorTable and bind every entry of this table
to the menuA ids defined in 1). This accelerator table is defined in
the main frame.
3) Do not show the menuA in the menubar. The purpose of this menu
is just to have some defined ids used by the accelerator table.

You can make accelerators without using a menu. Just create a wxAcceleratorTable and then set it in the frame. There is some code in demo/Main.py that shows this.

···

--
Robin Dunn
Software Craftsman
http://wxPython.org Java give you jitters? Relax with wxPython!