How to repeat code in an OnButton event while mouse button is held down by user

Good morning everyone, (meaning it is morning for me right here and now :-))

For my project I would like to have the following feature. When a user clicks and holds the left mouse button down a certain piece of code should be excuted repeatedly, until the user releases the left mouse button.

I found out that the wx.EVT_BUTTON event is not the way to go so I am experimenting with wx.EVT_LEFT_DOWN and wx.EVT_LEFT_UP.

Before re-inventing some wheel I ask this community if there is already such a feature which I simply did not find yet…

Hope to hear…

My first approach would be (untested, bugs can be kept):

  • use a ToggleButton

  • have an extra thread which

    • checks whether a flag “do the work routine” is true
    • returns if not
    • does the work routine once
    • checks whether the left mouse button is still pressed
      and the toggle button still toggled (user might have
      manually untoggled the button meanwhile and is holding
      the mousebutton for other reasons)
    • if yes:
      do work again
    • if not:
      • sets flag “do the work routine” to False
      • untoggles the toggle button
    • returns

The button press would simply set the flag “do the work
routine” to True.

Do you really need the user to hold down the mouse button, or
could they simply use the toggle button to “start/stop” the
work ?

Karsten

I conceptually favor the ToggleButton approach already proposed by Karsten above to trigger start and stop, but if you really need the user to be pressing the button you could catch wx.EVT_LEFT_DOWN when the user presses the button and wx.EVT_LEFT_UP when the user releases the button to trigger start and stop respectively instead. I would still use those events simply to set a flag and would run the actual computation in a separate thread as Karsten said above.

Hi, Adriaan

Wxpython triggers many events and provides an opportunity to catch them. All you have to do is define the state, handle the given events, drive your machine according to the context, and use the algorithm appropriately to do the output.

As you are doing and mentioned in the above posts, I think Wx.EVT_LEFT_DOWN and EVT_LEFT_UP should be the way to go, but perhaps there is no convenient wheel, or if any, buried in hundreds of thousands of code.

I like the FSM approach (see Finite-state machine - Wikipedia), and think this sample could be a help for you. Many authors have provided FSM libraries, and this is just an example of implementation.

import wx

class FSM(dict):
    def __call__(self, event, *args):
        context = self[self.state]
        if event in context:
            transaction = context[event]
            self.state = transaction[0] # the state transits here
            for act in transaction[1:]: # then execute the actions
                act(*args)

IDLE, BUSY = 'idle', 'busy' # states
DN, MV, UP = 'press', 'dragging', 'release' # inputs

class Panel(wx.Panel):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        
        self.handler = FSM({
            IDLE : {
                DN : (BUSY, self.on_pressed),
                UP : (IDLE, self.on_released)
            },
            BUSY : {
                MV : (BUSY, self.on_motion),
                UP : (IDLE, self.on_released),
            },
        })
        self.handler.state = IDLE
        
        ## btn = wx.Button(self, label="Take me")
        btns = [wx.Button(self, label=l)
                for l in "The quick brown fox jumped over the lazy dog".split()]
        for btn in btns:
            btn.Bind(wx.EVT_LEFT_DOWN, lambda v: self.handler(DN, v))
            btn.Bind(wx.EVT_LEFT_UP, lambda v: self.handler(UP, v))
            btn.Bind(wx.EVT_MOTION, lambda v: self.handler(MV, v))
        self.Bind(wx.EVT_BUTTON, print)
    
    def on_pressed(self, evt):
        print("I'm {}".format(self.handler.state))
        self._org = evt.Position
        evt.Skip() # skip to CaptureMouse
    
    def on_released(self, evt):
        print("I'm {}".format(self.handler.state))
        evt.Skip() # skip to ReleaseMouse
    
    def on_motion(self, evt):
        btn = evt.EventObject
        print("I'm {} at {}".format(self.handler.state, btn.Position))
        btn.Position += evt.Position - self._org
        evt.Skip()

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

cards-toy From a night owl… :grinning:

:memo:This post is related to my old article A new idea how to enable Emacs-like multi-stroke keymap in wxPython and hint to boost menubar and accelerators

@komoto48g
I like your state-transition table implementation (home-grown :astonished:)
@Adriaan
otherwise I would go for thread, and for toggling a python event at key down & up will do