-1

I am having some issues with the trace method. I am trying to build a form which can change depending on the first option.

MCVE:

from tkinter import *
from tkcalendar import Calendar, DateEntry
root = Tk()

def new_client_window_2(event=None, w=None, fn=None, on=None, sn=None, dob=None, ge=None, ctype=None, *args):
    print(type(w))
    children = w.winfo_children()
    for child in children:
        child.pack_forget()
        child.destroy()
    typeF = Frame(w, padx=10)
    typeF.pack(fill=X, expand=1)
    Label(typeF, text="Gender:", bg="white", width=10).pack(side=LEFT, fill=X, expand=1)
    tyOPTIONS = [None, "School", "Private", "NHS", "YOT", "Other"]
    typeE = StringVar(typeF)
    print(ctype)
    if ctype != "None":
        typeE.set(tyOPTIONS[tyOPTIONS.index(ctype)])
    else:
        typeE.set(tyOPTIONS[0])
    typeD = OptionMenu(*(typeF, typeE) + tuple(tyOPTIONS))#, command=lambda e=Event(), w=window, f=fn, o=on, s=sn, d=dob, g=ge, t=typeE.get(): self.new_client_window_2(e, w, f, o, s, d, g, ctype=t))
    typeD.pack(side=LEFT, fill=X, expand=1)
    typeE.trace("w", lambda e=Event(), w=w, f=fn, o=on, s=sn, d=dob, g=ge, t=typeE.get(): new_client_window_2(event=e, w=w, fn=f, on=o, sn=s, dob=d, ge=g, ctype=t))

def new_client_window(event):
    window = Frame(root, padx=5, pady=5)
    window.pack(fill=BOTH, expand=1)
    fnameF = Frame(window, padx=10)
    fnameF.pack(fill=X, expand=1)
    onameF = Frame(window, padx=10)
    onameF.pack(fill=X, expand=1)
    snameF = Frame(window, padx=10)
    snameF.pack(fill=X, expand=1)
    dobF = Frame(window, padx=10)
    dobF.pack(fill=X, expand=1)
    genderF = Frame(window, padx=10)
    genderF.pack(fill=X, expand=1)
    Label(fnameF, text="First Name:", bg="white", width=10).pack(side=LEFT, fill=X, expand=1)
    fname = Entry(fnameF)
    fname.pack(side=LEFT, fill=X, expand=1)
    Label(onameF, text="Other Name(s):", bg="white", width=10).pack(side=LEFT, fill=X, expand=1)
    oname = Entry(onameF)
    oname.pack(side=LEFT, fill=X, expand=1)
    Label(snameF, text="Surname:", bg="white", width=10).pack(side=LEFT, fill=X, expand=1)
    sname = Entry(snameF)
    sname.pack(side=LEFT, fill=X, expand=1)
    Label(dobF, text="DOB:", bg="white", width=10).pack(side=LEFT, fill=X, expand=1)
    cal = DateEntry(dobF, width=12, background='darkblue', foreground='white', borderwidth=2)
    cal.pack(side=LEFT, fill=X, expand=1)
    Label(genderF, text="Gender:", bg="white", width=10).pack(side=LEFT, fill=X, expand=1)
    geOPTIONS = [None, "Male", "Female", "Other"]
    genderE = StringVar(genderF)
    genderE.set(geOPTIONS[0])
    genderD = OptionMenu(*(genderF, genderE) + tuple(geOPTIONS))
    genderD.pack(side=LEFT, fill=X, expand=1)

    Button(window, text="Next", command=lambda e=Event(), w=window, f=fname, o=oname, s=sname, d=cal, g=genderE: new_client_window_2(event=e, w=w, fn=f, on=o, sn=s, dob=d, ge=g, ctype=None)).pack()

new_client_window(Event())
root.mainloop()

as you can see i need to pass some variables into this function from the previous section of the form (static form), then when "ctpyeE" is changed to one of the options i need to re-run the same function but this time know what ctpyeE is set as. I can then use that to adjust the rest of the form.

The error i am getting is from w.winfo_children(), w is the frame i am using to hold the form, however once ctypeE changes and the trace runs it changes its self to an empty string.

Exception in Tkinter callback
Traceback (most recent call last):
  File "C:\Program Files (x86)\Python37-32\lib\tkinter\__init__.py", line 1705, in __call__
return self.func(*args)
  File "C:\Users\Scott\Desktop\patient\caseload.py", line 892, in <lambda>
typeE.trace("w", lambda e=Event(), w=w, f=fn, o=on, s=sn, d=dob, g=ge, t=typeE.get(): self.new_client_window_2(event=e, w=w, fn=f, on=o, sn=s, dob=d, ge=g, ctype=t))
  File "C:\Users\Scott\Desktop\patient\caseload.py", line 877, in new_client_window_2
children = w.winfo_children()
AttributeError: 'str' object has no attribute 'winfo_children'

I am assuming the Trace function is setting its own variables in the function but i can't find any documentation on what these variables are so i can account for them.

Notes:

self.tku is my own class, cFrame and cButton are just Frame/Button with pack() added to make it a single line, it should be easy to adjust for those.

fn, on, sn, dob and ge are all Entry Widget objects. and w is the Frame that holds the form.

Scott Paterson
  • 392
  • 2
  • 17
  • Have you done any debugging to verify that `w` is what you think it is? Can you please create a [mcve] so we can reproduce your problem? – Bryan Oakley Jul 30 '19 at 16:17
  • @BryanOakley I know what w is, the issue is what it is, is wrong. It should be a Frame but after trace it becomes a String. This only Happens after the trace event, despite it not being changed at all. I have added the MCVE as you suggested – Scott Paterson Aug 01 '19 at 08:06

1 Answers1

1

In short, you're using the trace incorrectly.

When you create a variable trace, the function you associate with the trace will be called with three positional arguments (see What are the arguments to Tkinter variable trace method callbacks?). Thus, when your lambda is called, e is going to be set to the first positional argument, w is going to be set to the second positional argument, and f is going to be set to the third.

A simple solution is to make sure your lambda accepts these parameters before the named arguments:

typeE.trace("w", lambda name1, name2, operation, e=Event(), w=w, ...etc)
Bryan Oakley
  • 370,779
  • 53
  • 539
  • 685