I am continuing to have immense trouble receiving EVT_CHAR events on my custom control. After searching the internet for days, I am at a loss as to why I am not getting the events.
I understand that questions pertaining to EVT_CHAR are plenty - I have read through quite a number of them, however none of the solutions provided seem to work. I have ensured the following:
I have tried with and without wx.WANTS_CHARS on the frame.
I have tried binding to the frame and the control, both with and without the source parameter pointing to each.
There are no other keyboard-related event bindings, however I do have accelerators configured, as per the code below.
I have also tried to extend upon wx.TextEntry
. One possibility I was attempting to chase down is that the custom control will not receive these events as it does not appear to be a text entry control, and that the event only fires on non-readonly/editable controls. I had no luck figuring this out.
Version Information:
I am testing this on both Python 3.7, and most often on 3.6.
wxPython version 4.0.3
Windows 10.1709 Enterprise
The following is the code snippets from my frame class, responsible for constructing the GUI and listening on these events. I have also included the (currently useless) event handler for EVT_CHAR.
def do_create_gui(self):
classname = self.__class__.__name__
app = wx.App()
AppTitle = "%s: %s" % (self._comms.port, classname)
size = wx.Size(700, 450)
frame = wx.Frame(None, title=AppTitle, size=size)
panel = wx.Panel(frame)
panelSizer = wx.BoxSizer(wx.VERTICAL)
sizer = wx.BoxSizer(wx.VERTICAL)
# Configure Menu
fileMenu = wx.Menu()
copyitem = fileMenu.Append(wx.ID_COPY, "&Copy\tCtrl-C")
pasteitem = fileMenu.Append(wx.ID_PASTE, "&Paste\tCtrl-V")
fileMenu.AppendSeparator()
brkitem = fileMenu.Append(wx.ID_ANY, "&Break\tCtrl-B")
fileMenu.AppendSeparator()
quititem = fileMenu.Append(wx.ID_EXIT, "&Quit")
helpMenu = wx.Menu()
hotkeyitem = helpMenu.Append(wx.ID_ANY, "Program &Shortcuts")
menubar = wx.MenuBar()
menubar.Append(fileMenu, '&File')
menubar.Append(helpMenu, '&Help')
frame.SetMenuBar(menubar)
self._terminal = TerminalCtrl(panel)
self._terminal.SetSpacing(0)
self._terminal.SetWrap(True)
sizer.Add(self._terminal, 1, wx.EXPAND)
panelSizer.Add(panel, 1, wx.EXPAND)
panel.SetSizer(sizer)
frame.SetSizer(panelSizer)
frame.SetMinSize(wx.Size(313, 260))
frame.Show()
# Set up accelerators
accelC = wx.AcceleratorEntry(wx.ACCEL_CTRL, ord('C'), wx.ID_COPY)
accelV = wx.AcceleratorEntry(wx.ACCEL_CTRL, ord('V'), wx.ID_PASTE)
accelB = wx.AcceleratorEntry(wx.ACCEL_CTRL, ord('B'), brkitem.GetId())
accel = wx.AcceleratorTable([accelC, accelV, accelB])
frame.SetAcceleratorTable(accel)
# Bind on window events
frame.Bind(wx.EVT_CLOSE, self.onClose)
self._terminal.Bind(wx.EVT_CHAR, self.onChar)
# Bind Menu handlers
frame.Bind(wx.EVT_MENU, self.onClose, quititem)
frame.Bind(wx.EVT_MENU, self.showHotkeys, hotkeyitem)
frame.Bind(wx.EVT_MENU, lambda e: self.onCopy(), copyitem)
frame.Bind(wx.EVT_MENU, lambda e: self.onPaste(), pasteitem)
frame.Bind(wx.EVT_MENU, lambda e: self.send_break(), brkitem)
# Register for events from Serial Communications thread
EVT_SERIAL(frame, self.onSerialData)
# Ensure the terminal has focus
self._terminal.SetFocus()
self._wxObj = frame
self._tLock.release()
app.MainLoop()
def onChar(self, event):
code = event.GetUnicodeKey()
if code == wx.WXK_NONE:
code = event.GetKeyCode()
if (not 27 < code < 256) or event.HasAnyModifiers():
# So we don't consume the event
print('We don\'t process your kind here! (%d)' % code)
event.Skip()
return
print("CHAR: %s(%d)" % (chr(code), code))
In regards to the custom control, here is the definition.
import wx
from wx.lib.scrolledpanel import ScrolledPanel
class TerminalCtrl(ScrolledPanel, wx.Window):
def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition,
size=wx.DefaultSize, style=wx.TAB_TRAVERSAL,
name=wx.ControlNameStr):
pass
pass
I was trying to do a similar thing and it wasn't working for me either.
Based on my experimentation, the problem is caused by the TAB_TRAVERSAL flag. If your window is created with this flag, the TAB_TRAVERSAL mechanism appears to suck in all the EVT_CHAR events and your handler never gets called. That's probably a bug but I'm not sure.
This flag sometimes gets set by default: for wx.Panel this is the default window style, but for wx.Window it isn't. In my test program, when I tried to get keypresses in a Panel it didn't work, but it did if I used a wx.Window. When I changed the style parameter for Panel to avoid setting the TAB_TRAVERSAL flag, that also worked.
I also experimented with handling the EVT_CHAR_HOOK event, and calling event.DoAllowNextEvent(). The documentation (and I use the term advisedly) says this is necessary but apparently it's not.
Here is a version of my test program that actually works. By experimenting with it you can (sort-of) figure out what's going on. Commenting in and out various options or changing from wx.Panel to wx.Window was very enlightening.
import wx
class MainWindow(wx.Frame):
def __init__(self):
super().__init__(None, title="Char test")
self.label = wx.StaticText(self, label="Label")
self.panel = DaPanel(self)
bs = wx.BoxSizer(wx.VERTICAL)
bs.Add(self.label, wx.SizerFlags().Expand())
bs.Add(self.panel, wx.SizerFlags(1).Expand())
self.SetSizerAndFit(bs)
self.SetAutoLayout(True)
self.SetSize(wx.Size(800, 600))
self.Centre()
self.Show()
# self.Bind(wx.EVT_CHAR_HOOK, self.do_char_hook)
def do_char_hook(self, event):
# The functions DoAllowNextEvent and Skip seem to do the same thing
event.DoAllowNextEvent()
event.Skip()
# Calling either causes the EVT_KEY_DOWN event to occur for default
# wxPanel but make no difference for wxWindows.
class DaPanel(wx.Window):
def __init__(self, parent):
# default style wxPanel won't ever get EVT_CHAR events
# default style wxWindow will get EVT_CHARs
super().__init__(parent)
self.Bind(wx.EVT_CHAR, self.do_char)
# self.Bind(wx.EVT_KEY_DOWN, self.do_down)
def do_char(self, event):
print("char", event.GetUnicodeKey())
def do_down(self, event):
print("down", event.GetUnicodeKey(), self.HasFocus())
# if you run this handler you must Skip()
# or else no char event ever happens
event.Skip()
def main():
app = wx.App()
mw = MainWindow()
mw.Show(True)
app.MainLoop()
main()