2

I'm trying to create a sliding menubar for my tkinter application, but I've run into an issue that I suspect comes from my not locking a thread properly. While I have been dabbling with Python for a while, I'm relatively new to both tkinter and threaded applications, and I hoped someone here might be able to shed a little light on what I'm doing wrong.

My code is as follows:

class MenuBar():

def slide_out(self, event = None):

    def helper():
        while self.canvas.coords(self.bg)[0] > 100 and self.mouseIn:
            self.canvas.move(self.bg, -5, 0)
            sleep(0.01)

    self.mouseIn = True
    Thread(target = helper).start()

def slide_in(self, event):
    self.mouseIn = False
    self.canvas.coords(self.bg, 750, 0)

def __init__(self, canvas):
    self.canvas = canvas

    self.bgImg = ImageTk.PhotoImage(file = "Options Bar.png")
    self.bg = canvas.create_image(750, 0, anchor = "nw", image = self.bgImg, tags = ("menubar", "menubarbackground"))

    self.mouseIn = False

    self.canvas.tag_bind("menubar", "<Enter>", self.slide_out)
    self.canvas.tag_bind("menubar", "<Leave>", self.slide_in)

I'm working within a canvas widget as it makes stylization simpler. The error - which I receive upon mousing over the menu bar - is as follows:

Exception in thread Thread-1:
Traceback (most recent call last):
  File "C:\Python33\lib\threading.py", line 639, in _bootstrap_inner
    self.run()
  File "C:\Python33\lib\threading.py", line 596, in run
    self._target(*self._args, **self._kwargs)
  File "C:\Users\alex\Desktop\Python - VN Work\readingscreen.py", line 12, in helper
    while self.canvas.coords(self.bg)[0] > 100 and self.mouseIn:
  File "C:\Python33\lib\tkinter\__init__.py", line 2264, in coords
    self.tk.call((self._w, 'coords') + args))]
  File "C:\Python33\lib\tkinter\__init__.py", line 2262, in <listcomp>
    return [getdouble(x) for x in
ValueError: could not convert string to float: 'None'

Now the canvas method about which it is complaining works when I call it outside of a thread. It also merits mentioning that if I make any reference to self.bg in the helper function prior to the self.canvas.coords call no error occurs. Adding some useless reference however, hardly seems like a fix. I'm worried that if I don't address this now it will only exacerbate some other issue later on.

I'm I right to assume that I need to lock my thread so as to let it reference self.bg without issue, or is there something here I'm missing? I tried adding a lock to the MenuBar class, acquiring it and releasing it in the helper method, but to no avail.

Any one have any idea? Thanks.

1 Answers1

1

Tkinter isn't thread safe. You cannot call methods on widgets from any thread other than the one where the widget was created. Adding locking won't help.

For something as simple as sliding a menu in and out, you don't need threads. There are several examples of doing animation with Tkinter by leveraging the event loop. Basically the algorithm looks like this:

def animate(...):
    <move the widget one or two pixels>
    <if the widget needs to move some more>:
        self.canvas.after(100, animate)

What it does is simple: it moves your widget one or two pixels, checks to see if it's fully visible or not, and if not, it reschedules itself to run again in a few milliseconds. You can adjust the speed and smoothness of the animation by adjusting how many pixels you move on each iteration, and how many milliseconds between each frame of animation.

Bryan Oakley
  • 370,779
  • 53
  • 539
  • 685
  • Hmm, I tried to do this without threads initially, but ran into some bizarre issue. Admittedly though, I hadn't known about the canvas.after method. I'll try what you've suggested this afternoon, and approve your answer if I have any more luck. – Alex Sparling Jul 06 '13 at 16:30
  • 1
    Excellent, everything works just as I wanted it to. Thank you Bryan for pointing me to the .after method; it will no doubt be of much use to me in the future. If you don't mind my asking, are there any GUI frameworks that are threadsafe in Python? I tried using PyQt once upon a time, but it proved... painful. – Alex Sparling Jul 06 '13 at 18:54