How do i call a function everytime my entry is updated [duplicate] - python

I'm trying to make a simple temperature conversion calculator in python. What I want to do is to be able to type in a number, and have the other side automatically update, without having to push a button. Right now I can only get it to work in one direction. I can either code it so that it can go from F to C, or C to F. But not either way.
Obviously after is not the way to go. I need some kind of onUpdate or something. TIA!
import Tkinter as tk
root = tk.Tk()
temp_f_number = tk.DoubleVar()
temp_c_number = tk.DoubleVar()
tk.Label(root, text="F").grid(row=0, column=0)
tk.Label(root, text="C").grid(row=0, column=1)
temp_f = tk.Entry(root, textvariable=temp_f_number)
temp_c = tk.Entry(root, textvariable=temp_c_number)
temp_f.grid(row=1, column=0)
temp_c.grid(row=1, column=1)
def update():
temp_f_float = float(temp_f.get())
temp_c_float = float(temp_c.get())
new_temp_c = round((temp_f_float - 32) * (5 / float(9)), 2)
new_temp_f = round((temp_c_float * (9 / float(5)) + 32), 2)
temp_c.delete(0, tk.END)
temp_c.insert(0, new_temp_c)
temp_f.delete(0, tk.END)
temp_f.insert(0, new_temp_f)
root.after(2000, update)
root.after(1, update)
root.mainloop()

What you are looking for is variable trace() method. E.g.:
def callback(*args):
print "variable changed!"
var = DoubleVar()
var.trace("w", callback)
Attach trace callbacks for each of your DoubleVar, for temp_f_number one to update the temp_c_number value and vice versa. You'll likely also need to disable one callback function while inside another one, to avoid recursive update cycle.
Another note - do not edit the Entry fields. Instead, use variables' set() method. Entry fields will be updated automatically.
So, complete code could look like this:
import Tkinter as tk
root = tk.Tk()
temp_f_number = tk.DoubleVar()
temp_c_number = tk.DoubleVar()
tk.Label(root, text="F").grid(row=0, column=0)
tk.Label(root, text="C").grid(row=0, column=1)
temp_f = tk.Entry(root, textvariable=temp_f_number)
temp_c = tk.Entry(root, textvariable=temp_c_number)
temp_f.grid(row=1, column=0)
temp_c.grid(row=1, column=1)
update_in_progress = False
def update_c(*args):
global update_in_progress
if update_in_progress: return
try:
temp_f_float = temp_f_number.get()
except ValueError:
return
new_temp_c = round((temp_f_float - 32) * 5 / 9, 2)
update_in_progress = True
temp_c_number.set(new_temp_c)
update_in_progress = False
def update_f(*args):
global update_in_progress
if update_in_progress: return
try:
temp_c_float = temp_c_number.get()
except ValueError:
return
new_temp_f = round(temp_c_float * 9 / 5 + 32, 2)
update_in_progress = True
temp_f_number.set(new_temp_f)
update_in_progress = False
temp_f_number.trace("w", update_c)
temp_c_number.trace("w", update_f)
root.mainloop()

.trace is soon to be deprecated, use .trace_add instead:
var = tk.StringVar()
var.trace_add('write', callback)
Same functionality, but you must pass write or read instead of w or r.

Related

How to show comma only once in tkinter calculator

