1

I wrote this code in windows and it worked but when i copied it to my raspberry pi(running Jessie Debian based) it give runtime error

my code:

from Tkinter import *
from PIL import ImageTk,Image
from time import sleep
import thread
root = Tk()
img=ImageTk.PhotoImage(Image.open("1.jpg").resize((root.winfo_screenwidth()-3,root.winfo_screenheight()-3)))
panel = Label(root, image = img)
panel.pack(side = "bottom", fill = "both", expand = "yes")
if __name__ == '__main__':
    thread.start_new_thread(root.mainloop,())
    for i in xrange(1,5):
        sleep(1)
        img2 = ImageTk.PhotoImage(Image.open(str(i)+".jpg").resize((root.winfo_screenwidth()-3,root.winfo_screenheight()-3)))
        panel.configure(image = img2)
        panel.image = img2

when executed in linux it gives error:

Unhandled exception in thread started by <bound method Tk.mainloop of <Tkinter.Tk instance at 0x75b605d0>>
Traceback (most recent call last):
  File "/usr/lib/python2.7/lib-tk/Tkinter.py", line 1124, in mainloop
    self.tk.mainloop(n)
RuntimeError: Calling Tcl from different appartment

what should i do to run this code on both platforms

szym
  • 5,606
  • 28
  • 34
Himanshu Bansal
  • 475
  • 4
  • 19

1 Answers1

5

Tcl functions cannot be called from different threads. The fact that it works on Windows might be because the check for this condition is faulty.

What you need to do is have one "UI thread" that talks to Tk. Typically, all this thread explicitly does is:

  • set up all the widgets and event handlers
  • call root.mainloop()

If you then want to make changes to the UI later, you should do it within event handlers. If you want to execute some code in response to non-UI events, you can register custom event handlers at the root or schedule functions to be executed on the main loop using after.

from Tkinter import *
from PIL import ImageTk, Image

root = Tk()
panel = Label(root)
panel.pack(side = "bottom", fill = "both", expand = "yes")

def showImage(i):
    img = ImageTk.PhotoImage(Image.open(str(i)+".jpg"))
    img = img.resize((root.winfo_screenwidth()-3, root.winfo_screenheight()-3))
    panel.configure(image = img)
    panel.image = img

class Task(object):
    # A little helper to wrap a function with arguments.
    def __init__(self, fun, *args, **kwargs):
        self.fun = fun
        self.args = args
        self.kwargs = kwargs
    def __call__(self):
        self.fun(*self.args, **self.kwargs)

if __name__ == '__main__':
    for i in range(1, 5):
        # run showImage(i) on the mainloop after (i - 1) seconds
        root.after((i - 1) * 1000, Task(showImage, i))
    root.mainloop()

You can schedule functions from another thread if you need to:

import thread

def doStuff():
    for i in range(1, 5):
        sleep(1)
        root.after(0, Task(showImage, i))    

thread.start_new_thread(doStuff, ())

Note: I created Task to easily specify that I want to run showImage with given i. Normally, functools.partial would be fine, but apparently it does not play well with Tkinter due to dynamic create method and decorator, got error 'functools.partial' object has no attribute '__module__'

You could also do something like:

(lambda ii: lambda: showImage(ii))(i)

But that might be confusing when you return to the code at a later time.

If you don't anticipate having other tasks, you could just do:

def showImage(i):
    def newfun():
        img = ImageTk.PhotoImage(Image.open(str(i)+".jpg"))
        img = img.resize((root.winfo_screenwidth()-3, root.winfo_screenheight()-3))
        panel.configure(image = img)
        panel.image = img
    return newfun
Community
  • 1
  • 1
szym
  • 5,606
  • 28
  • 34
  • 1
    Your lambda capture of `i` will not work. The lambda will capture the reference to the global variable and not it's value. Therefore, all values of i will be 4. Better to use `functools.partial` to create your callback. – Dunes Apr 02 '16 at 07:32
  • Sigh, http://stackoverflow.com/questions/20594193/dynamic-create-method-and-decorator-got-error-functools-partial-object-has-no makes `functools.partial` not work with Tkinter – szym Apr 02 '16 at 07:41
  • In that case, create the lambda in a function, passing i as an argument. The lambda will be then capture the right value of i. Alternatively, you could create a callable class (with a `__call__` function) which has i as an attribute. – Dunes Apr 02 '16 at 07:45
  • It gives error `root.after(0, functools.partial(showImage, i)) File "D:\Anaconda2\lib\lib-tk\Tkinter.py", line 593, in after callit.__name__ = func.__name__ AttributeError: 'functools.partial' object has no attribute '__name__'` – Himanshu Bansal Apr 02 '16 at 07:54
  • @HimanshuBansal yes, I realized that when I tested it, and I updated the answer with a different solution to passing arguments to showImage. – szym Apr 02 '16 at 08:00
  • And the simple solution is to use a class and increment a counter that is an instance variable each time. –  Apr 02 '16 at 15:46