Home > OS >  change ttk button theme in threaded python program
change ttk button theme in threaded python program

Time:06-29

I know that TKinter is not save when you run it in threads. Never mind I created a program where I start the tkinter modul in a thread. It works just fine for me until I add themesettings to the code.

import tkinter as tk
import time
from tkinter import ttk
from _thread import *
import os
import random

raster = {"breite": 5, "hohe": 4}

def placeitems(itemname):
    print("placebuttononrightspot")

def none():
    pass

def startprozess(name, *mitgabe):
    print(name)
    if mitgabe == ():
        start_new_thread(name, ())
    else:
        start_new_thread(name, (mitgabe))

#normstyle = ttk.Style()
#pressstyle = ttk.Style()
#normstyle.configure('B1.TButton', foreground='black', background='light gray')
#pressstyle.configure('B2.TButton', foreground='white', background='blue')

def reboot():
    os.system('sudo reboot')

def Ö3_Website():
    print("dosomething")

def Youtube_Website():
    print("dosomething")

def pihole():
    print("dosomething")

def musiktest(aktion):
    print("dosomething")

def screenhauptmenu():
    global pos
    global screen_width
    global screen_height
    pos = [False]
    pos = pos * raster["breite"] * raster["hohe"]
    hauptmenu = tk.Tk()
    screen_width = hauptmenu.winfo_screenwidth()
    screen_height = hauptmenu.winfo_screenheight()
    hauptmenu.geometry(f'{screen_width}x{screen_height} 0 0')
    hauptmenu.title(hauptmenu)
    hauptmenu.resizable(False, False)
    hauptmenu.attributes('-fullscreen', True)
    normstyle = ttk.Style()
    pressstyle = ttk.Style()
    normstyle.configure('B1.TButton', foreground='black', background='light gray')
    pressstyle.configure('B2.TButton', foreground='white', background='blue')
    #global normstyle
    #global pressstyle
    global button0onscreenhauptmenu
    button0 = ttk.Button(hauptmenu, text="Musikplayer", command = lambda: startprozess(switchscreen, 'screenmusikplayer'), style = 'B2.TButton')
    placeitems(button0)
    global button1onscreenhauptmenu
    button1 = ttk.Button(hauptmenu, text="Einstellungen", command = lambda: startprozess(switchscreen, 'screeneinstellungen'), style="B1.TButton")
    placeitems(button1)
    global button2onscreenhauptmenu
    button2 = ttk.Button(hauptmenu, text="Adminbereich", command = lambda: startprozess(switchscreen, 'screenadminbereich'), style="B1.TButton")
    placeitems(button2)
    global button3onscreenhauptmenu
    button3 = ttk.Button(hauptmenu, text="Energieoptionen", command = lambda: startprozess(switchscreen, 'screenenergieoptionen'), style="B1.TButton")
    placeitems(button3)
    global button4onscreenhauptmenu
    button4 = ttk.Button(hauptmenu, text="Pi neustarten", command = lambda: startprozess(reboot), style="B1.TButton")
    placeitems(button4)
    global button5onscreenhauptmenu
    button5 = ttk.Button(hauptmenu, text="Ö3 Webplayer", command = lambda: startprozess(Ö3_Website), style="B1.TButton")
    placeitems(button5)
    global button6onscreenhauptmenu
    button6 = ttk.Button(hauptmenu, text="Youtube", command = lambda: startprozess(Youtube_Website), style="B1.TButton")
    placeitems(button6)
    global button7onscreenhauptmenu
    button7 = ttk.Button(hauptmenu, text="Pi-Hole Dashboard", command = lambda: startprozess(pihole), style="B1.TButton")
    placeitems(button7)
    global screenhauptmenuoffen
    while screenhauptmenuoffen == True:
        '''if button0onscreenhauptmenu == True:
            button0.configure(style = "B2.TButton")
        else:
            button0.configure(style = "B1.TButton")
        if button1onscreenhauptmenu == True:
            button1.configure(style = "B2.TButton")
        else:
            button1.configure(style = "B1.TButton")
        if button2onscreenhauptmenu == True:
            button2.configure(style = "B2.TButton")
        else:
            button2.configure(style = "B1.TButton")
        if button3onscreenhauptmenu == True:
            button3.configure(style = "B2.TButton")
        else:
            button3.configure(style = "B1.TButton")
        if button4onscreenhauptmenu == True:
            button4.configure(style = "B2.TButton")
        else:
            button4.configure(style = "B1.TButton")
        if button5onscreenhauptmenu == True:
            button5.configure(style = "B2.TButton")
        else:
            button5.configure(style = "B1.TButton")
        if button6onscreenhauptmenu == True:
            button6.configure(style = "B2.TButton")
        else:
            button6.configure(style = "B1.TButton")
        if button7onscreenhauptmenu == True:
            button7.configure(style = "B2.TButton")
        else:
            button7.configure(style = "B1.TButton")'''
        hauptmenu.update()
        time.sleep(0.1)
    hauptmenu.destroy()

