Elendil
Elendil

Reputation: 150

wxPython: Drag&Drop a wx.Button over a panel freezes the application

I'm trying to build an application where the user can drag&drop some button around the panel. I first have an error about a mouse capture event lost and I finally found that I have to catch this event to prevent the error.

But now, when I run the application, I can drag&drop the button however the application is totally frozen after I release the mouse's left button.

I have to stop it with Ctrl+C from the terminal otherwise my mouse is unusable in any other windows in my desktop environment.

I suspect a problem of mouse capturing event that is not well handled.

I'm working under Ubuntu 16.04 with Python 3.5 installed from package (apt). I tried with both wxPython 4.0.0 installed from package (apt) and also with the latest wxPython 4.0.4 installed from pip.

In both cases the application is totally frozen after a click or a drag&drop of the button.

import wx


class DragButton(wx.Button):
    def __init__(self, parent, id=wx.ID_ANY, label="", pos=(0, 0)):
        super().__init__(parent=parent, id=id, label=label, pos=pos)
        self._dragging = False

        self.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown)
        self.Bind(wx.EVT_LEFT_UP, self.OnLeftUp)
        self.Bind(wx.EVT_MOTION, self.OnMouseMove)
        self.Bind(wx.EVT_MOUSE_CAPTURE_LOST, lambda evt: None)

    def OnLeftDown(self, evt):
        print("Left down")
        if  not self.HasCapture():
            self.CaptureMouse()
        x, y = self.ClientToScreen(evt.GetPosition())
        originx, originy = self.GetPosition()
        dx = x - originx
        dy = y - originy
        self.delta = ((dx, dy))

    def OnLeftUp(self, evt):
        print("Left UPPPP")
        if self.HasCapture():
            self.ReleaseMouse()

    def OnMouseMove(self, evt):
        if evt.Dragging() and evt.LeftIsDown():
            x, y = self.ClientToScreen(evt.GetPosition())
            fp = (x - self.delta[0], y - self.delta[1])
            self.Move(fp)


class GDomFrame(wx.Frame):
    def __init__(self, parent, title):
        super().__init__(parent, title=title, size=(350, 300))

        self._init_ui()
        self.Centre()


    def _init_ui(self):
        panel = wx.Panel(self)

        self.button = DragButton(panel, label="Drag me", pos=(10, 10))


if __name__ == '__main__':
    print("wxPython version: {}".format(wx.__version__))
    app = wx.App()
    ex = GDomFrame(None, title='GDom Application')
    ex.Show()
    app.MainLoop()

With this code I expect to have a button that I can move around the panel several time.

Upvotes: 1

Views: 123

Answers (2)

Mr_and_Mrs_D
Mr_and_Mrs_D

Reputation: 34036

Works (at least on mac) dropping mouse capture stuff but can't make the EVT_BUTTON fire, so this won't work:

@@ -19,2 +19,3 @@ class Mywin(wx.Frame):
         self.btn.Bind(wx.EVT_LEFT_UP, self.OnMouseUp)
+        self.btn.Bind(wx.EVT_BUTTON, self.OnButton)
         print("Init pos:", self.btn.GetPosition())
@@ -42,4 +43,4 @@ class Mywin(wx.Frame):
             print(self.btn.GetPosition())
-        else:
-            self.OnButton(event)
+        # else:
+        #     self.OnButton(event)

I added the handling of the click in OnMouseUp:

"""Code from https://stackoverflow.com/a/56209991/281545"""
import sys

import wx

