Clearing specific widgets in tkinter - python

I am attempting to have a function in python that clears the screen upon a button being pressed. I am aware of grid_remove but am unsure of how to use it. Also is there a way to clear everything from a specific function, ie both "hi" and "clear"?
from tkinter import *
class Movies:
def __init__(self, master):
hi = Label(text = "Hello")
hi.grid(row = 0, column = 0)
clear = Button(text = "Click", command=self.clear)
clear.grid(row = 1, column = 0)
def clear(self):
hi.grid_remove()
root = Tk()
gui = Movies(root)
root.geometry("100x200+0+0")
root.mainloop()

You could use the built in winfo_children method if you're just wanting to toggle hiding / showing all of the widgets in whatever parent holds the widgets. Small example:
from tkinter import *
class Movies:
def __init__(self, master):
self.master = master
self.state = 1
for i in range(5):
Label(self.master, text='Label %d' % i).grid(row=0, column=i)
self.magic_btn = Button(self.master, text='Make the Magic!',
command=self.magic)
self.magic_btn.grid(columnspan=5)
def magic(self):
self.state = not self.state
for widget in self.master.winfo_children(): #iterate over all child widgets in the parent
#Comment out to clear the button too, or leave to toggle widget states
if widget != self.magic_btn: #or some other widget you want to stay shown
if self.state:
widget.grid()
else:
widget.grid_remove()
print(self.state)
root = Tk()
gui = Movies(root)
root.mainloop()

Related

how to implement dynamic tkinter listboxes?

So I was given some example code of how to essentially control multiple Listboxes within one function, it seems to work during the example code but after implementing i am struggling to see what I've missed out.
Example code:
import tkinter as tk
class MultiListbox(tk.Frame):
def __init__(self, parent):
super().__init__(parent)
for i in range(5):
lb = tk.Listbox(self, height=10, exportselection=False)
lb.pack(side="left", fill="y")
for j in range(10):
lb.insert("end", f"Listbox {i+1} value {j+1}")
lb.bind("<Double-1>", self.removeSeq)
def removeSeq(self, event):
lb = event.widget
curselection = lb.curselection()
index = curselection[0] if curselection else None
for listbox in self.winfo_children():
listbox.delete(index)
root = tk.Tk()
mlb = MultiListbox(root)
mlb.pack(side="top", fill="both", expand=True)
root.mainloop()
Where I am trying to implement logic:
Imports
import tkinter as tk,tkinter.ttk as ttk, pyautogui, numpy, easygui, cv2, os, time, _thread, re, math, subprocess
from tkinter import BOTH, END, LEFT
pyautogui.FAILSAFE = True
Class
class Acgm003App:
def __init__(self, master=None):
for i in range(5):
self.lb = tk.Listbox(self.modeSelect)
self.lb.configure(background='#2f2a2d', exportselection='false', font='{Arial} 12 {}', foreground='#feffff', height='23')
self.lb.configure(relief='flat', width='12')
self.lb.pack(side='left')
for j in range(10):
self.lb.insert("end", f"Listbox {i+1},{j+1}")
self.lb.bind("<Double-1>", self.getIndexLB)
Function
def getIndexLB(self, event):
print('hello')
self.lb = event.widget
curselection = self.lb.curselection()
index = curselection[0] if curselection else None
for listbox in self.lb.winfo_children():
print(index)
listbox.delete(index)
pass
I am just not getting anything back at all, I put print('hello') there just to make sure it was binded correctly, it prints just fine, but no result.
The code is intended to delete listbox items in other listboxes by taking the corresponding index of the curselection, sort of a work around to a tk.treeview.
Let me know if you can help!
I didn't test it but I think you use it with wrong object.
Original code use
lb = tk.Listbox(self, ...)
to add listbox to self and later it searchs children in self
for listbox in self.winfo_children():
You add listbox to self.modeSelect
tk.Listbox(self.modeSelect, ...)
so you should search children in self.modeSelect
for listbox in self.modeSelect.winfo_children():
But this method can make problem if you add other widgets in self.modeSelect because it will try to use .delete(index) also on other widgets. And then you should check if you get tk.Listbox
for child in self.modeSelect.winfo_children():
if isinstance(child, tk.Listbox):
child.delete(index)
EDIT:
import tkinter as tk
class Acgm003App:
def __init__(self, master=None):
self.modeSelect = tk.Frame(master)
self.modeSelect.pack()
# other child in `self.modeSelect`
self.label = tk.Label(self.modeSelect, text="Hello World")
self.label.pack(side='top')
for i in range(5):
self.lb = tk.Listbox(self.modeSelect)
self.lb.pack(side='left')
for j in range(10):
self.lb.insert("end", f"Listbox {i+1},{j+1}")
self.lb.bind("<Double-1>", self.getIndexLB)
def getIndexLB(self, event):
self.lb = event.widget
curselection = self.lb.curselection()
index = curselection[0] if curselection else None
for child in self.modeSelect.winfo_children():
# check if child is `tk.Listbox` or other widget
if isinstance(child, tk.Listbox):
child.delete(index)
root = tk.Tk()
app = Acgm003App(root)
root.mainloop()

