Python: Using Tkinter radio-buttons to add/remove fields from GUI - python

I'm currently working on a GUI-based (Tkinter) project used to reduce matrices, just for practice.
Before I dive into the actual math stuff, I'm working on a basic window to ask the user for information to format their matrix.
In one field, I have radio-buttons - one is "Fraction", the other is "Decimal" - they can choose either for their output format. What I want is, if the user chooses "Decimal", another Entry field will appear below so that they can enter the number of decimals to round to, and if they choose "Fraction", the field won't appear.
I've searched around a bit on Stack Overflow, and saw a bit about using "raise()" and "lower()" methods, but it's not working right now. If you guys can give any input, that would be great. General feedback about the GUI is also appreciated - I learned GUIs in Python in school a while back, so if the format is bad, let me know!
Thanks in advance!
Edit: I'm running Python 3.4.
import tkinter
class Reducer:
'''
Takes user input of a matrix and row-reduces it to RREF.
GUI-Based interface that asks the user for the # of rows and columns, as well as output formatting.
The program can output as a fraction or a decimal with a specified number of decimals.
'''
def __init__(self):
'''
Initializes the GUI interface that asks the user for input.
No parameters or returns.
'''
self.main_window = tkinter.Tk()
#Initalize frames.
self.info_frame = tkinter.Frame()
self.size_frame = tkinter.Frame()
self.format_frame = tkinter.Frame()
self.digit_frame = tkinter.Frame()
self.button_frame = tkinter.Frame()
#Object for the information frame.
self.info_label = tkinter.Label(self.info_frame, text="Enter the number of rows and columns, your preferred output format,"
" and the number of trailing decimals if applicable.", justify="left",
wraplength=275).pack()
#Objects for the size frame - the matrix's rows/columns are set here.
self.row_label = tkinter.Label(self.size_frame, text="Rows:").pack(side="left")
self.row = tkinter.Entry(self.size_frame, width=3).pack(side="left")
self.col_label = tkinter.Label(self.size_frame, text="Columns:").pack(side="left")
self.col = tkinter.Entry(self.size_frame, width=3).pack(side="left")
#Objects for the digit frame
self.digit_label = tkinter.Label(self.digit_frame, text="Digits:")
self.digit = tkinter.Entry(self.digit_frame, width=3)
self.digit_label.pack(side="left")
self.digit.pack(side="left")
#Objects for the format frame - the output formatting is specified here.
self.output_var = tkinter.IntVar()
self.output_var.set(0)
self.fraction = tkinter.Radiobutton(self.format_frame, text="Fraction", variable=self.output_var, value=0,
command=self.hide_digits()).pack(side="left")
self.decimal = tkinter.Radiobutton(self.format_frame, text="Decimal", variable=self.output_var, value=1,
command=self.show_digits()).pack(side="left")
#Object for the bottom frame
self.button = tkinter.Button(self.button_frame, text="Next", command=self.reduce).pack()
#Pack frames.
self.info_frame.pack(anchor="nw")
self.size_frame.pack(anchor="nw")
self.format_frame.pack(anchor="nw")
self.digit_frame.pack(anchor="nw")
self.button_frame.pack(anchor="nw")
tkinter.mainloop()
def show_digits(self):
self.digit_label.lift(self.col)
self.digit.lift(self.col)
def hide_digits(self):
self.digit_label.lower(self.col)
self.digit.lower(self.col)
def reduce(self):
pass
reducer = Reducer()

Here is an example of what I mentioned earlier! this makes a new entry field (one at any time).
import tkinter as tk
root = tk.Tk()
global num
num = 0
def create():
global entry
global num
if num == 0:
num = 1
entry = tk.Entry()
entry.pack()
def destroy():
global entry
global num
if num == 1:
num = 0
entry.destroy()
button1 = tk.Button(text = 'create entry', command = create)
button1.pack()
button2 = tk.Button(text = 'destroy entry', command = destroy)
button2.pack()
root.mainloop()
you could also disable and enable the entry field like this, but then the widget is always there so it's up to you:
import tkinter as tk
root = tk.Tk()
global entry
entry = tk.Entry(state = 'disabled')
entry.pack()
def create():
global entry
entry.config(state = 'normal')
def destroy():
global entry
entry.config(state = 'disabled')
button1 = tk.Button(text = 'create entry', command = create)
button1.pack()
button2 = tk.Button(text = 'destroy entry', command = destroy)
button2.pack()
root.mainloop()
hope that helps a bit.

Related

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 - 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!

I have made multiple tkinter entry widgets usign a while loop, and I'm having issues defining separate variables for each one

