[Python] Python Tkinter: wait for user input before continuing with the mainloop

Discussão em 'Python' iniciado por Stack, Junho 23, 2025.

  1. Stack

    Stack Membro Participativo

    I am using Tkinter with Python 3.11 on Windows.

    The following program creates a simple window with a menu. The user would click "Game -> Select game directory", whereupon game_directory_selection() function is called for the user to select a directory.

    If the chosen directory contains a single file with the name utilX.dat (X being 0..9), the directory is used as game directory and some general game configuration is read here

    self.conf = Config(self.game_directory)


    and then the specific player configuration is read here

    self.player = Player(self.game_directory, self.player_id)


    because 'X' in utilX.dat would be the player_id.

    However, if the directory contains several player files (let's say util1.dat, util3.dat and util7.dat), then the self.display_file_selection() function is called to allow the player to choose one. This is done by reusing the window to create a new Frame, a Label, a Listbox with the player files and a Button. Once the user has chosen one player file, this Frame is destroyed and the player specific file can be read.

    import tkinter as tk
    from tkinter import Menu, Text, Label, Button, Listbox, StringVar, ttk
    from tkinter import filedialog, messagebox
    from tkinter import ttk
    import os.path
    import sys
    import time
    from pathlib import Path

    #from inventory_view import InventoryViewer
    from player import Player
    from config import Config


    class Game:
    def __init__(self, root):
    self.game_directory = None
    self.filelist = None
    self._selected_indices = ''
    self.playerfile = None
    self.conf = None

    self.root = root
    self.root.title("My Game Client")

    self.create_main_window()

    print('playerfile', self.playerfile) # (**)

    def create_main_window(self):
    # Create menu
    self.menu = Menu(self.root)
    self.root.config(menu=self.menu)

    # Game menu
    self.game_menu = Menu(self.menu, tearoff=0)
    self.menu.add_cascade(label="Game", menu=self.game_menu)
    self.game_menu.add_command(label="Select game directory", command=self.game_directory_selection)
    self.game_menu.add_separator()
    self.game_menu.add_command(label="Save game") # TO DO: add command
    self.game_menu.add_command(label="Inventory viewer", command=self.inventory_view)
    self.game_menu.add_command(label="Exit", command=self.root.quit)



    def game_directory_selection(self):
    """ Choose game directory, or quit."""

    while True:
    self.game_directory = tk.filedialog.askdirectory()
    if not self.game_directory:
    if tk.messagebox.askyesno(
    icon='warning',
    title="Game directory selection",
    message="You haven't chosen a game directory. Do you want to quit?"):
    sys.exit(0)
    else:
    p = Path(self.game_directory)
    self.filelist = list(p.glob('util[0-9].dat'))

    if not self.filelist:
    tk.messagebox.showerror(
    title='Directory error',
    message='Player files not found in directory')
    else:
    # If there is more than one player file,
    # display a selection dialog to choose one.
    if len(self.filelist) > 1:
    filelist_tmp = [f.name for f in self.filelist]
    self.display_file_selection(filelist_tmp)
    else:
    self.playerfile = self.filelist[0]
    # Extract player ID from filename
    self.player_id = self.playerfile.stem[-1]
    print('self.player_id', self.player_id)
    break

    # Disable the menu entry for selecting game directory
    self.game_menu.entryconfigure('Select game directory', state=tk.DISABLED)

    # Read configuration files into self.conf variable
    self.conf = Config(self.game_directory)

    # Read player files into self.player variable
    self.player = Player(self.game_directory, self.player_id) # (*)



    def choose_file(self):
    self._selected_indices = self._listbox.curselection()
    self.playerfile = self.filelist[self._selected_indices[0]]

    # Extract player ID from filename
    self.player_id = self.playerfile.stem[-1]
    print('self.player_id in choose_file', self.player_id)
    self.gameselectionframe.destroy()



    def display_file_selection(self, filelist):

    # Destroy the previous frame if it exists
    # try:
    # _gameselectionframe = self.root.nametowidget('gameselectionframe')
    # if _gameselectionframe:
    # _gameselectionframe.destroy()
    # except KeyError:
    # pass

    self.gameselectionframe = ttk.Frame(self.root, padding=(2, 2, 2, 2), name='gameselectionframe')
    self.gameselectionframe.pack()

    display_label = ttk.Label(self.gameselectionframe, text="Several player files were found. Please choose one.", width=60)
    display_label.pack()

    pl = tk.StringVar(value=filelist)
    self._listbox = tk.Listbox(self.gameselectionframe, listvariable=pl, height=6, width=20)
    self._listbox.selection_set(0)
    self._listbox.pack()

    btn = ttk.Button(self.gameselectionframe, text='Select', command=self.choose_file)
    btn.pack()


    def inventory_view(self):
    if self.playerfile:
    self.player = Player(self.game_directory)
    #self.viewer = InventoryView(self.player.planets, self.conf)

    else:
    tk.messagebox.showerror(
    title='Player file error',
    message='No player file selected. Please select a game directory first.')



    if __name__ == "__main__":
    root = tk.Tk()
    game = Game(root)
    root.mainloop()


    The problem I have when there are several player files (like in the example with the 3 player files) is that

    self.player = Player(self.game_directory, self.player_id) # (*)


    is executed before the user has a chance to choose one player file. Then self.player_id is None and the file cannot be properly read. What I need in this case is to prevent the program from continuing until I have set self.player_id properly.

    I have tried making game_directory_selection() use a tk.Toplevel window with grab_set() to "halt" program/loop execution until the user chooses a file, but to no avail.

    I have also tried using quit() at the beginning of display_file_selection() to exit the main loop and then mainloop() at the end of display_file_selection() to resume the main loop.

    I have omitted the implementation of Config and Player classes because they are not relevant. They just read those files onto dictionaries for later use.

    Also, (**) prints None before the player chooses the directory. Which doesn't bother me because I don't want to print anything there, it is just there to illustrate the fact that the mainloop doest't wait for self.create_main_window() to finish before continuing.

    Of course, there may be a better way or best practice for doing this, instead of the way I am tackling it...

    In this question, they use wait_variable() but they say a normal GUI application does not generally work that way. I have also read this question.

    Thanks in advance.

    Continue reading...

Compartilhe esta Página