5

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
Community
  • 1
  • 1
M.Michel
  • 51
  • 1
  • 1
  • 4
  • maybe thread try to use tkinter elements before `mainloop()` starts and creates them. Maybe try to use `sleep()` in thread so `mainloop()` will have time to create all objects. – furas Nov 16 '17 at 12:22
  • 1
    You've posted way too much code. Please condense that down into a [mcve] – Bryan Oakley Nov 16 '17 at 12:25
  • I think tkinter does not support changes to the GUI made from threads different from the main one (the one where `mainloop` runs). So you need to find a way to avoid changing the GUI from the other threads. For instance, you can use `after` in the main thread to check if the other thread is finished (see the second part of my answer to [this question](https://stackoverflow.com/questions/47307157/python-tkinter-destroy-toplevel-after-function/47307404#47307404)) – j_4321 Nov 16 '17 at 12:30
  • ok thank you for your answeres. I shortened the code to the (near) minimum. @furas i tried it, didn't work – M.Michel Nov 16 '17 at 12:39
  • have you considered using only `.after` for database polling? It should be enough. – Sens4 Nov 16 '17 at 13:53

1 Answers1

6

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.

Bryan Oakley
  • 370,779
  • 53
  • 539
  • 685
  • Is there any possibility to get the single thread working with little changes to the code ? Or is it easier to code in a queue ? – M.Michel Nov 16 '17 at 13:05