This is my program and i packed my widgets so it would be left but somehow when i run it the widgets are not appearing from the left. The person and colour are appearing in the middle but the scary person and creature is appearing on the left. I want person and colour widget to to appear on the left too.
Here is my program
from Tkinter import *
import Tkinter
import tkMessageBox
class StoryMaker:
def __init__(self):
# Create the main window.
self.main_window = Tkinter.Tk()
#Create nine frames to group widgets.
self.first_frame = Tkinter.Frame()
self.second_frame = Tkinter.Frame()
self.third_frame = Tkinter.Frame()
self.fourth_frame = Tkinter.Frame()
self.fifth_frame = Tkinter.Frame()
# Create the widgets for the first frame.
self.prompt_label = Tkinter.Label(self.first_frame, text='Please enter information for a new story, then click the "Make Story" button.')
# Create the widgets for the second frame.
self.person_label = Tkinter.Label(self.second_frame, text='Person: ')
self.person_entry = Tkinter.Entry(self.second_frame, width= 15)
# Pack the second frame's widgets.
self.person_label.pack(side='left')
self.person_entry.pack(side='left')
# Create the widgets for the third frame.
self.colour_label = Tkinter.Label(self.third_frame, text='Colour: ')
self.colour_entry = Tkinter.Entry(self.third_frame, width= 15)
# Pack the third frame's widgets.
self.colour_label.pack(side='left')
self.colour_entry.pack(side='left')
# Create the widgets for the fourth frame.
self.scary_label = Tkinter.Label(self.fourth_frame, text='Scary person or creature: ', justify=LEFT)
self.scary_entry = Tkinter.Entry(self.fourth_frame, width= 15)
# Pack the fourth frame's widgets.
self.scary_label.pack(side='left')
self.scary_entry.pack(side='left')
# Pack the frames.
self.first_frame.pack()
self.second_frame.pack()
self.third_frame.pack()
self.fourth_frame.pack()
self.fifth_frame.pack()
# Enter the Tkinter main loop.
Tkinter.mainloop()
my_gui = StoryMaker()
The first thing I suggest is that you give each of the frames a distinct background color. This is just temporary, so you can see where each frame begins and ends. I think you'll find the results surprising:
self.first_frame = Tkinter.Frame(background="red")
self.second_frame = Tkinter.Frame(background="pink")
self.third_frame = Tkinter.Frame(background="green")
self.fourth_frame = Tkinter.Frame(background="blue")
self.fifth_frame = Tkinter.Frame(background="yellow")
When you do that you'll quickly see that the problem isn't that the labels are centered, but that the frame they are in is in the center. This is because you packed them with no options, which is the same as saying ...pack(side="top", fill=None, expand=False).
If you add fill="x") when packing first_frame, second_frame, etc, you'll see that your labels and entry widgets are indeed on the left side of their container.
My advice to people who are trying to learn tkinter is to take the "divide and conquer" approach. If you are using intermediate frames to organize your widgets, start by creating only those frames. Get the pack or grid options set so that these containing widgets are where you want them, and resize the way you want them to. Only after doing that should you add the interior widgets. That way, at any one time you're only trying to solve the layout problems of one group of widgets at a time.
If you're laying out a form, you might find it easier to use grid rather than pack. Typically in a form all of the labels in a group of labels will be the same size and aligned with each other. That's much easier to do by using grid and placing all of the widgets in a single frame, than it is to use multiple frames.
Related
I'm trying to resize a frame in tkinter, but the width does not change and function winfo_width() returns 1. How can i fix it?
from tkinter import *
root = Tk()
root.geometry('400x300')
Frame = LabelFrame(root, text="Test", width = 200)
Frame.grid(row = 0, column = 0)
label = Label(Frame, text = '').grid(row = 0, column=0)
print(Frame.winfo_width()) #output is 1 instead of 200
root.mainloop()
The width is returning 1 because the window hasn't been drawn yet. The actual width depends on the window being drawn since the actual width depends on many factors which can't be known before the window is actually drawn.
If you call root.update() before calling Frame.winfo_width() to force the window to be drawn, you will see it displaying the actual value.
As for how to change the width, that question is too broad to answer. Normally it's not wise to directly set the width of a frame. Tkinter by default will automaticaly resize a frame to fit its children. So, one way to make the frame wider is to add more widgets.
The width can also depend on how it is added to the display - whether you're using pack or grid or place, and how you have configured them. So, another way to make the frame wider is to use non-default options that cause the frame to grow or shrink to fit the space given to it.
If you want to specify an explicit size and ignore tkinter's automatic resizing, you can do that by turning off geometry propagation and then setting the width and height parameters for the frame. Depending on whether you're using grid or pack, you can call grid_propagate or pack_propagate with a False argument to disable propagation (place doesn't support geometry propagation).
Note that turning off geometry propagation is usually the least desirable solution because it requires you to do a lot more work to create a responsive UI. The best way to design GUI with tkinter is to focus on the size of the inner widgets and let tkinter compute the most efficient size for frames and the window itself.
As the others have pointed out how to set a static size frame using grid_propagate() I will show you how to set up your frame to resize automatically.
You need to tell the row and column to expand that the frame is in. This is done with columnconfigure() and rowconfigure(). Then you need to tell the frame to stick to all sides with sticky='nsew'. Adding widgets to the frame is no different then any other container. Simply tell the widget to be in the frame.
One potention issue I see is you are overwriting Frame() on this line: Frame = LabelFrame(root, text="Test", width = 200). This is a good example why you should not use import *. Instead do import tkinter as tk and use the tk. prefix for anything that needs it.
Example:
import tkinter as tk
root = tk.Tk()
root.geometry('400x300')
root.columnconfigure(0, weight=1)
root.rowconfigure(0, weight=1)
frame = tk.LabelFrame(root, text='Test', width=200)
frame.grid(row=0, column=0, sticky='nsew')
label = tk.Label(frame, text='label').grid(row=0, column=0)
root.mainloop()
Results:
Update:
If you do want something static make sure you define both height and width. If you only define one or the other then you will not see the frame in the window.
For a testable example for a static frame size:
import tkinter as tk
root = tk.Tk()
root.geometry('400x300')
root.rowconfigure(0, weight=1)
root.columnconfigure(0, weight=1)
frame = tk.LabelFrame(root, text='Test', height=200, width=200)
frame.grid(row=0, column=0)
frame.grid_propagate(False)
label = tk.Label(frame, text='label').grid(row=0, column=0)
root.mainloop()
Results:
Your frame can propagate on the grid based on the widgets on it, and not have fixed dimensions.
The output of 1 is due there being nothing on the Frame other than an empty Label. (It would still show 1 if there was no Label)
To get the output as 200, set the grid_propagate flag to False (only after setting your height and widht parameters), as follows:
frame = Frame(..., width=200)
frame.grid(row=0, column=0)
frame.grid_propagate(False)
I was trying out something new on Tkinter (I am still a newbie), but it keeps failing...maybe someone could help out?
I wanted to create a window with several Frames, so that I can open and close them and show that way different content. However I am already stuck with not being able to "place" the, in this case a button, to the frame. Instead I get a blank frame with nothing inside...
The reason I want to use the place manager is so that I can easily choose the x and y coordinates. I don't want to create empty columns just in order to get a button appear in the middle of the screen.
Here the code:
from Tkinter import *
root = Tk()
root.title("Tkinter window")
root.geometry("800x600")
StartFrame = Frame(root)
StartFrame.pack()
Button1 = Button(StartFrame, command = StartTkinter, text = "Start", bg = "white", fg = "black", height = 2, width = 15)
Button1.place(x=0, y=50)
root.mainloop()
The problem is that you forgot to specify the dimensions of the frame. So, by default, it is created to be just 1 pixel high and 1 pixel wide. This means that its contents will not be visible on the window.
To fix the problem, you can either set exact values for these dimensions when you create the frame:
StartFrame = Frame(root, height=600, width=800)
or you can do:
StartFrame.pack(expand=True, fill="both")
to have the frame fill all available space.
Relevant code:
self.propertyListWrapper = ttk.Frame(self.propertyMenu)
self.propertyListWrapper.pack( fill = tk.BOTH, expand = tk.YES )
self.propertyListCanvas = tk.Canvas(self.propertyListWrapper)
self.propertyListCanvas.pack( fill = tk.BOTH, expand = tk.YES, side = tk.LEFT )
self.propertyGrid = ttk.Frame(self.propertyListCanvas)
self.propertyListScrollbar = ttk.Scrollbar(self.propertyListWrapper)
self.propertyListScrollbar.config(command = self.propertyListCanvas.yview)
self.propertyListCanvas.config(yscrollcommand = self.propertyListScrollbar.set)
self.propertyListScrollbar.pack(side = tk.RIGHT, fill = tk.Y)
...
# where stuff is added to self.propertyGrid
...
self.propertyListCanvas.config( scrollregion = (0, 0, self.propertyGrid.winfo_width(), self.propertyGrid.winfo_height()))
self.propertyListCanvas.create_window((0,0), window = self.propertyGrid, anchor='nw')
This is what is currently happening:
As you can see, the scrollbar doesn't have a bar. Not very useful.
What am I doing wrong?
Here's a simplified Github Gist that duplicates the problem. 20 labels should be there, and you should be able to scroll through them, but the scrollbar doesn't appear.
The default for pack is to put something along the top edge. When you packed the canvas you're using the default, so it takes up the top of the gui, leaving blank space below. When you packed the scrollbar, you told it to go to the right, so it is at the right edge of the empty space below the canvas. If you want the scrollbar and canvas side-by-side, pack both on the right, or pack one on the right and one on the left.
The problem with your gist is that you're trying to get the width and height of the frame before it has been drawn. Since the frame hasn't been mapped to the screen (because you haven't added it to the canvas yet) it's width and height is 1 (one). You must make a widget visible before you can measure its width and height. That means to add it to a canvas or pack/place/grid it to a visible window and then wait for a screen repaint.
Why can't I see a red frame with the following code?
import Tkinter
root = Tkinter.Tk()
root.geometry("220x300")
container_frame = Tkinter.Frame(background = "red", width = 100, height = 120)
container_frame.pack()
widget_button = Tkinter.Button(master = container_frame)
widget_button.pack()
root.mainloop()
You don't see it because you have no padding between the button and the frame. By default, containers "shrink to fit" around their contents. Even if you add an explicit width or height to the frame, it will shrink to exactly fit its children.
There are several ways to achieve the effect you're looking for, but it's not clear exactly what effect you want. You can turn off this "shrink-to-fit" behavior (using container_frame.pack_propagate(False)). Or, you can add padding around the widget. Or, you can apply the background to the container of the frame. Or you could pack the frame to fill its container (the main window), then make sure the containing window is large enough to expose the frame.
For an example of that last suggestion, you can change one line to be this:
container_frame.pack(side="top", fill="both", expand=True)
If you change to:
widget_button.pack(padx=10, pady=10)
You can see that the frame has been resized when call widget_button.pack(...)
I've been playing around with tkinter a bit and I can't figure out why the "sticky" attribute doesn't seem to be working with my button. I've specified sticky to be NW which should cause my button to stick to the top left edge but for some reason it sticks to the top right. Any idea why?
from tkinter import *
from tkinter import ttk
def test():
name = userName.get()
text = "Hello {0}! Pleased to meet you.".format(name)
greeting.set(text)
window = Tk()
greeting = StringVar()
userName = StringVar()
name = Entry(window, textvariable=userName)
name.grid(column=1, row=1, sticky=NW)
button = Button(window, text="greeting", command=test)
button.grid(column=2, row=1, sticky=NW)
label = Label(window, textvariable=greeting)
label.grid(column=1, row=2, sticky=NW)
#creating a rectangle
canvas = Canvas(window)
canvas.grid(column=1, row=2)
#attributes are x,y coordinates of two points
x = canvas.create_rectangle(5,5,115,115)
mainloop()
The sticky attribute applies to the cell that the widget is in, rather than to the whole grid or whole window. So, the widget is anchored to the nw corner of its cell, it's just that you can't tell because the cell is exactly the same width as the button.
Since you are placing the button in the upper right cell (row 1, column 2) but say you want it in the upper left (of the whole window?) it's hard to know exactly what you want. Without knowing what you're trying to achieve it's hard to make any recommendations.
The easiest way to learn the grid layout manager is with paper and pencil. Get out some gridded graphing paper and draw your widgets on the paper. It then becomes obvious where to put your widgets.
You also need to learn about the rowconfigure and columnconfigure commands, especially with respect to the weight attribute. With this attribute you can identify which rows and columns grown and shrink to take up any extra space. It's also useful to know you can apply these attributes to empty rows and columns. This is useful if you want your interior widgets to stay the same size, and have any extra applied to the edges of your gui (generally not useful, though sometimes it is).
As a rough rule of thumb, each window should have one "major" widget -- the one that dominates the UI. Usually this is a canvas or text widget, but it doesn't have to be. Find that widget and give the row and column it is in a weight of 1 (one) so that it grows and shrinks as the user resizes the window. In your case this would be the canvas in row 2, column 1.
The point is that the grid cell is exactly the same size as the button - you won't notice if it in E or W...You can check that by placing all your widgets below each other (all row 0, and column 0-4) you'll see that in that case the button shows up NW. Hope this helps...