How do I destroy specific button in the loop when clicked? - python

I am trying to create a 10*10 board of buttons, which when clicked, the clicked button is destroyed and only that one. However, I don't know how to specify which button has been clicked.
from tkinter import *
root = Tk()
root.title("Board")
def buttonClick():
button.destroy()
for i in range(10):
for j in range(10):
button = Button(root, text="", padx=20, pady=10, command=buttonClick)
button.grid(row=i+1, column=j+1)
root.mainloop()

You have to create function which gets widget/button as argument and uses it with destroy()
def buttonClick(widget):
widget.destroy()
And first you have to create Button without command=
button = tk.Button(root, text="", padx=20, pady=10)
and later you can use this button as argument in command=.
button["command"] = lambda widget=button:buttonClick(widget)
It needs to use lambda to assign function with argument.
Because you create many buttons in loop so it also needs to use widget=button in lambda to create unique variable with value from button for every command. If you don't use it then all commands will use reference to the same (last) button - and click on every button will destroy only last button.
Full working code
import tkinter as tk # PEP8: `import *` is not preferred
# --- functions ---
def buttonClick(widget):
widget.destroy()
# --- main ---
root = tk.Tk()
root.title("Board")
for i in range(10):
for j in range(10):
button = tk.Button(root, text="x", padx=20, pady=10)
button["command"] = lambda widget=button:buttonClick(widget)
button.grid(row=i+1, column=j+1)
root.mainloop()
PEP 8 -- Style Guide for Python Code

This can be easily accomplished with a custom class if you're alright with that:
from tkinter import Button, Tk
root = Tk()
root.title("Board")
class Self_Destruct_Button(Button):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.configure(command=self.button_click)
def button_click(self):
self.destroy()
for i in range(10):
for j in range(10):
button = Self_Destruct_Button(root, text="", padx=20, pady=10)
button.grid(row=i + 1, column=j + 1)
root.mainloop()
So the custom class just assigns the command to a button_click method, which destroys the button.
As a side note, I also removed the wildcard import as that's not best practice.
Let me know if this works for you

Related

how to change the color of PanedWindow upon hovering over it, for multiple PanedWindow in tkinter?

I am trying to make PanedWindow change color when I hover mouse over it in tkinter.
now this works for a single iteration.
but when i try to do it for multiple panedwindows it only changes color of the last window.
import tkinter as tk
root = tk.Tk()
for i in range(10):
m1 = tk.PanedWindow(root, bd=4, relief="flat", bg="blue")
m1.pack()
def on_enter(e):
m1.config(background='OrangeRed3', relief="flat")
def on_leave(e):
m1.config(background='SystemButtonFace', relief="flat")
# Create a Button
button1 = tk.Button(m1, text=f"{i}")
button1.pack(pady=20)
# Bind the Enter and Leave Events to the Button
m1.bind('<Enter>', on_enter)
m1.bind('<Leave>', on_leave)
m1.add(button1)
tk.mainloop()
Since at each iteration of the loop all variables are overwritten, the functions are bound to the last created element. It is necessary to pass the desired element to the function. It is even better to collect everything created in dictionaries, so that in the future you can easily change them.
import tkinter as tk
from functools import partial
ms = {}
btns = {}
root = tk.Tk()
def on_enter(m, e):
m.config(background='OrangeRed3', relief="flat")
def on_leave(m, e):
m.config(background='SystemButtonFace', relief="flat")
for i in range(10):
ms[i] = tk.PanedWindow(root, bd=4, relief="flat", bg="blue")
ms[i].pack()
# Create a Button
btns[i] = tk.Button(ms[i], text=f"{i}")
btns[i].pack(pady=20)
# Bind the Enter and Leave Events to the Button
ms[i].bind('<Enter>', partial(on_enter, ms[i]))
ms[i].bind('<Leave>', partial(on_leave, ms[i]))
ms[i].add(btns[i])
tk.mainloop()

started using Tkinter, is it possible to show/hide a Label with a Checkbutton?