class Mywin(wx.Frame):
    def __init__(self, parent, title):
        super(Mywin, self).__init__(parent, title=title, size=(400, 200))
        print(wx.version())
        print(f'Python {sys.version} on {sys.platform}')
        self.panel = wx.Panel(self)
        vbox = wx.BoxSizer(wx.VERTICAL)
        self.btn = wx.Button(self.panel, -1, "click Me", pos=(10, 10))
        vbox.Add(self.btn, 0, wx.ALIGN_CENTER)
        self._dragging = False
        self._btn_x = self._btn_y = None
        self.btn.Bind(wx.EVT_LEFT_DOWN, self.OnMouseDown)
        self.btn.Bind(wx.EVT_MOTION, self.OnMouseMove)
        self.btn.Bind(wx.EVT_LEFT_UP, self.OnMouseUp)
        # self.btn.Bind(wx.EVT_LEFT_DCLICK, self.OnButton)
        # self.btn.Bind(wx.EVT_BUTTON, self.OnButton)
        print("Init pos:", self.btn.GetPosition())
        self.Centre()

    # event handling note we dont Skip
    def OnMouseDown(self, event):
        sx, sy = self.panel.ScreenToClient(self.btn.GetPosition())
        dx, dy = self.panel.ScreenToClient(wx.GetMousePosition())
        self._btn_x, self._btn_y = (sx - dx, sy - dy)

    def OnMouseMove(self, event, __minMove=2):
        if not (self._btn_x is self._btn_y is None):
            x, y = wx.GetMousePosition()
            if abs(self._btn_x - x) > __minMove or abs(self._btn_y - y) > __minMove:
                self._dragging = True
                self.btn.SetPosition(wx.Point(x + self._btn_x, y + self._btn_y))
            print(self.btn.GetPosition())

    def OnMouseUp(self, event):
        self._btn_x = self._btn_y = None
        if self._dragging:
            self._dragging = False
            print("Final pos:", self.btn.GetPosition())
            print(self.btn.GetPosition())
        else:
            # event.Skip()
            self.OnButton(event)

    def OnButton(self, event):
        print("OnButton", self.btn.GetPosition())

def main():
    app = wx.App()
    w = Mywin(None, title='Button demo')
    w.Show()
    app.MainLoop()

main()

# 4.2.1 osx-cocoa (phoenix) wxWidgets 3.2.2.1
# Python 3.11.1 (v3.11.1:a7a450f84a, Dec  6 2022, 15:24:06) [Clang 13.0.0 (clang-1300.0.29.30)] on darwin

Upvotes: 0

DALET philippe
DALET philippe

Reputation: 36

I have tested a similar script. It works fine on Windows, but not on ubuntu 16.04. I solved the problem like this.

def OnLeftDown(self, evt):
    print("Left down")
    if not self.HasCapture():
        self.CaptureMouse()
        self.ReleaseMouse() # <------

My script:

import wx

class Mywin(wx.Frame): 
    def __init__(self, parent, title): 
        super(Mywin, self).__init__(parent, title = title,size = (400,200))  
        self.InitUI()
        self.Centre() 

    def InitUI(self):
        self.panel = wx.Panel(self)
        vbox = wx.BoxSizer(wx.VERTICAL) 
        self.btn = wx.Button(self.panel,-1,"click Me",pos=(10, 10)) 
        vbox.Add(self.btn,0,wx.ALIGN_CENTER) 

        self.btn.Bind(wx.EVT_LEFT_DOWN, self.OnMouseDown)
        self.btn.Bind(wx.EVT_MOTION,  self.OnMouseMove)
        self.btn.Bind(wx.EVT_LEFT_UP,  self.OnMouseUp)
        print ("Init pos:",self.btn.GetPosition())

    def OnMouseDown(self, event):
        if (not self.btn.HasCapture()):
            self.btn.CaptureMouse()
            self.btn.ReleaseMouse()
            sx,sy   = self.panel.ScreenToClient(self.btn.GetPosition())
            dx,dy  = self.panel.ScreenToClient(wx.GetMousePosition())
            self.btn._x,self.btn._y   = (sx-dx, sy-dy)

    def OnMouseMove(self, event):
        if event.Dragging() and event.LeftIsDown():
            x, y = wx.GetMousePosition()
            self.btn.SetPosition(wx.Point(x+self.btn._x,y+self.btn._y))
            print(self.btn.GetPosition())

    def OnMouseUp(self, event):
        if (self.btn.HasCapture()): 
            self.btn.ReleaseMouse()
            print ("Final pos:",self.btn.GetPosition())

def main():
    app = wx.App()
    w = Mywin(None, title='Button demo')
    w.Show()
    app.MainLoop()

if __name__ == '__main__':
    main()

Upvotes: 2

Related Questions