Update variable and calculate inverse - python-3.x

I havea an app where I have two tkEntry widgets. I am trying to enter data into the first and have it populate it's inverse into the second. However sometimes it is easier to input the data into the second and have the first calculated.
For example:
If EntryOne=20 then EntryTwo=0.05.
But if EntryTwo=0.2 then EntryOne=5.
I am have two callbacks that I am using to calc the inverses, but it seems only the first one fires.
Can I have only one callback?
import tkinter as tk
root= tk.Tk()
root.title('XXX')
X_var = tk.DoubleVar()
Y_var = tk.DoubleVar()
def cleardata(Box):
Box.Delete(0,10)
def callback_X():
if not Y_var.get()==0:
X_var.set(1/Y_var.get())
X_Ent['validate']='focusout'
return True
def callback_Y():
if not X_var.get()==0:
Y_var.set(1/X_var.get())
Y_Ent['validate']='focusout'
return True
w=200
h=300
root.geometry(str(w)+"x"+str(h))
canvas = tk.Canvas(root, width = w, height = h)
canvas.pack(fill="both", expand=True)
X_lbl=tk.Label(root, text='X').place(x=68.5-25,y=(h/3))
X_Ent = tk.Entry(root, justify="center", textvariable=X_var, width=10, validate="focusout", validatecommand=None)
X_Ent['validatecommand']=callback_Y
X_Ent.place(x=68.5,y=(h/3))
Y_lbl=tk.Label(root, text='Y').place(x=68.5-25,y=(2*h/3))
Y_Ent = tk.Entry(root, justify="center", textvariable=Y_var, width=10, validate="focusout", validatecommand=None)
Y_Ent['validatecommand']=callback_X
Y_Ent.place(x=68.5,y=(2*h/3))
#def main():
root.mainloop()
Mon

Ok, first of all the code you posted here doesn't work for many reasons, but after fixing this errors I found that the reason your callback functions are not properly working is that the method sent to validatecommand must return True or False. I've done this and worked for me:
import tkinter as tk
root=tk.Tk()
X_var=tk.DoubleVar(value=1)
Y_var=tk.DoubleVar(value=1)
def callback_X():
if not Y_var.get()==0:
X_var.set(1/Y_var.get())
X_entry['validate']='focusout'
return True
def callback_Y():
if not X_var==0:
Y_var.set(1/X_var.get())
Y_entry['validate']='focusout'
return True
X_entry=tk.Entry(root, textvariable=X_var, validate='focusout',validatecommand=None)
Y_entry=tk.Entry(root, textvariable=Y_var, validate='focusout',validatecommand=None)
X_entry['validatecommand']=callback_Y
Y_entry['validatecommand']=callback_X
X_entry.pack()
Y_entry.pack()
root.mainloop()

I think your problem might be that in the first function you defined the "D" in "Def" was capitalized when it shouldn't be.

Related

Python tkinter create multiple variables

I started to code with tkinter and class methods. I try to code a to-do-list where I can create multiple entries by pressing a button below the entry. And a button next to the entry should change the color of the entry if pressed. The problem now is, that when I create multiple entries, the buttons only change the latest entry. So my question is how do I specify the entry when created?
Sry for obvious mistakes, Im new to coding :p
import tkinter as tk
from tkinter.constants import ANCHOR, CENTER, X
from tkinter import messagebox
class App():
def __init__(self):
self.window = tk.Tk()
self.window.title("To-Do-List")
self.window.geometry("700x700")
self.x_but, self.y_but = 0.05, 0.2
self.x_ent, self.y_ent = 0.05, 0.2
self.x_but2 = 0.3
self.check_var = True
self.start_frame()
self.grid1()
self.window.mainloop()
def start_frame(self):
self.label1 = tk.Label(text="To Do List", font=("", 30))
self.label1.place(relx=0.5, rely=0.05, anchor=CENTER)
def grid1(self):
self.button1 = tk.Button(text="Create", command= self.create_field)
self.button1.place(relx = self.x_but, rely= self.y_but)
def create_field(self):
self.y_but += 0.05
self.button1.place(relx= self.x_but, rely= self.y_but)
self.entry1 = tk.Entry()
self.entry1.place(relx= self.x_ent, rely= self.y_ent)
self.button_check = tk.Button(text="✅", height= 1,command=self.check)
self.button_check.place(relx= self.x_but2, rely=self.y_ent)
self.y_ent += 0.05
def check(self):
if self.check_var:
self.entry1.configure(bg="Green")
self.check_var = False
else:
self.entry1.configure(bg="White")
self.check_var = True
app = App()
You are changing the bg of self.entry1 which keeps overwriting itself each time an entry is created, so when button is clicked, it is always the last entry. The easiest solution is to define a parameter for check and then pass the required entry as an argument to it.
So the method would be:
def check(self,ent):
if self.check_var:
ent.configure(bg="Green")
self.check_var = False
else:
ent.configure(bg="White")
self.check_var = True
...and your button would be:
self.button_check = tk.Button(text="✅", height= 1,command=lambda x=self.entry1: self.check(x))
Also I hope you have a good reason to use place because using grid can make your life much easier, in this case.
For more explanation, read: Tkinter assign button command in loop with lambda

