python | tkinter and threading: "main thread is not in main loop"

Viewed 10101

I'm working on an "multilayered" GUI for my company to monitor temperature and status. Because I'm quite new to the python programming, I could use some help with my code.

Quick code explanation:

The code is structured by classes. The "Main" inits the main window (tkinter) and creates the other frames to display if wanted. Every other class, except "canvas", is a frame which will display different things.

The canvas in each contains an image and some text/variable text. A thread is used to get data from a database and change the text in the canvas.

The Problem:

Every time, the thread accessed the canvas and tries to change the text or create an new one, the error "main thread is not in main loop" is thrown.

Exception in thread Thread-1:
Traceback (most recent call last):
  File "/usr/lib/python3.4/threading.py", line 920, in _bootstrap_inner
    self.run()
  File "/usr/lib/python3.4/threading.py", line 868, in run
    self._target(*self._args, **self._kwargs)
  File "/home/pi/Documents/Programme/MM/TEST_Dateien/TEST_QUEUE.py", line 154, in __call__
    canvUbersicht.create_text(500,500, text="HOIIIIII")
  File "/usr/lib/python3.4/tkinter/__init__.py", line 2345, in create_text
    return self._create('text', args, kw)
  File "/usr/lib/python3.4/tkinter/__init__.py", line 2321, in _create
    *(args + self._options(cnf, kw))))
RuntimeError: main thread is not in main loop

If I init the thread without calling the canvas (e.g. "print("hey")), everything works fine.

Where does my mainloop / mainthread begin and where do they end?

I have already read several Questions and rearranged the code / thread, but it won't change anything.

I would be really happy, if someone could help me.

important code:

from tkinter import*
from DataSQL import*

class Main(Tk):
    def __init__(self, vollbild=False):
        print("Main INIT")
        Tk.__init__(self)                               #Initialisieren von Tkinter und somit erzeugen des Fensters
        Tk.geometry(self, ("1920x1080"))                #setzen von verschiedenen Parametern für das Fenster
        Tk.title(self, ("Laborüberwachung"))
        Tk.wm_overrideredirect(self, vollbild)

        container = Frame(self)                             #Frame "container" wird im eigenen Objekt(also main fenster) erstellt
        container.pack(side="top",fill="both",expand=True)  #Platzieren des Frames und ausfüllen des ganzen Fensters
        container.grid_rowconfigure(0, weight=1)            #Platzieren im Grid
        container.grid_columnconfigure(0, weight=1)         #Platzieren im Grid
        
        self.frames = {}                                #Erstellt leere Liste
        
        for F in (Ubersicht, Settings, Pin, BR167, BR213, Star3, Telematik):
            frame = F(container, self)                  #Objekt frame wird erzeugt durch aufruf von Konstruktoren der Klassen(Frameklassen zB "Übersicht")
            self.frames[F] = frame                      #Objekte werden in der Liste gespeichert, unter dem Namen der Klasse(zB "frames[Übersicht]")
            frame.grid(row=0, column=0, sticky="nsew")  #Platzieren des Frames im Grid, wobei das Grid aus nur einem Feld besteht, wird nur für die Funktionalität benötigt

        self.show_frame(Ubersicht)                      #Zeigt immer die Übersichtsseite bei StartUp
        #print(self.frames)

    def show_frame(self, cnt):                         #Methode zum Framewechsel und somit umschalten zwischen 'Tabs'
        frame = self.frames[cnt]
        frame.tkraise()
        print("frame raised",cnt)


class Ubersicht(Frame):

    def __init__(self, parent, controller):
        print("Ubersicht INIT")
        Frame.__init__(self, parent)
        
        self.interfaceImage = PhotoImage(file="/home/pi/Documents/Programme/Laborueberwachung3/IMG 1080/Übersicht.png")
        canvUbersicht = canv(self, controller, img=self.interfaceImage, Uber=1)
 
        self.__eventExit = threading.Event()
        self.__thread = threading.Thread(target=self, args=(canvUbersicht, self.__eventExit,))
        self.__thread.start()
        
    def __call__(self,canvUbersicht,stop_event):
        while not stop_event.wait(1):
            canvUbersicht.create_text(500,500,text="HEY")

class canv(Canvas):
    def __init__(self, parent, controller, img=None, Uber=None):
        Canvas.__init__(self, parent)
        print("canvas INIT")
        self.config(width=1920,height=1080)
        self.place(x=0,y=0)
        if (img != None):
            self.create_image(0,0,anchor=NW,image=img)
            
        if (Uber == 1):
            clickAreaSettings = self.create_rectangle(0,0,350,60, fill="", outline="")
            self.tag_bind(clickAreaSettings, '<Button-1>',lambda event: controller.show_frame(Pin))
        else:
            clickAreaBack = self.create_rectangle(0,0,350,60, fill="", outline="")
            self.tag_bind(clickAreaBack,'<Button-1>', lambda event: controller.show_frame(Ubersicht)) 

        clickAreaBR167 = self.create_rectangle(0,60,350,315, fill="", outline="")
        clickAreaBR213 = self.create_rectangle(0,316,350,570, fill="", outline="")
        clickAreaStar3 = self.create_rectangle(0,571,350,825, fill="", outline="")
        clickAreaTelematik = self.create_rectangle(0,826,350,1080, fill="", outline="")
        self.tag_bind(clickAreaBR167,'<Button-1>', lambda event: controller.show_frame(BR167))
        self.tag_bind(clickAreaBR213,'<Button-1>', lambda event: controller.show_frame(BR213))
        self.tag_bind(clickAreaStar3,'<Button-1>', lambda event: controller.show_frame(Star3))
        self.tag_bind(clickAreaTelematik,'<Button-1>', lambda event: controller.show_frame(Telematik))
       
app = Main(vollbild=False)
app.mainloop
1 Answers

Where does my mainloop / mainthread begin and where do they end?

The main thread is all the code that you don't explicitly call in another thread.

Tkinter is single-threaded, and all access to widgets must be from the same thread. When you do self.__thread = threading.Thread(target=self, ...) you are causing some tkinter code to run in another thread.

You will need to rewrite your application so that the data gathering is in a separate thread which communicates with the main GUI thread via a thread-safe queue. You can push information on the queue from the thread, and the gui thread can poll the queue.

Related