'Similar Window Setup in TKInter

I'm brand new to Tkinter and was trying to create a Speech Therapy type of application. Inside of this, I open three different windows with similar layouts. To start with I just duplicated my code to get it working however I now would like to clean it up, but I'm not sure how to make one "create_window" function or class that will allow me to place a new button or two as that is the only difference between the windows. Any help or suggestions are greatly appreciated!

Below are pictures of my program running to show how the windows are similar. (Sorry I can't post the pictures here because I don't have enough reputation)

Game Mode 1 Game Mode 2

I wasn't sure how to make a Minimal version of this since I am trying to reduce the redundancy of my code but below is my code. (Below that I put my current attempt at making this into a class so that I could just call "open_new_window" but that wasn't allowing me to create new buttons for one specific instance of opening a new window).

import tkinter
from queue import LifoQueue

from playsound import playsound
from tkinter import *

import google_api
import record_audio
import tkinter_window

import word_image_dictionary

def open_homepage_window(prev_win=None):
    if prev_win is not None:
        prev_win.destroy()
    window = Tk()
    window.title('Speaking with Peter Rabbit')
    window.geometry("800x500")
    img = PhotoImage(file="Photos/Homepage Image.png")
    label = Label(
        window,
        image=img,
        height=window.winfo_screenheight(),
        width=window.winfo_screenwidth()
    )
    label.place(x=0, y=0, relwidth=1, relheight=1)

    # Making my different activity buttons
    associations_btn = Button(window, text="Basic Associations", command=lambda: open_basic_association(window))
    type_say_see_btn = Button(window, text="Type, Say, See", command=lambda: open_type_say_see(window))

    # Putting them on the screen and configuring the Grid
    associations_btn.grid(row=0, rowspan=2, column=0, columnspan=2)
    type_say_see_btn.grid(row=1, rowspan=2, column=0, columnspan=2)

    # Organizing the grid for my button layout
    for row_number in range(3):
        Grid.rowconfigure(window, row_number, weight=1)
    # for column_number in range(4):
    #     Grid.columnconfigure(window, column_number, weight=1)

    window.mainloop()


def open_basic_association(prev_win):
    # Grid size variables
    # COLUMNS = 8
    # ROWS = 8
    # Tk variables
    tk = tkinter_window.TkInterWindow()
    tk.open_new_window(prev_win, "Basic Associations")

    main = tk.window
    # Loading right answer and wrong answer pictures
    red_x = PhotoImage(file="Photos/Red X.png").subsample(9, 9)
    green_check = PhotoImage(file="Photos/Green Check.png").subsample(9, 9)

    # Creating something to keep track of what the current answer is and load photos
    photos_and_answers = word_image_dictionary.Words()
    photos_and_answers.initializeStacks()

    # Making my grid to place items
    for row_number in range(ROWS):
        Grid.rowconfigure(main, row_number, weight=2)
    for column_number in range(COLUMNS):
        Grid.columnconfigure(main, column_number, weight=2)

    # Setting up the first image/answer for the user
    img = photos_and_answers.imgStack.pop()
    photos_and_answers.updateCurAnswer()
    main_img = Label(main, image=img)
    main_img.grid(row=1, rowspan=2, column=3, columnspan=3)

    # Setting up the record button for the user
    record_audio_btn = Button(main, text="Record Answer", command=lambda: record_audio.RecAUD())
    record_audio_btn.grid(row=4, column=4)
    # Seeing the recording text
    recording_text = Label(main, text="Your answer appears here", compound=LEFT)
    recording_text.grid(row=6, column=4, columnspan=2)
    check_recording_btn = Button(main, text="Check Recording",
                                 command=lambda: check_recording(recording_text, photos_and_answers.curAnswer,
                                                                 green_check, red_x))
    check_recording_btn.grid(row=4, column=5)
    # Seeing the correct answer
    see_answer_btn = Button(main, text="See Answer",
                            command=lambda: see_answer(recording_text, photos_and_answers.curAnswer))
    see_answer_btn.grid(row=5, column=4, columnspan=2)

    # Placing my back and next arrows
    back_arw_img = tkinter.PhotoImage(master=main, file=r"Photos/Back Arrow.png").subsample(7, 7)
    back_btn = Button(main, text="Back", command=lambda: open_homepage_window(main), image=back_arw_img)
    back_btn.grid(row=0, column=0)
    next_arw_img = tkinter.PhotoImage(master=main, file=r"Photos/Next Arrow.png").subsample(9, 9)
    next_arw_btn = Button(main, text="Next",
                          command=lambda: update_image(main_img, photos_and_answers.imgStack, photos_and_answers,
                                                       recording_text),
                          image=next_arw_img)
    next_arw_btn.grid(row=ROWS, column=COLUMNS)

    # Assigning back/next button rows and columns to be smaller than the rest
    Grid.rowconfigure(main, 0, weight=1)
    Grid.columnconfigure(main, 0, weight=1)
    Grid.rowconfigure(main, ROWS, weight=1)
    Grid.columnconfigure(main, COLUMNS, weight=1)

    main.mainloop()


