-
Notifications
You must be signed in to change notification settings - Fork 8
HowTo : Catching key events globally
The regular keyboard events go to the component (window) that currently has focus and do not propagate to the parent. This makes trying to catch key events globally a little tricky. There are several ways to solve this problem (keeping in mind there are probably more than presented here).
Before getting started, some notes about cases where you may be catching the wrong event or where you may not need global key catching at all:
- Many components will only receive key events if they have the Wx::WANTS_CHARS style flag enabled. If they do you need to catch Wx::EVT_CHAR (see Wx::EvtHandler#evt_char) rather than or in addition to Wx::EVT_KEY_DOWN (see Wx::EvtHandler#evt_key_down).
- For catching
Enter
-key presses on text controls, use style flag Wx::TE_PROCESS_ENTER, and catch event Wx::EVT_TEXT_ENTER (see Wx::EvtHandler#evt_text_enter).
Catching Wx::EVT_CHAR_HOOK (see Wx::EvtHandler#evt_char_hook) may be useful in certain situations. Note the use of flag Wx::WANTS_CHARS
Example for catching all key events within a frame:
require 'wx'
class MyFrame < Wx::Frame
def initialize(title, pos, size)
super(nil, pos: pos, size: size)
main = Wx::Panel.new(self, Wx::ID_ANY, style: Wx::WANTS_CHARS)
evt_char_hook :on_key_down
end
def on_key_down(evt)
Wx.message_box("KeyDown: ##{evt.key_code}\n")
evt.skip
end
end
Wx::App.run do
frame = MyFrame.new('Hello World', [50,50], [450,340])
frame.show
end
Override Wx::App#filter_event. This function is called early in event-processing, so you can do things like this (for the F1
-key):
require 'wx'
class MyFrame < Wx::Frame
def initialize(title, pos, size)
super(nil, pos: pos, size: size)
main = Wx::Panel.new(self, Wx::ID_ANY)
end
def on_help_f1(evt)
Wx.message_box("F1 pressed: ##{evt.key_code}\n")
end
end
class MyApp < Wx::App
def on_init
@frame = MyFrame.new('Hello World', [50,50], [450,340])
@frame.show
end
def filter_event(evt)
if evt.event_type == Wx::EVT_KEY_DOWN && evt.key_code == Wx::K_F1
@frame.on_help_f1(evt)
return 1
end
super
end
end
MyApp.run
Instead of overriding Wx::App#filter_event you can also install custom event filters using Wx::EvtHandler.add_filter.
require 'wx'
class MyFrame < Wx::Frame
def initialize(title, pos, size)
super(nil, pos: pos, size: size)
main = Wx::Panel.new(self, Wx::ID_ANY)
end
def on_help_f1(evt)
Wx.message_box("F1 pressed: ##{evt.key_code}\n")
end
end
class MyEventFilter < Wx::EventFilter
def initialize(frame)
super()
@frame = frame
end
def filter_event(evt)
if evt.event_type == Wx::EVT_KEY_DOWN && evt.key_code == Wx::K_F1
@frame.on_help_f1(evt)
return Wx::EventFilter::Event_Processed
end
Wx::EventFilter::Event_Skip
end
end
Wx::App.run do
frame = MyFrame.new('Hello World', [50,50], [450,340])
Wx::EvtHandler.add_filter(MyEventFilter.new(frame))
frame.show
end
This will work most of the time. It will fail if
- your application is not in the foreground. If you wish to catch keys even when your app is in the background, you will need to use platform-specific code;
- your window-manager grabs the key for itself, in which case your app won't see it;
- the key is pressed while a modal Wx::Dialog is showing.
You can recursively connect all components in a frame, therefore you will catch events no matter where the focus is.
require 'wx'
class MyDialog < Wx::Dialog
def initialize
super(nil, Wx::ID_ANY, 'My Dialog')
self.sizer = Wx::VBoxSizer.new { |vszr|
vszr.add Wx::HBoxSizer.new { |hszr|
hszr.add(Wx::StaticText.new(self, label: 'Some text:'), Wx::SizerFlags.new.border)
hszr.add(Wx::TextCtrl.new(self), Wx::SizerFlags.new.border)
}, Wx::SizerFlags.new
vszr.add Wx::HBoxSizer.new { |hszr|
hszr.add(Wx::Button.new(self, Wx::ID_OK, "&Ok"), Wx::SizerFlags.new.border)
hszr.add(Wx::Button.new(self, Wx::ID_CANCEL, "&Cancel"), Wx::SizerFlags.new.border)
}, Wx::SizerFlags.new
}
set_auto_layout(true)
self.sizer.set_size_hints(self)
self.sizer.fit(self)
connect_key_down(self, self.method(:on_key_down))
end
def connect_key_down(win, evh)
win.evt_key_down(evh)
win.each_child { |child| connect_key_down(child, evh) }
end
def on_key_down(evt)
if evt.key_code == Wx::K_F1
Wx.message_box("KeyDown: ##{evt.key_code}\n")
else
evt.skip
end
end
end
Wx::App.run do
dlg = MyDialog.new
dlg.show_modal
dlg.destroy
false
end
In this example (inspired by code form the wxWidgets wiki) a custom event handler is used to force certain key events to propagate up.
require 'wx'
class EventPropagator < Wx::EvtHandler
def initialize
super
evt_key_down :on_key_down
evt_key_up :on_key_up
end
def self.register_for(win)
win.each_child { |child| child.push_event_handler(self.new) }
end
def on_key_down(evt)
if evt.key_code == Wx::K_F1
evt.resume_propagation(1)
end
evt.skip
end
def on_key_up(evt)
if evt.key_code == Wx::K_F1
evt.resume_propagation(1)
end
evt.skip
end
end
class MyDialog < Wx::Dialog
def initialize
super(nil, Wx::ID_ANY, 'My Dialog')
self.sizer = Wx::VBoxSizer.new { |vszr|
vszr.add Wx::HBoxSizer.new { |hszr|
hszr.add(Wx::StaticText.new(self, label: 'Some text:'), Wx::SizerFlags.new.border)
hszr.add(Wx::TextCtrl.new(self), Wx::SizerFlags.new.border)
}, Wx::SizerFlags.new
vszr.add Wx::HBoxSizer.new { |hszr|
hszr.add(Wx::Button.new(self, Wx::ID_OK, "&Ok"), Wx::SizerFlags.new.border)
hszr.add(Wx::Button.new(self, Wx::ID_CANCEL, "&Cancel"), Wx::SizerFlags.new.border)
}, Wx::SizerFlags.new
}
set_auto_layout(true)
self.sizer.set_size_hints(self)
self.sizer.fit(self)
EventPropagator.register_for(self)
evt_key_down :on_key_down
end
def on_key_down(evt)
if evt.key_code == Wx::K_F1
Wx.message_box("KeyDown: ##{evt.key_code}\n")
end
end
end
Wx::App.run do
dlg = MyDialog.new
dlg.show_modal
dlg.destroy
false
end
In this example an event handler is connected for the Wx::EVT_HOTKEY event. Currently this only works for WXMSW and WXOSX.
require 'wx'
class MyFrame < Wx::Frame
def initialize(title, pos, size)
super(nil, pos: pos, size: size)
Wx::Panel.new(self, Wx::ID_ANY)
end
end
class MyApp < Wx::App
def on_init
if Wx.has_feature?(:USE_HOTKEY)
evt_hotkey Wx::K_F1, :on_help_f1
@frame = MyFrame.new('Hello World', [50,50], [450,340])
@frame.show
else
Wx.message_box('System-wide hotkeys are not supported.')
false
end
end
def on_help_f1(evt)
Wx.message_box("F1 pressed: ##{evt.key_code}\n")
end
end
MyApp.run
You can also make a hotkey part of a menu item.
require 'wx'
class MyWindow < Wx::Frame
def initialize(title)
super(nil, title: title)
@panel = Wx::Panel.new(self)
#----------------
menuBar = Wx::MenuBar.new
file_menu = Wx::Menu.new
mi = file_menu.append(Wx::ID_NEW, "&New\tCTRL+N", 'Create New File')
evt_menu(mi, :new_file)
mi = file_menu.append(Wx::ID_OPEN, "&Open\tCTRL+O", 'Open File')
evt_menu(mi, :open_file)
mi = file_menu.append(Wx::ID_SAVE, "&Save\tCTRL+S", 'Save File')
evt_menu(mi, :save_file)
menuBar.append(file_menu, 'File')
help_menu = Wx::Menu.new
mi = help_menu.append(Wx::ID_HELP, "&Help\tF1", 'Get help')
evt_menu(mi, :on_help)
menuBar.append(help_menu, 'Help')
#----------------
self.set_menu_bar(menuBar)
centre
end
def new_file(_)
Wx.message_box 'New File'
end
def open_file(_)
Wx.message_box 'Open File'
end
def save_file(_)
Wx.message_box 'Save File'
end
def on_help(_)
Wx.message_box 'F1 pressed.'
end
end
Wx::App.run do
window = MyWindow.new("wxRuby MenuBar Guide")
window.show
end
Using an accelerator table is also an option.
require 'wx'
class MyFrame < Wx::Frame
def initialize(title, pos, size)
super(nil, pos: pos, size: size)
Wx::Panel.new(self, Wx::ID_ANY)
set_accelerator_table Wx::AcceleratorTable.new([
Wx::AcceleratorEntry.new(Wx::ACCEL_NORMAL, Wx::K_ESCAPE, Wx::ID_CLOSE),
Wx::AcceleratorEntry.new(Wx::ACCEL_NORMAL, Wx::K_F1, Wx::ID_HELP)
])
evt_menu Wx::ID_CLOSE, :on_quit
evt_menu Wx::ID_HELP, :on_help
end
def on_help(_)
Wx.message_box("F1 pressed.")
end
def on_quit(_)
close(true)
end
end
Wx::App.run do
frame = MyFrame.new('Hello World', [50,50], [450,340])
frame.show
end
This may be useful to get a game-like behaviour. You can simply poll for key states using Wx.get_key_state (for instance in a Wx::Timer, or in idle events, etc.).
require 'wx'
class MyFrame < Wx::Frame
def initialize(title, pos, size)
super(nil, pos: pos, size: size)
Wx::Panel.new(self, Wx::ID_ANY)
end
end
class MyApp < Wx::App
def on_init
# create a global application timer
@timer = Wx::Timer.new(self, Wx::ID_ANY)
evt_timer @timer, :on_timer
@timer.start(30)
@frame = MyFrame.new('Hello World', [50,50], [450,340])
@frame.show
end
def on_exit
@timer.stop
end
def on_timer(_)
@timer.stop
Wx.message_box("F1 pressed.") if Wx.get_key_state(Wx::K_F1)
@timer.start
end
end
MyApp.run
-
-
Basic Guides
-
Widget Guides
-
Drawing Guides
-
Event Guides
-