0

In my program, I have a block of code like this:

    def engine_input_number_of_students_and_student_information(self, window):
        self.__input.input_number_of_students(self, window)
        print(self.number_of_students)

and the function input_number_of_students() was defined in class Input as below:

class Input:
    # Function to ask user to input number of student.
    # Print error and force the user to re-input if wrong data is given.
    def input_number_of_students(self, engine, window):
        sub = tk.Toplevel(master=window)
        sub.title("Number of students")
        sub.resizable(height=False, width=False)
        window.eval(f'tk::PlaceWindow {str(sub)} center')
        frm1 = tk.Frame(master=sub)
        frm1.grid(row=0, column=0, padx=10, pady=10)
        lbl = tk.Label(text="Enter number of students:", master=frm1)
        number_of_students_var = tk.StringVar()
        ent = tk.Entry(width=3, master=frm1, textvariable=number_of_students_var)
        lbl.grid(row=0, column=0, padx=5)
        ent.grid(row=0, column=1, padx=5)
        frm2 = tk.Frame(master=sub)
        frm2.grid(row=1, column=0, sticky="nsew", padx=5, pady=5)

        def ok():
            try:
                number_of_students = int(number_of_students_var.get())
                if number_of_students < 0:
                    messagebox.showerror(message="Error: number of students must be non-negative")
                    ent.delete(-1, tk.END)
                else:
                    engine.number_of_students = number_of_students
                    sub.destroy()
            except:
                messagebox.showerror(message="Invalid number of students")

        ok_btn = tk.Button(text="OK", master=frm2, command=lambda: ok)
        ok_btn.pack(ipadx=5)
        ok_btn.wait_variable(number_of_students_var)

which basically create a sub-window with an entry and a button OK. The user input the value in the entry, and press OK, that value will be stored in engine.number_of_students. But when I try to run this, function print(self.number_of_students) was executed right when I input some value in the entry and I didn't even press the button (it prints 0, obviously).

I also tried to replace number_of_students_var by engine.number_of_students, it stucks there forever!

Well I read that the wait_variable() will wait until the value of the parameter we pass into it is modified, then carry out the function, so the problem I'm having is... kinda make sense. But I feel that I don't truly understand this function and how to use it properly (like, what will wait for the modification of the parameter?) , so could you propose other ways to perform what I have described?

blank
  • 37
  • 1
  • 7
  • Using `wait_variable()` in this case is not appropriate. Use `wait_window()` instead. – acw1668 May 17 '21 at 06:40
  • @acw1668 could you please propose a more detailed way? I still cannot really imagine how we're gonna use the wait_window() – blank May 17 '21 at 06:47
  • Try replacing the `wait_variable()` line by `sub.wait_window(sub)`. – acw1668 May 17 '21 at 06:49
  • @acw1668 Well it does not close the sub-window when I press OK. It stays there forever. – blank May 17 '21 at 06:53
  • @acw1668 Bro, with your solution, and I removed the ```lambda``` in the ```command=lambda: ok``` and it worked! Thank you very much! By the way, can you explain how the ```lambda``` thing means and how does it work? I just know that it's an anonymous function, but I don't understand precisely how it performs and how it works. – blank May 17 '21 at 06:57
  • Should be `lambda: ok()` if using `lambda`. But since `ok()` takes no argument, it is not necessary to use `lambda` in this case. – acw1668 May 17 '21 at 07:02
  • using `lambda` in such case would be the same as `def func():` and then `your_func()` and then using like `command=func`. – Matiiss May 17 '21 at 08:34

1 Answers1

1

wait_variable waits for a variable to change. Because the variable you are waiting on is associated with an Entry widget, whenever you type in the entry widget the variable will instantly change. wait_variable senses this change and returns.

If you want to use wait_variable in this situation, you do not want to associate the variable with the entry widget. Instead, you would set the variable when the user presses the "ok" button. However, you have a separate problem which is that ok is never called because of how you've defined the button.

You're using lambda for the button, and inside the lambda which does ok. However, ok does not call ok, it simply refers to it. To call the function you need to do ok(). However, in this case, there's no need to use lambda at all. The button requires a callable, so you can directly pass ok:

ok_btn = tk.Button(text="OK", master=frm2, command=ok) 

Now, back to waiting on the variable. You need to do the following:

  • remove the definition of textvariable when defining the entry widget (eg: ent = tk.Entry(width=3, master=frm1))
  • have ok do ent.get() rather than number_of_students_var.get() since the entry widget is no longer directly associated with the entry widget.
  • have ok set the variable number_of_students_var, which will trigger wait_variable to return
Bryan Oakley
  • 370,779
  • 53
  • 539
  • 685