Replace python tkinter label widget with entry widget

I want to enable double click to edit a label. Is there a way to replace the label, which user double-clicks, with entry widget without destroying it?
This example lists label widgets and after double-click destroys it and sets entry widget at the end:
from tkinter import *
class MainWindow:
def __init__(self, root):
self.root = root
self.tasks = ['text1', 'text2', 'text3']
self.list_tasks()
def list_tasks(self):
self.tasks_frame = Frame(self.root)
for task in self.tasks:
task_label = Label(self.tasks_frame, text=task)
task_label.bind('<Double-Button-1>', self.replace_with_entry)
task_label.pack()
self.tasks_frame.pack()
def replace_with_entry(self, event):
widget = event.widget
widget.destroy()
entry_widget = Entry(self.tasks_frame)
entry_widget.pack()
root = Tk()
main_window = MainWindow(root)
root.mainloop()
I want entry widget in exactly the same place, where label was. I think it is possible with grid, but maybe there is a better way?
The simplest solution is not to replace it, but to instead overlay it. This is one circumstance where place is very useful.
For example:
def replace_with_entry(self, event):
widget = event.widget
entry_widget = Entry(widget)
entry_widget.place(x=0, y=0, anchor="nw", relwidth=1.0, relheight=1.0)
entry_widget.bind("<Return>", self.remove_entry)
entry_widget.focus_set()
def remove_entry(self, event):
entry = event.widget
label = entry.place_info()["in"]
label.configure(text=entry.get())
entry.destroy()
Here is the grid solution. I think it is acceptable, but you can wait to see if someone else provides a better solution.
from tkinter import *
from functools import partial
class MainWindow:
def __init__(self, root):
self.root = root
self.tasks = ['text1', 'text2', 'text3']
self.list_tasks()
def list_tasks(self):
self.tasks_frame = Frame(self.root)
for i, task in enumerate(self.tasks):
task_label = Label(self.tasks_frame, text=task)
task_label.bind('<Double-Button-1>', partial(self.replace_with_entry, i))
task_label.grid(row=i, column=0)
self.tasks_frame.pack()
def replace_with_entry(self, i, event):
widget = event.widget
widget.destroy()
entry_widget = Entry(self.tasks_frame)
entry_widget.grid(row=i, column=0)
root = Tk()
main_window = MainWindow(root)
root.mainloop()
I also made another version that uses tkinter variables to include the text from the label in the entry. It could be modified to allow switching back to the label with the modified text.
from tkinter import *
from functools import partial
class MainWindow:
def __init__(self, root):
self.root = root
self.tasks = ['text1', 'text2', 'text3']
self.list_tasks()
def list_tasks(self):
self.tasks_frame = Frame(self.root)
self.task_widgets = []
for i, task in enumerate(self.tasks):
textvar = StringVar()
textvar.set(task)
task_label = Label(self.tasks_frame, textvariable=textvar)
task_label.bind('<Double-Button-1>', partial(self.replace_with_entry, i))
task_label.grid(row=i, column=0)
task_entry = Entry(self.tasks_frame, textvariable=textvar)
self.task_widgets.append((task_label, task_entry, textvar))
self.tasks_frame.pack()
def replace_with_entry(self, i, event):
widget = event.widget
widget.grid_forget()
self.task_widgets[i][1].grid(row=i, column=0)
root = Tk()
main_window = MainWindow(root)
root.mainloop()

Can you config a widget after it has been created dynamically?

