I'm sorry if this has been asked already, but I haven't been able to find it. I'm also just starting to learn programming so feedback is appreciated. :)
My end goal is to create an 8 by "x" grid of buttons that change their own color when pushed. I want to use this to make a grid I can upload to the POV toy I've built. This code creates a column of 8 buttons each with a callback passing itself as an argument. The idea being the callback function can do things to the button like change it's color, or delete it.
import Tkinter
def unpack(i):
buttons[i].pack_forget()
print i
top = Tkinter.Tk() buttons = [] for i in range(0, 8):
buttons.append(Tkinter.Button(top, text='Hello', command=lambda: unpack(i)))
for button in buttons:
button.pack()
top.mainloop()
When I do this I get a windows with column of 8 buttons, and when I click on one one gets deleted. When I click on a second nothing happens. In my command prompt I get the number 7 printed no matter which button I press. I suspect the problem is in the for loop that creates the buttons, but I have no idea how to fix it.
Thanks!
Pass the button object to the callback function instead of the index, because the index is change after the item deletion in the list.
import Tkinter
top = Tkinter.Tk()
for i in range(0, 8):
btn = Tkinter.Button(top, text='Hello')
btn['command'] = lambda b=btn: b.pack_forget()
btn.pack()
top.mainloop()
NOTE: To prevent late binding problem, I used default parameter in the above code.
Related
I have a problem where the tkinter Scale widget seems to get stuck whenever I run a seemingly big function.
This is the code:
from tkinter import Tk, Button, Frame, Scale
root = Tk()
slider = Scale(root, orient='horizontal')
slider.pack()
frame = Frame(root)
frame.pack()
num = 0
def buttons():
for widget in frame.winfo_children():
widget.destroy()
for i in range(50):
Button(frame, text='Button' + str(i)).pack()
def basic():
global num
slider.set(num)
num += 1
print(num)
if num <= 100:
slider.after(100, basic)
if __name__ == '__main__':
buttons()
basic()
root.bind('<space>', lambda x: buttons())
root.mainloop()
What I want my program to do is update the slider normally even when I press 'Space' (meaning calling the buttons() function)
If you watch closely each time you press Space the slider will get stuck a little.
Since I'm using the slider for an Mp3 player in order to show time elapsed, this loss of time is extremely important for example for audio files of 10 or so seconds since the slider falls behind a lot making it seem as if it's working wrong \
I'd also like to point out that destroying the buttons and then repacking them is necessary for me.
I suspect that this happens because the program has to go over the buttons() function something that takes time since it's creating 50 buttons. Or am I mistaken?
Can I avoid that lag?
PS: As I mentioned in my comment:
I normally have a button that renames a (button) which is a song and in order for them to alphabetically ordered after renaming i need to recall the function that draws them. If I only configure tha name of the button (and not redraw them), it will stay in place and not move down or up depending on its name, while on the actual directory the order will change leading to inappropriate behavior such as playing the same song
Here are some images for better understanding:
Thanks in advance!
Look at this code:
import tkinter as tk
def config_buttons():
# Get the `text` of the first button
starting_value = int(buttons[0].cget("text")) + 1
# Iterate over all of the buttons
for i, button in enumerate(buttons, start=starting_value):
# Change the button's `text` and `command` atributes
button.config(text=i, command=lambda i=i:print("Clicked %i"%i))
root = tk.Tk()
buttons = []
add_button = tk.Button(root, text="+1 on all buttons", command=config_buttons)
add_button.pack()
for i in range(50):
button = tk.Button(root, text=i, command=lambda i=i:print("Clicked %i"%i))
button.pack()
buttons.append(button)
root.mainloop()
When the add_button buttons is pressed, I iterate over all of the buttons and change their text and command attributes. As I am not creating new buttons, the function runs very fast.
You can implement something similar in your code. Basically, avoid creating new buttons and just update the ones you already have on the screen.
So I want to make a button, that when I click on it, it gets deleted and another one appears, and this can be done an infinite amount of times
a=0
button1 = [1,2,3,4,5,6]
def ButtonClick():
global a
#Destroys the first button
button1[a].destroy()
a+a+1
#Making new button, that should do the same as the old button
button1[a] = tk.Button(text='hello',command=ButtonClick)
canvas1.create_window(250+a*50, 140, window=button1[a])
button1[a] = tk.Button(text='hello',command=ButtonClick)
canvas1.create_window(100, 140, window=button1[a])
As you can see the new button is also using command=ButtonClick so when I press on the created button it should do the same as the old one, but it doesn't and I am not sure why because when I change the command on the new button it says error so it is somehow refereeing to the def ButtonClick. But nothing happens when I press the new button. Can anyone help me out?
In order to destroy and rebuild a button, you will have to make it global. Here is a working example of a button that can be destroyed by its own function, only to be created again:
def ButtonClick():
global root
root.b.destroy()
root.b = Button(root,text="Hello",command=ButtonClick)
root.c.create_window(250+root.button1[root.bnumber]*50,140,window=root.b)
if root.bnumber<len(root.button1)-1:root.bnumber+=1
global root
root = Tk()
root.geometry('800x600')
root.resizable(0, 0)
root.c = Canvas(root,bg="#ffffff",width=800,height=600)
root.c.grid(row=0,column=0,sticky=W+S+E+N)
root.b = Button(root,text="Hello",command=ButtonClick)
root.c.create_window(100,140,window=root.b)
root.button1 = [1,2,3,4,5]
root.bnumber = 0
I made your button1 list and the current index (a) attributes of the tkinter widget, so it will be accessible as long as a function has access to the widget.
For this example, however, it would be easier to just move the window within the tkinter Canvas instead of removing and re-creating it.
I am trying to create a tkinter window with a button that is stuck at the bottom. I want the button to be used to add new items to the window (separate code, not shown here), but always keep the button at the bottom. Something like the following:
import tkinter as tk
m = tk.Tk(className="My window")
create = tk.Button(m, text="Create new item", width=25)
create.grid(row=inf, padx=40, pady=20)
m.mainloop()
except of course tkinter.grid() doesn;t accept inf as a valid value for row=. My question is how can I ensure that even as I add items to the tkinter window, my button will always remain on the bottom.
You can combine pack and grid.
Your base application could be this
upperframe.pack(side=tk.TOP)
bottomframe.pack(side=tk.BOTTOM)
Within the upperframe use your grid to add buttons and whatever. The bottom frame containing your changing buttons will be stuck there. You can use pack or grid or whatever you like there.
Creating a status bar goes much the same way.
I'm designing a GUI application using Tkinter and for this project, I need buttons for the menu. While looking into the buttons I wasn't blown away by the customization options that come with the buttons, especially when I found out that you can bind click arguments to rectangles.
This allows me to customize the "button" in (almost) limitless ways, but to allow me to put text on the button I need to create a rectangle element and a text element and bind them together using Tkinter's tag_bind property.
One of the design properties of the button that I wanted was active fill when the user moused over the element. Right now I'm just using activefill="" which works, except the text element and the button element will only fill while the mouse is over that element. So, for example, when I mouse over the button the button excluding the text will highlight and vise versa when I mouse over the text.
Below is a simplified (for brevity) version of what I use to generate the buttons;
button = canvas.create_rectangle(button_width, button_height, 10, 10, fill="000", activefill="111", tags="test")
text = canvas.create_text((button_width/2), (button_height/2), activefill="111", tags="test")
canvas.tag_bind("test", "<Button-1>", "foo")
Is there a way to bind the active fill function to a tag rather than a specific element?
Another option is that I completely missed a bunch of information about customizing the buttons in Tkinter, and I would not be apposed to learning about that.
Option 1
I would personally not go for the presented solution. I do not know if you are using the button provided by tk or ttk. But, with the tkinter.tk, you could absolutely change the appearance of the button.
Following, I give you an example that produces a button with the following characteristics:
Blue foreground
Flat appearance
When hovered, the background is green
When pressed, the background is red
The code is as follows:
import tkinter as tk
root = tk.Tk()
# Function hovering
def on_enter(e):
btn['background'] = 'green'
def on_leave(e):
btn['background'] = 'white'
# Create the button
btn = tk.Button(root, background='white', activebackground='red', foreground='blue',relief='flat',text='Test',width=20)
btn.pack()
# Bindings
btn.bind("<Enter>", on_enter)
btn.bind("<Leave>", on_leave)
# Loop
root.mainloop()
Option 2
If even after having tried the tk.Button, you are not glad with the result, I would create a Frame containing a Label (you can do nearly anything with that combination). Then, you could change the background of the frame according to any user action, like:
import tkinter as tk
root = tk.Tk()
# Function hovering
def on_enter(e):
lab['background'] = 'green'
def on_leave(e):
lab['background'] = 'white'
# Click
def on_click(e):
print("hi")
# Create the frame with a label inside
fr = tk.Frame(root)
lab = tk.Label(fr, text="Test", width=20, background="white")
# Packing
fr.pack()
lab.pack()
# Bindings
fr.bind("<Enter>", on_enter)
fr.bind("<Leave>", on_leave)
lab.bind("<Button-1>", on_click)
# Loop
root.mainloop()
You could even create a class with the above combination.
The reason I need to do this is because I need to change the text of a label without having to use entries; I want to use events instead.
I have tried this:
import tkinter as tk
root = tk.Tk()
root.bind("<Button-1>", lambda _: root.focus())
l = tk.Label(root, width=50, height=50, bg="white")
l.bind("<Button-1>", lambda _: l.focus())
l.bind("1", lambda _: l.config(bg="yellow"))
l.bind("2", lambda _: l.config(bg="white"))
l.pack()
root.mainloop()
When I ran the program, I expected to be able to change the colour of the label l to yellow by clicking on it (which I thought would set the focus to it) then pressing 1, and changing it back to white by pressing 2; provided that I didn't click outside of the label and set the focus to the root widget (where the keys 1 and 2 weren't bound to any callback).
I know that you can bind keys to callbacks (tested it), and I also know that it is possible to set the focus to widgets which aren't entries (tested that too), yet this doesn't seem to work.
Can anybody help me?
The problem is that you have two bindings for a button click: one on the label widget itself and one on the root window. Because of the way that events are processed, the binding on the root window will fire after the event on the label. That means that whatever focus you set on the label binding will get undone with the binding on the root window.
One solution is to change your binding on the click to set the focus to whatever was clicked on. With that, you don't need to set a binding on the label widget for a click.
root.bind("<Button-1>", lambda event: event.widget.focus_set())
Another solution would be to modify your binding on the label to prevent the binding on the root window from firing. You can do that by returning the string "break" from the function that is called.
def callback(event):
l.focus()
return "break"
l.bind("<Button-1>", callback)