'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)
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 |
