modal frame from wx python?

pkienzle pkienzle at gmail.com
Wed Feb 6 08:40:26 PST 2008


Hi! I'm trying to create a wx.Frame which has a method allowing the 
user to click at several points on the canvas and returning the points 
clicked.  This is like a modal dialog, except that only one window is 
modal, and it is only modal part of the time.

Does anyone have code for something like this?

I'm including the solutions I tried below.

Thanks in advance,

    - Paul

----

Attempted solutions for modal wx event processing:

1. Run a private event loop with a timer every 0.1s to see if the user
has done selecting.  This works, but it doesn't like it if the window
is closed before completion. I didn't test for it, but super-fast
clicking should let the user sometimes select an extra point, so not
a great solution.

    def event_loop(self, done=lambda:False, timeout=0):
        'run the eventloop until done or timeout'
        print "Running private event loop"
        loop = wx.EventLoop()

        # stop after timeout period
        def donewaiting(*args,**kw):
            print "Done waiting"
            loop.Exit()
        if timeout > 0:
            print "Starting timer"
            outtimer = wx.Timer(self, id=MODAL_TIMEOUT_TIMER)
            outtimer.Start(timeout*1000, oneShot=True)
            self.Bind(wx.EVT_TIMER, donewaiting, outtimer)

        # check if done every 0.1 s
        def checkdone(*args,**kw):
            if done(): loop.Exit()
        steptimer = wx.Timer(self, id=MODAL_STEP_TIMER)
        steptimer.Start(100, oneShot=False)
        self.Bind(wx.EVT_TIMER, checkdone, steptimer)
    
        loop.Run()
        steptimer.Stop()
        return

2. What seems like it should work is to use an event handler which
checks after processing each event whether or not this completes
the condition and so we can exit the loop.  However, ProcessEvent
never seems to be called.  Reading elsewhere I see that ProcessEvent
is not a virtual function in wx, so presumably we can't override
it in a subclass of wx.EvtHandler.
            
            
    def event_loop(self, done=lambda:False, timeout=0):
        'run the eventloop until done or timeout'
        print "Running private event loop"
        loop = wx.EventLoop()

        # stop after timeout period
        def donewaiting(*args,**kw):
            print "Done waiting"
            loop.Exit()
        if timeout > 0:
            print "Starting timer"
            outtimer = wx.Timer(self, id=MODAL_TIMEOUT_TIMER)
            outtimer.Start(timeout*1000, oneShot=True)
            self.Bind(wx.EVT_TIMER, donewaiting, outtimer)

        # Redirect input events to new handler
        class ModalHandler(wx.EvtHandler):
            def ProcessEvent(self, evt):
                print "Processing event"
                if done(): loop.Exit()
                return False # True if processed
        print "Pushing handler"   
        handler = ModalHandler()
        self.PushEventHandler(handler)
        print "enabled",handler.GetEvtHandlerEnabled()
        print "handler->next",handler.GetNextHandler()
        print "self->previous",self.GetPreviousHandler()
        loop.Run()
        self.PopEventHandler(False)
        print "Done!"

3. Revising attempt #2 somewhat, I send all events for the canvas through
my own event handler.  To make sure I see each one I bind each window
event to my own dispatcher as well as to the underlying window.  I
then use this handler rather than self in the canvas __init__.
            
This fails because either the window handler is not called or it
is called twice, depending on whether PushEventHandler and ProcessEvent
are called.  It also fails if the window is closed unexpectedly.
                
class ModalHandler(wx.EvtHandler):
    def __init__(self, window):
        print "Creating handler"  
        wx.EvtHandler.__init__(self)
        self.done = lambda:False
        window.PushEventHandler(self)
        self.window = window
        self.timer = wx.Timer()
        self.Bind(wx.EVT_TIMER, self.OnTimeout, self.timer)
        self.loop = wx.EventLoop()
    def _dispatch(self, evt):
        """Ick! ProcessEvent is not virtual, so we can't override
directly!"""
        #print "Processing event"
        self.window.ProcessEvent(evt)
        if self.done(): self.loop.Exit()
        print "Returning event"
        return True
    def EndModal(self):
        """Force the loop to exit"""
        self.done = lambda:True
    def Bind(self,evt,action,*args,**kw):
        # Force all events through ProcessEvent.  This is the first binding.
        # ProcessEvent will dispatch the event to the window itself, so be
        # sure to tell the window what to do with the event.  This is the
        # second binding.
        if wx.VERSION_STRING >= '2.5':  # Event handlers 2.5
            print "Binding 2.5"
            wx.EvtHandler.Bind(self.window,evt,action,*args,**kw)
            wx.EvtHandler.Bind(self,evt,self._dispatch)
        else:
            print "Binding 2.4"   
            evt(self,self.ProcessEvent)
            evt(self.window,*args,**kw)
    def OnTimeout(self):
        print "timeout"
        self.loop.Exit()
    def RunLoop(self, done=lambda:False, timeout=0):
        print "Running loop"
        self.done = done
        if timeout > 0: self.timer.Start(timeout, oneShot=True)
        try:
            self.loop.Run()
        finally:
            self.timer.Stop()
            self.done = lambda:False
        


-- 
View this message in context: http://www.nabble.com/modal-frame-from-wx-python--tp15306964p15306964.html
Sent from the wxWidgets - Users mailing list archive at Nabble.com.





More information about the wx-users mailing list