Mouse listener on toplevel panel

Hi!
I’m beginning with wxPython.
Environment: Linux (GTK), wxPython 4.2.0
I need to implement a mouse-event listener monitoring the whole screen (my app is running fullscreen).
A way I used with other GUIs was to bind a listener to a transparent panel covering the whole screen.
I’m discovering that it’s not possible with wxPython because mouse events do not propagate through the chain of event handlers. So only the lowest level will receive the event.
I read that I can subclass EventHandler and use tryBefore() or tryAfter() but it looks like an ugly hack.
Is there a better way to solve my problem?
Here’s a runnable program (Matplotlob is required).

#!/usr/bin/python3
import matplotlib as mpl
mpl.use( 'wxAgg')
import wx

class UpperPanel( wx.Panel):
  def __init__( self, parent):
    wx.Panel.__init__( self, parent)
    self.create_MPL()
    self.createTools()

    self.Bind( wx.EVT_MOUSE_EVENTS, self.actMouse)

    self.sz1 = wx.BoxSizer( wx.VERTICAL)
    self.sz1.Add( self.canvas,     proportion=1, flag=wx.TOP | wx.EXPAND)
    self.sz1.Add( self.tools_MPL, proportion=0, flag=wx.BOTTOM)

    self.sz2 = wx.BoxSizer( wx.VERTICAL)
    self.sz2.Add( self.tools, proportion=0, flag=wx.TOP )

    self.sz3 = wx.BoxSizer( wx.HORIZONTAL)
    self.sz3.Add( self.sz1, proportion=1, flag=wx.EXPAND)
    self.sz3.Add( self.sz2, proportion=0)

    self.SetSizer( self.sz3)

  def create_MPL( self):
    import matplotlib.figure
    import matplotlib.backends.backend_wxagg
    self.fig = matplotlib.figure.Figure( layout='constrained')
    self.ax = self.fig.add_subplot(111)
    self.canvas = mpl.backends.backend_wxagg.FigureCanvasWxAgg( self, -1, self.fig)
    self.tools_MPL = mpl.backends.backend_wxagg.NavigationToolbar2WxAgg( self.canvas)
    self.tools_MPL.Realize()

  def toolButton( self, label, handler):
    btn = wx.Button( self, wx.ID_ANY, label=label)
    btn.Bind( wx.EVT_BUTTON, handler, btn)
    self.tools.Add( btn, proportion=0, flag=wx.ALL | wx.CENTER, border=5)

  def createTools( self):
    self.tools = wx.BoxSizer( wx.VERTICAL) # create toolbox
    self.toolButton( 'T', self.action)
    self.toolButton( 'F', self.action)

  def action( self, event):
    print( 'Action on', event.GetEventObject().LabelText)

  def actMouse( self, év):
    cmd = év.GetEventObject()
    print( "Pos:",wx.GetMousePosition())
    év.Skip( True)

class Win( wx.Frame):
  def __init__( self):
    wx.Frame.__init__( self, None, -1, size=(1200,900))
    self.panel = UpperPanel( self)

class App( wx.App):
  def OnInit( self):
    win = Win()
    self.SetTopWindow( win)
    win.Show( True)
    return True

if __name__ == "__main__" :
  app = App( False)
  app.MainLoop()

I’m expecting mouse motion events over the whole app but I don’t get any over the Matplotlib canvas or Matplotlib toolbar and even over my own buttons (on right side).
Where’s my problem?

well, what about Event.Skip() :thinking:

Thanks for your answer.

what about Event.Skip()

Two reasons:
Document Events and Event Handling — wxPython Phoenix 4.2.1 documentation mention:
“the events of the classes deriving from [ wx.CommandEvent] are propagated by default to the parent window.”
Mouse events do not derive from CommandEvent.

Second: In our application, one of the low-level panels is a Matplotlib canvas. I don’t want to modify their event handler to add a Skip().

well, your transparent window has to Skip :sweat_smile: (Skip is just the manual command behaviour)

It’s always best to provide a runnable code sample. Otherwise you won’t get solutions, except if someone else had exactly the same problem.

@DietmarSchwertberger I edited my post to include a curated version of the program.

To get the events for the buttons and the canvas you can bind the events like this:

self.canvas.Bind( wx.EVT_MOUSE_EVENTS, self.actMouse)
self.btn1.Bind( wx.EVT_MOUSE_EVENTS, self.actMouse)
self.btn2.Bind( wx.EVT_MOUSE_EVENTS, self.actMouse)
self.tools_MPL.Bind( wx.EVT_MOUSE_EVENTS, self.actMouse)

