Search code examples
pythontkintertcltk-toolkit

Launch multiple tkinter applications in separate threads


I am trying to launch multiple instances of a tkinter class in separate threads. Everytime the second screen appears, the following error is thrown: "_tkinter.TclError: image "pyimage2" doesn't exist". I expected them to be two separate instances, but according to internet searches this is not the case.

Here's a simplified PoC:

import subprocess, threading, time
import tkinter
from PIL import Image, ImageTk


class App:
    def __init__(self, display: str):
        self.root = tkinter.Tk(screenName=display)
        self.background_image = Image.new("RGB", (200, 200), (200, 200, 200))
        self.background = ImageTk.PhotoImage(self.background_image)
        self.label_background = tkinter.Label(
            self.root, image=self.background, borderwidth=0
        )
        self.root.mainloop()


class X(threading.Thread):
    def __init__(self, display):
        super().__init__()
        self.display = display
        subprocess.Popen(["Xephyr", self.display])
        # wait for X server to become available
        while (
            subprocess.run(
                ["xset", "-q", "-display", self.display],
                stdout=subprocess.DEVNULL,
                stderr=subprocess.DEVNULL,
            ).returncode
            != 0
        ):
            time.sleep(0.01)

    def run(self):
        # blocking call
        self.app = App(self.display)


x1 = X(":1")
x1.start()
x2 = X(":2")
x2.start()
x2.join()

And the according output:

$ python3 tkinter_test.py
Exception in thread Thread-2:
Traceback (most recent call last):
  File "/usr/lib/python3.10/threading.py", line 1016, in _bootstrap_inner
    self.run()
  File "/home/user/pyrdp/tkinter_test.py", line 35, in run
    self.app = App(self.display)
  File "/home/user/pyrdp/tkinter_test.py", line 11, in __init__
    self.label_background = tkinter.Label(
  File "/usr/lib/python3.10/tkinter/__init__.py", line 3187, in __init__
    Widget.__init__(self, master, 'label', cnf, kw)
  File "/usr/lib/python3.10/tkinter/__init__.py", line 2601, in __init__
    self.tk.call(
_tkinter.TclError: image "pyimage2" doesn't exist

Is it even possible to run multiple tkinter instances in different threads or do I need a different approach here? These instances will have to run in two separate X servers and do not need to share any resources or communicate among each other.


Solution

  • Everytime the second screen appears, the following error is thrown: "_tkinter.TclError: image "pyimage2" doesn't exist". I expected them to be two separate instances, but according to internet searches this is not the case.

    They are separate instances, which is why you're seeing the error. You didn't specify a master for each image so each one defaults to the first instance of Tk that was created. Thus, all of your images belong to the first instance and are not visible in the second instance. Thus, image "pyimage2" doesn't exist. The second image has an internal name of pyimage2, but there's only an image with that name in the first instance of Tk.

    The solution is to pass a value for master when creating the image so that the image is created in the appropriate tkinter window.

    self.background = ImageTk.PhotoImage(self.background_image, master=self.root)