Search code examples
pythonuser-interfacetkinter

Tkinter GUI window items are really messy


So I created a garbled mess of code for a GUI that displays items (tarot cards) in a window in rows and columns. However, after creating more than four items, the items shift to the right leaving a large black empty space, beyond that I want the buttons for draw card and quit to be on the sides of the title, and make the entire thing generally just look a bit cleaner. If anyone could shed some light or provide suggestions I would really appreciate it. Thank you.

Window resizing issue Window resizing issue

Ideal layout Ideal layout

Also please note, I am brand new to Tkinter, I apologize in advance

def draw_one_card_command():
    card = draw_card()
    if card:
        card_name = f"Card: {card}"
        card_meaning = f"Meaning: {CARD_MEANINGS.get(card.name, 'Meaning not available')}"
        try:
            img = Image.open(CARD_IMAGES[card.name])
            if card.reversed:
                img = img.rotate(180)
            img = img.resize((100, 175))
            img = ImageTk.PhotoImage(img)
            card_label = ttk.Label(cards_frame.interior, text=card_name + "\n" + card_meaning, image=img, compound="top")
            card_label.image = img
            cards_frame.cards.append(card_label)
            row = len(cards_frame.cards) // 5
            column = len(cards_frame.cards) % 5
            card_label.grid(row=row, column=column, padx=5, pady=5, sticky="nsew")
        except FileNotFoundError:
            card_label = ttk.Label(cards_frame.interior, text=card_name + "\n" + card_meaning)
            card_label.grid(sticky="w", padx=5, pady=5)

def on_configure(event):
    cards_frame.canvas.configure(scrollregion=cards_frame.canvas.bbox("all"))

def on_title_configure(event):
    title_label.place(relx=0.5, rely=0.5, anchor=CENTER)

root = Tk()
root.title("PyTarot")
root.geometry("800x600")

# Define custom styles
root.tk_setPalette(background="black", foreground="purple")
root.style = ttk.Style(root)
root.style.configure("TFrame", background="black")  # Set frame background color to black
root.style.configure("TLabel", foreground="purple")  # Set label text color to purple
root.style.configure("TButton", foreground="purple")  # Set button text color to purple

frm = ttk.Frame(root, padding=10)
frm.grid(sticky="ew")

title_label = ttk.Label(frm, text="~ ~ ~ PyTarot ~ ~ ~", font=("Helvetica", 16, "bold"))
title_label.grid(column=0, row=0, columnspan=2, pady=10, sticky="ew")

draw_button = ttk.Button(frm, text="Draw a Card", command=draw_one_card_command)
draw_button.grid(column=0, row=1, padx=10, pady=10, sticky="ew")

quit_button = ttk.Button(frm, text="Quit", command=root.destroy)
quit_button.grid(column=1, row=1, padx=10, pady=10, sticky="ew")

cards_frame = Frame(root, bg="black")
cards_frame.grid(row=1, column=0, padx=10, pady=5, sticky="nsew")

cards_frame.grid_rowconfigure(0, weight=1)
cards_frame.grid_columnconfigure(0, weight=1)

cards_frame.canvas = Canvas(cards_frame, bg="black", highlightthickness=0)
cards_frame.canvas.grid(row=0, column=0, sticky="nsew")

cards_frame.scrollbar = ttk.Scrollbar(cards_frame, orient="vertical", command=cards_frame.canvas.yview)
cards_frame.scrollbar.grid(row=0, column=1, sticky="ns")
cards_frame.canvas.configure(yscrollcommand=cards_frame.scrollbar.set)

cards_frame.interior = Frame(cards_frame, bg="black")
cards_frame.canvas.create_window((0, 0), window=cards_frame.interior, anchor="nw")

cards_frame.interior.bind("<Configure>", on_configure)

cards_frame.cards = []

root.grid_rowconfigure(1, weight=1)
root.grid_columnconfigure(0, weight=1)

root.bind("<Configure>", on_title_configure)

root.mainloop()

Tried resizing with root.geometry("800x600")

Tried altering row = len(cards_frame.cards) // 5 and column = len(cards_frame.cards) % 5

But the blank space persists regardless

Tried editing different variables within:

draw_button = ttk.Button(frm, text="Draw a Card", command=draw_one_card_command) draw_button.grid(column=0, row=1, padx=10, pady=10, sticky="ew")

and

quit_button = ttk.Button(frm, text="Quit", command=root.destroy) quit_button.grid(column=1, row=1, padx=10, pady=10, sticky="ew")

not much changed, and what did change was not what I intended


Solution

  • Suggest to use Text widget for showing those tarot cards, then you don't need to calculate the row and column values because of wrapping feature of Text widget.

    For the buttons and label alignment issue in the top frame, you can use .pack() on the two frames frm and cards_frame and don't need to bind <Configure> event on the root window to move the title_label.

    Below is the modified code:

    def draw_one_card_command():
        card = draw_card()
        if card:
            card_name = f"Card: {card}"
            card_meaning = f"Meaning: {CARD_MEANINGS.get(card.name, 'Meaning not available')}"
            try:
                img = Image.open(CARD_IMAGES[card.name])
                if card.reversed:
                    img = img.rotate(180)
                img = img.resize((100, 175))
                img = ImageTk.PhotoImage(img)
                card_label = ttk.Label(cards_frame.container, text=card_name + "\n" + card_meaning, image=img, compound="top")
                card_label.image = img
            except FileNotFoundError:
                card_label = ttk.Label(cards_frame.container, text=card_name + "\n" + card_meaning)
    
            ### insert the card into the text widget
            cards_frame.container.window_create("end", window=card_label, padx=5, pady=5)
            cards_frame.cards.append(card_label)
    
    root = Tk()
    root.title("PyTarot")
    root.geometry("800x600")
    
    # Define custom styles
    root.tk_setPalette(background="black", foreground="purple")
    root.style = ttk.Style(root)
    root.style.configure("TFrame", background="black")  # Set frame background color to black
    root.style.configure("TLabel", foreground="purple")  # Set label text color to purple
    root.style.configure("TButton", foreground="purple")  # Set button text color to purple
    
    frm = ttk.Frame(root, padding=10)
    frm.pack()  ### use pack() to pack the frame at the top side
    
    draw_button = ttk.Button(frm, text="Draw a Card", command=draw_one_card_command)
    draw_button.grid(column=0, row=0, padx=10, pady=10, sticky="ew")
    
    title_label = ttk.Label(frm, text="~ ~ ~ PyTarot ~ ~ ~", font=("Helvetica", 16, "bold"))
    title_label.grid(column=1, row=0, pady=10, sticky="ew")
    
    quit_button = ttk.Button(frm, text="Quit", command=root.destroy)
    quit_button.grid(column=2, row=0, padx=10, pady=10, sticky="ew")
    
    cards_frame = Frame(root, bg="black")
    cards_frame.pack(fill="both", expand=1)  ### used pack()
    
    ### use Text widget for showing the cards
    cards_frame.container = Text(cards_frame, width=1, height=1, bd=0)
    cards_frame.container.pack(side="left", fill="both", expand=1)
    
    cards_frame.scrollbar = ttk.Scrollbar(cards_frame, orient="vertical", command=cards_frame.container.yview)
    cards_frame.scrollbar.pack(side="right", fill="y")
    
    cards_frame.cards = []
    
    root.mainloop()