Related
I am trying to create a UNIT CONVERTER which is a GUI application in Python using Tkinter. I have created one main OptionMenu and two other OptionMenus. These other two OptionMenus are dependent on the main OptionMenu i.e.upon selecting a value from the main OptionMenu, the list of values in the other two OptionMenus changes. I have created two buttons "Convert" and "Reset". In the Reset Button, I am trying to reset the selections on all three OptionMenus.
Source Code
import tkinter as tk
from tkinter import messagebox
from math import *
root = tk.Tk()
root.title("Unit Converter")
root.geometry("600x400")
# A function for updating the dropdown lists upon selecting the operation.
def updateSubLists(self):
y.set('')
z.set('')
subUnitListFrom['menu'].delete(0,'end')
subUnitListTo['menu'].delete(0,'end')
for item in list(listOfUnits.get(x.get())):
subUnitListFrom['menu'].add_command(label=item,command=tk._setit(y,item))
subUnitListTo['menu'].add_command(label=item,command=tk._setit(z,item))
y.set(list(listOfUnits.get(x.get()))[0])
z.set(list(listOfUnits.get(x.get()))[0])
# A callback function to validate if the data entered is only digit or not.
def validateUserInput(input):
"""This method validates the data entered by the User to check if the entered data is a number or not."""
if input.isdigit() == True:
return True
elif input == "":
return True
else:
messagebox.showinfo("Information","Only number is allowed")
return False
# A function for resetting the entries selected.
def resetEntries():
""" This method helps in resetting the entries given as a input or selected by the User"""
x.set("")
unitList["menu"].delete(0,'end')
for item in list(listOfUnits.keys()):
unitList['menu'].add_command(label=item,command=tk._setit(x,item))
x.set(list(listOfUnits.keys())[0])
#updateSubLists('')
y.set('')
z.set('')
subUnitListFrom['menu'].delete(0,'end')
subUnitListTo['menu'].delete(0,'end')
for item in list(listOfUnits.get(x.get())):
subUnitListFrom['menu'].add_command(label=item,command=tk._setit(y,item))
subUnitListTo['menu'].add_command(label=item,command=tk._setit(z,item))
y.set(list(listOfUnits.get(x.get()))[0])
z.set(list(listOfUnits.get(x.get()))[0])
# Lists and Sub-lists creation
#listOfUnits = ['Area','Energy','Frequency','Length','Mass','Pressure','Speed','Temperature',
#'Time','Volume']
listOfUnits = {"Area":['Square Kilometer','Squatre Meter','Square Mile','Square Yard','Square Foot','Square Inch','Hectare','Acre'],
"Energy":['Joule','Kilo Joule','Gram Calorie','Kilo Calorie'],
"Frequency":['Hertz','Kilohertz','Megahertz','Kilohertz'],
"Length":['Kilometer','Meter','Centimeter','Millimeter','Micrometer','Nanometer','Mile','Yard','Foot','Inch'],
"Mass":['Tonne','Kilogram','Microgram','Milligram','Gram','Pound','Ounce'],
"Pressure":['Bar','Pascal','Pound per square inch','Standard atmosphere','Torr'],
"Speed":['Miles per hour','Meter per second','Foot per second','Kilometer per hour','Knot'],
"Temperature":['Celcius','Farhenheit','Kelvin'],
"Time":['Nanosecond','Microsecond','Millisecond','Second','Minute','Hour','Day','Week','Month','Calender Year','Decade','Century'],
"Volume":['Litre','Millilitre','Imperial Gallon','Imperial Pint']
}
# label text for header title
headerLbl = tk.Label(root,text="UNIT CONVERTER",fg="black",bg="light grey",font = ("Times New Roman", 30,"bold","italic","underline"))
headerLbl.grid(row = 0,column = 1,columnspan = 3)
# Label text for conversion tye selection
lbl1 = tk.Label(root,text="Type of Conversion:")
lbl1.grid(row = 1,column = 0,padx=20,pady=20)
# OptionMenu creation for the list of Units to select
global x
x = tk.StringVar()
x.set(list(listOfUnits.keys())[0])
unitList = tk.OptionMenu(root,x,*listOfUnits.keys(),command=updateSubLists)
unitList.grid(row = 1,column = 1,padx=20,pady=40)
# Label text for conversion type selection
lbl2 = tk.Label(root,text="From")
lbl2.grid(row = 2,column = 0)
# OptionMenu creation for the list of Sub Units to select.
global y
y = tk.StringVar()
y.set(list(listOfUnits.get(x.get()))[0])
subUnitListFrom = tk.OptionMenu(root,y,*listOfUnits.get(x.get()))
subUnitListFrom.grid(row=2,column=1,padx=20,pady=40)
# Entry widget for From label
fromEntry = tk.Entry(root,width=20)
valid_info = root.register(validateUserInput) # register the function for the validation
fromEntry.config(validate="key",validatecommand=(valid_info,'%P')) # Adding the properties for validation elements
fromEntry.grid(row=2,column=2)
# Label text for conversion type selection
lbl3 = tk.Label(root,text="To")
lbl3.grid(row = 3,column = 0)
# OptionMenu creation for the list of Sub Units to select.
global z
z = tk.StringVar()
z.set(list(listOfUnits.get(x.get()))[0])
subUnitListTo = tk.OptionMenu(root,z,*listOfUnits.get(x.get()))
subUnitListTo.grid(row=3,column=1)
# Entry widget for From label
ToEntry = tk.Entry(root,width=20,state="readonly")
ToEntry.grid(row=3,column=2)
# Logic for the convert button
convert_button = tk.Button(root,text="CONVERT",fg="black",bg ="yellow",font=("Times New Roman",12,"bold"))
convert_button.grid(row=4,column=1,padx=20,pady=40)
# Logic for the reset button
reset_button = tk.Button(root,text="RESET",fg="black",bg="yellow",font=("Times New Roman",12,"bold"),command=resetEntries)
reset_button.grid(row=4,column=2,padx=20,pady=40)
root.mainloop()
Problem Statement:
When clicked on Reset, logic works successfully but when I again select a new value in the main OptionMenu, the corresponding list of values are not reflecting in the other two OptionMenus. I am not able to understand "after I click the Reset Button , why my other two dropdowns are not reflecting the corresponding values when I change the value of the main OptionMenu".
You forget to pass updateSubLists as the third argument of tk._setit(...) inside resetEntries():
def resetEntries():
""" This method helps in resetting the entries given as a input or selected by the User"""
x.set("")
unitList["menu"].delete(0,'end')
for item in list(listOfUnits.keys()):
unitList['menu'].add_command(label=item,command=tk._setit(x,item,updateSubLists))
...
from tkinter import *
import pandas as pd
df = pd.DataFrame({'item': list('abcde'), 'default_vals': [2,6,4,5,1]})
def input_data(df):
box = Tk()
height = str(int(25*(df.shape[0]+2)))
box.geometry("320x" + height)
box.title("my box")
#initialise
params, checkButtons, intVars = [], [], []
default_vals = list(df.default_vals)
itemList = list(df.item)
for i,label in enumerate(itemList):
Label(box, text = label).grid(row = i, sticky = W)
params.append(Entry(box))
params[-1].grid(row = i, column = 1)
params[-1].insert(i, default_vals[i])
intVars.append(IntVar())
checkButtons.append(Checkbutton(variable = intVars[-1]))
checkButtons[-1].grid(row = i, column = 3)
def sumbit(event=None):
global fields, checked
fields = [params[i].get() for i in range(len(params))]
checked = [intVars[i].get() for i in range(len(intVars))]
box.destroy()
#add submit button
box.bind('<Return>', sumbit)
Button(box, text = "submit",
command = sumbit).grid(row = df.shape[0]+3, sticky = W)
box.focus_force()
mainloop()
return fields, checked
I am new to tkinter and not sure what I a trying to do is possible.
At present, my script (simplified here to a function rather than a class) builds a box with all the default values entered in the fields:
Instead, I want to start with empty fields which, once the corresponding checkButton is clicked will get the default value (should still be able to manually change it through the field as happens now), and also, once any value is entered in a given field, the corresponding checkButton is selected.
Are these possible?
It is possible, but let me preface my solution with a few cautions on your current code:
It's rarely advisable to do a star import (from tkinter import *) as you don't have any control over what gets imported into your namespace. It's more advisable to explicitly import what you need as a reference:
import tkinter as tk
tk.Label() # same as if you wrote Label()
tk.IntVar() # same as if you called IntVar()
The behaviour you wanted, while possible, might not be necessarily user friendly. What happens when a user has already entered something, and unchecks the checkbox? Or what happens if the checkbox was selected and then the user deleted the information? These might be things you want to think about.
Having said that, the solution is to use add a trace callback function over your variable(s). You'll also need to add a StringVar() for the Entry boxes as you wanted a two way connection:
# add strVars as a list of StringVar() for your Entry box
params, checkButtons, intVars, strVars = [], [], [], []
During your iteration of enumerate(itemList), add these:
# Create new StringVar()
strVars.append(StringVar())
# add a trace callback for tracking changes over the StringVar()
strVars[-1].trace_add('write', lambda var, var_idx, oper, idx=i: trace_strVar(idx))
# update your Entry to set textvariable to the new strVar
params.append(Entry(box, textvariable=strVars[-1]))
# similarly, add a trace for your IntVar
intVars[-1].trace_add('write', lambda var, var_idx, oper, idx=i: trace_intVar(idx))
You'll need to define the two trace callback functions before you iterate through the widget creations:
def trace_intVar(idx):
# if Checkbox is checked and Entry is empty...
if intVars[idx].get() and not params[idx].get():
# prefill Entry with default value
params[idx].insert(0, df.default_vals[idx])
def trace_strVar(idx):
# if Entry has something...
if strVars[idx].get():
# and Checkbox is not checked...
if not intVars[idx].get():
# Set the checkbox to checked.
intVars[idx].set(True)
# but if Entry is empty...
else:
# Set the Checkbox to uncheck.
intVars[idx].set(False)
Remember I mentioned the behaviour - I took a little liberty to clear the Checkbox if Entry is empty. If you however don't wish to do that, you'll need to modify the handling a little.
Note on the way the trace_add is written. The callback function is always passed with three default arguments, namely the Variable Name, The Variable Index (if any) and Operation (see this great answer from Bryan Oakley). Since we don't need any in this case (we can't reverse reference the variable name to the linked index between the variable lists), we'll have to manually wrap the callback with another lambda and ignore the three arguments:
lambda var, # reserve first pos for variable name
var_idx, # reserve second pos for variable index
oper, # reserve third pos for operation
idx=i: # pass in i by reference for indexing point
trace_intVar(idx) # only pass in the idx
You cannot just pass lambda...: trace_intVar(i) as i will be passed by value instead of reference in that case. Trust me, I've made this error before. Therefore we pass another argument idx with its default set to i, which will now be passed by reference.
If trace_add doesn't work, use trace('w', ...) instead.
For prosperity, here's the complete implemented solution to your question:
from tkinter import *
import pandas as pd
df = pd.DataFrame({'item': list('abcde'), 'default_vals': [2,6,4,5,1]})
def input_data(df):
box = Tk()
height = str(int(25*(df.shape[0]+2)))
box.geometry("320x" + height)
box.title("my box")
#initialise
params, checkButtons, intVars, strVars = [], [], [], []
default_vals = list(df.default_vals)
itemList = list(df.item)
def trace_intVar(idx):
if intVars[idx].get() and not params[idx].get():
params[idx].insert(0, df.default_vals[idx])
def trace_strVar(idx):
if strVars[idx].get():
if not intVars[idx].get():
intVars[idx].set(True)
else:
intVars[idx].set(False)
for i,label in enumerate(itemList):
Label(box, text = label).grid(row = i, sticky = W)
strVars.append(StringVar())
strVars[-1].trace_add('write', lambda var, var_idx, oper, idx=i: trace_strVar(idx))
params.append(Entry(box, textvariable=strVars[-1]))
params[-1].grid(row = i, column = 1)
#params[-1].insert(i, default_vals[i]) # <-- You don't need this any more
intVars.append(IntVar())
intVars[-1].trace_add('write', lambda var, var_idx, oper, idx=i: trace_intVar(idx))
checkButtons.append(Checkbutton(variable = intVars[-1]))
checkButtons[-1].grid(row = i, column = 3)
def sumbit(event=None):
global fields, checked
fields = [params[i].get() for i in range(len(params))]
checked = [intVars[i].get() for i in range(len(intVars))]
box.destroy()
#add submit button
box.bind('<Return>', sumbit)
Button(box, text = "submit",
command = sumbit).grid(row = df.shape[0]+3, sticky = W)
box.focus_force()
mainloop()
return fields, checked
I need to retrieve the value of Radiobutton clicked and then use this value .
What is the way to retrieve the value of a Radiobutton clicked ?
the code to setup the Radiobutton is:
radio_uno = Radiobutton(Main,text='Config1', value=1,variable = 1)
radio_uno.pack(anchor=W,side=TOP,padx=3,pady=3)
radio_due = Radiobutton(Main,text='Config2', value=2,variable =1)
radio_due.pack(anchor=W,side=TOP,padx=3,pady=3)
radio_tre = Radiobutton(Main,text='Config3', value=3,variable = 1)
radio_tre.pack(anchor=W,side=TOP,padx=3,pady=3)
This is one solution:
Create a tk.IntVar() to track which button was pressed. I'm assuming you did a from tkinter import *.
radio_var = IntVar()
You'll need to change the way you declared your buttons:
radio_uno = Radiobutton(Main,text='Config1', value=1,variable = radio_var)
radio_due = Radiobutton(Main,text='Config2', value=2,variable = radio_var)
radio_tre = Radiobutton(Main,text='Config3', value=3,variable = radio_var)
Then use the get() method to view the value of radio_var:
which_button_is_selected = radio_var.get()
Then you can make an enum or just three if clauses that'll do stuff depending on which button is chosen:
if(which_button_is_selected == 1):
#button1 code
elif(which_button_is_selected == 2):
#button2 code
else(which_button_is_selected == 3):
#button3 code
I'm making a revision quiz program using Tkinter. The program will automatically get questions from an array and then output the questions using a Label. When the submit button is pressed, the program will check the answer and then should update the Label to the next question in the array but it doesn't, there is no error message. The score is updated but the Label just doesn't update.
def entryQuestion():
entryOpen = open('./files/entry.csv','r')
entryFile = csv.reader(entryOpen, delimiter = ',')
global entryQuestionArray
entryQuestionArray = []
for topic, question, answer in entryFile:
for i in range(0,1):
entryQuestionArray.append(question)
entryQuestionArray = random.sample(entryQuestionArray, len(entryQuestionArray))
arrayLength = len(entryQuestionArray)
for x in entryQuestionArray:
global qOut
qOut = x
global entryQuestion
entryQuestion = Label(entryQ, text = x)
entryQuestion.grid(row = 1, column = 0, columnspan = 3)
global answerEntry
answerEntry = StringVar()
global answerEntryBox
answerEntryBox = Entry(entryQ, textvariable = answerEntry)
answerEntryBox.grid(row = 2, column = 0, columnspan = 3)
submitEntryAnswer = Button(entryQ, text = 'Submit Answer', command = entryQuestionCheck)
submitEntryAnswer.grid(row = 3, column = 2)
def entryQuestionCheck():
entryOpen = open('./files/entry.csv','r')
entryFile = csv.reader(entryOpen, delimiter = ',')
tempScore = score
for topic, question, answer in entryFile:
if qOut == question:
if answerEntry.get() == answer:
tempScore = tempScore + 1
else:
tempScore = tempScore + 0
return
Can anyone help?
Each Widget only needs 1 call, on the top or in a main class, you need to call him only once:
class MainPage(#values):
self.label1 = tk.Label(self, #values)
and in the function, if you need to change your values, for example the text, you only need to pass self values to function, and change him, like this:
def changeValues(self):
self.label["text"] = "the text that u want to put in"
or:
def changeValues(label):
label["text"] = "the text that u want to put in"
and in the main class, or in the scope, you need to call the function with the values, if is in the scope (not in a class) you need to pass the widget in the values:
class MainPage(#values):
self.label1 = tk.Label(self, #values)
changeValues(self)
or:
label1 = tk.Label(self, #values)
changeValues(label1)
Edit
So I asked this question earlier and I received some good insight, but I feel like my question wasn't really answered. I'm building a small program to practice with python and making GUI's and I'm having a small problem with a button command.
#This is the temperature menu:
def temperM(self, *args):
self.clearscreen(self.frame)
self.frame2 = Frame(self.root)
self.frame2.grid(column = 0, row = 0)
self.firstunit = StringVar()
self.secondunit = StringVar()
self.entryspace = IntVar()
self.displayspace = IntVar()
#Create back button
#This is the part that needs to be fixed
self.back = Button(self.frame2, text = "< Back",
command = lambda: self.redo(self.frame2))
self.back.grid(column = 1, row = 3)
self.label = Label(self.frame2, text = "Convert from: ")
self.label.grid(column = 1, row = 1, padx = 4)
#Create the check boxes
self.celsius = Checkbutton(self.frame2, text = "Celsius",
variable = self.firstunit, onvalue = 'celsius')
self.celsius.grid(column = 2, row = 1)
self.fahrenheit = Checkbutton(self.frame2, text = "Fahrenheit",
variable = self.secondunit, onvalue = 'fahrenheit')
self.fahrenheit.grid(column = 3, row = 2)
#Create entry space to recieve text
#This is where the problem starts.
self.entry = Entry(self.frame2, width = 7,
textvariable = self.entryspace)
self.entry.grid(column = 3, row = 3)
self.compute = Calculate(self.entryspace.get())
self.button = Button(self.frame2, text = "Calculate",
command = lambda: self.displayspace.set(self.compute.celtoFah()))
self.button.grid(column = 3, row = 4)
self.display = Label(self.frame2, textvariable = self.displayspace)
self.display.grid(column = 2, row = 2)
I have this function inside of a class Menu with def__init__(self, root) which creates all the different menu options.
class Calculate:
def __init__(self, number):
self.number = number
def celtoFah(self):
try:
self.temp = Temperature()
self.number = float(self.number)
return self.temp.C2F(self.number)
except ValueError:
pass
And I have this class which holds all the different calculations that will be used in the code.
What I'm having trouble with is with my button command command = lambda: self.displayspace.set(self.compute.celtoFah()). When I run the code and press 'Calculate' which runs the command, self.displayspace.set(), it doesn't set self.displayspace to what I believe the returned value should be. Instead it returns and sets self.displayspace to what self.entryspace.get() is originally without modifications which is 32 and 0 respectively, which causes me to believe that the line self.compute = Calulate(self.entryspace.get()) is not updating when I put in a new value so self.entryspace is not getting a new value but its retaining the same initial value established by IntVar(). Am I doing something wrong in my code for self.entryspace not to be updating with a new value? At first I had it as a StringVar() which would convert to a float in celtoFah but I was throwing ValueError because it was receiving an empty string even after a user inputs a value. I really want to keep all calculations in a separate class since I will be having 20+ in the final version, but should I move these commands into class Menu or is there another I can do this by having a separate class? If you need to see my full code here is a link to it on github: https://github.com/Flameancer/Unit-Conversion-Program-in-Python
In general, you don't pass values between classes, but between instances of those classes. At any given time, you may have 0, 1, or 30 different Foo instances; how does a Bar instance even know which one you want?
The first question is, who's calling that something method on that Bar? Whoever it is, he has the value. Maybe it should be the Foo instance that's doing the calling.
For that to happen, the foo instance has to know about a bar instance. Maybe you want to create one in the constructor:
class Foo:
def __init__(self, argument):
# ...
self.bar = Bar(42)
# ...
… and now you can use it the same way as any other member:
def function(self, *args):
# ...
randomness = self.bar.something()
self.displayfield.set(randomness)
# ...
Or maybe you're already constructing one somewhere, and you just want to pass it to the Foo instance as a constructor:
class Foo:
def __init__(self, argument, bar):
# ...
self.bar = bar
# ...
bar = Bar(42)
foo = Foo(23, bar)
Or maybe you want to construct a new one locally each time you call the function method. Or maybe you want a global Bar instance shared by everyone, no matter how many Foo instances you have. Or…
As you can see, there are many different possible relationships between a Foo instance and a Bar instance, and which one is appropriate depends entirely on what Foo and Bar actually represent. This is the code idea behind object modeling: there are things of some kind that your objects represent, and the relationships between those things are reflected in the relationships between those objects.