Enabling button upon text entry - python

New to Tkinter and a while since I did Python.
I have a page with multiple entry boxes.
I want the button to be disabled unless all boxes have text in them.
When it is enabled the button will open up a new form.
Here's the code:
from tkinter import *
def only_numeric_input(P):
# checks if entry's value is an integer or empty and returns an appropriate boolean
if P.isdigit() or P == "": # if a digit was entered or nothing was entered
return True
return False
def toggle_state(*_):
if entry1.var.get():
button['state'] = 'disabled'
else:
button['state'] = 'normal'
def raise_frame(frame):
frame.tkraise()
main_win = Tk()
main_win.geometry('500x500')
###################################################
second_frame = Frame(main_win)
second_frame.place(x=0, y=0, width=500, height=250)
Main_frame = Frame(main_win)
Main_frame.place(x=0, y=0, width=500, height=250)
###################################################
#I want it so that the button only allows the user to press it if the entry widget has numbers inside. That
#will then open up the 2nd frame
entry_1 = Entry(Main_frame)
entry_1.place(x=200, y=50)
entry_1_check = Main_frame.register(only_numeric_input)
entry_1.configure(validate="key", validatecommand=(entry_1_check, "%P"))
button1=Button(Main_frame, text='Admin', width=20, bg='brown', fg='white',
command=lambda:[toggle_state, raise_frame(second_frame)])
button1.place(x=0, y=0)
main_win.mainloop()
P.S this may not be preferred method but I am just figuring some stuff out :)

In the description of the question, several entry boxes are mentioned so I made my solution work with an arbitrary number of entry boxes (4 in the example).
What should change the state of the button is when all entries contain numbers, therefore it does not make sense to execute toggle_state() in the button's command. On the contrary, it should be executed when the entries' content is modified.
My solution to toggle the state of the button, is to call the toggle_state() function inside the only_numeric_input() function so that it is executed every time the content of one of the entries is changed. However this means that the function is executed before the entry content is changed, so entry.get() cannot be used. Therefore I use a dictionary to keep track of which entry is empty: not_empty = {<entry name>: bool, ...} which is convenient because we can get the entry name with "%W" in validatecommand. I update this dictionary inside only_numeric_input() before executing toggle_state().
Here is the code:
from tkinter import Entry, Tk, Button, Frame
def only_numeric_input(P, W):
if P.isdigit() or P == "":
not_empty[W] = P != "" # update state of the entry in dictionary
toggle_state() # update button's state
return True
return False
def toggle_state():
none_empty = True
for b in not_empty.values():
none_empty = none_empty and b
if none_empty: # all entries contain numbers
button['state'] = 'normal'
else:
button['state'] = 'disabled'
def raise_frame(frame):
frame.tkraise()
main_win = Tk()
main_win.geometry('500x500')
main_win.grid_columnconfigure(0, weight=1)
main_win.grid_rowconfigure(1, weight=1)
second_frame = Frame(main_win)
second_frame.grid(row=0, column=0, sticky='nsew')
main_frame = Frame(main_win)
main_frame.grid(row=0, column=0, sticky='nsew')
entry_check = main_frame.register(only_numeric_input)
not_empty = {} # keep track of entries' content
# create the entries
for i in range(4):
entry = Entry(main_frame, validate="key", validatecommand=(entry_check, "%P", "%W"))
entry.pack()
not_empty[str(entry)] = False
button = Button(main_frame, text='Admin', width=20, bg='brown', fg='white',
state='disabled',
command=lambda: raise_frame(second_frame))
button.pack()
main_win.mainloop()

Related

How to get a widget value created in a for loop when button is clicked in python tkinter