def open_type_say_see(prev_win):
    # Grid size variables
    COLUMNS = 8
    ROWS = 8
    # Tk Variables
    tk = tkinter_window.TkInterWindow()
    tk.open_new_window(prev_win, "Type Say See Hear")
    main = tk.window
    # Setting up answer images
    red_x = PhotoImage(file="Photos/Red X.png").subsample(9, 9)
    green_check = PhotoImage(file="Photos/Green Check.png").subsample(9, 9)

    # Creating something to keep track of what the current answer is and load photos
    photos_and_answers = word_image_dictionary.Words()
    photos_and_answers.initializeStacks()

    # Making my grid to place items
    for row_number in range(ROWS):
        Grid.rowconfigure(main, row_number, weight=2)
    for column_number in range(COLUMNS):
        Grid.columnconfigure(main, column_number, weight=2)

    # Setting up the first image/answer for the user
    img = photos_and_answers.imgStack.pop()
    photos_and_answers.updateCurAnswer()
    main_img = Label(main, image=img)
    main_img.grid(row=1, rowspan=2, column=3, columnspan=3)

    # TODO
    # Set up the input typing
    entry = Entry(main, width=50)
    entry.focus_set()
    entry.grid(row=3, column=3, columnspan=4)
    check_entry_btn = Button(main, text="Check Answer",
                             command=lambda: check_text_entry(entry, photos_and_answers.curAnswer, recording_text,
                                                              green_check, red_x, record_audio_btn,
                                                              check_recording_btn))
    check_entry_btn.grid(row=5, column=3, columnspan=2)

    # TODO figure out how to reconfigure button commands and maybe make most of this into a class?
    # Setting up the record button for the user
    record_audio_btn = Button(main, text="Record Answer", command=lambda: record_audio.RecAUD())
    record_audio_btn.grid(row=4, column=4)
    record_audio_btn.grid_remove()
    # Seeing the recording text
    recording_text = Label(main, text="Your answer appears here", compound=LEFT)
    recording_text.grid(row=6, column=4, columnspan=2)
    check_recording_btn = Button(main, text="Check Recording",
                                 command=lambda: check_recording(recording_text, photos_and_answers.curAnswer,
                                                                 green_check, red_x))
    check_recording_btn.grid(row=4, column=5)
    check_recording_btn.grid_remove()

    # Seeing the correct answer
    see_answer_btn = Button(main, text="See Answer",
                            command=lambda: see_answer(recording_text, photos_and_answers.curAnswer))
    see_answer_btn.grid(row=5, column=5, columnspan=2)

    # Placing my back and next arrows
    back_arw_img = tkinter.PhotoImage(master=main, file=r"Photos/Back Arrow.png").subsample(7, 7)
    back_btn = Button(main, text="Back", command=lambda: open_homepage_window(main), image=back_arw_img)
    back_btn.grid(row=0, column=0)
    next_arw_img = tkinter.PhotoImage(master=main, file=r"Photos/Next Arrow.png").subsample(9, 9)
    next_arw_btn = Button(main, text="Next",
                          command=lambda: update_image(main_img, photos_and_answers.imgStack, photos_and_answers,
                                                       recording_text),
                          image=next_arw_img)
    next_arw_btn.grid(row=ROWS, column=COLUMNS)

    # Assigning back/next button rows and columns to be smaller than the rest
    Grid.rowconfigure(main, 0, weight=1)
    Grid.columnconfigure(main, 0, weight=1)
    Grid.rowconfigure(main, ROWS, weight=1)
    Grid.columnconfigure(main, COLUMNS, weight=1)

    main.mainloop()


def see_answer(text_box, cur_answer):
    text_box['text'] = cur_answer
    text_box['image'] = ""
    text_box.grid()


def check_text_entry(user_input, cur_answer, answer_text, check, red_x, record_btn, check_recording_btn):
    user_text = user_input.get()
    if user_text == cur_answer:
        answer_text['text'] = "Correct!"
        answer_text['image'] = check
        record_btn.grid()
        check_recording_btn.grid()
        check_recording_btn.configure(command=lambda: see_answer(answer_text, cur_answer))
    else:
        answer_text['text'] = "Incorrect"
        answer_text['image'] = red_x


def check_recording(recording_text, cur_answer, check, red_x):
    if cur_answer != "Done":
        text = str(google_api.GoogleAPI().get_transcript())
        recording_text['text'] = "We heard the following possibilities:\n" + text
        recording_text.grid()
        print("cur_answer: " + cur_answer)
        if text.casefold().__contains__(cur_answer.casefold()):
            recording_text['image'] = check
            print("found")
        else:
            recording_text['image'] = red_x
            print("not found")
    else:
        recording_text['text'] = "You went through all the words!"
        recording_text.grid()


def update_image(label, img_stack, photos_and_answers, recording_text):
    # Changing image if there is another image waiting and update answer
    if img_stack:
        img = img_stack.pop()
        label.configure(image=img)
        label.image = img
        photos_and_answers.updateCurAnswer()
        recording_text.grid_remove()