How to make a variable equal to the input received from the input field in tkinter?

I was wondering how to make this code work.I always get 12 in the console.
from tkinter import *
s = 12
root = Tk()
root.geometry("200x200")
root.title("Program")
e = Entry(root)
e.pack()
def clicked():
e.get = s
print(s)
button = Button(root,command=clicked,text="ok")
button.pack()
root.mainloop()
You have to change the function clicked as follows:
def clicked():
s=e.get()
print(s)
There were two errors in your code:
You were trying to assign the value 12 to a function.
You were not calling the function (using parenthesis).
this line here:
e.get = s
says the method of e named get is equally to s.
Which is nonsense. You want s to be equally to what is returned by e.get.
to have something returned you need to invoke this method first.
So the logical right way to do this is by:
s = e.get()
Note that it is a variable in the enclosed namespaces of your function.
To make it global you need to global the variable.
from tkinter import *
s = 12
root = Tk()
root.geometry("200x200")
root.title("Program")
e = Entry(root)
e.pack()
def clicked():
#global s
s = e.get()
print(s)
button = Button(root,command=clicked,text="ok")
button.pack()
b2 = Button(root, text='print', command=lambda:print(s))
b2.pack()
root.mainloop()
I have used .place() instead of .pack() and placed my same entry on the same position as it was place before, but this time if value changes as you click on button OK.
Use e.insert(0,"Value") to insert any value in a entry.
This worked for me. Also let me know did it worked for you as well?
from tkinter import *
s = 12
root = Tk()
root.geometry("200x200")
root.title("Program")
e = Entry(root)
e.place(relx=0.1, rely = 0.1)
def clicked():
e = Entry(root)
e.insert(0, s)
e.place(relx=0.1, rely = 0.1)
button = Button(root,command=clicked,text="ok")
button.place(relx=0.4, rely = 0.2)
root.mainloop()

tkinter catch value from lambda function

I'm trying to wrap my head around this problem.
Say I have a code like this:
def get_input(data_A, data_B):
all_data = [data_A.get(),dataB.get()]
return(all_data)
def the_gui():
root = Tk()
data_A = Entry(root)
data_B = Entry(root)
button = Button(root, text='Submit', command=lambda: get_input(data_A, data_B))
mainloop()
My goal is to get the value of data_A and data_B once I clicked the submit button.
I tried to use global variable and everything, but I kept failing to catch the value.
The only thing that works is when I put the whole get_input() function inside the_gui() function. However, I don't think that's a good practice to implement.
Any suggestions?
Here is a simple example of how you could write this to get the results you are looking for.
When using global is that all your root window and related fields are in a function. So you would have to define global in both function and this is not what you want to do.
Typically you will want to write the root window in the global namespace and not in a function or write it into a class so you can avoid global's all-together.
button = Button(...) may not be doing what you think it is. This does not return a value from the command once clicked. Tkinter buttons do not care about anything being returned. So you have to record that value elsewhere.
I am not sure how you code is working as you do not use geometry managers and mainloop() should be attached to the root window so I have added those in as well.
Example 1:
import tkinter as tk
def get_input():
global a_and_b
a_and_b = [data_a.get(), data_b.get()]
# If you want to keep a running record of all values submitted
# then you can do this instead:
# a_and_b.append([data_a.get(), data_b.get()])
def print_a_b():
print(a_and_b)
root = tk.Tk()
a_and_b = []
data_a = tk.Entry(root)
data_b = tk.Entry(root)
data_a.pack()
data_b.pack()
tk.Button(root, text='Submit', command=get_input).pack()
tk.Button(root, text='Print A/B List', command=print_a_b).pack()
root.mainloop()
Example 2 using OOP:
import tkinter as tk
class App(tk.Tk):
def __init__(self):
super().__init__()
self.a_and_b = []
self.data_a = tk.Entry(self)
self.data_b = tk.Entry(self)
self.data_a.pack()
self.data_b.pack()
tk.Button(self, text='Submit', command=self.get_input).pack()
tk.Button(self, text='Print A/B List', command=self.print_a_b).pack()
def get_input(self):
self.a_and_b = [self.data_a.get(), self.data_b.get()]
def print_a_b(self):
print(self.a_and_b)
if __name__ == '__main__':
App().mainloop()

