Window size of tkinter program - python

Actually, I thought that the tkinter window is as large as it needs to be. Unfortunately, it's not true for me (I use Lubuntu 16.04).
My question:
Can please someone explain me the examples below? I don't understand tkinter's behaviour.
Here an example:
import tkinter as tk
root = tk.Tk()
menubar = tk.Menu(root)
file_menu = tk.Menu(menubar)
menubar.add_cascade(label="This is a very long string because it's just a test . . . ", menu=file_menu)
file_menu.add_command(label="New", accelerator="Ctrl+N")
root.config(menu=menubar)
root.mainloop()
Screenshot:
Window is not large enough. Why?
Another example (with container Frame):
import tkinter as tk
root = tk.Tk()
container = tk.Frame(root).pack()
menubar = tk.Menu(container)
file_menu = tk.Menu(menubar)
menubar.add_cascade(label="This is a very long string because it's just a test . . . ", menu=file_menu)
file_menu.add_command(label="New", accelerator="Ctrl+N")
root.config(menu=menubar)
root.mainloop()
Screenshot: very, very small! You can't really see it. But why is so?

The windows size in Tk is a function of the geometry manager for the window. In the case of your toplevel window the geometry manager is 'wm' which means it is managed by the platform window manager. This asks the application window for a size or sets a default. The menu widget does not provide a size so doesn't affect the geometry calculations. So you get a default toplevel size as you have no widgets managed inside.
In the second case you created a frame with no size dimensions which defaults to a height and width of 0. You pack this which sets the toplevel wdget to use the pack geometry manager for its slaves. This asks the slaves (your frame) how much space they require and sets the master (the toplevel) to a suitable size to contain them. Here that is 0. If you set the width and height of the frame the toplevel will expand to contain that.

Related

How do i resize this labelframe in tkinter?

So I tried to make this labelframe wider by using the basic width and width option.
Here's my given minimal code.
from tkinter import *
from tkinter import ttk
app = Tk()
app.resizable(False, False)
mainLayout = ttk.Frame(app, padding=10)
mainLayout.grid()
settings = ttk.Labelframe(mainLayout, text="Settings", padding=10, width=1000)
settings.grid()
ttk.Label(settings, text="Length limit (in seconds)").grid()
ttk.Spinbox(settings, from_=60, to=600, width=4).grid()
app.mainloop()
minimalized preview:
used in application:
i want to get this labelframe little bit bigger and make the inside centered, But i had no knowledge to do so, Any help will apreciated!
It seems like you just want to have a main_frame in the app. For simplicity I've used .pack with the options fill and expand with the constants tkinter.BOTH to stretch the widget in both (x,y) direction and True to consume extra space. (This is one of the reasons why wildcard imports are discouraged, you can be unaware of overwriting something, use import tkinter as tk instead). Same happens with the LabelFrame, you may could delete one of the containers, but that is up to you.
In LabelFrame I have configured the grid and gave the instruction that the column 0 should get the extra space with the priority/weight 1.
In addition, I gave your Spinbox a little bit more width, changed the size of the window and separated the constructor from the geometrymethod.
To get in touch with the geometry management in tkinter, you could play around with the instructions (e.g. comment some out) and see what happens.
from tkinter import *
from tkinter import ttk
app = Tk()
app.geometry('500x500')
app.resizable(False, False)
mainLayout = ttk.Frame(app, padding=10)
mainLayout.pack(fill=BOTH,expand=True)
settings = ttk.Labelframe(mainLayout, text="Settings", padding=10, width=1000)
settings.pack(fill=BOTH,expand=True)
settings.columnconfigure(0,weight=1)
my_label = ttk.Label(settings, text="Length limit (in seconds)")
my_label.grid()
my_spinbox = ttk.Spinbox(settings, from_=60, to=600, width=20)
my_spinbox.grid()
app.mainloop()

When designing a desktop app to be resizable, are there any hidden drawbacks to using two sequential Tk instances?

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.
Here's the code in your answer with a minor change — the finalFrame doesn't get created until the openMainWindow() function is called since it's not needed until then. I think this is a little more logical instead of having creation and usage 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()
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.

tkinter's .pack_propagate() method

