Skip to content
Advertisement

Tkinter button color flashing for no reason

I’m making a refresh function to change the color of a button in tkinter, the problem is that this color must come from another function that returns a string and everytime I update everything flashes for a moment.

I’ve tried using StringVar() and only update the color without updating the whole layout but it would just get stuck in the loop and never display the layout. I tried also dumping all objets into a list and then changing the whole grid at once instead of one by one without much success.

The core of the code is :

root = Tk()
frame=Frame(root)
root.title("States")
Grid.rowconfigure(root, 0, weight=1)
Grid.columnconfigure(root, 0, weight=1)
frame.grid(row=0, column=0, sticky=N+S+E+W)
grid=Frame(frame)
grid.grid(sticky=N+S+E+W, column=0, row=7, columnspan=2)
Grid.rowconfigure(frame, 7, weight=1)
Grid.columnconfigure(frame, 0, weight=1)

def loop_1():
        for x in range(4):
            for y in range(3):
                btnxy(x,y,frame)
        for x in range(4):
            Grid.columnconfigure(frame, x, weight=1)
        for y in range(3):
            Grid.rowconfigure(frame, y, weight=1)
        root.after(1000,loop_1)
loop_1()

where btnxy creates a button and places it in a grid

def btnxy(x,y,frame):
    index=4*y+x+1
    if index>8:
        message="Relay "+str(index-8)
        btn = Button(frame,text=message, command=lambda p=(index-8): Relay(index-8), bg="blue", relief="groove", height=10, width=30)
    else:
        message="Entrée "+str(index)
        color=check_color(index)
        btn = Button(frame,text=message, command="", bg=color, relief="groove", height=10, width=30)
    btn.grid(column=x, row=y, sticky=N+S+E+W

And check_color is a function that returns a string with either “red” or “green”

To my understandig this should not take much processing power and thus update easly but it flashes instead.

Advertisement

Answer

I tried to tinker a bit with your code, but I honestly could not get it to work and do not understand the logic behind it. There are multiple points that are not ok:

1) global imports are bad. Do not use from tkinter import *. Instead use import tkinter as tk. This improves readability, makes things explicit and has many other benefits (You can easily find out more on Google on this)

2)You use the function loop1 to place the buttons in the desired frame. That’s all well and good, but you don’t pass the frame as an argument, and the same holds for root. If you indeed modify your code to account for this, you will see that the flashing disappears, though you will have problems with root.after due to the lack of arguments.

The solution here is to change the structure of the app so that the buttons and the root are easily accessible and available. A way to get you started can be this code:

import tkinter as tk
from random import shuffle


def get_random_colors():
    colors = ["red" for i in range(4)] + ["blue" for i in range(4)] + ["green" for i in range(4)]
    shuffle(colors)
    return colors


class MyGUI(tk.Tk):

    def __init__(self):
        tk.Tk.__init__(self)

        self.frame = tk.Frame()
        self.frame.pack()

        self.n_rows = 3
        self.n_cols = 4

        self.bt_list = []

        for x in range(self.n_rows):
            for y in range(self.n_cols):
                self.bt_list.append(tk.Button(self.frame, text="Button {}.{}".format(x, y)))
                self.bt_list[-1].grid(row=x, column=y)

    def change_colors(self):
        colors = get_random_colors()
        for index, button in enumerate(self.bt_list):
            button.configure(bg=colors[index])
        self.after(5000, self.change_colors)

root = MyGUI()
root.after(5000, root.change_colors)
root.mainloop()

First of all, the main frame is now an attribute of the class, so it suffices to just call self.frame whenever I need it in the class, as long as self was passed to the function.
Then I decided to store the buttons in a list, so that I can always access them in my class through self. This is useful because in the change_colors function I can just loop through the list and change the button through .configure(bg=<color here>).
Last point is the .after. I call it once outside the the class constructor just after I define root, and then inside the change_colors function using self. The final code should change the colors of the button randomly every 5 seconds.

Hope it helps!

User contributions licensed under: CC BY-SA
3 People found this is helpful
Advertisement