Forum: Poser Python Scripting


Subject: more on: Running multiple Tkinter scripts.

tromnek opened this issue on Jan 21, 2005 ยท 12 posts


tromnek posted Fri, 21 January 2005 at 3:46 PM

I did some more work on this. It turns out that the real curlprit is Tkinter.mainloop(). I found some stuff on this topic and the best approach for my purpose is to hijack Tkinter's event loop. Here's what I currently have;

<br></br>class prpcdApp:<br></br>    """Your typical application class<br></br>    """<br></br>    def __init__(self, master, textMessage):<br></br>(snip..rest.of.init...)<br></br>    def Update(self):<br></br>      if self.scene: self.scene.ProcessSomeEvents()<br></br>      self.master.lift()<br></br>      self.master.after(50, self.Update)<br></br>(snip..rest.of.class...)<br></br><br></br>#--- Class to Hijack and Restore Tkinters event loops ---<br></br>class NewtMainLoop:<br></br>  """Class to hijack Tkinter and Tkinter.Misc mainloop()<br></br>  """<br></br>  import Tkinter<br></br>  def __init__(self, master):<br></br>      pass<br></br>      self.master = master<br></br><br></br>  def mainloop(self):<br></br>      if self.AmINewt():                        # Return if we are currently hijacked<br></br>       return<br></br>      self.master.after(75, self.ChangeToNewt ) # Hijack Tkinter's event loops, but wait a moment ...<br></br>      self.master.mainloop()                    #   until we run the 'real' event loop.<br></br>      self.GetBetter()                          # Restore the 'real' event loop<br></br><br></br>  def AmINewt(self):<br></br>      if hasattr( Tkinter, 'IGotBetterMainLoop' ):<br></br>        if not Tkinter.IGotBetterMainLoop == None:<br></br>          return 1<br></br>      return 0<br></br><br></br>  def ChangeToNewt(self):<br></br>      # Check if the mainloop() is currently hijacked, if so return.<br></br>      if self.AmINewt():<br></br>        print "I'm already a newt."<br></br>        return<br></br><br></br>      # Save Tkinter's default event loops for later restoration.<br></br>      Tkinter.IGotBetterMainLoop          = Tkinter.mainloop<br></br>      Tkinter.Misc.IGotBetterMiscMainLoop = Tkinter.Misc.mainloop<br></br><br></br>      # Our dummy event loops.<br></br>      def ChangeToNewtMainLoop(n=0):<br></br>        pass<br></br>        print 'call to mainloop()'<br></br>      def ChangeToNewtMiscMainLoop(self, n=0):<br></br>        pass<br></br>        print 'call to Misc.mainloop()'<br></br><br></br>      # Hijack the event loops.<br></br>      Tkinter.mainloop      = ChangeToNewtMainLoop<br></br>      Tkinter.Misc.mainloop = ChangeToNewtMiscMainLoop<br></br>      print "She turned me into a newt."<br></br><br></br>  def GetBetter(self):<br></br>      # Restore Tkinter's default event loops<br></br>      Tkinter.mainloop      = Tkinter.IGotBetterMainLoop<br></br>      Tkinter.Misc.mainloop = Tkinter.Misc.IGotBetterMiscMainLoop<br></br>      print "I Got Better."<br></br><br></br>      # Empty our stuff so we can use it for a logic test.<br></br>      Tkinter.IGotBetterMainLoop          = None<br></br>      Tkinter.Misc.IGotBetterMiscMainLoop = None<br></br><br></br>#--- Activate the loop ---------------<br></br>prpcd = prpcdApp(Tkinter.Tk(), '')      # Create our app, pass Tkinter.Tk() to self.master<br></br>prpcd.Update()<br></br>NewtMainLoop(prpcd.master).mainloop()   # Hijack and run mainloop(), pass a Tkinter.Tk() instance<br></br>print "Left mainloop() in main"         #   so it can call Tk().after() and Tk().mainloop()<br></br><br></br>#--- End Tk loop ------ --------------<br></br>

Let me know what you folks think.


duckmango posted Fri, 21 January 2005 at 4:33 PM

Tromnek: OK, I'm bringing my scale and pet duck over to see how much you weigh. I think this code proves that ... you're a witch! ;) So to try this out, do both scripts need this hijacked mainloop, or is it one 'regular' mainloop and one newtered one? And how many scripts have you tried to run simultaneously with this framework? Thanks...duckmango


tromnek posted Fri, 21 January 2005 at 5:27 PM