I am experimenting with Tkinter, as I was trying to figure out is there a way to set the tkinter's window size without using canvas. I came upon this how to set frame size question on SO's Question & Answer. So I went ahead and test it by writing a very small program to display a text label. But I found out it is "missing", or disappear when I use frame.pack_propagate(0)
import tkinter as tk
root = tk.Tk()
frame = tk.Frame(root, width=400, height=400)
# Does not work at the moment, textBox is missing
# frame.pack_propagate(0)
frame.pack()
textBox = tk.Label(frame, text="(x,y): ")
textBox.pack()
root.mainloop()
So my question is, can you explain why my textBox (Label) is not appearing when I use the frame.pack_propagate(0) instead of frame.pack() method? And secondly, is there a way to set the window size without using a canvas? I want to know because I am writing a series of small programs to teach my friend about tkinter, before introducing canvas to him. It would be nice if the window size are all the same across my tkinter samples. And I am just wondering as well (curious). Thank you very much.
I am using python 3.2.2 on MAC OS 10.5.8.
pack_propagate only sets a flag, it doesn't cause the frame to be placed in the widget. It is not a substitute for calling pack.
In other words you must do this:
# put the frame in its parent
frame.pack()
# tell frame not to let its children control its size
frame.pack_propagate(0)
# put the textbox in the frame
textBox.pack()
To answer your second question: Yeah, there is a way.
tkinters Tk do have the Tk.geometry function. When you just call it without arguments, you will get the current geometry in form of 'widthxheight+x+y', so for example (on Windows 10) '200x200+26+26' when you create your first Tk window. Using that format you can resize the Tk by, e.g., writing: root.geometry('400x500+60+60') to set the width to 400, the height to 500 and place it at the coordinates (60|60).
This works for Tk alswell as for Toplevel. But Toplevel also takes the arguments height and width when initialized or configured. If you want them to keep their size when packing something inside just use root.pack_propagate(False) on them.
By the way there is something similar for the grid manager: root.grid_propagate(False)

Frame resizing issue in Tk/ttk python

I'm creating a GUI using Tkinter/ttk in Python 2.7, and I'm having an issue where a Frame will resize itself once a widget is placed inside of it. I am new to Python and haven't used Tkinter before.
example:
ROOT = Tk()
FRAME = ttk.Frame(ROOT, width=300, height=300, relief='groove')
FRAME.grid()
ROOT.mainloop()
will produce a grooved frame 300x300, if i place a widget inside it like so:
ROOT = Tk()
FRAME = ttk.Frame(ROOT, width=300, height=300, relief='groove')
BUTTON = ttk.Button(FRAME, text="DON'T READ THIS TEXT")
FRAME.grid()
BUTTON.grid()
ROOT.mainloop()
the frame will shrink down to fit the button. Any way to force the frame not to resize?
To force the frame to keep its original dimensions turn "geometry propagation" off. In your case you would call FRAME.grid_propagate(False).
Speaking as someone with over 15 years of Tk experience, might I suggest that you almost certainly don't need this feature. Tk's ability to "shrink to fit" is really great, and makes it really easy to create GUIs with proper resize behavior. Once you start turning geometry propagation off you'll find you'll either have GUIs with bad resize behavior, or you'll spend a lot of time tweaking sizes and widget placement.
if you want to add padding, then use widg.grid(ipadx=..., ipady=..., padx=..., pady=...)
otherwise, you will need to add more context on what layout you're trying to achieve
This works for me:
app = Application()
app.master.title('Example')
app.master.geometry('640x480')
app.mainloop()
Apllication is Frame in my case with grid layout. It resizes to size of master window, so we need to change size of master window.
This should do the job: ROOT.geometry("640x480")

Why does Tkinter frame resize when text box is added to it?

With this code, the window is 500 by 500, which is what I'm going for:
from tkinter import *
root = Tk()
frame = Frame(root, width=500, height=500)
frame.pack()
root.mainloop()
When I add a text box to the frame, though, it shrinks to just the size of the text box:
from tkinter import *
root = Tk()
frame = Frame(root, width=500, height=500)
text = Text(frame, width=10, height=2) # THESE TWO
text.pack() # LINES HERE
frame.pack()
root.mainloop()
Why does this happen, and how can I prevent it from happening?
The frame by default has "pack propagation" turned on. That means the packer "computes how large a master must be to just exactly meet the needs of its slaves, and it sets the requested width and height of the master to these dimensions" (quoting from the official tcl/tk man pages [1]).
For the vast majority of cases this is the exact right behavior. For the times that you don't want this you can call pack_propagate on the master and set the value to false. I think in close to 20 years of tk programing I've only needed to do this a half dozen times or so, if that.
Another choice you have is to use wm_geometry to set the size of the toplevel after you've created all the widgets. This does effectively the same thing as if the user had manually resized the window. This only works for toplevel windows though, you can't use wm_geometry on a frame.

Categories

Resources