I'm building tkinter python calculator. And I want to add comma button. But as we know comma appears in every calculator's window only once. I made a condition that makes that you can type only once but when you type other sign you can type more than one comma sign. How to do if condition that you can type only once in any case.
from tkinter import*
from tkinter.ttk import *
root=Tk()
def show_point():
if e.get()==".":
pass
else:
e.insert(END,".")
e=Entry(root,width=30,justify="right",font=(None,20))
e.grid(row=0,column=0,columnspan=3,ipady=10,sticky=W)
but19 = Button(root,text=".", style='my.TButton',command=show_point)
but19.grid(row=5,column=3,ipadx=10,ipady=15)
root.mainloop()
I think this is what your looking for:
from tkinter import*
from tkinter.ttk import *
root = Tk()
def correct(inp):
if inp == '':
return True
if ' ' in inp:
return False
try:
float(inp)
except ValueError: #catching error because strings cannot be converted to string
return False
else:
return True
reg = root.register(correct) #registering validation
e = Entry(root, width=30, justify="right", font=(None, 20),validate='key', validatecommand=(reg, '%P')) #assigning it while declaring
e.grid(row=0, column=0, columnspan=3, ipady=10, sticky=W)
#but19 = Button(root,text=".", style='my.TButton',command=correct)
# but19.grid(row=5,column=3,ipadx=10,ipady=15)
root.mainloop()
This is just validation, your allowing the user to just enter decimals, anything other than that will not be allowed at all.
Take a look here for more info on validation
Cheers
Try that instead of the if statement
input = e.get()
try:
input.index(".")
except:
e.insert(END, ".")
I think what you need to something called "validation" which Entry widgets support. This will allow you to make sure the character isn't entered more than once (or any other rule you want to enforce), either by clicking on the Button or by manually typing it in. For reference see Adding validation to an Entry widget.
Here's how to do it in the context of what you're trying to do:
from tkinter import*
from tkinter.ttk import *
PERIOD = '.'
root = Tk()
def insert_point():
if e.get().count(PERIOD) < 1: # Allow at most one in entry.
e.insert(END, PERIOD)
def check_okay(new_value):
return new_value.count(PERIOD) < 2 # Only zero or one allowed in entry.
ok_command = root.register(check_okay) # Register the callback function.
e = Entry(root, width=30, justify="right", font=(None, 20),
validate='all', validatecommand=(ok_command, '%P'))
e.grid(row=0, column=0, columnspan=3, ipady=10, sticky=W)
but19 = Button(root, text=PERIOD, style='my.TButton', command=insert_point)
but19.grid(row=5, column=3, ipadx=10, ipady=15)
root.mainloop()

How to get the tick boxes to change value in the second window?

I got example code were there are two windows and in the second one there's a tick box that doesn't change value when it is ticked. How can I fix this? I tried returning the value of the tickbox however that failed as well.
from tkinter import *
root = Tk()
def open_custom_gui():
custom_gui()
b = Button(root,command=open_custom_gui)
b.grid(row=1,column=0)
def custom_gui():
def getinfo():
print(var1.get())
custom= Tk()
var1 = IntVar()
tickbox_1 = Checkbutton(custom,text='TEST',variable=var1,)
tickbox_1.grid(row=0,column=0)
b = Button(custom,command=getinfo)
b.grid(row=1,column=0)
custom.mainloop()
root.mainloop()
The problem has something to do with calling Tk() twice. You can fix that by explicitly creating a second Toplevel window.
from tkinter import *
root = Tk()
def open_custom_gui():
custom_gui()
b = Button(root, command=open_custom_gui)
b.grid(row=1, column=0)
def custom_gui():
def getinfo():
print(var1.get())
custom = Toplevel() # CHANGE THIS (don't call Tk() again)
var1 = IntVar()
tickbox_1 = Checkbutton(custom, text='TEST', variable=var1)
tickbox_1.grid(row=0, column=0)
b = Button(custom, command=getinfo)
b.grid(row=1, column=0)
custom.mainloop()
root.mainloop()
Alternatively you can also fix it by specifying the second Tk instance when you create the IntVar tkinter variable:
def custom_gui():
def getinfo():
print(var1.get())
custom = Tk()
var1 = IntVar(master=custom) # ADD a "master" keyword argument
tickbox_1 = Checkbutton(custom, text='TEST', variable=var1)
tickbox_1.grid(row=0, column=0)
b = Button(custom, command=getinfo)
b.grid(row=1, column=0)
custom.mainloop()
However I would suggest using the first approach because the documentation says the following (about adding the argument to the IntVar constructor):
The constructor argument is only relevant if you’re running Tkinter with
multiple Tk instances (which you shouldn’t do, unless you really know what
you’re doing).

getting around ints and floats with button initialisation for Tkinter calculator