def screeneinstellungen():
    global pos
    global screen_width
    global screen_height
    pos = [False]
    pos = pos * raster["breite"] * raster["hohe"]
    einstellungen = tk.Tk()
    screen_width = einstellungen.winfo_screenwidth()
    screen_height = einstellungen.winfo_screenheight()
    einstellungen.geometry(f'{screen_width}x{screen_height} 0 0')
    einstellungen.title(einstellungen)
    einstellungen.resizable(False, False)
    einstellungen.attributes('-fullscreen', True)
    normstyle = ttk.Style()
    pressstyle = ttk.Style()
    normstyle.configure('B1.TButton', foreground='black', background='light gray')
    pressstyle.configure('B2.TButton', foreground='white', background='blue')

    global button0onscreeneinstellungen
    button0 = ttk.Button(einstellungen, text="Hauptmenü", command = lambda: startprozess(switchscreen, 'screenhauptmenu'), style="B2.TButton")
    placeitems(button0)
    global screeneinstellungenoffen
    while screeneinstellungenoffen == True:
        if button0onscreeneinstellungen == True:
            button0.configure(style = "B2.TButton")
        else:
            button0.configure(style = "B1.TButton")
        einstellungen.update()
        time.sleep(0.1)
    einstellungen.destroy()

def screenmusikplayer():
    print("weiterer screen mit vielen Tasten")

button0onscreenhauptmenu = True
button1onscreenhauptmenu = False
button2onscreenhauptmenu = False
button3onscreenhauptmenu = False
button4onscreenhauptmenu = False
button5onscreenhauptmenu = False
button6onscreenhauptmenu = False
button7onscreenhauptmenu = False
button0onscreeneinstellungen = False


def switchscreen(toscreen):
    global screenhauptmenu
    global screeneinstellungen
    def startupscreenhauptmenu():
        global screenhauptmenuoffen
        global screeneinstellungenoffen
        global screenadminbereichoffen
        global screenmusikplayeroffen
        global screenenergieoptionenoffen
        screenhauptmenuoffen = True
        screeneinstellungenoffen = False
        screenadminbereichoffen = False
        screenmusikplayeroffen = False
        screenenergieoptionenoffen = False
        startprozess(screenhauptmenu)
    def startupscreeneinstellungen():
        global screenhauptmenuoffen
        global screeneinstellungenoffen
        global screenadminbereichoffen
        global screenmusikplayeroffen
        global screenenergieoptionenoffen
        screenhauptmenuoffen = False
        screeneinstellungenoffen = True
        screenadminbereichoffen = False
        screenmusikplayeroffen = False
        screenenergieoptionenoffen = False
        startprozess(screeneinstellungen)
    def startupscreenmusikplayer():
        global screenhauptmenuoffen
        global screeneinstellungenoffen
        global screenadminbereichoffen
        global screenmusikplayeroffen
        global screenenergieoptionenoffen
        screenhauptmenuoffen = False
        screeneinstellungenoffen = False
        screenadminbereichoffen = False
        screenmusikplayeroffen = True
        screenenergieoptionenoffen = False
        startprozess(screenmusikplayer)
    startup = 'startup'   str(toscreen)
    eval(startup)()

def beginsteuerung():
    global screenhauptmenuoffen
    screenhauptmenuoffen = True
    screenhauptmenu()

if __name__ == '__main__':
    beginsteuerung()

if I run this code in this or a little other version I get an errorcode: main thread is not in main loop. I tried to configure the theme once in the main loop and import it with global in the function, I tried to configure it in the function with same or different variablenames.

I hope my code is not that hard to read. I hope you can help me with that problem. Thank you!

CodePudding user response:

I found the solution by writing a testcode so it's easier to read it. I don't know why the code has to be that complicated but I want to share my experience of the last days. The following codes have almost the same functionallity for the user. The second code doesn't work because of the style lines. The 3rd works again because of an extra function called 'screenmanager' in my code

#Code without coloured buttons
#Import some Libraries
import tkinter as tk
import time
from tkinter import ttk
from _thread import *
import os
#Var to tell a screen to close
screenopen = True
#threadstarter
def startprozess(name, *var):
    print(name)
    time.sleep(1)
    if var == ():
        start_new_thread(name, ())
    else:
        start_new_thread(name, (var))
#screen1   
def screen():
    hauptmenu = tk.Tk()
    screen_width = hauptmenu.winfo_screenwidth() / 2
    screen_height = hauptmenu.winfo_screenheight()
    hauptmenu.geometry(f'400x400 50 50')
    hauptmenu.title("hauptmenu")
    hauptmenu.resizable(False, False)
    hauptmenu.attributes('-fullscreen', True)
    button0 = ttk.Button(hauptmenu, text="Reload", command = lambda: [setfalse(), startprozess(screen2)])
    button0.place(x = 10, y = 10, heigh = 50, width = 100)
    global screenopen
    while screenopen == True:
        hauptmenu.update()
        time.sleep(0.1)
    hauptmenu.destroy()
#closeoldscreen and allow new screen to open
def setfalse():
    global screenopen
    screenopen = False
    time.sleep(0.5)
    screenopen = True
#screen2
def screen2():
    hauptmenu2 = tk.Tk()
    screen_width = hauptmenu2.winfo_screenwidth() / 2
    screen_height = hauptmenu2.winfo_screenheight()
    hauptmenu2.geometry(f'400x400 50 50')
    hauptmenu2.title("hauptmenu")
    hauptmenu2.resizable(False, False)
    hauptmenu2.attributes('-fullscreen', True)
    button1 = ttk.Button(hauptmenu2, text="Reload", command = lambda: [setfalse(), startprozess(screen)])
    button1.place(x = 10, y = 10, heigh = 50, width = 100)
    button2 = ttk.Button(hauptmenu2, text="do", command = lambda: startprozess(dosome))
    button2.place(x = 110, y = 10, heigh = 50, width = 100)
    global screenopen
    while screenopen == True:
        hauptmenu2.update()
        time.sleep(0.1)
    hauptmenu2.destroy()
#rando function to simulate something
def dosome():
    print("This is some code")
    

screen()

Then I added some lines to define 2 types of styles for the buttons

#code with coloured buttons which doesn't work
#import some Libraries
import tkinter as tk
import time
from tkinter import ttk
from _thread import *
import os
#var to tell screen to close
screenopen = True
#threadstarter
def startprozess(name, *var):
    print(name)
    time.sleep(1)
    if var == ():
        start_new_thread(name, ())
    else:
        start_new_thread(name, (var))
#screen1  
def screen():
    hauptmenu = tk.Tk()
    screen_width = hauptmenu.winfo_screenwidth() / 2
    screen_height = hauptmenu.winfo_screenheight()
    hauptmenu.geometry(f'400x400 50 50')
    hauptmenu.title("hauptmenu")
    hauptmenu.resizable(False, False)
    hauptmenu.attributes('-fullscreen', True)
    style1 = ttk.Style()
    style2 = ttk.Style()
    style1.configure('B1.TButton', foreground='black', background='light gray')
    style2.configure('B2.TButton', foreground='white', background='blue')
    button0 = ttk.Button(hauptmenu, text="Reload", command = lambda: [setfalse(), startprozess(screen2)], style="B2.TButton")
    button0.place(x = 10, y = 10, heigh = 50, width = 100)
    global screenopen
    while screenopen == True:
        hauptmenu.update()
        time.sleep(0.1)
    hauptmenu.destroy()
#close old screen and allow new screen to open
def setfalse():
    global screenopen
    screenopen = False
    time.sleep(0.5)
    screenopen = True