Part of my program prompts the user to enter a sample number into an entry field, and then click a button which generates that number of entry widgets beneath it (to enter information about each sample). The entry widgets appear exactly as expected, one beneath the other for the entered number of samples.
I can't figure out how to separate the variables for each of those new entry widgets now. I had hoped that each one would allow me to enter a different value, and then call them back via .get(). That's not the case, they ALL change together to whatever I type into one. Below is the section of code that I believe is the issue:
normval_f= IntVar()
x= samp.get()
f=1
while f<=x:
f_entry = ttk.Entry(mainframe, width=7, textvariable=normval_f)
f_entry.grid(column=1, row=f+12)
f_label = ttk.Label(mainframe, text="Sample "+str(f)+ " value").grid(column=2, row=f+12, sticky=W)
f=f+1
List and Dictionaries can be very useful when dealing with large amounts or potently large amounts of widgets.
Below is a bit of code to accomplish what you described in your question.
I use a list called self.my_entries to store all the entries that will be created based on the value of what the user types in. I also added a little error handling just in case the user tries to type in something other than a number or nothing is enter when the button is pressed.
The first entry and button are placed on the root window and for all of the entry fields that are going to be created we add a frame below the first button. This will allow us to manage the entry fields a bit easier if we want to reset the field later.
from tkinter import *
class Example(Frame):
def __init__(self):
Frame.__init__(self)
self.pack()
self.my_entries = []
self.entry1 = Entry(self)
self.entry1.grid(row = 0, column = 0)
Button(self, text="Set Entry Fields", command=self.create_entry_fields).grid(row = 1, column = 0)
self.frame2 = Frame(self)
self.frame2.grid(row = 2, column = 0)
def create_entry_fields(self):
x = 0
try:
x = int(self.entry1.get())
if x != 0:
for i in range(x):
self.my_entries.append(Entry(self.frame2))
self.my_entries[i].grid(row=i, column=1)
f_label = Label(self.frame2, text="Label {}: ".format(i+1))
f_label.grid(row=i, column=0)
Button(self.frame2, text="Print to console", command=self.print_all_entries).grid(row=x, column=0, sticky = "nsew")
Button(self.frame2, text="Reset", command=self.clear_frame2).grid(row=x, column=1, sticky = "nsew")
except:
print("Invalid entry. Only numbers are allowed.")
def print_all_entries(self):
for i in self.my_entries:
print(i.get())
def clear_frame2(self):
self.my_entries = []
self.frame2.destroy()
self.frame2 = Frame(self)
self.frame2.grid(row = 2, column = 0)
if __name__ == '__main__':
root = Tk()
test_app = Example()
root.mainloop()
Let me know if you have any questions about the above code.
A few suggestions; don't use a while loop, use a for loop instead. Secondly, I'd suggest storing each of your variables inside a list, or as I have done, in a list. At the moment, you are overwriting your variables each time you go through the loop so you need to create a new one each time and store it somewhere.
Here is an example of storing a number of fields in a dictionary.
from tkinter import *
class App(Frame):
def __init__(self,master):
Frame.__init__(self, master)
fields = ['name','age','gender']
self.field_variables = {}
for idx,field in enumerate(fields):
self.field_variables[field] = StringVar()
f_entry = Entry(self,textvariable=self.field_variables[field])
f_entry.grid(column=2,row=idx)
f_label = Label(self,text=field)
f_label.grid(column=1,row=idx)
go_btn = Button(self,text="Go",command=self.get_all)
go_btn.grid()
def get_all(self):
print(self.field_variables)
if __name__ == '__main__':
root = Tk()
app = App(root)
app.grid()

Python's tkinter smart entry password

I want to create a password entry.
One easy solution is:
password = Entry(root, font="Verdana 22")
password.config(show="*");
but the problem is that to avoid typos, I want to show the item clicked to be visible only for a few seconds, while everything else is hidden. After a few seconds everything is hidden.
It's not easy to do exactly what you want with Tkinter, but here's something close: when you press a key it displays the whole contents of the Entry, but after one second the text is hidden again.
I developed this on Python 2; to use it on Python 3 change Tkinter to tkinter.
import Tkinter as tk
class PasswordTest(object):
''' Password Entry Demo '''
def __init__(self):
root = tk.Tk()
root.title("Password Entry Demo")
self.entry = e = tk.Entry(root)
e.pack()
e.bind("<Key>", self.entry_cb)
b = tk.Button(root, text="show", command=self.button_cb)
b.pack()
root.mainloop()
def entry_cb(self, event):
#print(`event.char`, event.keycode, event.keysym )
self.entry.config(show='')
#Hide text after 1000 milliseconds
self.entry.after(1000, lambda: self.entry.config(show='*'))
def button_cb(self):
print('Contents:', repr(self.entry.get()))
PasswordTest()
It would be tricky to only display the last char entered. You'd have to modify the displayed string manually while maintaining the real password string in a separate variable and that's a bit fiddly because the user can move the insertion point cursor at any time.
On a final note, I really don't recommend doing anything like this. Keep passwords hidden at all times! If you want to reduce the chance of typos in newly-chosen passwords, the usual practice is to make the user enter the password twice.
This a simple trick to visible the password with a checkbutton.
When it is checked the password will be visible
from Tkinter import *
from tkinter import ttk
def show_hide_psd():
if(check_var.get()):
entry_psw.config(show="")
else:
entry_psw.config(show="*")
window = Tk()
window.wm_title("Password")
window.geometry("300x100+30+30")
window.resizable(0,0)
entry_psw = Entry(window, width=30, show="*", bd=3)
entry_psw.place(x = 5, y = 25)
check_var = IntVar()
check_show_psw = Checkbutton(window, text = "Show", variable = check_var, \
onvalue = 1, offvalue = 0, height=2, \
width = 5, command = show_hide_psd)
check_show_psw.place(x = 5, y = 50)
window.mainloop()