if __name__ == '__main__':
    open_homepage_window()
from tkinter import *
import tkinter

import main
import word_image_dictionary


class TkInterWindow():
    def __init__(self):
        self.ROWS = 8
        self.COLUMNS = 8

        self.window = None
        self.feedback_text = None

        self.back_btn = None
        self.next_arw_btn = None

        self.back_arw_img = None
        self.next_arw_img = None
        self.main_img = None

        self.photos_and_answers = None

    def open_new_window(self, prev_win, title):
        prev_win.destroy()
        self.window = Tk()
        self.window.title(title)
        self.window.geometry("800x500")

        # Creating something to keep track of what the current answer is and load photos
        self.photos_and_answers = word_image_dictionary.Words()
        self.photos_and_answers.initializeStacks()

        # Making my grid to place items
        for row_number in range(self.ROWS):
            Grid.rowconfigure(self.window, row_number, weight=2)
        for column_number in range(self.COLUMNS):
            Grid.columnconfigure(self.window, column_number, weight=2)

        # Setting up the main image
        img = self.photos_and_answers.imgStack.pop()
        self.photos_and_answers.updateCurAnswer()
        self.main_img = Label(self.window)
        self.main_img.configure(image=img)
        self.main_img.image = img
        self.main_img.grid(row=1, rowspan=2, column=3, columnspan=3)

        # Placing the feedback text box
        self.feedback_text = Label(self.window, text="Your answer appears here", compound=LEFT)
        self.feedback_text.grid(row=6, column=4, columnspan=2)

        # Placing my back and next arrows
        self.back_arw_img = PhotoImage(master=self.window, file=r"Photos/Back Arrow.png").subsample(7, 7)
        self.back_btn = Button(self.window, text="Back", command=lambda: main.open_homepage_window(self.window),
                               image=self.back_arw_img)
        self.back_btn.grid(row=0, column=0)
        self.next_arw_img = PhotoImage(master=self.window, file=r"Photos/Next Arrow.png").subsample(9, 9)
        self.next_arw_btn = Button(self.window, text="Next",
                                   command=lambda: self.update_image(),
                                   image=self.next_arw_img)
        self.next_arw_btn.grid(row=self.ROWS, column=self.COLUMNS)

        self.window.mainloop()

    def update_image(self):
        # Changing image if there is another image waiting and update answer
        if self.photos_and_answers.imgStack:
            img = self.photos_and_answers.imgStack.pop()
            self.main_img.configure(image=img)
            self.main_img.image = img
            self.photos_and_answers.updateCurAnswer()
            self.feedback_text.grid_remove()

    def close_window(self):
        self.window.destroy()



Solution 1:[1]

I realized that what I was doing was calling the mainloop() function in the class. If however, I call this when creating my new window then I can add buttons and labels before calling the mainloop(). Like in the following manner:

    tk = tkinter_window.TkInterWindow()
    tk.open_new_window(prev_win, "Basic Associations")

    record_btn = Button(tk.window, text="test", command=lambda: tk.update_image())
    record_btn.grid(row=2, column=2)
    tk.window.mainloop()

Below is the modified function from the class code in the question. The only difference is the commenting out of self.window.mainloop() at the end.

def open_new_window(self, prev_win, title):
        prev_win.destroy()
        self.window = Tk()
        self.window.title(title)
        self.window.geometry("800x500")

        # Creating something to keep track of what the current answer is and load photos
        self.photos_and_answers = word_image_dictionary.Words()
        self.photos_and_answers.initializeStacks()

        # Making my grid to place items
        for row_number in range(self.ROWS):
            Grid.rowconfigure(self.window, row_number, weight=2)
        for column_number in range(self.COLUMNS):
            Grid.columnconfigure(self.window, column_number, weight=2)

        # Setting up the main image
        img = self.photos_and_answers.imgStack.pop()
        self.photos_and_answers.updateCurAnswer()
        self.main_img = Label(self.window)
        self.main_img.configure(image=img)
        self.main_img.image = img
        self.main_img.grid(row=1, rowspan=2, column=3, columnspan=3)

        # Placing the feedback text box
        self.feedback_text = Label(self.window, text="Your answer appears here", compound=LEFT)
        self.feedback_text.grid(row=6, column=4, columnspan=2)

        # Placing my back and next arrows
        self.back_arw_img = PhotoImage(master=self.window, file=r"Photos/Back Arrow.png").subsample(7, 7)
        self.back_btn = Button(self.window, text="Back", command=lambda: main.open_homepage_window(self.window),
                               image=self.back_arw_img)
        self.back_btn.grid(row=0, column=0)
        self.next_arw_img = PhotoImage(master=self.window, file=r"Photos/Next Arrow.png").subsample(9, 9)
        self.next_arw_btn = Button(self.window, text="Next",
                                   command=lambda: self.update_image(),
                                   image=self.next_arw_img)
        self.next_arw_btn.grid(row=self.ROWS, column=self.COLUMNS)

        # self.window.mainloop()

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Peter Walkwitz