#screen2
def screen2():
    hauptmenu2 = tk.Tk()
    screen_width = hauptmenu2.winfo_screenwidth() / 2
    screen_height = hauptmenu2.winfo_screenheight()
    hauptmenu2.geometry(f'400x400 50 50')
    hauptmenu2.title("hauptmenu")
    hauptmenu2.resizable(False, False)
    hauptmenu2.attributes('-fullscreen', True)
    style1 = ttk.Style()
    style2 = ttk.Style()
    style1.configure('B1.TButton', foreground='black', background='light gray')
    style2.configure('B2.TButton', foreground='white', background='blue')
    button1 = ttk.Button(hauptmenu2, text="Reload", command = lambda: [setfalse(), startprozess(screen)], style="B2.TButton")
    button1.place(x = 10, y = 10, heigh = 50, width = 100)
    button2 = ttk.Button(hauptmenu2, text="do", command = lambda: startprozess(dosome), style="B1.TButton")
    button2.place(x = 110, y = 10, heigh = 50, width = 100)
    global screenopen
    while screenopen == True:
        hauptmenu2.update()
        time.sleep(0.1)
    hauptmenu2.destroy()
#random function to simulate something
def dosome():
    print("This is some code")
    

screen()

And this 3rd code I created before the second one and I was wondering why it works but with the second code above I finally know what I have to change in my original code

#code with coloured buttons with 'screenmanager' which works again
#import some Libraries
import tkinter as tk
import time
from tkinter import ttk
from _thread import *
import os
#var to tell screen to close
screenopen = True
#symbol var for screenmanager to switch between screens easy
screenvar = False
#threadstarter
def startprozess(name, *var):
    print(name)
    time.sleep(1)
    if var == ():
        start_new_thread(name, ())
    else:
        start_new_thread(name, (var))
#the new screenmanager
def screenmanager():
    global screenopen
    global screenvar
    screenopen = False
    time.sleep(2)
    screenopen = True
    if screenvar == False:
        startprozess(screen)
        screenvar = True
    else:
        startprozess(screen2)
        screenvar = False
#screen1
def screen():
    hauptmenu = tk.Tk()
    screen_width = hauptmenu.winfo_screenwidth() / 2
    screen_height = hauptmenu.winfo_screenheight()
    hauptmenu.geometry(f'400x400 50 50')
    hauptmenu.title("hauptmenu")
    hauptmenu.resizable(False, False)
    hauptmenu.attributes('-fullscreen', True)
    style1 = ttk.Style()
    style2 = ttk.Style()
    style1.configure('B1.TButton', foreground='black', background='light gray')
    style2.configure('B2.TButton', foreground='white', background='blue')
    button0 = ttk.Button(hauptmenu, text="Reload", command = lambda: [setfalse(), startprozess(screenmanager)], style="B2.TButton")
    button0.place(x = 10, y = 10, heigh = 50, width = 100)
    global screenopen
    while screenopen == True:
        hauptmenu.update()
        time.sleep(0.1)
    hauptmenu.destroy()
#closeoldscreen and allow new to open
def setfalse():
    global screenopen
    screenopen = False
    time.sleep(0.5)
    screenopen = True
#screen2
def screen2():
    hauptmenu2 = tk.Tk()
    screen_width = hauptmenu2.winfo_screenwidth() / 2
    screen_height = hauptmenu2.winfo_screenheight()
    hauptmenu2.geometry(f'400x400 50 50')
    hauptmenu2.title("hauptmenu")
    hauptmenu2.resizable(False, False)
    hauptmenu2.attributes('-fullscreen', True)
    style1 = ttk.Style()
    style2 = ttk.Style()
    style1.configure('B1.TButton', foreground='black', background='light gray')
    style2.configure('B2.TButton', foreground='white', background='blue')
    button1 = ttk.Button(hauptmenu2, text="Reload", command = lambda: [setfalse(), startprozess(screenmanager)], style="B2.TButton")
    button1.place(x = 10, y = 10, heigh = 50, width = 100)
    button2 = ttk.Button(hauptmenu2, text="do", command = lambda: startprozess(dosome), style="B1.TButton")
    button2.place(x = 110, y = 10, heigh = 50, width = 100)
    global screenopen
    while screenopen == True:
        hauptmenu2.update()
        time.sleep(0.1)
    hauptmenu2.destroy()
#random function to simulate something
def dosome():
    print("This is some code")
    

screen()

Last but not least I'm not sure why it works that way, but I think when I call the new screen at the threadstarter directly I give the new screen some variables which will end in the errormessage main thread not in main loop in the second code. Eventually someone has ideas what the real bug in the second code is.

  • Related