GeeSama
GeeSama

Reputation: 39

New Tk window opens when changing background color

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.

Upvotes: 0

Views: 107

Answers (1)

figbeam
figbeam

Reputation: 7176

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.

Upvotes: 1

Related Questions