In order to ensure that my program will be compatible with any screen size, I first have a very small 'setup' window open when the program is launched, which will then allow the user to select the desired dimensions for the main window.
Once the 'finalize' button on the 'setup' window is pressed, the setup window disappears and the main window opens. This is handled by calling .destroy()
on the setup window and creating a new instance with Tk()
inside the button's command
function.
To make the example code more compact and highlight the part which is relevant to this question, I left out the size selector and just set the main window to be a fixed 800x800 pixels here:
import tkinter as tk
# Start out small, to fit on any screen size
startWindow = tk.Tk()
startWindow['width'] = 400
startWindow['height'] = 200
startWindow.title("Setup")
# Use lists here so that the widgets created inside 'initializeMainWindow' will be
# accessible from the global scope.
mainWindow = [None]
mainWindowButtons = [None]
# Closes the 'setup' window and opens a new window which will be the main application.
# Also initializes all widgets which will belong to the new window.
def initializeMainWindow():
startWindow.destroy()
mainWindow[0] = tk.Tk()
print("New window initialized.")
mainWindow[0]['width'] = 800
mainWindow[0]['height'] = 800
mainWindow[0].title("Main Window")
mainWindowButtons[0] = tk.Button(master=mainWindow[0], text="Test", command=testNewWindow)
mainWindowButtons[0].place(x=350, y=375, width=100, height=50)
mainWindow[0].bind('<Key>', test2)
#mainWindow[0].mainloop()
# To demonstrate that the new window is interactive
def testNewWindow():
print("Success!")
# Works whether or not 'mainloop' is called on the new window
def test2(e):
print("Also success! '" e.keysym "' key pressed.")
setSizeButton = tk.Button(master=startWindow, text="Resize", command=initializeMainWindow)
setSizeButton.place(x=150, y=75, width=100, height=50)
# This prints BEFORE the 'setup' window is closed, as expected
# If 'mainloop' is uncommented, it works the same except IDLE won't show the '>>>' prompt
# after the text "Not yet initialized".
if startWindow:
print("Not yet initialized.")
#startWindow.mainloop()
This works exactly as I intend it to so far. However I'm aware that when an application has more than one window, it's standard to use Toplevel()
and not create multiple instances of Tk()
. But this example isn't trying to run multiple instances of Tk()
at once, instead, they're sequential: think of it as a separate 'launcher' program that then opens the main program, as is common on many desktop games. (This is exactly how I'm using it in the full program).
Before building on a potentially flawed foundation, I'd like to know if there are any hidden problems which could surface later with this approach. If the consensus is that it's better to switch to using Toplevel()
or even have two separate Python files, I'd rather find out sooner than later!
I have already viewed this question and answer: What's the difference between tkinter's Tk and Toplevel classes? but they don't cover this specific question.
A related issue: I also experimented with calling .mainloop()
vs. not calling it, and at least with the Mac version of IDLE and using Python 3.9.4, it seems to be optional. This was discussed here When do I need to call mainloop in a Tkinter application?
and it looks like the reason omitting it still works is that IDLE has its own event loop (credit to Ori for this solution https://stackoverflow.com/a/8684277/18248018).
If this is the case, is it advisable to explicitly call .mainloop()
anyway (where I have it commented out in the example code) for reliability? I haven't tested this yet, but if it's something IDLE does, I'd guess the automatic event loop functionality might not transfer over when I convert the program to a standalone app using py2app
, without explicit calls to .mainloop()
in the .py file.
CodePudding user response:
Following martineau's suggestion in the comments of switching between two different Frame
widgets, I rewrote the code in my question as the following. This achieves the same visual effect as the original code, and eliminates any need for a second Tk()
instance.
This is a minimal example of the method I will be using in my resizable application:
import tkinter as tk
window = tk.Tk()
# Start out with a small popup, so it will fit on any screen size.
# Width and height must be specified as attributes of the frame, so that the
# frame can set the window's size.
setupFrame = tk.Frame(master=window, bg='green', width=400, height=200)
# Using 'pack' will cause the window's size to be equal to setupFrame's size
# Using 'place' here would not work: the window would open with the default size
# (small and square) and cut off the frame.
setupFrame.pack()
# This frame will reset the size of the window and will display the program's
# main content
finalFrame = tk.Frame(master=window, bg='lightblue')
sizeScalingExample = tk.Label(master=finalFrame, bg='purple', fg='white',
text="This label's size is set with `place` and "
"depends on the frame's size.")
# In the actual program, these values will be determined by user input
W = 800
H = 800
def openMainWindow(w, h):
# Hide the 'launcher' once it is no longer needed
setupFrame.pack_forget()
finalFrame['width'] = w
finalFrame['height'] = h
finalFrame.pack()
sizeScalingExample.place(x = 0.125*w, y = 0.375*h, width=0.75*w, height=0.25*h)
resizeButton = tk.Button(master=setupFrame, text="Finalize", bg="yellow",
fg="darkblue", command=lambda: openMainWindow(W, H))
resizeButton.place(x=150, y=75, width=100, height=50)
window.mainloop()
Although the ability to implement the same functionality using only Frame
widgets means there is no practical reason to use a second Tk()
instance, I'd still be interested from a theoretical perspective to learn about any unexpected outcomes which could result from using the original approach.
CodePudding user response:
Here's the code in your answer with a minor change — the finalFrame
doesn't get created until the openMainWindow()
function is call since it's not needed until then. I think this is a little more logical instead of having things scattered about.
import tkinter as tk
window = tk.Tk()
# Start out with a small popup, so it will fit on any screen size.
# Width and height must be specified as attributes of the frame, so that the
# frame can set the window's size.
setupFrame = tk.Frame(master=window, bg='green', width=400, height=200)
# Using 'pack' will cause the window's size to be equal to setupFrame's size
# Using 'place' here would not work: the window would open with the default size
# (small and square) and cut off the frame.
setupFrame.pack()
# In the actual program, these values will be determined by user input
W = 800
H = 800
def openMainWindow(w, h):
# finalFrame # Uncomment if ever needed.
# Destroy the 'launcher' frame once it is no longer needed.
setupFrame.destroy()
# This frame will reset the size of the window and will display the
# program's main content.
finalFrame = tk.Frame(master=window, bg='lightblue', width=w, height=h)
sizeScalingExample = tk.Label(master=finalFrame, bg='purple', fg='white',
text="This label's size is set with `place` and "
"depends on the frame's size.")
finalFrame.pack()
sizeScalingExample.place(x = 0.125*w, y = 0.375*h, width=0.75*w, height=0.25*h)
resizeButton = tk.Button(master=setupFrame, text="Finalize", bg="yellow",
fg="darkblue", command=lambda: openMainWindow(W, H))
resizeButton.place(x=150, y=75, width=100, height=50)
window.mainloop()