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()
Related
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()
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
I have some problems with Tkinter, I want so retrieve the selected item of an option menu when pressing a button, but when testing the funcionality with a small testfuntion 'getdataset', I only get the predefined datavariable ('Birthdata') as an output so it seems that the datavariable.get() method is not returning the selected option in the optionmenu. I have looked everywhere but cannot seem to find the answer. Any help is appreciated.
Code with option menu
root = Tk()
root.configure(background='white')
def getdataset():
print(datavariable.get())
datavariable = StringVar(root)
datavariable.set('Birthdata')
PickData = OptionMenu(root, datavariable,'Birthdata', 'Marriagedata', 'Deathdata',command=getdataset)
PickData.grid(column=1,columnspan=2,row=1)
Code to initialize test function with button click:
wordcloud = Button(root, text = 'Word Cloud', bg='white', width=20, height = 5, command=getdataset)
Output after multiple button clicks:
Birthdata
Birthdata
Birthdata
I w
You should be getting an error when selecting an option from your OptionMenu.
Specifically:
TypeError: getdataset() takes 0 positional arguments but 1 was given
To fix that we can add a argument that will handle this. Update your function to look like this:
def getdataset(_=None):
The reason for this is due to how your 2 different commands are interacting with this function. The OptionMenu command is sending an argument to the function when called where the Button command is not. This is not obvious at first but if you write something like:
def getdataset(arg=None):
print(arg)
You will see that when you press the Button it will print None and when you select something from the menu it will print the value of the selection.
You code should look something like this:
import tkinter as tk
def getdataset(_=None):
print(datavariable.get())
root = tk.Tk()
root.configure(background='white')
datavariable = tk.StringVar(root)
datavariable.set('Birthdata')
tk.OptionMenu(root, datavariable, 'Birthdata', 'Marriagedata', 'Deathdata', command=getdataset).grid()
tk.Button(root, text='Word Cloud', bg='white', width=20, height=5, command=getdataset).grid()
root.mainloop()
import tkinter
import random
# GUI
window = tkinter.Tk()
window.title("Hangman")
window.geometry("640x400+100+100")
frame = tkinter.Frame(window)
frame.pack()
button_animal = tkinter.Button(frame)
button_animal['text'] = "Animal"
button_animal['background'] = 'yellow'
button_animal.pack()
button_capital = tkinter.Button(frame)
button_capital['text'] = "Capital"
button_capital['background'] = 'blue'
button_capital.pack()
This is what I wrote so far, what am I supposed to do to make the button to make event happens?
if I click animal, it should play a game, but I am not sure how to do at first.
You should add command parameter to your button initialization. You can find more information in here tkinter button widget
Basically, you can bind your button to function in two ways:
button_animal = tkinter.Button(frame, command=your_function)
or if your function requires arguments, you can use lambda, like:
button_animal = tkinter.Button(frame, command=lambda : your_function(arg))
If you want to bind multiple function, you can do following:
button_animal = tkinter.Button(frame, command=lambda :[funct1(arg),funct2(arg)])
I have a list of tkinter widgets that I want to change dynamically.
How to delete the widgets from the window?
You can call pack_forget to remove a widget (if you use pack to add it to the window).
Example:
from tkinter import *
root = Tk()
b = Button(root, text="Delete me", command=lambda: b.pack_forget())
b.pack()
root.mainloop()
If you use pack_forget, you can later show the widget again calling pack again. If you want to permanently delete it, call destroy on the widget (then you won't be able to re-add it).
If you use the grid method, you can use grid_forget or grid_remove to hide the widget.
One way you can do it, is to get the slaves list from the frame that needs to be cleared and destroy or "hide" them according to your needs. To get a clear frame you can do it like this:
from tkinter import *
root = Tk()
def clear():
list = root.grid_slaves()
for l in list:
l.destroy()
Label(root,text='Hello World!').grid(row=0)
Button(root,text='Clear',command=clear).grid(row=1)
root.mainloop()
You should call grid_slaves(), pack_slaves() or slaves() depending on the method you used to add the widget to the frame.
You simply use the destroy() method to delete the specified widgets like this:
lbl = tk.Label(....)
btn = tk.Button(....., command=lambda: lbl.destroy())
Using this you can completely destroy the specific widgets.
You say that you have a list of widgets to change dynamically. Do you want to reuse and reconfigure existing widgets, or create all new widgets and delete the old ones? It affects the answer.
If you want to reuse the existing widgets, just reconfigure them. Or, if you just want to hide some of them temporarily, use the corresponding "forget" method to hide them. If you mapped them with pack() calls, you would hide with pack_forget() (or just forget()) calls. Accordingly, grid_forget() to hide gridded widgets, and place_forget() for placed widgets.
If you do not intend to reuse the widgets, you can destroy them with a straight destroy() call, like widget.destroy(), to free up resources.
clear_btm=Button(master,text="Clear") #this button will delete the widgets
clear_btm["command"] = lambda one = button1, two = text1, three = entry1: clear(one,two,three) #pass the widgets
clear_btm.pack()
def clear(*widgets):
for widget in widgets:
widget.destroy() #finally we are deleting the widgets.
Today I learn some simple and good click event handling using tkinter gui library in python3, which I would like to share inside this thread.
from tkinter import *
cnt = 0
def MsgClick(event):
children = root.winfo_children()
for child in children:
# print("type of widget is : " + str(type(child)))
if str(type(child)) == "<class 'tkinter.Message'>":
# print("Here Message widget will destroy")
child.destroy()
return
def MsgMotion(event):
print("Mouse position: (%s %s)" % (event.x, event.y))
return
def ButtonClick(event):
global cnt, msg
cnt += 1
msg = Message(root, text="you just clicked the button..." + str(cnt) + "...time...")
msg.config(bg='lightgreen', font=('times', 24, 'italic'))
msg.bind("<Button-1>", MsgClick)
msg.bind("<Motion>", MsgMotion)
msg.pack()
#print(type(msg)) tkinter.Message
def ButtonDoubleClick(event):
import sys; sys.exit()
root = Tk()
root.title("My First GUI App in Python")
root.minsize(width=300, height=300)
root.maxsize(width=400, height=350)
button = Button(
root, text="Click Me!", width=40, height=3
)
button.pack()
button.bind("<Button-1>", ButtonClick)
button.bind("<Double-1>", ButtonDoubleClick)
root.mainloop()
Hope it will help someone...
You can use forget method on the widget
from tkinter import *
root = Tk()
b = Button(root, text="Delete me", command=b.forget)
b.pack()
b['command'] = b.forget
root.mainloop()
I found that when the widget is part of a function and the grid_remove is part of another function it does not remove the label. In this example...
def somefunction(self):
Label(self, text=" ").grid(row = 0, column = 0)
self.text_ent = Entry(self)
self.text_ent.grid(row = 1, column = 0)
def someotherfunction(self):
somefunction.text_ent.grid_remove()
...there is no valid way of removing the Label.
The only solution I could find is to give the label a name and make it global:
def somefunction(self):
global label
label = Label(self, text=" ")
label.grid(row = 0, column = 0)
self.text_ent = Entry(self)
self.text_ent.grid(row = 1, column = 0)
def someotherfunction(self):
global label
somefunction.text_ent.grid_remove()
label.grid_remove()
When I ran into this problem there was a class involved, one function being in the class and one not, so I'm not sure the global label lines are really needed in the above.