It works best if this code loads first. Then run as many scripts as you want. Close and reopen them in any order. You can even close this code before others and things will work fine closeing what ever you like in any order, but don't run any new ones. There are some combinations that will make it 'crash', I need to enumerate them in the doc text.


tromnek posted Fri, 21 January 2005 at 5:55 PM

The main thing to watch out for are foreign scripts where the main function performs some kind of cleanup after it's regular 'mainloop()'. This is because the hijacked mainloop() returns immediately. So scripts should do all their cleanup in their class, not in main.

There are also the standard issues of global() pollution.
This can have some disasterous effects when scripts are running at the same time.


tromnek posted Sat, 22 January 2005 at 6:10 PM

fixed the premature newted mainloop(). in 'def ChangeToNewtMiscMainLoop(self, n=0)' after the print statement add self.master.wait_window( self ) that should keep us in the newterd mainloop until the foreign script destroy()s itself. Just like expected. I also got rid of the self.master.lift(),(duh!), in my Update method. I also cancel the self.master.after(...) when my apps cleanup code. With this done. You can quit your app before other running 'newtered' scripts and everybody stays happy (I think).


tromnek posted Sun, 23 January 2005 at 10:54 PM

Oops, self.master.wait_window(self) doesn't help. I guess the newterd scripts will have to be able to handle an immediate return from their mainloop(). Sorry, I thought it was working because all the GUI stuff seemed to be working. Also, I noticed an oddity with Tkinter stuff that relies on 'variable changes', like Radiobutton's 'variable=foo, value=bar'. I had to change my stuff and add a 'command=' to those things.


duckmango posted Mon, 24 January 2005 at 11:07 AM

More on 'variable changes': I've also noticed a problem with StringVar variables in foreign scripts, as in the widgets don't display them anymore. I'll experiment to try to id this further.


duckmango posted Mon, 24 January 2005 at 3:26 PM

Follow up: In P5, the textvariable setting in the Label widget stops displaying after running the hijack script. Tkinter snippet: self.myMessage = StringVar() self.myMessage.set("How do you know she's a ...") Label(parent, textvariable=self.myMessage).grid(row=0) This bug appears only in P5. It's OK in PP, as well as in standalone python.


tromnek posted Mon, 24 January 2005 at 7:49 PM

Looks like I'm going to have to newter Tkinter.Tk() also. Maybe add to
def ChangeToNewt():

<br></br>      Tkinter.IGotBetterTk = Tkinter.Tk<br></br>      def ChangeToNewtTk():<br></br>        return Tkinter.Toplevel()<br></br><br></br>      Tkinter.Tk = ChangeToNewtTk<br></br>

and add to def IGotBetter():

<br></br>      Tkinter.Tk = IGotBetterTk<br></br>

But now the script that does this must run before any other scripts. Like a 'Protecter of the Realm' script.
KingArthur = NewtMainLoop( Tkinter.Tk() )
It will also need to schedule time for poser.ProcessSomeEvents(),
KingArthur.Update()

It seems to work, but I spoke too soon before. I'll test some more when I get a chance thursday.


tromnek posted Mon, 24 January 2005 at 11:10 PM

Here's the beginning of a 'Protector of the Realm'. It should work well enough as it is. It needs to be run before any other scripts are loaded.

tromnek posted Fri, 28 January 2005 at 1:41 PM

Ok. I think this has everything I want. You can create a standalone 'judge/protector' (now named Sir Bedevere) with;
NewtTk().mainloop()

or your app can create a separate 'Sir Bedevere' along with itself by;

root = NewtTk().Tk()<br></br>(snip...)<br></br>root.mainloop()

or your app can keep 'Sir Bedevere' all to itself by;

root = NewtTk().Tk(newtoplevel=0)<br></br>(snip...)<br></br>root.mainloop()

Let me know if there are any problems with this approach (or any suggestions).
duckmango, I renamed a bunch of symbols from earlier versions you saw, hope it isn't too confusing.


tromnek posted Sun, 30 January 2005 at 9:47 AM

Ok. Last beta version 0.14 (I hope).

I had to hijack Tkinter.Misc.quit() also. I added some tracking of newtered (hijacked) scripts so that confirmation of closing can warn a user that other scripts will also close.

I'm releasing the code under the GNU 'Lesser' public license so that this class can be used in commercial scripts.

If I don't hear any complaints or problems within the next week or two, I'll release a non-beta version 1.15 back to this thread.

Duckmango, thanks for the help.
Now I can get back to my 'poser remote procedure call daemon' which is what started all of this stuff.