0

I have a GUI that contains several frames, each containing multiple labels/entry fields. I am trying to add a "Settings" option that will allow the user to change the background color of all frames & labels. So far I have managed to accomplish the task however with a caveat of a new Tk window popping up with the selected background instead of updating on the current window.

import tkinter as tk
from tkinter import ttk
from tkinter import colorchooser


bg_hex = '#f0f0f0f0f0f0'  #default background color

def pick_color():
    global bg_hex
    bg_color = colorchooser.askcolor()
    bg_hex = bg_color[1]
    Master().update()
    print(bg_hex)


class Master(tk.Tk):

    def __init__(self, *args, **kwargs):
        tk.Tk.__init__(self, *args, **kwargs)
        container = tk.Frame(self)
        container.pack(side='top', fill='both', expand=True)
        container.grid_rowconfigure(0, weight=1)
        container.grid_columnconfigure(0, weight=1)


        self.frames = {}

        for F in (HomePage, PageOne, PageTwo, Settings):        
            frame = F(container, self)
            self.frames[F] = frame
            frame.config(bg = bg_hex)
            frame.grid(row=0, column=0, sticky='nsew')
        self.show_frame(HomePage)

    def show_frame(self, cont):
        frame = self.frames[cont]
        frame.tkraise()



class HomePage(tk.Frame):

    def __init__(self, parent, controller):
        tk.Frame.__init__(self, parent)
        label = tk.Label(self, text='Home Page', font=('Verdana', 12), bg=bg_hex)
        label.pack(pady=5)
        button1 = tk.Button(self, text='Page One', command=lambda: controller.show_frame(PageOne))
        button1.pack(pady=5, ipadx=2)
        button2 = tk.Button(self, text='Page Two', command=lambda: controller.show_frame(PageTwo))
        button2.pack(pady=5)
        button3 = tk.Button(self, text='Settings', command=lambda: controller.show_frame(Settings))
        button3.pack(side='bottom', pady=10)

class PageOne(tk.Frame):

    def __init__(self, parent, controller):
        tk.Frame.__init__(self, parent)
        tk.Label(self, text='Page One', font='Verdana 14 bold underline', bg=bg_hex).grid(row=0, columnspan=2, pady=5)
        button1 = tk.Button(self, text='Back to Home', command=lambda: controller.show_frame(HomePage))
        button1.grid()


class PageTwo(tk.Frame):

    def __init__(self, parent, controller):
        tk.Frame.__init__(self, parent)
        tk.Label(self, text='Page Two', font='Verdana 14 bold underline', bg=bg_hex).grid(row=0, columnspan=2,pady=5)
        button1 = tk.Button(self, text='Back to Home', command=lambda: controller.show_frame(HomePage))
        button1.grid()

class Settings(tk.Frame):

    def __init__(self, parent, controller):
        tk.Frame.__init__(self, parent)
        tk.Label(self, text='Settings', font='Verdana 14 bold underline', bg=bg_hex).grid(row=0, columnspan=2,pady=5)
        button1 = tk.Button(self, text='Back to Home', command=lambda: controller.show_frame(HomePage))
        button1.grid() 
        button2 = tk.Button(self, text='Choose Background', command= pick_color)
        button2.grid()

Master().mainloop()

No errors occur when running the the block of code, but when you select the "Choose Background" button and pick a color, a new Tk window opens with the selected background color instead of updating the current Tk window.

**Updating code to reflect no global variables in hopes that it helps someone else down the road.

I added self.controller = controller under each Frame class, combined pick_color and color_update into 1 function and placed under the Tk class.

def pick_color_bg(self):
    bg_color = colorchooser.askcolor()
    bg_hex = bg_color[1]
    # Loop through pages and contained widgets and set color
    for cls, obj in self.frames.items():
        obj.config(bg=bg_hex)   # Set frame bg color
        for widget in obj.winfo_children():
            if '!label' in str(widget):
                widget.config(bg=bg_hex)    # Set label bg color

Lastly, change the Button command to command=self.controller.pick_color_bg. With these changes I was able to eliminate the need for global variables.

GeeSama
  • 39
  • 4

1 Answers1

1

In the function pick_color() you create a new instance of Tk() which gets the new color:

Master().update()   # Creates a new root window!

To change the color of the existing root window you will have to save a reference to it. Also you will have to write a function in the class Master() which updates the bg color. The color does not update automatically when you call update(), you have to configure the bg color for each frame.

some more

I'm having trouble reading your code without rewriting it quite a bit. You use the name Master for the class that instantiates the root window. I would call it Application or similar as the name master usually means a master as in "master and server" or maybe "parent". Also you use the name frame, which usually is the name of a frame, as the name for the different page class instances (HomePage, ... etc). This makes it difficult to read. It's like the word blue written with red letters.