Below is a follow on from this question...
Python & Tkinter - buttons command to set label textvariable issue
I've finished the calculator but here is the problem:
My buttons were all built using a for-loop; function buttons included. I like the fact that the code is short and don't really want to go and remove all function buttons from the for-loop but i might have to to get around the problem where I don't get a float value returned for the division of two integers.
i.e 12/8 = 1 according to this calculator.
Any clever ideas how I could do that without removing the operators form the for-loop?
from Tkinter import *
import Tkinter as tk
import tkMessageBox
# main window
root = Tk()
root.title('Calculator')
# button set
buttons = ['1','2','3','4','5','6','7','8','9','0','+','-','/','*','.']
sum_value = StringVar()
def appear(x):
return lambda: output_window.insert(END,x)
# output window
output_window = tk.Entry(root, textvariable=sum_value, width=20, font = 'courier 10')
output_window.grid(row=0, columnspan=3, sticky=(E,W))
def equals():
try:
result = eval(output_window.get())
except:
result = 'INVALID'
output_window.delete(0,END)
output_window.insert(0,result)
def refresh():
output_window.delete(0,END)
# button creation
r=1
c=0
for i in buttons:
if c < 2:
tk.Button(root, text = i, command = appear(i), pady = 3).grid(row = r, column = c, sticky = (N,S,E,W))
c += 1
else:
tk.Button(root, text = i, command = appear(i), pady = 3).grid(row = r,column = c,sticky = (N,S,E,W))
r += 1
c = 0
# clear and equal button
equal = tk.Button(root,text='=',padx = 5, pady=3, command=equals)
equal.grid(row=6,column=0,sticky=(N,S,E,W))
clear = tk.Button(root,text='CLEAR',padx = 5, pady=3,command = refresh)
clear.grid(row=6,column=1, columnspan = 2,sticky=(N,S,E,W))
#menu
menubar = Menu(root)
def quit1():
if tkMessageBox.askokcancel("Quit","Are you sure you want to quit?"):
root.destroy()
viewMenu = Menu(menubar)
viewMenu.add_command(label='Quit', command = quit1)
menubar.add_cascade(label="Home", menu=viewMenu)
root.config(menu=menubar)
root.mainloop()
Write from __future__ import division as the first line of your program. This will make / into the floating point division operator. Of course, now 8/4 will give 2.0 not the integer 2. (If you wanted integer division also, you could add a // button, but I gather you want this to work like a standard hand-held calculator.)

Label and value display

I'm using a simple code that displays the square root of a number in a label, but for some reason the values get overlaped in a way that I couldn't avoid it, which means that, if I use it for a number that has a exact square root, then the answer goes messed up with the previous answer of many digits.
I've been use the next code so far:
from Tkinter import *
def square_calc():
x = x_val.get()
sqx = x ** 0.5
print x, "** 0.5 =", sqx
sqx_txt = Label(root, text = "x ** 0.5 =").grid(row=3, column=0)
sqx_lab = Label(root, text = sqx).grid(row=3, column=1)
root = Tk()
root.title("Calculating square root")
x_val = DoubleVar()
x_lab = Label(root, text = "x").grid(row=0, column=0)
nmb = Entry(root, textvariable = x_val).grid(row=0, column=1)
calc = Button(root, text = "Calculate", command=square_calc).grid(columnspan=2)
y_lab = Label(root, text = " ").grid(row=3, column=0)
root.mainloop()
The problem is that, every time you call square_calc(), you are simply creating and placing another Label. The right way to do this is to create a Label outside the function, then have the function update the Label's text with mylabel.config(text='new text').
The display is getting messed-up because every time your square_calc() function is called it creates new pair of Labels, but this may leave some parts of any previous ones visible. Since the one on the left is the same every time, so it's not noticeable with it, but the text in one on the right in column 1 is potentially different every time.
A simple way to fix that is to make the Label a global variable and create it outside the function, and then just change its contents in the function. As with all Tkinter widgets, this is can be done after it's created by calling the existing obj's config() method.
Here's a minimally-modified version of your code that illustrates doing that. Note, it also adds a sticky keyword arugment to the grid() method call for the label to left-justify it within the grid cell so it's closer to the text label immediately to its left (otherwise it would be center-justified within the cell).
from Tkinter import *
def square_calc():
x = x_val.get()
sqx = x ** 0.5
# print x, "** 0.5 =", sqx
sqx_txt = Label(root, text = "x ** 0.5 =").grid(row=3, column=0)
sqx_lab.config(text=sqx)
sqx_lab.grid(row=3, column=1, sticky=W)
root = Tk()
root.title("Calculating square root")
x_val = DoubleVar()
x_lab = Label(root, text = "x").grid(row=0, column=0)
nmb = Entry(root, textvariable = x_val).grid(row=0, column=1)
calc = Button(root, text = "Calculate", command=square_calc).grid(columnspan=2)
y_lab = Label(root, text = " ").grid(row=3, column=0)
sqx_lab = Label(root, text = " ")
root.mainloop()
There's another potentially serious flaw in your code. All those assignments of the form
variable = Widget(...).grid(...)
result in assigning the value None to the variable because that's what the grid() method returns. I didn't fix them because they do no harm in this cause since none of those variables are ever referenced again, but it would have been a problem if the new globally variable sqx_lab had been done that way since it is referenced elsewhere.

Tkinter StringVar() only accepts the most recent input

I'm trying to make a small application that displays words from a list one-by-one. Below is my code:
from Tkinter import *
master = Tk()
master.title('Serial Position Effect')
myArray = ['book','chair','door']
def cycle(myArray, k):
t.set(myArray[k])
t = StringVar()
w = Label(master, height=3, width=15, font=('Helvetica', 118), textvariable = t)
for n in range(0,3):
cycle(myArray, n)
w.pack()
master.mainloop()
I was expecting the label to show book, chair, and door, but it only showed door on the window. I tried to modify the for loop like:
for n in range(0,3):
for x in range(0,10000):
cycle(myArray, n)
Because I thought the problem was that the program was cycling through the words too quickly. But with this modified code, the application, again, only showed door after a short delay. (The delay was probably because it was counting up to 10000.)
Finally I approached this a little differently - a little less efficient but I thought by coding it like this I would be able to identify the problem in my original code:
from Tkinter import *
master = Tk()
master.title('Serial Position Effect')
#myArray = ['book','chair','door']
#def cycle(myArray, k):
# t.set(myArray[k])
t = StringVar()
w = Label(master, height=3, width=15, font=('Helvetica', 118), textvariable = t)
for n in range(0,10000)
t.set('book')
for n in range(0,10000)
t.set('chair')
for n in range(0,10000)
t.set('door')
w.pack()
master.mainloop()
Again, the window only displayed door.
I'm new to GUI programming with Python and Tkinter. I would really appreciate it if someone could help me out with this issue.
Thanks (=
The window won't even show up until you call mainloop, so calling set thousands of times won't have any visible effect, except for the very last call. You should use after to register callback functions that change the label some seconds in the future.
from Tkinter import *
master = Tk()
master.title('Serial Position Effect')
myArray = ['book','chair','door']
def cycle(myArray, k):
t.set(myArray[k])
t = StringVar()
w = Label(master, height=3, width=15, font=('Helvetica', 118), textvariable = t)
w.pack()
cycle(myArray,0)
master.after(1000, lambda: cycle(myArray, 1))
master.after(2000, lambda: cycle(myArray, 2))
master.mainloop()
You can also have the after-registered function call after itself, if you want the words to cycle forever.
from Tkinter import *
master = Tk()
master.title('Serial Position Effect')
myArray = ['book','chair','door']
cur_idx = -1
def cycle():
global cur_idx
cur_idx = (cur_idx + 1) % len(myArray)
t.set(myArray[cur_idx])
master.after(1000, cycle)
t = StringVar()
w = Label(master, height=3, width=15, font=('Helvetica', 118), textvariable = t)
w.pack()
cycle()
master.mainloop()

Categories

Resources