I'm trying to create a basic counting app so that I can go in the field and count multiple items at once. I created the majority of the widgets with a for loop and I am trying to get the value of the label self.count and add to it.
Is there a way to retrieve the values with the references I have stored?
Since the values are in different memory locations and I don't know how to tell which button is pressed based on the below code.
(Note: the buttons should only interact with the values directly above them.)
import tkinter as tk
from tkinter import ttk
from dataclasses import dataclass
#dataclass
class fwidgets:
frame: str
framenum: int
widnum: int
widgets: list
ref: list
class App:
def addcount(self):
pass
def resetcount(self):
pass
def main(self):
win = tk.Tk()
largedoublefrm = ttk.Frame(win)
largesinglefrm = ttk.Frame(win)
smalldoublefrm = ttk.Frame(win)
smallsinglefrm = ttk.Frame(win)
combofrm = ttk.Frame(win)
largedoublefrm.grid(column=2, row=0)
largesinglefrm.grid(column=0, row=0)
smalldoublefrm.grid(column=3, row=0)
smallsinglefrm.grid(column=1, row=0)
combofrm.grid(column=4, row=0)
mainframe = fwidgets('win', 200, 5, ('largesinglefrm', 'smallsinglefrm',
'largedoublefrm', 'smalldoublefrm', 'combofrm'),
(largesinglefrm, smallsinglefrm, largedoublefrm,
smalldoublefrm, combofrm))
self.refframe = []
x=0
for wid in mainframe.ref:
ttk.Label(wid, text=mainframe.widgets[0]).pack()
self.count = ttk.Label(wid, text='0', font=("Arial", 20))
add = tk.Button(wid, text='ADD', height=5, width=15,
command=self.addcount)
reset = tk.Button(wid, text='RESET', height=5, width=15,
command=self.resetcount)
self.count.pack()
add.pack()
reset.pack()
frame = fwidgets(mainframe.widgets[x], x, 3, ('count', 'add', 'reset'),
(self.count, add, reset))
self.refframe.append(frame)
x += 1
print(self.refframe)
win.mainloop()
if __name__ == '__main__':
app = App()
app.main()
I'm not sure where to go from here. Any help would be appreciated.
To find the relevant objects first remove win.mainloop(), since only one
mainloop is permitted per tk.tk instance.
Then modify your code like this.
def addcount(self, i):
print(i.winfo_children())
def resetcount(self, i):
print(i.winfo_children())
add = tk.Button(wid, text='ADD', height=5, width=15,
command = lambda w = wid: self.addcount(w))
reset = tk.Button(wid, text='RESET', height=5, width=15,
command = lambda w = wid: self.resetcount(w))
# win.mainloop()
Now when you click a button a list of relevant objects will be displayed.

tkinter window doesn't load correctly after first file run in Spyder

