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))
...
I am trying to create option menus in a loop, and the number of option menus is dependent on a variable. So I'm trying to use exec in my code.
I used the following to pass the value of 'i' to connect to which variable is changing the value.
But once I call the trace, the option I select in the Option menu, does not get updated in the Option menu box. If I do not call the trace funtion, it is getting updated in the display.
trackProcessMenu is the callback function.
Please let me know, where I am making the mistake.
Adding my code:
for i in range(0,numOfLibFiles):
exec('self.processOptionMenuVar_%d = StringVar()'%i)
process_menu = ("ff","ss","tt","fff","sss","ttt")
exec('self.processOptionMenu_%d = OptionMenu(self, self.processOptionMenuVar_%d, *process_menu )'%(i,i))
exec('self.processOptionMenu_%d.config(indicatoron=0,compound=RIGHT,image= self.downArrowImage, anchor = CENTER , direction = RIGHT)'%i)
exec('self.processOptionMenuVar_%d.set("--")'%i)
exec('self.processOptionMenu_%d.grid(row = i, column =1, sticky = N ,padx=30, pady =7 )'%i)
def trackProcessMenu(self,*args):
i = args[0]
exec('process = self.processOptionMenuVar_%d.get()'%i)
You should not use exec this way. A good rule of thumb is that you should never use exec until you can answer the question "why should I never use exec?" :-) exec has it's uses, but this isn't one of them.
Instead of trying to automagically generate variable names, keep your widgets in a list or dictionary.
For example:
option_vars = []
option_menus = []
for i in range(0,numOfLibFiles):
process_menu = ("ff","ss","tt","fff","sss","ttt")
var = StringVar()
om = OptionMenu(self, var, *process_menu)
om.config(indicatoron=0,compound=RIGHT,image= self.downArrowImage, anchor = CENTER , direction = RIGHT)
var.set("--")
om.grid(row = i, column =1, sticky = N ,padx=30, pady =7)
option_vars.append(var)
option_menus.append(om)
With the above, you can now reference the variables and menus with a simple index:
print("option 1 value is:", option_vars[1].get())
I've been searching around and i am not able to find a proper explanation of the syntax of OptionMenu within Tkinter.
how would i get the current chosen option with in the OptionMenu?
def homeTeamOption(self, frame, sortedList):
def func():
print(homeTeamName)
return
homeTeam = tk.StringVar(frame)
returnValueAwayTeam = []
options = sortedList
homeTeamName = tk.StringVar()
drop = OptionMenu(frame, homeTeamName, *options, command=func())
drop.place(x=200, y= 100, anchor="nw")
To get the value of the OptionMenu you need to get the value of the associated variable. In your case it would be:
homeTeamName.get()
If you want to do this via the command, you must set the option to a reference to the function:
drop = OptionMenu(...command=func)
I am trying to write a simple Python program that will allow a user to input an IP address in decimal, or dotted-decimal format, then convert it to the opposite format and display it in the same entry box (ie, if they enter a decimal IP address, they can click a button and their input will be replaced with the dotted-decimal equivalent).
The problem I'm having is with pulling the data out of the entry box, then putting the new data back into the entry box. I've written an example with just the GUI code, and none of my other conversion logic, to simplify the problem:
import tkinter as tk
root = tk.Tk()
root.title("Test")
win1 = tk.Frame(root)
win1.grid()
x = tk.StringVar()
y = tk.StringVar()
xBox = tk.Entry(win1)
xBox.grid(row = 0, column = 0)
xBox.textvariable = x
yBox = tk.Entry(win1)
yBox.grid(row = 1, column = 0)
yBox.textvariable = y
button = tk.Button(win1,text = "Calculate", command = lambda: copyVal())
button.grid(row = 2, column = 0)
def copyVal():
print("x: " + x.get())
print("y: " + y.get())
xVal = x.get()
print("xval: " + xVal)
y.set(xVal)
root.update_idletasks()
root.mainloop()
Here's what I expect to happen with this code:
The value entered in the top box should be stored in StringVar x.
Clicking the "Calculate" button should run the copyVal() function:
copyVal() gets the value of StringVar x and stores it as xVal.
copyVal() sets the value of StringVar y to match xVal.
The text in the bottom box should now match the text in the top box.
Instead, it does not retrieve the value of StringVar x, so there's nothing to set StringVar y to.
I've tried the following variations:
Using xVal = xBox.get() instead of xVal = x.get(): this retrieves the contents of the top entry box, and sets the value of StringVar y to match it, but the bottom entry box does not change.
Using command = copyVal() instead of command = lambda: copyVal(): the copyVal function executes immediately upon program execution, rather than when the button is pressed.
Moving the copyVal function outside the root mainloop: raises a NameError exception when the button is pressed (copyVal is seen as not defined).
Moving root.update_idletasks() outside the copyVal function has no effect.
I've looked around for solutions to this issue, but no matter how many people I find who are experiencing similar problems, none of their fixes seem to resolve the issue for me (I usually see them told to use StringVar() to get/set values). I am completely new to working with Tkinter, so I'm sure this is something really basic that I'm overlooking, and I appreciate any advice anyone can offer.
Python objects often allow you to add attributes to them arbitrarily:
>>> class Foo:
... pass
...
>>> foo = Foo()
>>> foo.a = 1 # No error. It makes a new attribute.
>>> foo.a
1
>>>
>>> def foo():
... pass
...
>>> foo.a = 1 # Works with function objects too.
>>> foo.a
1
>>>
So, when you do:
xBox.textvariable = x
...
yBox.textvariable = y
you are not actually setting the Entrys' textvariable options to x and y. Instead, you are creating new attributes named textvariable on each of those objects.
To fix the problem, either set each Entry's textvariable option when you create the widgets:
xBox = tk.Entry(win1, textvariable=x)
...
yBox = tk.Entry(win1, textvariable=y)
or use the .config method to change them later:
xBox.config(textvariable=x)
...
yBox.config(textvariable=y)
I have created a list of entries in a for-loop. All entries are stored in a list so I can just obtain all of the inputs later:
inputs = [e.get() for e in self.entries]
However, I have also created a button next to each entry in the for-loop (so they each call the same function). How can I make it so that it recognizes which button belongs to which row/entry? Is there something I can do with event?
row = 0
self.entries = []
self.comments = []
for n in names:
e = Entry(self.top, bd = 5)
e.insert(0, n)
e.grid(column = 1, row = self.row, sticky = 'NSWE', padx = 5, pady = 5)
self.entries.append(e)
self.comments += [""]
commentButton = Button(self.top, text = "comment", command = self.commentSelected)
commentButton.grid(column = 3, row = self.row, sticky = 'NSWE', padx = 5, pady = 5)
self.row = self.row + 1
Yes -- use Callback Shims ( Currying Functions )
( courtesy Russell Owen )
I find I often wish to pass extra data to a callback function, in addition that that normally given. For instance the Button widget sends no arguments to its command callback, but I may want to use one callback function to handle multiple buttons, in which case I need to know which button was pressed.
The way to handle this is to define the callback function just before you pass it to the widget and include any extra information that you require. Unfortunately, like most languages, Python doesn't handle the mixing of early binding (information known when the function is defined) and late binding (informtation known when the function is called) particularly well. I personally find the easiest and cleanest solution is:
Write my callback function to take all desired data as arguments.
Use a callback shim class to create a callable object that stores my function and the extra arguments and does the right thing when called. In other words, it calls my function with the saved data plus the data that the caller supplies.
I hope the example given below makes this clearer.
The callback shim I use is RO.Alg.GenericCallback, which is available in my RO package. A simplified version that does not handle keyword arguments is given in the example below. All shim code is based on a python recipe by Scott David Daniels, who calls this "currying a function" (a term that is probably more common than "callback shim").
#!/usr/local/bin/Python
""" Example showing use of a callback shim"""
import Tkinter
def doButton(buttonName):
""" My desired callback.
I'll need a callback shim
because Button command callbacks receive no arguments.
"""
print buttonName, "pressed"
class SimpleCallback:
""" Create a callback shim.
Based on code by Scott David Daniels
(which also handles keyword arguments).
"""
def __init__(self, callback, *firstArgs):
self.__callback = callback
self.__firstArgs = firstArgs
def __call__(self, *args):
return self.__callback (*(self.__firstArgs + args))
root = Tkinter.Tk()
buttonNames = ( "Button 1", "Button 2", "Button 3" )
for name in buttonNames:
callback = SimpleCallback( doButton, name )
Tkinter.Button( root, text = name, command = callback ).pack()
root.mainloop()
You can also use lambda:
from tkinter import *
def bla(b):
...
root = Tk()
buttons = []
for i in range(...):
button = Button(root)
button.configure(command=lambda b=button: bla(b)) # Make sure the Button object already exists
buttons.append(button)
button.pack()
root.mainloop()
As far as I see, you can't create the buttons in a single list comprehension now, but it is simpler and more readable than a class imho.