I would rewrite it with names that are more descriptive which makes it easier to grasp. Then the problems will be easier to find and correct.

and even more

Took me a while but here is an example of how it could work with minor changes:

import tkinter as tk
from tkinter import ttk
from tkinter import colorchooser


bg_hex = '#f0f0f0f0f0f0'  #default background color

def pick_color():
    global bg_hex
    bg_color = colorchooser.askcolor()
    bg_hex = bg_color[1]
    root.update_color()     # Call function for updating color
    print(bg_hex)


class Master(tk.Tk):

    def __init__(self, *args, **kwargs):
        tk.Tk.__init__(self, *args, **kwargs)
        container = tk.Frame(self)
        container.pack(side='top', fill='both', expand=True)
        container.grid_rowconfigure(0, weight=1)
        container.grid_columnconfigure(0, weight=1)


        self.frames = {}

        for F in (HomePage, PageOne, PageTwo, Settings):        
            frame = F(container, self)
            self.frames[F] = frame
            frame.config(bg = bg_hex)
            frame.grid(row=0, column=0, sticky='nsew')
        self.show_frame(HomePage)

    def show_frame(self, cont):
        frame = self.frames[cont]
        frame.tkraise()

    def update_color(self):
        # Loop through pages and contained widgets and set color
        for cls, obj in self.frames.items():
            obj.config(bg=bg_hex)   # Set frame bg color
            for widget in obj.winfo_children():
                if '!label' in str(widget):
                    widget.config(bg=bg_hex)    # Set label bg color



class HomePage(tk.Frame):

    def __init__(self, parent, controller):
        tk.Frame.__init__(self, parent)
        label = tk.Label(self, text='Home Page', font=('Verdana', 12), bg=bg_hex)
        label.pack(pady=5)
        button1 = tk.Button(self, text='Page One', command=lambda: controller.show_frame(PageOne))
        button1.pack(pady=5, ipadx=2)
        button2 = tk.Button(self, text='Page Two', command=lambda: controller.show_frame(PageTwo))
        button2.pack(pady=5)
        button3 = tk.Button(self, text='Settings', command=lambda: controller.show_frame(Settings))
        button3.pack(side='bottom', pady=10)

class PageOne(tk.Frame):

    def __init__(self, parent, controller):
        tk.Frame.__init__(self, parent)
        tk.Label(self, text='Page One', font='Verdana 14 bold underline', bg=bg_hex).grid(row=0, columnspan=2, pady=5)
        button1 = tk.Button(self, text='Back to Home', command=lambda: controller.show_frame(HomePage))
        button1.grid()


class PageTwo(tk.Frame):

    def __init__(self, parent, controller):
        tk.Frame.__init__(self, parent)
        tk.Label(self, text='Page Two', font='Verdana 14 bold underline', bg=bg_hex).grid(row=0, columnspan=2,pady=5)
        button1 = tk.Button(self, text='Back to Home', command=lambda: controller.show_frame(HomePage))
        button1.grid()

class Settings(tk.Frame):

    def __init__(self, parent, controller):
        tk.Frame.__init__(self, parent)
        tk.Label(self, text='Settings', font='Verdana 14 bold underline', bg=bg_hex).grid(row=0, columnspan=2,pady=5)
        button1 = tk.Button(self, text='Back to Home', command=lambda: controller.show_frame(HomePage))
        button1.grid() 
        button2 = tk.Button(self, text='Choose Background', command= pick_color)
        button2.grid()

root = Master()     # Save a reference to the root window
root.mainloop()

I would recommend against changing color by means of a function in global scope. I think it would be better placed as a function of the Master() class. Then you would not have to use global variables.

figbeam
  • 7,001
  • 2
  • 12
  • 18
  • Thank you! this worked for me. I couldn't find much info online when it came to this particular topic. I utilized the same template you provided to extend the functionality to allow the user to change the font color of the labels as well. I've taken your recommendations in renaming the Class Master() as my knowledge of OOP is rudimentary at best and I was confusing myself thinking it was a parent class. I will have to use the two functions in the global scope for the time being until I wrap my head around structuring it under the ```Class App()```. Thank you again :) – GeeSama Jun 17 '19 at 03:14
  • There is a good thread which discusses OOP structure: [Best way to structure a tkinter application](https://stackoverflow.com/a/17470842/8172933) – figbeam Jun 17 '19 at 04:51
  • Thanks again for pointing me in the right direction @figbeam. I read the thread you linked and few others that help me update the code to not depend on global variables. – GeeSama Jun 19 '19 at 01:02