Get the same input multiple times in python tkinter?

I am making a program to get multiple user names and store them in a XML file. The XML part works fine, but I am having trouble with the GUI. I want to be able to ask the user how many users he wants to input, then repeat an Entry that number of times. How can I do that, while maintaining a quit function? I tried:
def quitter():
exit()
quit()
quitterButton = Button(master, text="Quit", command=quitter)
mainCanvas.create_window(50, 330, window=quitterButton, tag="quitter")
num = int(raw_input("How many users are you going to put in?"))
for x in range(0,num):
#Create User entry Variable
userEnter = StringVar()
usrVar = ""
#Create enter box:
userEntryBox = Entry(master, textvariable = userEnter)
mainCanvas.create_window(250, 300, window=userEntryBox, tag="UserEnterA")
def gotInput(self):
usrVar = userEnter.get();
mainCanvas.create_text(250, 330, text="User Inputted: " + usrVar, tags="0")
mainCanvas.delete("UserEnterA")
#Bind entry box
userEntryBox.bind('<Key-Return>', gotInput)
userEntryBox.wait_window(userEntryBox)
#Create a new user element
newUsr= ET.Element('Member')
#Add element to the Root
root.append(newUsr)
#Make a sub element Name, set name
usrName = ET.SubElement(newUsr, 'Name')
usrName.text = usrVar;
...
tree.write('./output.xml')
What is the best way to go around it? I won't know the number of inputs, and I want the quit button to work at all times .
Behavior of your program is a bit unclear for me, but I try to help.
First solution: show tkinter askstring dialog num times. You can break for loop if user press Cancel button. It's not exactly what you want, but it's very easy to implement:
from tkinter import *
import tkinter.simpledialog as simpledialog
def add_users():
n = simpledialog.askinteger('', 'How many users are you going to put in?', initialvalue=1, minvalue=1, maxvalue=10)
if not n: # 'Cancel'
return
for i in range(n):
user = simpledialog.askstring('', 'User #%s from #%s' % (i+1, n))
if user is None: # 'Cancel'
return
# Do something useful
print(user)
root = Tk()
Button(root, text='Add users', command=add_users).pack(padx=50, pady=50)
Button(root, text='Quit', command=root.destroy).pack(pady=30)
root.mainloop()
Second (if you want to put entry and all new names to window with quit button):
from tkinter import *
import tkinter.simpledialog as simpledialog
class YourApp():
def __init__(self):
self.root = Tk()
Button(self.root, text='Quit', command=self.root.destroy).pack(pady=20)
self.ask_button = Button(self.root, text='Add users', command=self.add_users)
self.ask_button.pack(padx=50, pady=50)
self.root.mainloop()
def add_users(self):
self.users_count = 0
self.user_name = StringVar()
self.frame = Frame(self.root)
self.frame.pack()
self.users_count = simpledialog.askinteger('', 'How many users are you going to put in?', initialvalue=1, minvalue=1, maxvalue=10)
self.user_entry = Entry(self.frame, textvariable=self.user_name)
self.user_entry.pack(pady=10)
self.user_entry.bind('<Key-Return>', self.on_new_user)
self.user_entry.focus_set()
def on_new_user(self, event):
# your code
print(self.user_name.get())
Label(self.frame, text='User Inputted: %s' % self.user_name.get()).pack()
self.users_count -= 1
if not self.users_count:
self.user_entry.destroy()
self.user_name.set('')
YourApp()
There are three geometry managers in Tkinter: place (it's very similar to canvas in your case), grid and pack.
Canvas usually used for drawing pictures or graphs.

Categories

Resources