Returning a value from a tkinter form

I'm using code where I need to ask the user for input, using a tkinter window (I'm not using tkinter in other parts of the code).
My issue is that I simply need to use the tkinter window to return a value upon pressing the OK button on the form, which will also close down the form. The only way I can get it to work so far is by using a global variable. I've searched for other solutions to this but either they don't return a value (they simply print it) or don't allow passing text for the prompt.
Thanks in advance if you can help with this.
from tkinter import *
def input_text(prompt):
def ok():
global ret
ret = entry.get()
master.destroy()
master = Tk()
lbl = Label(master, text=prompt)
lbl.pack()
entry = Entry(master)
entry.pack()
entry.focus_set()
butt = Button(master, text = "OK", width = 10, command = ok)
butt.pack()
mainloop()
print("I am here!")
ret=""
input_text("Enter something")
print("ret is:", ret)
After a good night's sleep I've solved the problem :-)
The solution was to create a class and return the response via an attribute. Here's the code for the archive ... just in case anyone out there has a similar question.
from tkinter import *
class InputForm():
def __init__ (self, prompt):
self.prompt = prompt
self.response = ""
def ok():
self.response = entry.get()
master.destroy()
master = Tk()
lbl = Label(master, text=self.prompt)
lbl.pack()
entry = Entry(master)
entry.pack()
entry.focus_set()
butt = Button(master, text = "OK", width = 10, command = ok)
butt.pack()
mainloop()
abc = InputForm("Enter something").response
print("returned value is:", abc)

Passing OptionMenu into a callback (or retrieving a reference to the used widget)

