tkinter catch value from lambda function - python-3.x

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()

Related

Tkinter how update main window combobox values from Toplevel list python3.8

I have 3 modules (small, dont worry).
main_module = it has a combobox and a button. Comobobox list must be update each time a list (in module2) increases in number of names (combo values). Button calls the second window (module2)-->
myapp_second_window.py which has a entry box and another button. We write a name in the entry, push the button...voila..the list increases. In the origina app the list is created automatically when (2) is called.
Now I pass the list to a Pages.variable that is in -->
my_pages_to_connect_modules.
So, when app start I can populate combobox calling (2) to generate a Pages.variable list or populate combobox with json previously written.
The problem? --> how populate combobox while app is running. I mean, we go to (2) create a new name in entry come back to (1) and it is already there.
main_module
import tkinter as tk
from tkinter import*
from tkinter import ttk
import myapp_second_window
from myapp_second_window import SecondClass
root= Tk()
root.geometry("500x500")
root.title('myAPP_Main_Window')
class MainClass:
def __init__(self, parent,myapp_second_window):
self.parent = parent
self.my_widgets1()
def call_second_page (self):
Window2 = tk.Toplevel(root)
Window2.geometry('400x300')
myapp_second_window.SecondClass(Window2)
def my_widgets1(self):
self.field1_value = StringVar()
self.field1 = ttk.Combobox(self.parent, textvariable=self.field1_value)
self.field1['values'] = [1,2] # Pages.variable comes Here
self.field1.grid( row=0, column=0)
self.myButton = tk.Button(self.parent, text = "Call Second module", command = self.call_second_page)
self.myButton.grid(row=2, column=0)
if __name__ == '__main__':
app = MainClass(root, myapp_second_window)
root.mainloop()
myapp_second_window.py
import tkinter as tk
from tkinter import*
from tkinter import ttk
root= Tk()
root.minsize(550,450)
root.maxsize(560,460)
root.title('myAPP_Second_Window')
class SecondClass:
def init(self, parent):
self.parent = parent
self.my_widgets()
self.names = []
def my_widgets(self):
mylabel = Label(self.parent, text='Insert new name in next widget:')
mylabel.grid(column=0, row=0, sticky=W, pady=3)
button1 = tk.Button(self.parent, text="Click to enter Names in list", command=self.addToList)
button1.grid(column=3, row=0, sticky=W, pady=3)
self.name = StringVar()
valueEntry = tk.Entry(self.parent, textvariable= self.name)
valueEntry.grid(row=1, column=0, sticky=W, pady=3)
def addToList(self):
self.names.append(self.name.get())
print('listentries', self.names)
Pages.list_of_names = self.names
my_pages_to_connect_modules.
class Pages():
list_of_names = " "
It`s been challenging to me, every help is welcome. But please dont say just that I must update main window, I need to know how. Thanks to all of you.

How to use a single button to take input from multiple Tkinter entries?

This program is being written in Tkinter. I am writing a program that will have multiple entry boxes where the user will input certain parameters. I want there to be a single button that saves all the entries from all the entry boxes to be used later by another part of my program. At this moment, the entry boxes and the button are done but the button does not do anything. How could I go about making the button read and save all the entries? Thanks!
You just need to get the data in the Entries and store them as variables, inside functions and globalize those variables. After that just call all the functions in a separate function. And then give this function as a command to the button.
import tkinter as tk
root = tk.Tk()
e_1 = tk.Entry(root)
e_1.pack()
e_2 = tk.Entry(root)
e_2.pack()
e_3 = tk.Entry(root)
e_3.pack()
var_1 = 0
var_2 = 0
var_3 = 0
def func_1():
global var_1
var_1 = e_1.get()
def func_2():
global var_2
var_2 = e_2.get()
def func_3():
global var_3
var_3 = e_3.get()
def store_all():
func_1()
func_2()
func_3()
print(var_1)
print(var_2)
print(var_3)
b = tk.Button(root, text="get", width=10, command=store_all)
b.pack()
root.mainloop()
I have used print() inside the function to confirm to you that the values are stored successfully. You can just remove those.
Here is an example of a program that reads contents of one Entry and prints it:
https://effbot.org/tkinterbook/entry.htm#patterns
Below you can find code in python 3:
from tkinter import *
master = Tk()
e = Entry(master)
e.pack()
e.focus_set()
def callback():
print(e.get())
b = Button(master, text="get", width=10, command=callback)
b.pack()
mainloop()
Just add more Entry widgets and read them all in the callback method.

How can a function change a global variables (Tikinter example)?

I am new in Oython. I wrote a simple calculator I can't understand How the function that we called it to "evaluate" works.
I have known that we have the local variables in the functions and the functions cant change the variables outside the function except we define it as output,
But in this function, It changes an outside variable and defines a new label.
I expect that we input ans as an input and take an output. and another strange problem is that we didn't give e as input but this function takes variable e with e.get() comment. How does the function know that we have a variable e when we didn't give it to the function,
My second question is how does "configure" work?
This is my code:
from tkinter import*
root=Tk()
label1 = Label(root,text="Enter your expression:")
label1.pack()
def evaluate(event):
data=e.get()
ans.configure(text="Answer:"+ str(eval(data)))
e = Entry(root)
e.bind("<Return>",evaluate)
e.pack()
ans = Label(root)
ans.pack()
root.mainloop()
The reason your function works is because Python is someone accepting of this situation.
Python will first try to edit a local variable when it cannot find ans as a local variable it then assumes it must be in the global name space and then checks there. This does not always work but can if the condition are right.
Here is a code example that shows where the function will guess correctly that your variable is in global and one function that fails at it.
import tkinter as tk
root = tk.Tk()
def print_some_number():
some_number = some_number + 5
print(some_number)
def testing():
lbl.configure(text="After button press")
some_number = 5
lbl = tk.Label(root, text="Before button press")
lbl.pack()
tk.Button(root, text="Update lavel", command=testing).pack()
tk.Button(root, text="Print some number", command=print_some_number).pack()
root.mainloop()
The print statement function will fail because we try to assign a local variable before assignment. We know it has been assigned in global but the function wont check because it looks like we are trying to assign it locally. The way to fix this is by adding the global statement like below:
import tkinter as tk
root = tk.Tk()
def print_some_number():
global some_number # global added here for this kind of fuction
some_number = some_number + 5
print(some_number)
def testing():
lbl.configure(text="After button press")
some_number = 5
lbl = tk.Label(root, text="Before button press")
lbl.pack()
tk.Button(root, text="Update lavel", command=testing).pack()
tk.Button(root, text="Print some number", command=print_some_number).pack()
root.mainloop()

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()

python tkinter.Radiobutton can't get value

I'm writing a python code with tkinter (python3) but I have some problems. I have two classes _MainScreen and _RegisterScreen (this last is nested in _MainScreen). In _RegisterScreen I had implemented a simple question with tkinter.Radiobutton (choose your sex: male, female). The idea is to catch the user selection, but when I run the script, the value assigned to the variable is empty (""). However, if I run the class _RegisterScreen alone, it works. I hope you can show me where is my error. Thanks in advance.
Here is an abstraction (32 lines) of my code (250 lines):
import tkinter
class _MainScreen(tkinter.Frame):
def __init__(self):
self.root = tkinter.Tk()
self.new_account(self.root)
self.root.mainloop()
def new_account(self, frame):
tkinter.Button(frame, text="Create new account",
command=self.create_new_account).pack(anchor="center", pady=(0,15))
def create_new_account(self):
_RegisterScreen()
class _RegisterScreen(tkinter.Frame):
def __init__(self):
self.root = tkinter.Tk()
tkinter.Label(self.root, text="Sex").grid(row=1, padx=(0,10), sticky="w")
self.sex_option = tkinter.StringVar()
tkinter.Radiobutton(self.root, text="Male", variable=self.sex_option,
value="Male", command=self._selected).grid(row=1, column=1)
tkinter.Radiobutton(self.root, text="Female", variable=self.sex_option,
value="Female", command=self._selected).grid(row=1, column=2)
tkinter.Button(self.root, text="Submit",
command=self._login_btn_clickked).grid(row=3, columnspan=4, pady=20)
self.root.mainloop()
def _login_btn_clickked(self):
sex = self._selected()
print(sex)
def _selected(self):
return self.sex_option.get()
_MainScreen()
#_RegisterScreen() # comment the above line and uncomment this line
# to test the _RegisterScreen object alone.
After doing some research on how tkinter's RadioButton widget works, I believe I have a solution to your problem:
Here's your new _RegisterScreen function:
class _RegisterScreen(tkinter.Frame):
def __init__(self):
self.gender = "NA" #Variable to be changed upon user selection
self.root = tkinter.Tk()
tkinter.Label(self.root, text="Sex").grid(row=1, padx=(0,10), sticky="w")
self.sex_option = tkinter.StringVar()
#This Radiobutton runs the setMale function when pressed
tkinter.Radiobutton(self.root, text="Male", variable=self.sex_option,
value="Male", command=self.setMale).grid(row=1, column=1)
#This Radiobutton runs the setFemale function when pressed
tkinter.Radiobutton(self.root, text="Female", variable=self.sex_option,
value="Female", command=self.setFemale).grid(row=1, column=2)
tkinter.Button(self.root, text="Submit",
command=self._login_btn_clickked).grid(row=3, columnspan=4, pady=20)
self.root.mainloop()
def _login_btn_clickked(self):
sex = self.gender #gets the value stored in gender and assigns it to sex
print(sex)
def setMale(self):
self.gender="Male" #sets gender to Male
def setFemale(self):
self.gender="Female" #sets gender to Female
Ultimately, you want to run 2 separate functions for either RadioButton.
When the Male Radiobutton gets clicked, it runs the setMale function.
When the Female Radiobutton gets clicked, it runs the setFemale function.
I believe you were confused about what RadioButton's variable and value attributes actually are (as was I before looking further into this).
I learned more about what those those do in this video: https://www.youtube.com/watch?v=XNF-y0QFNcM
I hope this helps! ~Gunner

Resources