0

it is incredible how the knowledge of something so basic can be buried in a so hard to find place. I have spent close to a month googling for an answer--watching videos, reading documents, books, etc--of how to stop a while loop to get input from an user using Tkinter.

My code below is looping twice and leaving the loop before I am able to provide an input!!!

Anyway, theory is appreciated, but a sample code will be of greater help. Thank you very, very much indeed.

# Python 3.5.1
from tkinter import *
from tkinter import ttk

loop = 0

while loop < 2:

    print ('loop')

    def user_data():
        user_input = data.get()
        print (user_input)

    lb=ttk.Label(root, text="Enter data")
    data=ttk.Entry(root)
    bt=ttk.Button(root, text='Ok', command=user_data)

    lb.grid(row=0, column=1)
    data.grid(row=0, column=2)
    bt.grid(row=0, column=3)

    loop += 1

print ('left loop')

root.mainloop()
30
  • Where are you defining root? Commented Aug 16, 2016 at 19:56
  • This is hard to do because this simply isn't how graphical user interfaces are designed to work. It's a bit like saying you've searched for a month on how to cook steak in a blender. If you need to pause and get information from a user, you should be using a modal dialog. See effbot.org/tkinterbook/tkinter-dialog-windows.htm Commented Aug 16, 2016 at 20:03
  • Good one, Bryan. Steak in a blender. It took me almost five months to research that one! Commented Aug 16, 2016 at 20:10
  • @ Hobbes, I am defining 'root' right above 'loop = 0', just forgot to past it. Commented Aug 16, 2016 at 20:16
  • @Bryan, unless dialog windows are more powerful than what I can see, I don't think it is a workable solution to a form that will require seven input/entry boxes in a single row with the option to add n number of rows. I am new to Python, but as far as I could see, input(), for instance, stops a program/loop until a user provides some data. The problem with input() is that I only saw it working in the shell, not in a GUI. Commented Aug 16, 2016 at 20:27

2 Answers 2

1

Ok, I think the joke is on me after all. :-) I spent last three days telling you this can't be done easily, and it turns out I was wrong.

I'll give you the code soon, just let me explain where I went wrong. While I was writing my last comment, the main thing I wanted to explain to you is that when you run a console Python program inside a GUI console, those are two different processes, and the event loop of a GUI has nothing to do with how the inner Python program works.

In a GUI application, it breaks down since there is just one process, and its event loop cannot at the same time run (being responsive to normal app events like repainting a window or handling clicks/keypresses) and stay blocked (wait for you to click a button). It occured to me the things would be very simple if tkinter would enable the user to make additional event loops. It's not unreasonable: since tkinter already constructs one for its own purposes, it just needs to expose that constructor (or some other means of construction) to the user. And there won't be any GIL problems, since the other event loop just stays blocked (only one is actually executing).

Once I knew what I was searching, it wasn't so hard to find: you construct a Tk variable (of any type, Boolean works nice for this semantics), toggle its value, and toggle it back in the callback. In the meantime you wait for it to change. Easy as pie. :-)

I tried to change your code as little as possible, but of course, once you know the mechanism, you can do it much more nicely. I hope I've returned your faith in tkinter. :-)

from tkinter import *
from tkinter import ttk

root = Tk()
loop = 0
block = BooleanVar(root, False)

while loop < 2:

    print ('loop')

    def user_data():
        user_input = data.get()
        print (user_input)
        block.set(False)

    lb=ttk.Label(root, text="Enter data")
    data=ttk.Entry(root)
    bt=ttk.Button(root, text='Ok', command=user_data)

    lb.grid(row=0, column=1)
    data.grid(row=0, column=2)
    bt.grid(row=0, column=3)

    block.set(True)
    root.wait_variable(block)
    loop += 1

print ('left loop')
root.mainloop()
Sign up to request clarification or add additional context in comments.

5 Comments

Just a comment: of course, I still think this isn't how normal GUI applications should work. @Bryan Oakley has solved your X. I hope I have solved your Y. (See mywiki.wooledge.org/XyProblem for terminology.) And I think it was an interesting and educational experience. :-)
@ Veky, GREAT!!! I know stackoverflow asks not to be saying thanks here, but I where else to do it.
Just accept the answer if it answers your question. :)
The answer hit the bullseye. Thanks a lot!
Hm... Thank you. :-)
0

Ok, after a lot of discussion, it appears that you don't actually need to wait for anything. A GUI is in a perpetual state of waiting for events, so you don't need to add additional waiting. All you need is a "save" button, and a way for the user to input a variable number of values.

To allow the user to enter multiple values, you can add a button that adds more entry widgets. The only real trick is to keep a list of the entry widgets so that you can get the values out later in your program.

The following is a working example that illustrates the technique.

Note: This is not how I would actually do this in a real GUI because it relies on global variables. The point is to illustrate the general principle of dynamically adding entry widgets.

import tkinter as tk

def add_entry():
    """
    Add two entries, one for a description, one for an amount,
    along with some labels
    """
    # create the label and entry widgets
    label1 = tk.Label(entry_frame, text="Description:")
    label2 = tk.Label(entry_frame, text="Amount:")
    entry1 = tk.Entry(entry_frame)
    entry2 = tk.Entry(entry_frame)

    # lay them out on the screen
    column, row = entry_frame.grid_size()
    label1.grid(row=row, column=0, sticky="e", pady=2)
    entry1.grid(row=row, column=1, sticky="ew", pady=2, padx=4)
    label2.grid(row=row, column=2, sticky="e", pady=2)
    entry2.grid(row=row, column=3, sticky="ew", pady=2, padx=4)
    entry_frame.grid_rowconfigure(row, weight=0)
    entry_frame.grid_rowconfigure(row+1, weight=1)

    # save the entries, so we can retrieve the values
    entries.append((entry1, entry2))

    # give focus to the new entry widget
    entry1.focus_set()

def save():
    # iterate over the entries, printing the values
    for description_entry, value_entry in entries:
        print("description: %s value: %s" % 
              (description_entry.get(), value_entry.get()))

# this is our global list of entries
entries = []

# create the main window and buttons
root = tk.Tk()

entry_frame = tk.Frame(root)
button_frame = tk.Frame(root)

entry_frame.pack(side="top", fill="both", expand=True)
button_frame.pack(side="bottom", fill="x")

add_button = tk.Button(button_frame, text="add another entry", command=add_entry)
save_button = tk.Button(button_frame, text="Save", command=save)

add_button.pack(side="left")
save_button.pack(side="right")

# create the first entry
add_entry()

# start the main loop -- this is where the GUI starts waiting,
# and why you don't need to add your own loop.
root.mainloop()

1 Comment

thanks a lot! It might be a path to what I need, I will dive deep into your example to try to make it fit my need. Somewhere, I will need to check if the figures of the categories add up to the total amount. If I can do this using "if", I should be safe. Otherwise, I will probably be back to the "while loop" problem.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.