So I have been looking for the answer to this but can't find any examples.
I want to know if you can create several buttons or labels or whatever widget in tkinter with all the same variable name and then be able to target that widget directly after its created.
Here is an example of some code that will create 5 buttons with the same variable name and if you press the button it will print the text on said button.
import tkinter as tk
btn_names = ["1st Button", "2nd Button", "3rd Button", "4th Button", "5th Button"]
class MyButton(tk.Frame):
def __init__(self, parent, *args, **kwargs):
tk.Frame.__init__(self, parent, *args, **kwargs)
self.parent = parent
self.createButtons()
def createButtons(self):
row_count = 0
for n in range(5):
someButton = tk.Button(self.parent, text=btn_names[n], command= lambda t=btn_names[n]: self.getText(t))
someButton.grid(row = row_count, column = 0)
row_count += 1
def getText(self, text):
print(text)
if __name__ == "__main__":
root = tk.Tk()
myApp = MyButton(root)
root.mainloop()
Now what I can't figure out is if it is possible to also make changes to said button. Like I now want to change the buttons background and foreground colors but I have no way of targeting the button I want to edit.
I can't just do this:
someButton.config(background = "black", foreground = "white")
as all the buttons are named someButton.
So is it possible to be able to edit a widget created in this manor after it has been created?
I'm not sure this is the best way to do it, but it is possible.
Instead of passing a command to your button when you originally create it, add a line where you configure the command to your lambda function and pass someButton as an argument. Then in your callback function, ensure you configure the button passed to change its background color.
import tkinter as tk
btn_names = ["1st Button", "2nd Button", "3rd Button", "4th Button", "5th
Button"]
class MyButton(tk.Frame):
def __init__(self, parent, *args, **kwargs):
tk.Frame.__init__(self, parent, *args, **kwargs)
self.parent = parent
self.createButtons()
def createButtons(self):
row_count = 0
for n in range(5):
someButton = tk.Button(self.parent, text=btn_names[n])
someButton.configure(command=lambda t=btn_names[n], btn = someButton: self.getText(t, btn))
someButton.grid(row = row_count, column = 0)
row_count += 1
def getText(self, text, btn):
print(text)
btn.configure(background = 'black')
if __name__ == "__main__":
root = tk.Tk()
myApp = MyButton(root)
root.mainloop()

import a python file that create a window when main window button clicks

I am creating 2 window in my program and i am using two class, since the code is complex, i separate it in 2 different python file. After i imported the second window file, how can i make sure it open without having this error which show in this picture
The original result should look like this after the new window button clicked:
Coding for Main Window:
from tkinter import *
import classGUIProgram
class Window(Tk):
def __init__(self, parent):
Tk.__init__(self, parent)
self.parent = parent
self.initialize()
def initialize(self):
self.geometry("600x400+30+30")
self.wButton = Button(self, text='newWindow', command = self.OnButtonClick)
self.wButton.pack()
def OnButtonClick(classGUIProgram):
classGUIProgram.top = Toplevel()
master = Tk()
b = classGUIProgram.HappyButton(master)
master.mainloop()
if __name__ == "__main__":
window = Window(None)
window.title("title")
window.mainloop()
Coding for Second Window:
from tkinter import *
class HappyButton:
def __init__(self, master):
frame = Frame(master)
frame.pack()
self.printButton = Button(frame, text="Print message", command=self.printMessage)
self.printButton.pack(side=LEFT)
self.quitButton = Button(frame, text="Quit", command= quit)
self.quitButton.pack(side=LEFT)
self.downloadHistoryCB=Checkbutton(frame, text="Download History")
self.downloadHistoryCB.pack(side=LEFT)
def printMessage(self):
print("Wow this actually worked!")
master = Tk()
b = HappyButton(master)
master.mainloop()
You're creating extra Tk windows. Here is an example of using Toplevel widgets and another file.
mainWindow.py
import tkinter as tk
import secondWindow as sW
class MainWindow(tk.Tk):
def __init__(self):
super().__init__()
self.title("Main Window")
self.geometry("600x400+30+30")
tk.Button(self, text = "New Window", command = self.new_window).pack()
tk.Button(self, text = "Close Window", command = self.close).pack()
self._second_window = None
def new_window(self):
# This prevents multiple clicks opening multiple windows
if self._second_window is not None:
return
self._second_window = sW.SubWindow(self)
def close(self):
# Destory the 2nd window and reset the value to None
if self._second_window is not None:
self._second_window.destroy()
self._second_window = None
if __name__ == '__main__':
window = MainWindow()
window.mainloop()
secondWindow.py
import tkinter as tk
class SubWindow(tk.Toplevel):
def __init__(self, master):
super().__init__(master)
self.title("Sub Window")
self.geometry("400x300+30+30")
# Change what happens when you click the X button
# This is done so changes also reflect in the main window class
self.protocol('WM_DELETE_WINDOW', master.close)
tk.Button(self, text = "Print", command = self.printMessage).pack()
def printMessage(self):
print("Wow this actually worked!")
When using another file be sure to not have any global code you don't want running. Your classes don't have to inherit from Tk and Toplevel, this is just an example. But you need to ensure you only ever have one instance of Tk otherwise you get the behaviour you encountered

Is it possible to keep the same window for every class in python's tkinter?

I am trying to create a program in tkinter which allows me to open an initial window then to keep it throughout all classes used. For example, if I was to create a button in a window then when I click this button, it would exuecute a method that destroys the widget, and then executes a new class that builds a new screen within the same window, such as text opposed to a button.
from tkinter import *
class Window1:
def __init__(self, master):
self.master = master
self.label = Button(self.master, text = "Example", command = self.load_new)
self.label.pack()
def load_new(self):
self.label.destroy()
## Code to execute next class
class Window2:
def __init__(self, master):
self.master = master
self.label = Label(self.master, text = "Example")
self.label.pack()
def main():
root = Tk()
run = Window1(root)
root.mainloop()
if __name__ == '__main__':
main()
I understand this is less practical, but I am curious. Cheers.
Tk() creates main window and variable root gives you access to this window. You can use root as argument for Window2 and you will have access to main window inside Window2
from tkinter import *
class Window1:
def __init__(self, master):
# keep `root` in `self.master`
self.master = master
self.label = Button(self.master, text="Example", command=self.load_new)
self.label.pack()
def load_new(self):
self.label.destroy()
# use `root` with another class
self.another = Window2(self.master)
class Window2:
def __init__(self, master):
# keep `root` in `self.master`
self.master = master
self.label = Label(self.master, text="Example")
self.label.pack()
root = Tk()
run = Window1(root)
root.mainloop()
--
Probably nobody use another class to create Label in place of Button ;)
--
EDIT: In this example using names Window1 and Windows2 is misleading because there is only one window and two classes which use this window. I would rather use names FirstOwner, SecondOwner
Everything is implemented in one Tk class and in this case there always is only one window.
from tkinter import *
from tkinter import ttk
class MainWindow():
def __init__(self, mainWidget):
self.main_frame = ttk.Frame(mainWidget, width=300, height=150, padding=(0, 0, 0, 0))
self.main_frame.grid(row=0, column=0)
self.some_kind_of_controler = 0
self.main_gui()
def main_gui(self):
root.title('My Window')
self.main_label_1 = ttk.Label(self.main_frame, text='Object_1')
self.main_label_1.grid(row=0, column=0)
self.main_label_2 = ttk.Label(self.main_frame, text='Object_2')
self.main_label_2.grid(row=1, column=0)
self.main_label_3 = ttk.Label(self.main_frame, text='Object_3')
self.main_label_3.grid(row=2, column=0)
self.setings_button = ttk.Button(self.main_frame, text='Setings')
self.setings_button.grid(row=0, column=1)
self.setings_button.bind('<Button-1>', self.setings_gui)
self.gui_elements = [self.main_label_1,
self.main_label_2,
self.main_label_3,
self.setings_button]
def setings_gui(self, event):
self.gui_elements_remove(self.gui_elements)
root.title('Setings')
self.main_label_1 = ttk.Label(self.main_frame, text='Object_1')
self.main_label_1.grid(row=2, column=0)
self.main_menu_button = ttk.Button(self.main_frame, text='Main menu')
self.main_menu_button.grid(row=0, column=1)
self.main_menu_button.bind('<Button-1>', self.back_to_main)
self.some_kind_of_controler = 1
self.gui_elements = [self.main_label_1,
self.main_menu_button]
def back_to_main(self, event):
if self.some_kind_of_controler == 1:
self.gui_elements_remove(self.gui_elements)
else:
pass
self.main_gui()
def gui_elements_remove(self, elements):
for element in elements:
element.destroy()
def main():
global root
root = Tk()
root.geometry('300x150+50+50')
window = MainWindow(root)
root.mainloop()
if __name__ == '__main__':
main()

Categories