So far I have tried the advice from u/OA998
"""https://www.reddit.com/r/learnpython/comments/45h05k/solved_kernel_crashing_when_closing_gui_spyder/
My code is supposed to just open up a window with two labels and an entry field. The code works fine if I restart the Kernel but just running it after closing the window will result in an window popping up that has no text (from the StringVar). The third time it doesn't even open anymore. I'm not quite sure what causes this."""
Code:
""" create a GUI inside a class (this allows variables to be added and changed by several parts in the GUI). By using the “self” keyword we can access the attributes and methods of the class in python. It binds the attributes with the given arguments.
from tkinter import *
""" create a GUI inside a class (this allows variables to be added and changed by several parts in the GUI)"""
""" By using the “self” keyword we can access the attributes and methods of the class in python. It binds the attributes with the given arguments."""
class TESTGUI:
def __init__(self, window):
"""We will use an entry widget, and StringVar to keep track of the current text in the
box, and a label to display a message."""
"""labelpositioning"""
margin = 2
self.spacer = Label(window, width=margin, height=margin)
self.spacer.grid(column=0,row=0)
"""for toogle button create StringVar in tkinter class to hold the text"""
"""labeltexts"""
self.labelText_1 = StringVar()
self.labelText_1.set("begin experiment")
self.labelText_2 = StringVar()
self.labelText_2.set("calibrate")
"""labelformat"""
self.label = Label(window, textvariable=self.labelText_1, width=12, height=3, borderwidth=3, relief=SOLID)
self.label.grid(column=1, row=1)
self.label = Label(window, textvariable=self.labelText_2, width=12, height=3, borderwidth=3, relief=SOLID)
self.label.grid(column=2, row=1)
"""labelbutton"""
self.button = Button(window, text="press", command=self.pressed_button_1)
self.button.grid(column=1,row=2)
self.button = Button(window, text="press", command=self.pressed_button_2)
self.button.grid(column=2,row=2)
"""Entry(password)Label"""
self.entryLabel_1 = Label(window, text = "enter your password")
self.entryLabel_1.grid(column=0,row=3)
""" Next add a StringVar to hold the password and Entry box to type the password."""
""" The trace function will call a checkStrength() function when the StringVar is changed."""
self.password = StringVar()
self.password.trace("w", lambda name, index, mode, password=self.password:self.checkStrenght())
self.entry = Entry(window, textvariable=self.password)
self.entry.grid(column=1, row=3)
""" then create the StringVar to hold the strength string, and the label to display it. """
self.strenghtText = StringVar()
self.strenghtText.set("")
self.strenghtLabel = Label(window, textvariable=self.strenghtText, width=10)
self.strenghtLabel.grid(column=3, row=3)
"""CheckStrenghtFunctionOfEntry"""
def checkStrenght(self):
lenght = len(self.password.get())
if lenght == 0:
self.strenghtText.set("")
self.strenghtLabel.config(bg="SystemWindowBody")
elif lenght >= 1:
self.strenghtText.set("strong")
self.strenghtText.config(bg = "green3")
"""ButtonFunction"""
def pressed_button_1(self):
if self.labelText_1.get() == "begin experiment":
self.labelText_1.set("abort experiment")
else:
self.labelText_1.set("begin experiment")
def pressed_button_2(self):
if self.labelText_2.get() == "calibrate":
self.labelText_2.set("recalibrate")
else:
self.labelText_2.set("calibrate")
""" define Variables for window size """
width=300
height=300
""" define tk for used variable to use tkinter class """
if __name__ == "__main__":
window = Tk()
window.minsize(width, height)
window.title("Photonics Lab")
gui = TESTGUI(window)
window.mainloop()

TKINTER : How to keep track of Dynamically Created Buttons and action something when it is pressed

I am trying to find a solution for dynamically created buttons. For e.g. my app is creating 5 buttons dynamically and each button have different functionality within a callback function.
what I am trying to do is, if Button 5 is pressed, how would I compare it in function if Button 5 is clicked. I am not creating separate function for each and every button. There is one single function, within that I have logic for every button.
from tkinter import *
root = Tk()
masterFrame = Frame(root,bg = 'orange',height =300,width =600)
masterFrame.grid(row = 0,column = 0,sticky ='nsew')
ButtonString = ['DashBoard','Report','Tools','Settings','About']
# How should I compare what function to call when Button object is passed
def viewButton(btn):
def DashBoard():
pass
def Report():
pass
def Tools():
pass
#This is what I want to do
# if btn == 'DashBoard:
# DashBoard()
# if btn == 'Report:
# Report()
for index,btn in enumerate(ButtonString):
#Button text remains same for all Buttons which is "View"
btn = Button(masterFrame,text = 'View',command = viewButton)
btn.grid(row = index ,column = 0,padx =10,pady =10)
root.rowconfigure(0,weight = 1)
root.columnconfigure(0,weight =1)
root.mainloop()
from tkinter import *
from functools import partial
root = Tk()
masterFrame = Frame(root, bg='orange', height=300, width=600)
masterFrame.grid(row=0, column=0, sticky='nsew')
ButtonString = ['DashBoard', 'Report', 'Tools', 'Settings', 'About']
# Calling this function from somewhere else via Queue
def viewButton(btn):
print(btn)
def DashBoard():
pass
def Report():
pass
def Tools():
pass
# This is what I want to do
# if btn == 'DashBoard:
# DashBoard()
# if btn == 'Report:
# Report()
for index, btn_name in enumerate(ButtonString):
btn = Button(masterFrame, text='View '+str(btn_name), command=partial(viewButton, btn_name))
btn.grid(row=index, column=0, padx=10, pady=10)
root.rowconfigure(0, weight=1)
root.columnconfigure(0, weight=1)
root.mainloop()
you can use above code to check which button is pressed. You can change arguments according to your need.

Tkinter - Retrieve Values from Dynamically Generated Widgets - Callback

I'm trying to make a GUI through Tkinter that will calculate production based on some user input. Based on the number of systems the user selects, I have that number of option menus pop up for the inverter type and that number of entry widgets pop up for modules per string, strings per inverter, and inverters per system. See the picture for an example if the user selects 2 systems.
I'm using a callback function to grab the user selected number of systems real time to dynamically generate the inverter/module widgets discussed above.
My issue is that I'm unable to retrieve the values from these widgets. My attempt is shown in the weather calculation function.
I'm assuming the issue is because I generate the widgets/variables within the callback function. However, I haven't been able to figure out a way to dynamically generate the number of widgets based on user input outside of the callback function.
Any assistance with this would be greatly appreciated!
class Window:
# Define User Inputs:
def __init__(self, master):
master.title('Production Analysis Tool')
# EQUIPMENT PARAMETERS
# callback function to create entry boxes based on number of systems
def callback(*args):
self.system_size = int(self.system_size_raw.get())
# Modules per String
self.L3 = Label(root, text = "Number of Modules Per String").grid(row=20, column=1, sticky=E)
self.modules_string_raw = IntVar(root)
modules_per_string =[]
for i in range(self.system_size):
self.label = Label(root, text = "System {}".format(i+1)).grid(row=21+i, column=1, sticky=E)
self.widget = Entry(root).grid(row=21+i, column=2, sticky=W)
modules_per_string.append(self.widget)
# Number of Systems
self.L1 = Label(root, text = "Number of Systems").grid(row=1, column=1, sticky=E)
self.system_size_raw = IntVar(root)
choices = [1,2,3,4,5,6,7,8,9,10]
self.popupMenu2 = OptionMenu(root, self.system_size_raw, *choices).grid(row=1, column=2, sticky=W)
self.system_size_raw.trace("w", callback)
#Calculation Function
def weather_calculation(self):
# Get Values from User Input
self.mod_strings = np.float(self.modules_string_raw.get())
root = Tk()
root.configure()
window = Window(root)
root.mainloop()
All you need to do is save a reference to your Entry widgets in a list. You can then iterate over that list to get the value of each widget.
It appears that you're already saving the widgets to the list variable modules_per_string. All you need to do is make that global or an object attribute rather than a local variable so other functions can reference it.
As Bryan Oakley said, make list for widgets to store each objects of entries and label in two list.
For Example:
import tkinter as tk
class Demo:
def __init__(self):
self.root = tk.Tk()
self.root.geometry("600x600")
systems_label = tk.Label(self.root, text="No Of Systems:")
systems_label.place(x=100, y=20)
no_Of_System_Ent = tk.Entry(self.root, width=15)
no_Of_System_Ent.place(x=200, y=20)
submit_Button = tk.Button(self.root, text="Submit", command=lambda: self.process(no_Of_System_Ent.get()))
submit_Button.place(x=350,y=20)
def display(self,sys_len):
for i in range(sys_len):
buffer = self.obj_of_entries[i].get()
print(buffer)
def delete(self,sys_len):
for i in range(sys_len):
self.obj_of_entries[i].destroy()
self.obj_of_labels[i].destroy()
def process(self,length_sys):
self.obj_of_entries = []
self.obj_of_labels = []
y_pos = 80
for i in range(int(length_sys)):
#Adding objects of label in list 'obj_of_labels'
self.obj_of_labels.append(tk.Label(self.root,text="System "+str(i)))
self.obj_of_labels[len(self.obj_of_labels)-1].place(x=100,y=y_pos)
#Adding objects of entry in list 'obj_of_entries'
self.obj_of_entries.append(tk.Entry(self.root,width=15))
self.obj_of_entries[len(self.obj_of_entries)-1].place(x=200,y=y_pos)
#Increments Y by 50
y_pos = y_pos + 50
self.delete_Button = tk.Button(self.root, text="Delete All", command=lambda: self.delete(int(length_sys)))
self.delete_Button.place(x=200,y=400)
self.print_Button = tk.Button(self.root, text="Print All", command=lambda: self.display(int(length_sys)))
self.print_Button.place(x=350,y=400)
ob=Demo()
In this example:
I created a entry and button in the init function to take no of systems from user.
def __init__(self):
self.root = tk.Tk()
self.root.geometry("600x600")
systems_label = tk.Label(self.root, text="No Of Systems:")
systems_label.place(x=100, y=20)
no_Of_System_Ent = tk.Entry(self.root, width=15)
no_Of_System_Ent.place(x=200, y=20)
submit_Button = tk.Button(self.root, text="Submit", command=lambda: self.process(no_Of_System_Ent.get()))
submit_Button.place(x=350,y=20)
After clicking submit button,it will go to process function.
Ps: length_sys is the no of systems.
def process(self,length_sys):
self.obj_of_entries = []
self.obj_of_labels = []
y_pos = 80
for i in range(int(length_sys)):
#Adding objects of label in list 'obj_of_labels'
self.obj_of_labels.append(tk.Label(self.root,text="System "+str(i)))
self.obj_of_labels[len(self.obj_of_labels)-1].place(x=100,y=y_pos)
#Adding objects of entry in list 'obj_of_entries'
self.obj_of_entries.append(tk.Entry(self.root,width=15))
self.obj_of_entries[len(self.obj_of_entries)-1].place(x=200,y=y_pos)
#Increments Y by 50
y_pos = y_pos + 50
self.delete_Button = tk.Button(self.root, text="Delete All", command=lambda: self.delete(int(length_sys)))
self.delete_Button.place(x=200,y=400)
It will append the entry and label obj in its respective list and place the current obj in GUI window.
At Last,It will increment y axis by 80 so that next label and entry comes down to the previous one.
If user clicks delete all button,then it will go to delete all list obj of both entries and labels.
Ps: sys_len is the no of systems.
def delete(self,sys_len):
for i in range(sys_len):
self.obj_of_entries[i].destroy()
self.obj_of_labels[i].destroy()
To see the content,use this code:
(It will print in the Python shell so you can see if data is correct or not.)
def display(self,sys_len):
for i in range(sys_len):
buffer = self.obj_of_entries[i].get()
print(buffer)
I think I solved the doubt.
Ciao!

Tkinter remove/overwrite elements from Frame

I created a button that retrieves a list from a DataFrame based on some input from a text field. Everytime the button is pressed, the list will be refreshed. I output the list (as an OptionMenu) in a separate Frame (outputFrame). However, every time I press this button, a new OptionMenu is added to the Frame (instead of overwriting the previous one). How can I make sure that the content of 'ouputFrame' is overwritten each time I press the button?
# start
root = Tkinter.Tk()
# frames
searchBoxClientFrame = Tkinter.Frame(root).pack()
searchButtonFrame = Tkinter.Frame(root).pack()
outputFrame = Tkinter.Frame(root).pack()
# text field
searchBoxClient = Tkinter.Text(searchBoxClientFrame, height=1, width=30).pack()
# function when button is pressed
def getOutput():
outputFrame.pack_forget()
outputFrame.pack()
clientSearch = str(searchBoxClient.get(1.0, Tkinter.END))[:-1]
# retrieve list of clients based on search query
clientsFound = [s for s in df.groupby('clients').count().index.values if clientSearch.lower() in s.lower()]
clientSelected = applicationui.Tkinter.StringVar(root)
if len(clientsFound) > 0:
clientSelected.set(clientsFound[0])
Tkinter.OptionMenu(outputFrame, clientSelected, *clientsFound).pack()
else:
Tkinter.Label(outputFrame, text='Client not found!').pack()
Tkinter.Button(searchButtonFrame, text='Search', command=getOutput).pack()
root.mainloop()
We can actually update the value of the OptionMenu itself rather than destroying it (or it's parent) and then redrawing it. Credit to this answer for the below snippet:
import tkinter as tk
root = tk.Tk()
var = tk.StringVar(root)
choice = [1, 2, 3]
var.set(choice[0])
option = tk.OptionMenu(root, var, *choice)
option.pack()
def command():
option['menu'].delete(0, 'end')
for i in range(len(choice)):
choice[i] += 1
option['menu'].add_command(label=choice[i], command=tk._setit(var, choice[i]))
var.set(choice[0])
button = tk.Button(root, text="Ok", command=command)
button.pack()
root.mainloop()

Categories

Resources