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())
Related
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'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'm trying to create a couple of functions which do things in a sequential order. First they need to open a new window and display a label, then they need to wait for some seconds, then they need to call another function. However, I'm struggling to get the functions to wait, all the methods I've tried (.after, .sleep, .wait_visibility) seem to be ignored and it just skips to the next function call without pausing.
Here's what I have (sorry if it's messy, I'm new to python):
from tkinter import *
import time
root =Tk()
root.geometry('600x600')
def scale_screen(event = None):
global s_screen
s_screen = Toplevel(root)
s_screen.title('Residual Inhibition Tester')
s_screen.geometry('600x600')
s_screen.transient(root)
s_screen.bind('<Return>', sel)
global var
var = IntVar()
scale = Scale(s_screen, variable = var, orient = HORIZONTAL, length = 1000)
scale.focus_set()
scale.pack(anchor=CENTER)
button = Button(s_screen, text="Select", command=sel)
button.pack(anchor=CENTER)
def sel(event = None):
label = Label(s_screen)
selection = "Value = " + str(var.get())
label.config(text = selection)
interval_screen()
def interval_screen():
global i_screen
i_screen = Toplevel(root)
i_screen.geometry('600x600')
i_screen.transient(root)
i_label = Label(i_screen, text = "Please Wait")
i_label.pack(anchor = CENTER)
s_screen.destroy()
i_screen.after(3000, masker_screen)
#time.sleep(3)
#i_screen.after(300,i_label.configure(text="Playing New Masker Noise"))
#root.wait_visibility(window = i_screen)
def masker_screen():
global m_screen
m_screen = Toplevel(root)
m_screen.geometry('600x600')
m_screen.transient(root)
m_label = Label(m_screen, text = "Playing New Masker Noise").pack(anchor = CENTER)
m_screen.after(3000, lambda: scale_screen(event = None))
i_screen.destroy()
b1 = Button(root, command = scale_screen).pack(anchor=CENTER)
root.bind('<Return>', scale_screen)
root.mainloop()
In this example, the program will run but just skip the interval_screen entirely and just do the masker_screen. I'm also not averse to just using one screen and using the .configure methods to change the label text if that's easier.
Thanks!
Without seeing all the ways you tried it, it's impossible to know what you did wrong. In general you should never call time.sleep and you should never call after with just a single argument. Also, when you use after with two arguments, the second argument must be a reference to a function.
The proper way to do this is to have your first function call your second function via after:
def interval_screen():
...
i_screen.after(3000, maker_screen)
def masker_screen():
...
m_screen.after(3000, lambda: scale_screen(event = None))
Note that in your updated question you're using after incorrectly:
m_screen.after(3000, scale_screen(event = None))
You're calling the function scale_screen(...) immediately, and giving the result of that to the after function. If you need to pass arguments to your function you must create another function that does not require arguments. The simplest way to do this is with lambda, though you can also use functools.partial or you can create your own function.
Here's a piece of simplified code which doesn't work as i want it to:
def get_tl(self,x):
self.var_tl = IntVar()
if x == "Random (max = 6)":
self.var_tl.set(randint(1,6))
else:
ask_tl = Toplevel()
def destroy_t_set_tl():
self.var_tl.set(entry_tl_t.get())
ask_tl.destroy()
label_tl_t = Label(ask_tl, text="length:").pack(side=LEFT)
entry_tl_t = Entry(ask_tl, width=25)
entry_tl_t.pack(side=LEFT)
button_enter_tl_t = Button(ask_tl, text="Enter", command=destroy_t_set_tl).pack(side=LEFT)
self.label_tl = Label(self, text="length:").grid(row=1,column=0)
# This only shows the right number when "Random (max = 6)". When "Manual" it shows 0
self.show_tl = Label(self, text=self.var_tl.get()).grid(row=1,column=1)
def get_values(self):
# This always shows the right number.
self.total_label = Label(self, text=self.var_tl.get()).grid(row=4,column=0)
The function get_tl is called by an OptionMenu widget which gives x the values: "Manual" or "Random (max = 6)".
When this function is called I want it to choose a random number or open a Toplevel window which ask the user a number through an Entry. After the random number is chosen or the user has given a number. The number needs to be displayed as a label so the user can see if the number is correct.
The label only show the right number when "Random (max = 6)". When "Manual" it shows 0
After a button is pressed the function get_values is called. This however does give the right number regardless if it is manual or random.
I'm probably making a simple mistake here. But I fail to see it.
In this part:
def get_tl(self,x):
self.var_tl = IntVar()
You're recreating the variable over and over again, so it holds the default value of 0, as explained in the documentation:
VALUE is an optional value (defaults to 0)
Then you set the variable only if x == "Random (max = 6)", so in all other cases it will remain at its default.
Possibly you want to remove this line:
self.var_tl = IntVar()
You should have it only in the constructor of your class. Then all your methods will share the same instance pointed by self.var_tl.
This seems to be the answer to my own question:
def get_tl(self,x):
def tl():
self.label_tl = Label(self, text="length:").grid(row=1,column=0)
self.show_tl = Label(self, text=self.var_tl.get()).grid(row=1,column=1)
if x == "Random (max = 6)":
self.var_tl.set(randint(1,6))
tl()
else:
ask_tl = Toplevel()
def destroy_t_set_tl():
self.var_tl.set(entry_tl_t.get())
ask_tl.destroy()
tl()
label_tl_t = Label(ask_tl, text="length:").pack(side=LEFT)
entry_tl_t = Entry(ask_tl, width=25)
entry_tl_t.pack(side=LEFT)
button_enter_tl_t = Button(ask_tl, text="Enter", command=destroy_t_set_tl).pack(side=LEFT)
def get_values(self):
self.total_label = Label(self, text=self.var_tl.get()).grid(row=4,column=0)
Now both the option "Manual" and "Random" will call the function tl() which will show the number so the user can check it.
I also moved the self.var_tl = IntVar() to the constructor of the class. It might not be the optimal solution but for me it works.
I am making a GUI Program in Tkinter and am running into problems.What I want to do is draw 2 checkboxes and a button. According to the user input next steps should take place. A part of my code has been shown below :-
CheckVar1 = IntVar()
CheckVar2 = IntVar()
self.C1 = Checkbutton(root, text = "C Classifier", variable = CheckVar1, onvalue = 1, offvalue = 0, height=5,width = 20).grid(row=4)
self.C2 = Checkbutton(root, text = "GClassifier", variable = CheckVar2, onvalue = 1, offvalue = 0, height=5, width = 20).grid(row=5)
self.proceed1 = Button(root,text = "\n Proceed",command = self.proceed(CheckVar1.get(),CheckVar2.get())).grid(row=6)
# where proceed prints the combined values of 2 checkboxes
The error that I am getting is typical ie a default value of both the selected checkboxes gets printed up and then there is no further input. The error that I get is NullType Object is not callable.
I searched on the net and I think the answer is related to lambda events or curry.
Please help ..
You're passing the value of self.proceed(CheckVar1.get(),CheckVar2.get()) to the Button constructor, but presumably what you want is for command to be set to a function which will call self.proceed(CheckVar1.get(),CheckVar2.get()) and return a new, possibly different value every time the button is pressed. You can fix that with a lambda, or by wrapping the call in a short callback function. For example, replace the last line with:
def callback():
return self.proceed(CheckVar1.get(), CheckVar2.get())
self.proceed1 = Button(root, text="\n Proceed", command=callback).grid(row=6)
This is pretty typical Tkinter. Remember: when you see a variable called command in Tkinter, it's looking for a function, not a value.
EDIT: to be clear: you're getting 'NullType Object is not callable' because you've set command to equal the return value of a single call to self.proceed (that's the NullType Object). self.proceed is a function, but its return value is not. What you need is to set command to be a function which calls self.proceed.
Like Peter Milley said, the command option needs a reference to a function (ie: give it a function name (ie: no parenthesis). Don't try to "inline" something, create a special function. Your code will be easier to understand and to maintain.