Search code examples
multithreadingwxpythonglcanvas

Mouse click events are being blocked when using wxPython with a GLCanvas


I’ve created a wxPython application and placed a GLCanvas in the wxFrame. I’ve bound the EVT_LEFT_DOWN event to the GLCanvas, but when running the program, the mouse click events don’t seem to respond in real-time. I have to wait until the program is closed to see that the clicked events are printed together.

It feels like the main thread’s mainLoop is blocking the mouse click events. How can I make adjustments to address this, and do you have any suggestions?

I've added an additional code snippet. I'm running the code on macOS with Python version 3.10, wxPython version 4.2.1, and PyOpenGL version 3.1.7.

import wx
from wx import glcanvas
from OpenGL.GL import *
import random


class MyCanvas(glcanvas.GLCanvas):
    def __init__(self, parent):
        glcanvas.GLCanvas.__init__(self, parent, -1)
        self.context = glcanvas.GLContext(self)

        self.Bind(wx.EVT_PAINT, self.on_paint)
        self.Bind(wx.EVT_LEFT_DOWN, self.on_left_down)

    def on_left_down(self, event):
        x, y = event.GetPosition()
        print(f"Mouse clicked at ({x}, {y})")

    def on_paint(self, event):
        dc = wx.PaintDC(self)
        self.SetCurrent(self.context)
        self.on_draw()

    def on_draw(self):
        glClearColor(random.randrange(256)/255, random.randrange(256)/255, random.randrange(256)/255, 1.0)
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
        self.SwapBuffers()


def main():
    app = wx.App()
    frame = wx.Frame(None)
    sizer = wx.BoxSizer()
    frame.SetSizer(sizer)
    canvas = MyCanvas(frame)
    sizer.Add(canvas, 1, wx.EXPAND)
    frame.Show()
    app.MainLoop()


if __name__ == '__main__':
    main()

Here is a demonstration of the above code's execution.


Solution

  • I suspect the issue is whatever you're using to run the code in debug mode.

    Run this from the command line, avoiding that debug/development tool.

    import wx
    from wx import glcanvas
    from OpenGL.GL import *
    import random
    
    
    class MyCanvas(glcanvas.GLCanvas):
        def __init__(self, parent):
            glcanvas.GLCanvas.__init__(self, parent, -1)
            self.context = glcanvas.GLContext(self)
    
            self.Bind(wx.EVT_PAINT, self.on_paint)
            self.Bind(wx.EVT_LEFT_DOWN, self.on_left_down)
            self.Bind(wx.EVT_RIGHT_DOWN, self.on_right_down)
            self.Bind(wx.EVT_LEFT_UP, self.on_left_up)
            self.Bind(wx.EVT_MOTION, self.on_mouse_motion)
            self.Bind(wx.EVT_LEFT_DCLICK, self.on_dclick)
            self.Bind(wx.EVT_MOUSEWHEEL, self.on_scroll)
    
        def on_left_down(self, event):
            self.x, self.y = event.GetPosition()
            print(f"Mouse click Down at ({self.x}, {self.y})")
            self.Refresh(False)
    
    
        def on_right_down(self, event):
            self.x, self.y = event.GetPosition()
            print(f"Mouse Right click Down at ({self.x}, {self.y})")
    
        def on_dclick(self, event):
            print(f"Mouse Doubled Clicked")
            event.Skip()
    
        def on_left_up(self, event):
            self.x, self.y = event.GetPosition()
            print(f"Mouse click Released at ({self.x}, {self.y})")
    
        def on_mouse_motion(self, event):
            if event.Dragging() and event.LeftIsDown():
                prev_x, prev_y = self.x, self.y
                self.x, self.y = event.GetPosition()
                print(f"Mouse movement from ({prev_x, prev_y}) to ({self.x, self.y})")
                self.Refresh(False)
    
        def on_scroll(self, event):
            up_down = event.GetWheelRotation()
            if up_down < 1:
                print(f"Mouse scroll Down")
            else:
                print(f"Mouse scroll Up")
    
    
        def on_paint(self, event):
            dc = wx.PaintDC(self)
            self.SetCurrent(self.context)
            self.on_draw()
    
        def on_draw(self):
            glClearColor(random.randrange(256)/255, random.randrange(256)/255, random.randrange(256)/255, 1.0)
            glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
            self.SwapBuffers()
    
    
    def main():
        app = wx.App()
        frame = wx.Frame(None)
        sizer = wx.BoxSizer()
        frame.SetSizer(sizer)
        canvas = MyCanvas(frame)
        sizer.Add(canvas, 1, wx.EXPAND)
        frame.Show()
        app.MainLoop()
    
    
    if __name__ == '__main__':
        main()
    

    Running on Linux:

    enter image description here