( https://wiki.wxpython.org/self.Bind%20vs.%20self.button.Bind )

If the number of widgets is limited, I would suggest you do this.
A more generic way would be to create the windows and then recursively walk through your hierarchy of windows and children and bind each.
You can see how to walk through here in StructurePrinter: https://github.com/wxGlade/wxGlade/blob/206a3679e431f927435e3f72d264087e3d857ea9/utilities.py

The filtering approach with TryBefore or EventFilter will probably create more overhead as each and every event will go through your Python code.

wx.EventFilter — wxPython Phoenix 4.2.1 documentation
wx.EvtHandler — wxPython Phoenix 4.2.1 documentation

I have never used these, I think, but you don’t need to subclass, just add a TryBefore method to your panel, which is derived from wx.EventHandler

class UpperPanel( wx.Panel):
  ...
  def TryBefore(self, event):
    print(event)
    return wx.Panel.TryBefore(self, event)

well, Matplotlib has its own AI :mask:
oops, I forgot the playground :rofl:

import sys
import wx
from wx import aui
import matplotlib as mpl

class MplPanel( wx.Panel):

    def __init__( self, parent):
        super().__init__(parent)
        self.create_MPL()
        self.createTools()

        self.Bind( wx.EVT_MOUSE_EVENTS,
            lambda _: print(f'mpl wx Pos: {wx.GetMousePosition()}'))

        hbox = wx.BoxSizer(wx.HORIZONTAL)

        vbox = wx.BoxSizer( wx.VERTICAL)
        vbox.Add( self.canvas, wx.EXPAND)
        vbox.Add( self.tools_MPL)
        hbox.Add(vbox, wx.EXPAND)

        vbox = wx.BoxSizer(wx.VERTICAL)
        vbox.Add( self.tools)
        hbox.Add(vbox)

        self.SetSizer(hbox)

    def create_MPL( self):
        import matplotlib.figure
        import matplotlib.backends.backend_wxagg
        self.fig = matplotlib.figure.Figure( layout='constrained')
        self.ax = self.fig.add_subplot(111)
        self.canvas = mpl.backends.backend_wxagg.FigureCanvasWxAgg(
            self, -1, self.fig)
        self.canvas.mpl_connect(
            'motion_notify_event', lambda e: print(f'mpl Pos: ({e.x}, {e.y})'))
        self.tools_MPL = mpl.backends.backend_wxagg.NavigationToolbar2WxAgg(
            self.canvas)
        self.tools_MPL.Realize()

    def toolButton( self, label, handler):
        btn = wx.Button(self, label=label)
        btn.Bind( wx.EVT_BUTTON, handler, btn)
        self.tools.Add( btn, proportion=0, flag=wx.ALL | wx.CENTER, border=5)

    def createTools( self):
        self.tools = wx.BoxSizer( wx.VERTICAL) # create toolbox
        self.toolButton( 'T', self.action)
        self.toolButton( 'F', self.action)

    def action( self, evt):
        print( 'Action on', evt.GetEventObject().LabelText)

class Log(wx.Panel):

    def __init__(self, parent):
        super().__init__(parent)

        log = wx.TextCtrl(
                self,
                style=wx.TE_MULTILINE|wx.TE_READONLY|wx.HSCROLL)
        log.SetBackgroundColour(wx.SystemSettings.GetColour(
                                                    wx.SYS_COLOUR_HIGHLIGHT))
        log.SetForegroundColour(wx.SystemSettings.GetColour(
                                                    wx.SYS_COLOUR_HIGHLIGHTTEXT))

        vbox = wx.BoxSizer(wx.VERTICAL)
        vbox.Add(log, 1, wx.LEFT|wx.TOP|wx.RIGHT|wx.EXPAND, 5)
        self.SetSizer(vbox)
        sys.stdout = log
        print('Logging started..')
        self.Show()
        log.Bind( wx.EVT_MOUSE_EVENTS,
            lambda _: print(f'wx  Pos: {wx.GetMousePosition()}'))

class Gui(wx.Frame):

    def __init__(self):
        super().__init__(None, title='Mpl events')

        mb = wx.MenuBar()
        help_menu = wx.Menu()
        ID_About = wx.NewIdRef()
        help_menu.Append(ID_About, "restore pains...")
        mb.Append(help_menu, "Help")
        def menu_about(_):
            for entry in self.mgr.GetAllPanes():
                if not entry.IsShown():
                    entry.Show()
                self.mgr.Update()
        self.Bind(wx.EVT_MENU, menu_about, id=ID_About)
        self.SetMenuBar(mb)

        self.mgr = aui.AuiManager(
            flags=wx.aui.AUI_MGR_DEFAULT|wx.aui.AUI_MGR_LIVE_RESIZE)
        self.mgr.SetManagedWindow(self)
        self.mgr.Bind(aui.EVT_AUI_PANE_CLOSE,
                    lambda evt: print(f"on close: '{evt.GetPane().caption}'"))

        p0 = MplPanel(self)
        p1 = Log(self)

        s = self.GetSize()
        self.mgr.AddPane(p0, aui.AuiPaneInfo().Floatable().
            Caption('My Plot').MinSize(s - (int(s[0]*0.5), 0)).Dock())
        self.mgr.AddPane(p1, aui.AuiPaneInfo().Float().
            Caption("Log").MinSize(s - (int(s[0]*0.5), 0)).Dockable())
        self.mgr.Update()

        self.Show()
        self.Bind( wx.EVT_MOUSE_EVENTS,
            lambda _: print(f'wx  Pos: {wx.GetMousePosition()}'))

app = wx.App()
Gui()
app.MainLoop()