The loop in the program created a number of buttons with the same name. How to destroy them?
For example:
for i in range(5):
global btn
btn=Button(text=name,command=startfile_)
btn.place(x=5,y=5)
def destroy_it():
btn.destroy()#Its destroying only 1
destroy_btn(text=name,command=destroy_it)
It strongly depends on your use case. By the way, I suggest you to ensure your code is reproducible as-is, so that we can adapt our answers to your specific case.
Here some ways to address your problem.
1. save all buttons in a list and iterate over the list to delete them
pros: very easy to use and understand
cons: you need to pass your list along the code to work on it
import tkinter as tk
root = tk.Tk()
buttons = []
for i in range(5):
btn = tk.Button(root, text=f'test{i}', command=None) # TODO fill with your command
btn.pack()
buttons.append(btn)
def destroy_it(buttons):
# You must know the list of buttons to destroy
for btn in buttons.copy():
btn.destroy()
buttons.remove(btn) # also delete buttons from the list
tk.Button(root, text="Destroy all buttons", command=lambda: destroy_it(buttons)).pack()
root.mainloop()
2. destroy all widgets that satisfy a specific rule (i.e. Buttons with a specific text)
pros: you have a large flexibility on the widgets you are going to delete
cons: you may accidentally delete widgets, you must correctly deal with it
import tkinter as tk
root = tk.Tk()
for i in range(5):
btn = tk.Button(root, text=f'test{i}', command=None) # TODO fill with your command
btn.pack()
def destroy_it():
# Iterate over any widget and destroy it if it is a button with text "test....."
for child in root.winfo_children():
if isinstance(child, tk.Button) and child['text'].startswith('test'):
child.destroy()
tk.Button(root, text="Destroy all buttons", command=destroy_it).pack()
root.mainloop()
3. Give your buttons a name and address them by name
pros: easy to include in the code and to understand
cons: you must remember the names you used and you may get KeyError
import tkinter as tk
root = tk.Tk()
for i in range(5):
btn = tk.Button(root, name=f'btn{i}', text=f'test{i}', command=None) # TODO fill with your command
btn.pack()
def destroy_it():
# Get each button by its name
for i in range(5):
btn = root.nametowidget(f'.btn{i}')
btn.destroy()
tk.Button(root, text="Destroy all buttons", command=destroy_it).pack()
root.mainloop()
There are probably many other ways to achieve it, such as associating an "autodestroy" method to each button that is triggered by command, or include your buttons in a Frame and destroy the frame at once... But you may start from the examples above
Related
I am making blackjack in tkinter and instead of placing buttons over the existing buttons i want to toggle them when, say a new game.
There are many ways of doing this. One option (I think he simplest one) is to get your buttons in a frame that you can pack and unpack using pack and packing_forget. In this case you need another frame where your button frame is the only packed widget, so the buttons will appear in the same place when you pack them again. You can also resize the frame so things on it will become invisible when it becomes really small. Another option is to use a canvas where your buttons are canvas objects. You can them move or hide them as you want.
In addition to #Flavio Moraes answer you can use grid_remove() method to save a widget before remove it (if you don't need it anymore you can also destroy() the widget:
import tkinter as tk
from tkinter import ttk
root = tk.Tk()
root.geometry("300x80")
root.title('Toogle button')
def crea():
""" restore button """
btn.grid(column=0, row=0, sticky='nsew')
def remo():
"""remove button"""
btn.grid_remove()
def btn_off():
btn.after(1500, remo)
btn.after(3000, crea)
btn = ttk.Button(root, text='Hide', command=btn_off)
btn.grid(column=0, row=0, rowspan=2, sticky='nsew')
root.columnconfigure(0, weight=3)
root.mainloop()
I really need to be able to delete a button onscreen into a label. All I need to do is remove the button, and put the label in place of it. However, I do not know how to remove buttons.
I am running Windows 10, Python 3.9.2.
Are you looking for something like this?:
import tkinter as tk
def remove_button():
global label
# Get the grid parameters passed in button when it was created
button_grid_info = button.grid_info()
button.grid_forget()
label = tk.Label(button_grid_info["in"], text="This is a Label widget")
# Put the label exactly where the button was:
label.grid(**button_grid_info)
root = tk.Tk()
button = tk.Button(root, text="Click me", command=remove_button)
button.grid(row=1, column=1)
root.mainloop()
grid_forget removes the widget without destroying it. If you used <button>.pack, use pack_forget. If you used <button>.place, use place_forget.
I'm using buttons to call def variables that create labelframes. I'm switching between the buttons like tabs and want to display my new frames underneath respectively yet the old labelframe is left behind and is not defined to be called for erasing.
Here's an example of how i'm phrasing the code.
from tkinter import *
root = Tk()
root.state('zoomed')
def A1():
lf=LabelFrame(root,text='new frame')
lf.pack()
d=Button(lf, text='Added', width=0, borderwidth=3)
d.pack()
a = Button(root, text="add", command=A1)
a.pack()
b=Button(root,text="Delete me",command=lambda:b.pack_forget())
b.pack()
c=Button(root,text="Delete Added",command=lambda:lf.pack_forget())
c.pack()
root.mainloop()
Thank you for your time and advice.
You could:
Make a function to delete the frame.
Create the button with no command initially.
Set the button's command when the frame is created.
Then, when the frame is deleted, reset the button's command.
Such as this:
from tkinter import *
root = Tk()
root.state('zoomed')
# Added a function to delete the frame and reset the button's command
def remove_frame(lf):
lf.pack_forget()
c.config(command=None)
def A1():
lf=LabelFrame(root,text='new frame')
lf.pack()
d=Button(lf, text='Added', width=0, borderwidth=3)
d.pack()
# Sets the remove_frame function to the button's command.
# Since lf is created locally it will need to be passed to the remove_frame function.
c.config(command=lambda: remove_frame(lf))
a = Button(root, text="add", command=A1)
a.pack()
b=Button(root,text="Delete me",command=lambda:b.pack_forget())
b.pack()
c=Button(root,text="Delete Added") # Create the button with no command.
c.pack()
root.mainloop()
Currently this will allow you to add several frames, but only delete one.
If the goal is to only have one frame, disabling the add button until the frame is deleted would be the easiest option.
If you wanted to delete them sequentially or all of them, appending the frames to a list and using a pack_forget on each item in the list would be one method of accomplishing this.
I am working on a program that requires multiple windows, and the first one to appear is the login window, I used the Toplevel widget in order to make other windows its children, but this code keeps showing two windows instead of one.
from Tkinter import Frame, Toplevel
from ttk import Label, Entry, Button
class loginWindow(Toplevel):
def __init__(self):
Toplevel.__init__(self)
self.title("Title")
self.frame = Frame(self)
self.frame.pack()
self.__make_layout()
self.mainloop()
def __make_layout(self):
self.frame.user_name_label = Label(text="User name:")
self.frame.user_name_text = Entry()
self.frame.user_name_label.grid(row=0, column=0)
self.frame.user_name_text.grid(row=0, column=1)
self.frame.password_label = Label(text="Password:")
self.frame.password_text = Entry()
self.frame.password_label.grid(row=1, column=0)
self.frame.password_text.grid(row=1, column=1)
self.frame.login_button = Button(text="Login")# , command=self.__create_window)
self.frame.login_button.grid(row=2, column=0, columnspan=2)
if __name__ == '__main__':
win1 = loginWindow()
All of the widgets created in _make_layout are created without a parent. This means they're children of the default root. You need to pass a parent to each of them, the same way you do to the Frame. Like this:
self.frame.user_name_label = Label(self.frame, text="User name:")
self.frame.user_name_text = Entry(self.frame)
# etc.
When I run your exact code, I don't get a second window, on any platform I try. The closest I get is on OS X, where an entry for the default root window appears in the Window menu, but the window itself still doesn't appear and the widgets all end up on the Toplevel (although not on the Frame where you wanted them). But it certainly would be legal for Tkinter to show a second window here, and put some or all of your widgets on it.
This must be a platform dependent issue, since abarnert isn't having issues with multiple windows. I use OS X with XQuartz and the following code gives me two windows:
from Tkinter import Toplevel, Tk
Toplevel().mainloop()
However, this code gives me one window:
from Tkinter import Toplevel, Tk
Tk().mainloop()
I believe your first window should be declared Tk() and subsequent windows should be Toplevel().
So I've been trying to make some basic GUIs with tkinter (not te be confused with Tkinter) and I ran into a problem for which I know no solution and can't really find anything on the almighty Google...
I have a small SQLite database with a table of directories on my pc. I would like to draw all directorypaths into a label and add a 'rempve' button next to that label. The button should be able to remove directory from the database and also remove it from the GUI. I also have a 'add' button where one can add directories to the database and this new directory should be shown in the GUI. This is my basic layout:
---------------
| ADD |
|dir1 REMOVE|
|dir2 REMOVE|
---------------
I use the gridlayout to show the buttons and labels. Most things work, all database related stuff works. Also when starting the GUI the current directories and 'remove'-buttons are shown nicely. BUT... when using the 'remove' button the directory does not disappear from the GUI even though it is not in the database anymore, restarting the GUI fixes it of course. Adding a label works... but I'm not sure if I'm doing it correctly...
How can I somehow 'repaint' the GUI with the new information?
This is my code for the GUI:
class GUI():
def __init__(self,db):
self.root = Tk()
self.root.title("Example")
self.frame = ttk.Frame(self.root, padding="3 3 12 12")
self.frame.rowconfigure(5, weight=1)
self.frame.columnconfigure(5, weight=1)
self.frame.grid(sticky=W+E+N+S)
lbl = ttk.Label(self.frame, text="", width=17)
lbl.grid(row=0, column=2, sticky=W)
ttk.Button(self.frame, text="Add directory", command=lambda:self.load_file(db), width=30).grid(row=0, column=0, sticky=W, padx=(500,50))
ttk.Button(self.frame, text="Sort files", command=lambda:self.sort(db,lbl), width=17).grid(row=0, column=1, sticky=W)
self.draw(db)
self.root.mainloop()
def load_file(self,db):
fname = filedialog.askdirectory()
db.addPath(fname)
self.draw(db)
def remove_dir(self,db,pid):
db.removePath(pid)
self.draw(db)
def sort(self,db,lbl):
lbl['text'] = 'Sorting...'
sortFiles.moveFiles(db)
lbl['text'] = 'Done!'
def draw(self,db):
i = 0
paths = db.getPaths()
for path in paths:
ttk.Label(self.frame,text=path[1]).grid(row=1+i,column=0,sticky=W)
ttk.Button(self.frame, text="Remove directory", command=lambda:self.remove_dir(db,path[0]), width=17).grid(row=1+i,column=1, sticky=E)
i = i+1
for child in self.frame.winfo_children(): child.grid_configure(padx=5, pady=5)
if i == 0:
ttk.Label(self.root,text='No directories added yet').grid(row=1,column=0,sticky=W)
If you prefer to redraw the GUI every time you add or delete something, you need to first destroy any old widgets before creating new ones. For example:
def draw(self, db):
# first, delete any existing widgets
for child in self.frame.winfo_children():
child.destroy()
# next, redraw all the widgets
paths = db.getPaths()
for path in paths:
...
You have another bug, which is how you're using lambda. As it stands with the code in the question, all of your callbacks will see the same value. By specifying the value as a keyword argument to the lambda you'll get the right value:
ttk.Button(..., command=lambda p=path[0]:self.remove_dir(db, p)...)
Unrelated to the actual problem, I don't think you need to be passing db around. Assuming you only use a single db, I recommend you do self.db = db in your GUI constructor. That will make your code just a little easier to maintain because your method signatures will be simplified.
Finally, there's really no need to completely redraw the GUI when you delete one item. You can delete just one label and button at a time. This requires that you spend a little more time thinking about how you manage data in your program. If, for example, you keep a reference to each label and button, you can delete it when you delete the path from the database. Your removeDir function might look something like:
def removeDir(self, pid):
label, button = self.widgets(pid)
label.destroy()
button.destroy()