0

I have a list of frames that each have an optionmenu with the same list of choices. When a choice is made in that specific optionmenu, I've only been able to get the last entry widget to change, not the corresponding one. In other widgets I've been able to use something like "lambda F=F:function(args)" but that isn't working here.

I've tried a trace on the variable in the option menu, I've tried wrapper functions, I've tried every combination of a lambda in the command section of the optionmenu widget. Most approaches create errors, some, like the one attached, modify the bottom frame/entry but not the correct corresponding one.

This doesn't seem like it should be too hard. If the option for the top frame selected is "Continuous" or "Discrete", the entry next to it should be 'normal' state with "?..?" in the box, if it is categorical, it should change to be 'disabled' with no contents. I could do this easily if I could somehow pass the Frame dictionary key to the "updateOnChange" function, but I can't, it only allows a single argument to be passed and that is the string value of mType[F].

from tkinter import *

def updateOnChange(type):
    print(type)
    if type.upper()=='CATEGORICAL':
        rangeEntry[F].delete(0,END)
        rangeEntry[F].config(state='disabled')
        print("runCat")
    else:
        rangeEntry[F].config(state='normal')
        rangeEntry[F].delete(0,END)
        rangeEntry[F].insert(0,'?..?')
        print("runCont")

mType={}
frame={}
om={}
rangeEntry={}
root=Tk()
Frames=['FrameOne','FrameTwo']
miningTypes=['Continuous','Categorical','Discrete']
for F in Frames:
    mType[F]=StringVar(root)
    if F=='FrameOne':
        mType[F].set("Continuous")
    else:
        mType[F].set("Categorical")
    frame[F]=Frame(root,borderwidth=3,relief=SUNKEN)
    frame[F].pack(side=TOP,fill=X)
    rangeEntry[F]=Entry(frame[F],width=20,font=("Arial",12))
    om[F]=OptionMenu(frame[F],mType[F],*miningTypes,command=updateOnChange)
    om[F].pack(side=LEFT)
    rangeEntry[F].pack(side=LEFT)
mainloop()


``
Karl Knechtel
  • 62,466
  • 11
  • 102
  • 153
jbokusky
  • 3
  • 2
  • Does this answer your question? [How to pass arguments to a Button command in Tkinter?](https://stackoverflow.com/questions/6920302/how-to-pass-arguments-to-a-button-command-in-tkinter) – Karl Knechtel Aug 18 '22 at 03:36

1 Answers1

0

Your updateOnChange function hard-coded the entry to be changed as rangeEntry[F], which points to the last Entry widget created in your for loop. To properly associate each entry, you should pass the widget as a parameter:

def updateOnChange(type, entry):
    if type.upper()=='CATEGORICAL':
        entry.delete(0,END)
        entry.config(state='disabled')
        print("runCat")
    else:
        entry.config(state='normal')
        entry.delete(0,END)
        entry.insert(0,'?..?')
        print("runCont")

And then pass the parameter in your command:

om[F]= OptionMenu(frame[F],mType[F],*miningTypes,command=lambda e, i=rangeEntry[F]: updateOnChange(e, i))
Henry Yik
  • 22,275
  • 4
  • 18
  • 40
  • Thank you so much Henry. Clearly I need to understand lambdas better. Is there a clear way to describe what "lambda e, i=rangeEntry[F]: updateOnChange(e, i)" is actually doing. Is it just assigning the current iteration of rangeEntry to the variable e? – jbokusky Aug 26 '19 at 03:07
  • `e` is the result of your selection of `OptionMenu`. `i=rangeEntry(F)` is assigning the current iteration of rangeEntry. This is needed due to late binding of Python - you can read [this post](https://stackoverflow.com/questions/17677649/tkinter-assign-button-command-in-loop-with-lambda) for more. – Henry Yik Aug 26 '19 at 03:12
  • Thanks so much. Very helpful. So if I'm understanding, optionmenu automatically gives the selection to the command option. So e as the first attribute just grabs that selection. After that, I'm free to pass any widgets in the current iteration as additional arguments to the function after the colon. – jbokusky Aug 26 '19 at 03:44