just looking for an example, I know its possible with buttons but I wanted to use the different states of a Checkbutton (onvalue and offvalue) to show and hide a label.
You can achieve this using the check button property command to call a function every time user changes the state of the check button.
def show_hide_func():
if cb_val == 1:
your_label.pack_forget()
# if you are using grid() to create, then use grid_forget()
elif cb_val == 0:
your_label.pack()
cb_val = tk.IntVar()
tk.Checkbutton(base_window, text="Click me to toggle label", variable=cb_val , onvalue=1, offvalue=0, command=show_hide_func).pack()
For detailed documentation about the check button and other Tkinter widgets read here
Simply use comman=function to run code which hide (pack_forget()/grid_forget()/place_forget()) and show it again (pack()/grid(...)/place(...)).
With pack() can be the problem because it will show it again but at the end of other widgets - so you could keep Label inside Frame which will not have other widgets. Or you can use pack(before=checkbox) (or simiar options) to put again in the same place (before checkbox)
Label inside Frame
import tkinter as tk
# --- functions ---
def on_click():
print(checkbox_var.get())
if checkbox_var.get():
label.pack_forget()
else:
label.pack()
# --- main ---
root = tk.Tk()
frame = tk.Frame(root)
frame.pack()
label = tk.Label(frame, text='Hello World')
label.pack()
checkbox_var = tk.BooleanVar()
checkbox = tk.Checkbutton(root, text="select", variable=checkbox_var, command=on_click)
checkbox.pack()
root.mainloop()
use pack(before=checkbox)
import tkinter as tk
# --- functions ---
def on_click():
print(checkbox_var.get())
if checkbox_var.get():
label.pack_forget()
else:
label.pack(before=checkbox)
# --- main ---
root = tk.Tk()
label = tk.Label(root, text='Hello World')
label.pack()
checkbox_var = tk.BooleanVar()
checkbox = tk.Checkbutton(root, text="select", variable=checkbox_var, command=on_click)
checkbox.pack()
root.mainloop()

Python Tkinter simple value process

I just new for the GUI and need little help.
a=int(input())
if a==0:
print("hi")
else:
print("hello")
I want to change input process to click button, like a switch.
left button -> a=0
right button -> a=1
window=tkinter.Tk()
window.title("")
window.geometry("640x640+100+100")
window.resizable(True, True)
a=tkinter.Button(window, text="left")
a.pack()
b=tkinter.Button(window, text="right")
b.pack()
window.mainloop()
I can see left, right button but I don't know how to put values.
Is there any example I can use?
Thanks
Does this example help You:
from tkinter import Tk, Button
def switch(btn1, btn2):
btn1.config(state='disabled')
btn2.config(state='normal')
print(btn1['text'])
window = Tk()
window.title("")
on = Button(window, text="On", command=lambda: switch(on, off))
on.pack(side='left', expand=True, fill='x')
off = Button(window, text="Off", command=lambda: switch(off, on))
off.pack(side='left', expand=True, fill='x')
off.config(state='disabled')
window.mainloop()
If You have questions ask but here is pretty good site to look up tkinter widgets and what they do, their attributes.
Also I suggest You follow PEP 8
You need to add a function to each one that will be executed when the buttons are clicked like this:
import tkinter as tk
def left_clicked():
print("Left button clicked")
right_button.config(state="normal") # Reset the button as normal
left_button.config(state="disabled") # Disable the button
def right_clicked():
print("Right button clicked")
right_button.config(state="disabled")
left_button.config(state="normal")
window = tk.Tk()
window.title("")
window.geometry("640x640+100+100")
# window.resizable(True, True) # Unneeded as it is already the default
left_button = tk.Button(window, text="left", command=left_clicked)
left_button.pack(side="left")
right_button = tk.Button(window, text="right", command=right_clicked,
state="disabled")
right_button.pack(side="right")
window.mainloop()

Python - tkinter button freeze