I'm working on a (toplevel in a) GUI that consists of an array of 8 OptionMenus, each of them containing the same option list. Currently, Im building these widgets using a for-loop, and I save references in a dictionary. All OptionMenus link to the same (lambda) callback function.
To stay practical: the items in the option list represent a sequence of processing steps, and the user can alter the order of processes.
A change in one of the lists will result in one process being executed twice, and one process not at all. However, I want each item to occur only once. Hence, each user input should be accompanied by a second OptionMenu alteration.
For example: initial order 1-2-3 --> user changes the second process: 1-3-3, which autocorrects to: 1-3-2, where each process is again executed only once.
To my understanding, I can only get this to work if I have a reference to the OptionMenu that was just altered (from within the callback function). I was looking into passing the widget into the callback. The sample code is an attempt to implement the second suggested method, but the result is not what I would have expected.
The thing is that the OptionMenu widget seems to behave somewhat differently from other widgets. The OptionMenu does not allow for a re-defintion of the command function. No matter what input I pass along with the command function, the callback only seems to retrieve the OptionMenu selection, which is insufficient information for me to determine my process order.
Suggestions would be much apreciated!
import tkinter as tk
class Application(tk.Frame):
def __init__(self, master=None):
super().__init__(master)
self.grid(row=0, column=0, sticky=tk.N+tk.S+tk.E+tk.W)
self.create_widgets()
def create_widgets(self):
self.active_procs = ['proc 1','proc 2','proc 3','proc 4',
'proc 5','proc 6','proc 7','proc 8']
itemnr, widgets = dict(), dict()
for index in range(8):
name_construct = 'nr' + str(index)
itemnr[name_construct] = tk.StringVar(root)
itemnr[name_construct].set(self.active_procs[index])
widgets[name_construct] = tk.OptionMenu(self, itemnr[name_construct], *self.active_procs,
command=lambda widget=name_construct:
self.order_change(widget))
widgets[name_construct].grid(row=index+2, column=2, columnspan=2,
sticky="nwse", padx=10, pady=10)
def order_change(self,widget):
print(widget)
root = tk.Tk()
root.title("OptionMenu test")
app = Application(master=root)
root.mainloop()
The OptionMenu will pass the new value to the callback, so you don't have to do anything to get the new value. That's why your widget value isn't the value of name_construct -- the value that is passed in is overwriting the default value that you're supplying in the lambda.
To remedy this you simply need to add another argument so that you can pass the value of name_construct to the callback to go along with the value which is automatically sent.
It would look something like this:
widgets[name_construct] = tk.OptionMenu(..., command=lambda value, widget=name_construct: self.order_change(value, widget))
...
def order_change(self, value, widget):
print(value, widget)
Note: the OptionMenu isn't actually a tkinter widget. It's just a convenience function that creates a standard Menubutton with an associated Menu. It then creates one item on the menu for each option, and ties it all together with a StringVar.
You can get the exact same behavior yourself fairly easily. Doing so would make it possible to change what each item in the menu does when selected.
For those interested, below you can find an example code of how I got the widget behaviour I wanted. I took Bryan's advice to replace the OptionMenu for a Menubutton/Menu combination. I also made use of this post to find duplicate entries in my process order list.
Any thoughts or suggestions on how to implement this in a cleaner or shorter way, or how to get the same functionality with a different interface (e.g. drag and drop), are ofcourse welcome!
import tkinter as tk
class Application(tk.Frame):
def __init__(self, master=None):
super().__init__(master)
self.grid(row=0, column=0, sticky=tk.N+tk.S+tk.E+tk.W)
self.create_widgets()
def create_widgets(self):
# Assisting text
l1 = tk.Label(self, text = "Data in", font=(None, 15))
l1.grid(row=0, column=2)
l2 = tk.Label(self, text = u'\N{BLACK DOWN-POINTING TRIANGLE}', font=(None, 15))
l2.grid(row=1, column=2)
l3 = tk.Label(self, text = "Data out", font=(None, 15))
l3.grid(row=11, column=2)
l4 = tk.Label(self, text = u'\N{BLACK DOWN-POINTING TRIANGLE}', font=(None, 15))
l4.grid(row=10, column=2)
# Process list
self.active_procs = ['proc a','proc b','proc c','proc d',
'proc e','proc f','proc g','proc h']
self.the_value, self.widgets, self.topmenu = dict(), dict(), dict()
for index in range(8):
name_construct = 'nr' + str(index)
self.the_value[name_construct] = tk.StringVar(root)
self.the_value[name_construct].set(self.active_procs[index])
self.widgets[name_construct] = tk.Menubutton(self, textvariable=
self.the_value[name_construct],
indicatoron=True)
self.topmenu[name_construct] = tk.Menu(self.widgets[name_construct],
tearoff=False)
self.widgets[name_construct].configure(menu=self.topmenu[name_construct])
for proc in self.active_procs:
self.topmenu[name_construct].add_radiobutton(label=proc, variable=
self.the_value[name_construct],
command=lambda proc=proc,
widget=name_construct:
self.order_change(proc,widget))
self.widgets[name_construct].grid(row=index+2, column=2, columnspan=2,
sticky="nwse", padx=10, pady=10)
def order_change(self,proc,widget):
# Get the index of the last changed Menubutton
index_user_change = list(self.widgets.keys()).index(widget)
procs_order = [] # Current order from widgets
for index in range(8):
name_construct = 'nr' + str(index)
procs_order.append(self.widgets[name_construct].cget("text"))
# 1 change may lead to 1 double and 1 missing process
doubles = self.list_duplicates_of(procs_order,proc)
if len(doubles) == 2: # If double processes are present...
doubles.remove(index_user_change) # ...remove user input, change the other
missing_proc = str(set(self.active_procs)^set(procs_order)).strip('{"\'}')
index_change_along = int(doubles[0])
# Update references
self.active_procs[index_user_change] = proc
self.active_procs[index_change_along] = missing_proc
# Update widgets
name_c2 = 'nr'+str(index_change_along)
self.the_value[name_c2].set(self.active_procs[index_change_along])
self.widgets[name_c2].configure(text=missing_proc)
def list_duplicates_of(self,seq,item):
start_at = -1
locs = []
while True:
try:
loc = seq.index(item,start_at+1)
except ValueError:
break
else:
locs.append(loc)
start_at = loc
return locs
root = tk.Tk()
root.title("OptionMenu test")
app = Application(master=root)
root.mainloop()

Resources