Why EVT_CHAR_HOOK is not triggered during mouse capture?

Hi there :wave:

There are two ways to get key events: EVT_KEY_DOWN and EVT_CHAR_HOOK.
Both trigger wx.KeyEvent but the behavior during mouse capture is different.
During mouse capture, EVT_KEY_DOWN triggers the event but EVT_CHAR_HOOK does not.

Is there any reason for this design? Or does anyone know it?
If not, I’d like to propose that the EVT_CHAR_HOOK can trigger the key event even while the mouse is captured.

The reason is as follows:

Recently, the wxagg backend of matplotlib has changed the key event binder from EVT_KEY_DOWN to EVT_CHAR_HOOK since 3.4.
This allows you to get key events of so-called navigation keys [alt], [Tab], and [Left | Right | Up | Down].
This change is nice, however, when you start capturing the mouse (i.e. dragging), the key event will no longer be triggered.

This will cause undesired behaviors for me. For example, if you press a key while dragging, say [ESC] key to cancel dragging, the key event does not come and the dragging state cannot be canceled.

I don’t think this depends on OS.
I added a sample code to see this action. (Please substitute ‘Vippi.png’ to appropriate image file)

Code Example (click to expand)
import wx

class TestPanel(wx.Panel):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        
        self._org = None
        
        vippi = wx.Bitmap('Vippi.png')
        self.canvas = wx.Panel(self, size=vippi.GetSize()) # to get keys
        ## self.canvas = wx.StaticBitmap(self, bitmap=vippi) # no focus, no keys
        
        def on_paint(evt):
            dc = wx.PaintDC(self.canvas)
            dc.DrawBitmap(vippi, 0, 0, False)
            evt.Skip()
        self.canvas.Bind(wx.EVT_PAINT, on_paint)
        
        self.canvas.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown)
        self.canvas.Bind(wx.EVT_LEFT_UP, self.OnLeftUp)
        self.canvas.Bind(wx.EVT_MOTION, self.OnMouseMove)
        
        def on_keys(evt):
            print(evt.EventType, evt.KeyCode)
            evt.Skip()
        self.canvas.Bind(wx.EVT_KEY_DOWN, on_keys) # 10057 - ok while capturing
        self.canvas.Bind(wx.EVT_CHAR_HOOK, on_keys) # 10055 - no while capturing

    def OnLeftDown(self, evt):
        self._org = evt.Position
        self.canvas.CaptureMouse()
        evt.Skip()

    def OnLeftUp(self, evt):
        self._org = None
        if self.canvas.HasCapture():
            self.canvas.ReleaseMouse()
        evt.Skip()

    def OnMouseMove(self, evt):
        if self._org is not None:
            self.canvas.Position += evt.Position - self._org
        evt.Skip()

if __name__ == "__main__":
    app = wx.App()
    frm = wx.Frame(None)
    panel = TestPanel(frm)
    frm.Show()
    app.MainLoop()

Well, I haven’t tried it, but as I understand the docu either use Skip if you don’t want to do anything at the hook or use the method DoAllowNextEvent: in both cases you should end up at your key event (for otherwise the name HOOK would be blunder) :face_with_hand_over_mouth:

Thank you for your reply.
After that, I tried some tests and noticed that my proposal was not a good idea for some cases.
In the worst case, if EVT_CHAR_HOOK triggers the key event during mouse capture, the event will go to the mainframe and display the menu, which will result in AssertionError: ‘mouse capture lost’.

The solution might be that both EVT_KEY_DOWN and EVT_CHAR_HOOK are handled depending on whether dragging or not.