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

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()

Related

Changing tab widgets from another tab in Tkinter

I am creating a GUI using tkinter based on the structure described here. I have some tabs that look identical but with different variables. So I decided to define a class for tabs and add them to the main window. I am going to configure some widgets in one tab from another tab. In line 11, a function is defined that when a button in tab_2 is clicked, tab_1's button background color changes to green. Whereas its working, I have two question:
Is it possible not to define channel_1 as an attribute of main_window? I think there must be better way to do so, specifically, if the GUI is going to be used as module (then main_window will not be defined).
Is it possible to know which tab is open, so when button in each tab is clicked, configurations in the other one changes only?
import tkinter as tk
from tkinter import ttk
class Channel(tk.Frame):
def __init__(self, parent, *args, **kwargs):
tk.Frame.__init__(self, parent, *args, **kwargs)
self.parent = parent
self.btn = tk.Button(self.parent, text = 'click me', command = self.change_green)
self.btn.pack()
def change_green(self):
main_window.channel_1.btn.config(bg = 'green') # line 11
class MainApplication(tk.Frame):
def __init__(self, parent, *args, **kwargs):
tk.Frame.__init__(self, parent, *args, **kwargs)
self.parent = parent
self.tab_control = ttk.Notebook(self.parent)
self.tab_1 = ttk.Frame(self.tab_control)
self.tab_2 = ttk.Frame(self.tab_control)
self.tab_control.add(self.tab_1, text = 'tab 1')
self.tab_control.add(self.tab_2, text = 'tab 2')
self.tab_control.pack(fill = 'both', expand = 1)
self.channel_1 = Channel(self.tab_1)
self.channel_2 = Channel(self.tab_2)
if __name__ == "__main__":
root = tk.Tk()
main_window = MainApplication(root) # <<<< here defined main_window
main_window.pack(side="top", fill="both", expand=True)
root.mainloop()
I would create class MyTab and keep its widgets in this class, not in channel. It can also keep access to other tab(s) to button in one tab can change color in other tab.
Using tab's parent (self.master) I can get active tab, list of all tabs and activate other tab.
import tkinter as tk
from tkinter import ttk
class MyTab(tk.Frame):
def __init__(self, master, *args, **kwargs):
super().__init__(master, *args, **kwargs)
#self.master = master # super() already set it
self.btn = tk.Button(self, text='click me', command=self.change_green)
self.btn.pack()
self.other_tab = None # default value at start
def change_green(self):
if self.other_tab:
# change color in other tab
self.other_tab.btn.config(bg = 'green')
# get active tab ID
print('active tab ID:', self.master.select())
# get button in active tab
active_tab = root.nametowidget(self.master.select())
print('active tab - btn text:', active_tab.btn['text'])
# get all tabs
print('all tabs:', self.master.children.items())
# set other tab as active
self.master.select(self.other_tab)
class MainApplication(tk.Frame):
def __init__(self, master, *args, **kwargs):
super().__init__(master, *args, **kwargs)
#self.master = master # super() already set it
self.tab_control = ttk.Notebook(self.master)
self.tab_1 = MyTab(self.tab_control)
self.tab_2 = MyTab(self.tab_control)
self.tab_1.other_tab = self.tab_2
self.tab_2.other_tab = self.tab_1
self.tab_control.add(self.tab_1, text = 'tab 1')
self.tab_control.add(self.tab_2, text = 'tab 2')
self.tab_control.pack(fill = 'both', expand = 1)
if __name__ == "__main__":
root = tk.Tk()
main_window = MainApplication(root)
main_window.pack(side="top", fill="both", expand=True)
root.mainloop()

tkinger - How do I get the correct widget to update in a dynamically created grid?