I have problem with freezing application in Python. I used tkinter library to make some app. When I using Send button it calls the functions that lasts 3 minutes and freezing application on this 3 minutes... I want to see all logs from this function "live". I can't wait 3 minutes and then get all the logs.
Below is example of my code:
import tkinter as tk
from tkinter import ttk
from tkinter import scrolledtext
import time
class Frames:
def main_frame(self, win):
# Main Frame
main = ttk.LabelFrame(win, text="")
main.grid(column=0, row=0, sticky="WENS", padx=10, pady=10)
return main
def button_frame(self, win):
# Button Frame
butt_frame = ttk.LabelFrame(win, text="Button")
butt_frame.grid(column=0, row=0, sticky='NWS')
def _send_button():
for i in range(10):
self.write_text_console("{0}\n".format(i))
time.sleep(0.5)
# Send Button
ttk.Label(butt_frame, text=" ").grid(column=0, row=8)
button = tk.Button(butt_frame, text="Send", command=_send_button, foreground='black')
button.grid(column=0, row=13, sticky=tk.EW)
def scrolled_text_widget(self, win):
ttk.Label(win, text=" ").grid(column=0, row=1)
ttk.Label(win, text="Console Output:").grid(column=0, row=2, sticky=tk.W)
self.scr = scrolledtext.ScrolledText(win, width=100, height=10, wrap=tk.WORD, state="disabled")
self.scr.grid(column=0, row=3, columnspan=5)
def write_text_console(self, string, color="black", tag="console"):
self.scr.configure(state='normal')
self.scr.insert('end', string, tag)
self.scr.tag_config(tag, foreground=color)
self.scr.configure(state='disabled')
self.scr.see("end")
win = tk.Tk()
win.geometry("845x300")
win.resizable(0, 0)
frames = Frames()
main = frames.main_frame(win)
frames.button_frame(main)
frames.scrolled_text_widget(main)
win.mainloop()
This example showing my problem. When you click the Send button it freeze app for 5s. But I need to see the logs during the loop.
How can I resolve it?
Tkinter is running a loop in your main thread, that's why your app freezes when you click on a button. The solution is to create a new thread.
1- You have to import threading
import threading
2- Start a new thread in your _send_button() function. It should be like this.
def _send_button():
def click_button():
for i in range(10):
self.write_text_console("{0}\n".format(i))
time.sleep(0.5)
threading.Thread(target=click_button).start()
Learn More About Threading In Python
To freeze the button, add state= DISABLED in the specific button widget.
from tkinter import *
#Create an instance of tkiner frame
root= Tk()
#Define the geometry of the function
root.geometry("400x250")
Button(root, text="OK", state= DISABLED).pack(pady=20)
root.mainloop()

Change the colour of a tkinter button generated in a loop

In python tkinter, I've got a program that generates multiple buttons with a default fg of red
from tkinter import *
root = Tk()
def dothis(i):
print(i)
button.config(fg='green')
for i in range(5):
button = Button(root, width=30, text="button{}".format(i), command=lambda i=i: dothis(i))
button.config(fg='red')
button.pack()
This creates this window:
In this program, I have attempted to make it so that once the button is pressed, the colour of the text (fg) turns green. Instead, when dothis(i) is called, it changes the colour of the last button generated to green. This is not what I want.
To summarise, when I click button3, I want to see this:
But instead, I see this (the last generated button is modified, not the one I want):
How can I work around this, while still keeping the buttons generated in a loop?
Note: The buttons must also be modifiable after changing the colour e.g. Once changed to green, it can be turned back to red.
You got the correct lambda expression, but the parameter you passed isn't related to the buttons you created. You should pass the Button widget as a parameter instead:
from tkinter import *
root = Tk()
def dothis(button):
button.config(fg='green')
for i in range(5):
button = Button(root, width=30, text="button{}".format(i))
button.config(fg='red', command=lambda i=button: dothis(i))
button.pack()
root.mainloop()
To achieve toggling between red and green, you can use ternary operator:
def dothis(button):
button.config(fg='green' if button["foreground"]=="red" else "red")
If you insist on all buttons except the last one being anonymous, and using command instead of binding events, you can use partials:
from tkinter import *
from functools import partial
root = Tk()
button_callbacks = {}
def on_click(button):
button.config(fg="green")
for i in range(5):
button = Button(root, width=30, text=f"button{i}", fg="red")
callback_name = f"on_click_{i}"
button_callbacks.update({callback_name: partial(on_click, button=button)})
button.config(command=button_callbacks[callback_name])
button.pack()
Using event binding would be a bit more straight-forward, but the behavior is not exactly the same as triggering a callback using command. Here's what that might look like:
from tkinter import *
root = Tk()
def on_click(event):
button = event.widget
button.config(fg="green")
for i in range(5):
button = Button(root, width=30, text=f"button{i}", fg="red")
button.bind("<Button-1>", on_click)
button.pack()

Categories