0

The following function accepts a list as input and then waits for the user to enter chars in an entry widget. Each time chars are entered, a listbox widget displays all of the items from the input list that include those characters, thus filtering the input list to only selected items. When the user is satisfied with the selected items, he/she clicks the 'ok' button and the function returns an output list, which is a list of items filtered from the input list.

import tkinter as tk

root = tk.Tk()
root.geometry("500x400")

# === PopupFilter Function ====

def PopupFilter(InputList):

    OutputList = [] # list of selected items from InputList
    
    def UserTypedCharacter(event): # user typed character in entry box
        global OutputList
        # get current text in entry box
        typed = SearchEntry.get()
        OutputList = [] # clear OutputList
        for item in InputList: # check entry box text against InputList
            if typed.lower() in item.lower():
                OutputList.append(item)
        SearchListbox.delete(0,tk.END)
        SearchListbox.insert(tk.END,*OutputList)

    def ExitFunction():
        global OutputList
        SearchFrame.place_forget() # remove popup from screen
        return OutputList # return result

    # frame containing widgets for this function
    SearchFrame = tk.Frame(root,bg="lightgrey",height=350,width=200)
    SearchFrame.place(x=200,y=20)

    # entry where user enters characters to filter list
    SearchEntry=tk.Entry(SearchFrame,width=30)
    SearchEntry.place(x=5,y=30)
    SearchEntry.focus() # put cursor in entry box
    # Create a binding on the entry box
    SearchEntry.bind("<KeyRelease>", UserTypedCharacter)

    # listbox displaying filtered items based on chars in entry box
    SearchListbox=tk.Listbox(SearchFrame,width=30,height=15)
    SearchListbox.place(x=5,y=60)
    
    # button pressed to end the function and return OutputList
    OkButton = tk.Button(SearchFrame,text=" OK ")
    OkButton.config(command=ExitFunction)
    OkButton.place(x=5,y=310)

# === Usage example ===

FruitList = ['pear','peach','plum','mango','apple','banana']

OutputList = PopupFilter(FruitList)

print(OutputList) # this prints immediately, and does not wait for the return statement

# === Root mainloop ===
root.mainloop()

As you can see in the usage example, the issue is that my function returns None immediately. It does not wait for the return keyword. I need to use some sort of loop or other mechanism to keep the function active until the return keyword is used, but I have no idea how to do this. Any help appreciated.

fmex
  • 121
  • 6
  • `PopupFilter` doesn't return anything. It has no return statement. it just runs a series of tkinter functions and then exits. You're setting `OutputList = PopupFilter(FruitList)` as if you think that `PopupFilter()` contains something like `return OutputList`, but it doesn't. Why do you think it's supposed to not return immediately, and also return `OutputList`? – Random Davis Nov 11 '22 at 22:06
  • It does include the statement 'return OutputList' in the 'ExitFunction' code. But that is clearly not the way to do it. How could I have it return the output list correctly, when the user clicks the 'ok' button? – fmex Nov 11 '22 at 22:08
  • Surely this is what you need. Your function must return so that you can run `root.mainloop()`. That is the loop you are looking for. – quamrana Nov 11 '22 at 22:09
  • The only code that affects `OutputList` is in `ExitFunction` and `UserTypedCharacter`, both of which are only called when you either type something, or click the OK button. So it's not clear why you expect anything else to be happening other than what currently is. Why not just print it in `ExitFunction`? – Random Davis Nov 11 '22 at 22:10
  • 2
    `PopupFilter` can't return the list like you want. tk uses a loop to run the UI things. So if you block the `PopupFilter` function waiting on a return then the UI wont load. What you need to do is once the person clicks `OK` then in ur `ExitFunction` grab what is selected and save it in a variable somewhere. Then you can access that variable from where ever you want. Maybe even just print it from the `ExitFunction`. – TeddyBearSuicide Nov 11 '22 at 22:10
  • All advice appreciated. Perhaps the solution is to have the function update a listbox outside of the function itself. That way, the function runs until the user clicks the 'ok' button. At that point, the function shuts down but I'm still left with the filtered list in the external listbox. – fmex Nov 11 '22 at 22:20
  • It doesn't have to be a listbox or any UI element. You could just have a `list` object somewhere where once they click OK it populates that `list` with the filtered options. But w.e works for you, do it. – TeddyBearSuicide Nov 11 '22 at 23:02
  • @TeddyBearSuicide: your comments are a bit misleading. OP _can_ return the list the way they want - tkinter has a way to wait for some event before returning from a function without blocking the UI. – Bryan Oakley Nov 11 '22 at 23:31

1 Answers1

1

If you're trying to create what is in essence a modal dialog box, tkinter has special functions to wait for specific events. For example, you can call a function that waits until a variable has been set, or you can wait until a widget has been destroyed. While waiting, events will be processed as usual.

In your case you can wait for SearchFrame to be destroyed, and trigger that with the button.

The solution looks something like this:

def PopupFilter(InputList):
    global OutputList
    ...
    SearchFrame = tk.Frame(root,bg="lightgrey",height=350,width=200)
    ...
    OkButton.config(command=SearchFrame.destroy)
    ...
    SearchFrame.wait_window()

    return OutputList

In the above code, SearchFrame.wait_window() won't return until SearchFrame has been destroyed, which happens when the user clicks the button. At that point, OutputList should have been changed and you can return what's in that variable (or anything else)

It's best if this is done in a class so that you don't have to use global variables.

Also, you should take care that the user can't cause this dialog to appear multiple times at the same time or you'll end up with a bunch of event handlers.

Bryan Oakley
  • 370,779
  • 53
  • 539
  • 685
  • Related: https://stackoverflow.com/q/74370984/13629335 – Thingamabobs Nov 11 '22 at 23:40
  • Many thanks! This is an important piece of information about tkinter that I was not aware of. As usual, your advice is extremely helpful and knowledgeable. – fmex Nov 13 '22 at 01:33