I'm creating a grid of Frames from a list of lists. I have a Frame class that has a popup menu bound to Button-1. I'd like to use this pop-up menu to update the label inside the Frame that called the popup menu. Right now only the last Frame's label text is getting updated.
I've tried playing around with bind tags, as I thought maybe the Button-1 is getting bound to only the most recently created widget, but it seems like this was a red herring. I'm actually totally stumped on where to go from here.
#!/bin/env python
import tkinter
import tkinter.messagebox
def create_grid():
grid = []
for i in range(0, 3001, 1000):
row = []
for j in range(8):
row.append(i+j)
grid.append(row)
grid.reverse()
return grid
class GridFrame(tkinter.Frame):
def __init__(self, root, index, *args, **kwargs):
tkinter.Frame.__init__(self, root, *args, **kwargs)
self.labeltext = tkinter.StringVar()
self.labeltext.set("+")
self.popup_menu = tkinter.Menu(root, tearoff=0)
self.popup_menu.add_command(label="Set label to 'test'", command=self.update_label)
self.popup_menu.add_command(label="Do nothing", command=print)
self.bind_all("<Button-1>", self.popup)
self.bind_all("<Button-3>", self.reset_label)
self.index_label = tkinter.Label(self, text="{0:04d}".format(index))
self.index_label.pack()
self.framelabel = tkinter.Label(self, textvariable=self.labeltext)
self.framelabel.pack()
def popup(self, event):
try:
self.popup_menu.tk_popup(event.x_root, event.y_root, 0)
finally:
self.popup_menu.grab_release()
def reset_label(self, event):
self.labeltext.set("+")
def update_label(self):
self.labeltext.set("test")
class GridGUI:
def __init__(self, root, grid, *args, **kwargs):
self.root = root
root.title("Grid")
for i, row in enumerate(grid):
for j, index in enumerate(row):
gridframe = GridFrame(root, index)
gridframe.config(borderwidth=3, relief="raised")
gridframe.grid(row=i, column=j, padx=2, pady=2, ipadx=20, ipady=30, sticky="nsew")
def main():
grid = create_grid()
root = tkinter.Tk()
menubar = tkinter.Menu(root)
filemenu = tkinter.Menu(menubar, tearoff=0)
filemenu.add_command(label="Exit", command=root.quit)
menubar.add_cascade(label="File", menu=filemenu)
root.config(menu=menubar)
my_gui = GridGUI(root, grid)
root.mainloop()
main()
I expect the label in the Frame the popup menu originates would update. With my current code it seems only the label in the most recently created Frame updates.
Your problem is that you're using bind_all, and that you're resetting that binding every time you create a new GridFrame. Thus, only the binding to the last GridFrame will be the one recognized by tkinter. When tkinter detects the binding, it calls the popup method of the final GridFrame.
You can work around this since the event object tells you which widget was clicked on. From that, you can determine which GridFrame instance was clicked on. However, there is a simpler solution.
The solution is to not use bind_all, and instead bind to each widget individually. When the binding fires, it will then call the function associated with the appropriate object.
After creating the widgets, apply a binding to every widget:
class GridFrame(tkinter.Frame):
def __init__(self, root, index, *args, **kwargs):
...
for widget in (self, self.index_label, self.framelabel):
widget.bind("<Button-1>", self.popup)
This has the added benefit that you can have any other widgets on the screen that accept button clicks (buttons, scrollbars, etc) without having them be affected by the global binding to a mouse click.
If you don't mind ugly hacks, here's one way to do it. Instead of creating an individual popup menu within each frame, create only one in your main GUI, check for the popup location, and alter the corresponding StringVar.
import tkinter
import tkinter.messagebox
y = []
def create_grid():
grid_list = []
for i in range(0, 3001, 1000):
row = []
for j in range(8):
row.append(i+j)
grid_list.append(row)
grid_list.reverse()
return grid_list
class GridFrame(tkinter.Frame):
def __init__(self, root, index, num, *args, **kwargs):
tkinter.Frame.__init__(self, root, *args, **kwargs,name=f"{num}")
self.labeltext = tkinter.StringVar()
y.append(self.labeltext)
self.labeltext.set("+")
self.index_label = tkinter.Label(self, text="{0:04d}".format(index))
self.index_label.pack()
self.framelabel = tkinter.Label(self, textvariable=self.labeltext)
self.framelabel.pack()
class GridGUI:
def __init__(self, root, grid_list, *args, **kwargs):
root.title("Grid")
num = 0
for i, row in enumerate(grid_list):
for j, index in enumerate(row):
gridframe = GridFrame(root, index, num)
gridframe.config(borderwidth=3, relief="raised")
gridframe.grid(row=i, column=j, padx=2, pady=2, ipadx=20, ipady=30, sticky="nsew")
num+=1
self.popup_menu = tkinter.Menu(root, tearoff=0)
self.popup_menu.add_command(label="Set label to 'test'", command=lambda: self.update_label(self.event))
self.popup_menu.add_command(label="Do nothing", command=lambda: self.reset_label(self.event))
root.bind_all("<Button-1>", self.popup)
root.bind_all("<Button-3>", self.reset_label)
def popup(self, event):
self.event = event
try:
self.popup_menu.tk_popup(event.x_root, event.y_root, 0)
finally:
self.popup_menu.grab_release()
def reset_label(self, event):
result = str(event.widget).split(".")[1]
y[int(result)].set("+")
def update_label(self,event):
result = str(event.widget).split(".")[1]
y[int(result)].set("test")
def main():
grid = create_grid()
root = tkinter.Tk()
menubar = tkinter.Menu(root)
filemenu = tkinter.Menu(menubar, tearoff=0)
filemenu.add_command(label="Exit", command=root.quit)
menubar.add_cascade(label="File", menu=filemenu)
root.config(menu=menubar)
my_gui = GridGUI(root, grid)
root.mainloop()
main()

Proper Use Of Inheritance in Tkinter using Button widget

I am writing a GUI in Tkinter using Python 2.11 using an OOP approach and trying to learn inheritance. I wrote a child class called ButtonField which is inherited from tk.Button. Inside ButtonField I have a method called pressMe which changes the color and text of the button when pressed.
My eventual goal is to have more buttons in the GUI and all the associated methods contained in the ButtonField class for cleaner and more manageable code.
When I press the Button the text "In Press Me Method" is displayed on the screen so the method is perhaps working but the button widget does not change text or background color.
import Tkinter as tk
class MainWindow(tk.Frame):
def __init__(self, parent, *args, **kwargs):
tk.Frame.__init__(self, parent, *args, **kwargs)
self.parent = parent
self.olFrame = tk.LabelFrame(text = 'Initial Frame', bg = 'grey')
self.olFrame.grid(column = 0, row = 0, sticky = 'w')
self.simpleButton = ButtonField(self.olFrame, text = "Press Me",bg= "green"
,command = ButtonField(self).pressMe)
self.simpleButton.grid(column = 0, row = 0)
class ButtonField(tk.Button):
def __init__(self, parent, *args, **kwargs):
tk.Button.__init__(self, parent, *args, **kwargs)
self.parent = parent
def pressMe(self):
print "In Press Me Method"
self.configure(text = "Pressed Now", background = "yellow")
#self.parent.configure(self, text = "Pressed Now", background = "yellow") #returns TclError: unknow option "-text"
root = tk.Tk()
root.geometry('500x400')
root.title('Test GUI')
root.configure(background = "black")
a = MainWindow(root)
root.mainloop()
Consider this line of code:
self.simpleButton = ButtonField(..., command = ButtonField(self).pressMe)
It is exactly the same as if you did this:
another_button = Button(self)
self.simpleButton = ButtonField(..., command=another_button.pressMe)
Because you are calling the function of a different button which is invisible, you don't see the changes. If you want the button to call its own function you will need to do it in two steps:
self.simpleButton = ButtonField(...)
self.simpleButton.configure(command=self.simpleButton.pressMe)

Clearing specific widgets in tkinter

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()

Python Tkinter Input Box

Good day.
I am trying to create my own input box for use in my project.
basically what i am trying to do is run my main form which will call the second. the user will provide some data on the second and when the press the ok/close button on the second for the data will be passed back to the first. similar in functionality to the inputbox.
here is what i have created, but being new to python i am not sure where i am going wrong/nor can i quick figure out when to put the return.
My Class is here
import tkinter as tk
class MainWindow():
def __init__(self, parent):
top = self.top = tk.Toplevel(parent)
self.myLabel = tk.Label(top, text='Enter a Grouping Name')
self.myLabel.pack()
self.myEntryBox = tk.Entry(top)
self.myEntryBox.focus_set()
self.myEntryBox.pack()
self.mySubmitButton = tk.Button(top, text='OK', command=self.DestWin)
self.mySubmitButton.pack()
def DestWin(self):
self.top.destroy()
The method to call it is here
abc=configurator.MainWindow(root)
Not exactly sure what you are trying to achieve, but if you are trying to get values from one window to another, below you can find an extended example based on your code.
import tkinter as tk
class MainWindow():
def __init__(self, parent):
top = self.top = tk.Toplevel(parent)
self.myLabel = tk.Label(top, text='Enter a Grouping Name')
self.myLabel.pack()
self.myEntryBox = tk.Entry(top)
self.myEntryBox.focus_set()
self.myEntryBox.pack()
self.mySubmitButton = tk.Button(top, text='OK', command=self.DestWin)
self.mySubmitButton.pack()
def DestWin(self):
# call callback function setting value in MyFrame
self.callback(self.myEntryBox.get())
self.top.destroy()
def set_callback(self, a_func):
self.callback = a_func
class MyFrame(tk.Frame):
def __init__(self, parent, **kwargs):
super().__init__(parent, **kwargs)
self.pack()
self.myLabel1 = tk.Label(parent, text='Click OK to enter the group name')
self.myLabel1.pack()
self.mySubmitButton1 = tk.Button(parent, text='OK', command=self.get_group_name)
self.mySubmitButton1.pack()
def get_group_name(self):
mw = MainWindow(None)
# provide callback to MainWindow so that it can return results to MyFrame
mw.set_callback(self.set_label)
def set_label(self, astr = ''):
self.myLabel1['text'] = astr
root = tk.Tk()
mf = MyFrame(root)
root.mainloop()
The screenshot:
The text from the right window, when OK is pressed, will be shown in the left window. This is achieved through callbacks. MainWindow takes a callback function, and when you press OK, it is executed. The callback is set_label from MyFrame.
Hope this helps.

Categories