I am running into a problem with a tkinter program, I have the LabelFrame grouping a set of labels and entries, however, it is not grouping my widgets. My code for the LabelFrame is as follows:
(edit: i managed to get the Label to display, however, it is not grouping my widgets.)
root=Tk()
message_frame=LabelFrame(root,text="testing",padx=0,pady=0,width=100,height=100).grid(padx=5,pady=10)
message_label=Label(message_frame,text="Message").grid(row=1,column=0,sticky=W)
pub_label=Label(message_frame,text="Public Key").grid(row=2,column=0,sticky=W)
priv_label=Label(message_frame,text="Public Key").grid(row=3,column=0,sticky=W)
message_entry=Entry(message_frame,textvariable=message,width=50).grid(row=1,column=1,sticky=W)
pub_entry=Entry(message_frame,textvariable=pub_key,width=50).grid(row=2,column=1,sticky=W)
priv_entry=Entry(message_frame,textvariable=private_key,width=50).grid(row=3,column=1,sticky=W)
In Tkinter, the typical workflow is to create a widget and then place it using some geometry manager on two separate lines.
If I'm not mistaken, the .grid method on Tkinter widgets returns None. So if you print message_frame right after you create it, you will probably see that it is None. When you use that passed to the next widgets, they assume you want to put it on the root widget...
The easy fix is to do something like:
message_frame=LabelFrame(root,text="testing",padx=0,pady=0,width=100,height=100)
message_frame.grid(row=0,column=0)
And you probably want to do the same with all the widgets since I doubt you actually want pub_label = priv_label = None ...
Give the frame some size attributes:
from Tkinter import *
root = Tk()
message_frame = LabelFrame(root,text="testing",padx=0,pady=0,width=100,height=100).grid(row=0,column=0,padx=5,pady=10)
Once the width and height are defined, the frame shows up fine.
If you post some of your frame's contents, it might make it clearer if this is not the issue.
Related
I am trying to make a date chooser using python. I am using spinboxes, however I was wondering whether I could get all 5 widgets into one grid space, so it seemed like all 5 widgets are really one widget. Hopefully the following code articulates the problem better.
import tkinter as tk
root=tk.Tk()
Day=tk.IntVar()
Month=tk.IntVar()
Year=tk.IntVar()
Label1=tk.Label(root,text="Label Label Label Expanding Row")
Label1.grid(row=1,column=1)
DayEntry=tk.Spinbox(root,textvariable=Day,bg="white",from_=0, to_=31,width=2)
DayEntry.grid(row=2,column=1)
MonthEntry=tk.Spinbox(root,textvariable=Month,bg="white",from_=0, to_=12,width=2)
MonthEntry.grid(row=2,column=3)
YearEntry=tk.Spinbox(root,textvariable=Year,bg="white",from_=2000, to_=20019,width=4)
YearEntry.grid(row=2,column=5)
Divider1=tk.Label(root,text="/")
Divider1.grid(row=2,column=2)
Divider2=tk.Label(root,text="/")
Divider2.grid(row=2,column=4)
root.mainloop()
The solution is to put all of the widgets in a frame.
datepicker = tk.Frame(root)
datepicker.grid(row=2, column=0)
DayEntry=tk.Spinbox(datepicker,textvariable=Day,bg="white",from_=0, to_=31,width=2)
MonthEntry=tk.Spinbox(datepicker,textvariable=Month,bg="white",from_=0, to_=12,width=2)
YearEntry=tk.Spinbox(datepicker,textvariable=Year,bg="white",from_=2000, to_=20019,width=4)
Divider1=tk.Label(datepicker,text="/")
Divider2=tk.Label(datepicker,text="/")
DayEntry.grid(row=0,column=1)
Divider1.grid(row=0,column=2)
MonthEntry.grid(row=0,column=3)
Divider2.grid(row=0,column=4)
YearEntry.grid(row=0,column=5)
Why, in this really basic code snippet, the data label (lb_data) is not appearing in its parent widget (fr_body), and instead appears in fr_header?
(The heights, widths and reliefs are just there to show the location of the Frame widgets)
fr_header=ttk.Frame(root,width=100,height=100,relief=GROOVE).grid(row=0,column=0)
lb_title=ttk.Label(fr_header,text="Title Title Title").grid(row=0,column=0)
fr_body=ttk.Frame(root,width=150,height=150,relief=SUNKEN).grid(row=1,column=0)
lb_data=ttk.Label(fr_body,text="Data").grid(row=0,column=0)
The .grid method returns None. So, fr_header is actually being assigned the value None. When you pass that to your label Tkinter sees the None and assumes you want to make the master widget the root widget (Tk()).
I never create a widget and grid at the same time as then you lose your easy handle on the widget you just created. Do it this way instead:
fr_header=ttk.Frame(root,width=100,height=100,relief=GROOVE)
fr_header.grid(row=0,column=0)
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)
How do I randomly add buttons to a Tkinter GUI? I need it to be able to create a button, then put it anywhere on the window, is this possible? I am using Python 2.6 on Windows.
If you want random button placement (or anything not aligned along a grid, etc.), you can use the place geometry manager. Depending on platform, overlapped buttons may not behave as you expect, though, so you may want to avoid them.
Here's a simple example:
from Tkinter import *
from random import random
root = Tk()
frame = Frame(root, height=200, width=200)
for i in range(10):
Button(frame, text=str(i)).place(x=random() * 150, y=random() * 180)
frame.pack()
root.mainloop()
There are several options to choose from. For example, you could design on a grid where you have six buttons per row. Then it's just a matter of starting at row 0, incrementing the column for each button. When you get to the last column, reset the column to 0 and increment the row by one.
Another option is to use a text widget as the container, and embed your buttons in the text widget with wrapping enabled. With this trick the buttons will fill a row automatically and wrap if the user grows or shrinks the main windows. It's a tiny bit more work, but it works well if that's the behavior you want.
I'm attempting to write a basic Tkinter GUI that has a Text widget at the top, then a Button widget left aligned under it, then another Text widget underneath the button. The problem I'm having is, after packing the Button widget to the left, when I then go to pack the second Text widget, it puts it next to the button on the right, rather than underneath the button. This happens regardless of what I set the side argument to for the second Text widget Here's a simple piece of code that demonstrates this behaviour:
from Tkinter import *
root = Tk()
w = Text(root)
w.pack()
x = Button(root, text="Hi there!")
x.pack(side=LEFT)
y = Text(root)
y.pack(side=BOTTOM)
root.mainloop()
So how would I go about setting up the second Text widget so that it appears below the button, rather than to the right of it?
There are generally two solutions to layout problems:
switch to using grid. It becomes real easy to do layouts like what you are trying to accomplish. Grid can solve probably 95% of all layout issues (it's amazing when you think about it -- Tk does with one manager what most toolkits need half a dozen to accomplish!)
use multiple frames. If some widgets need to be stacked top-to-bottom and some left-to-right you can't always get what you want packing everything in a single frame. Use one frame for the top-to-bottom parts of the layout and additional frames for the left-to-right content.
Also realize that widgets don't have to be children of the widget in which they are packed/gridded. You can use the "in" parameter to put widgets in some other container than their parent.
For example, in your specific example you can create three frames, top, middle, bottom. Pack these top-to-bottom in your toplevel window. Then you can pack the first text widget in the top, the button or buttons horizontally in the middle, and the other text widget in the bottom.
The advantage to such an approach is that it makes it much easier to change the layout in the future (which in my experience always happens at some point). You don't have to re-parent any of your widgets, just pack/place/grid them in some other container.
In your short example it doesn't make much difference, but for complex apps this strategy can be a life saver.
My best advice is this: layout isn't an afterthought. Do a little planning, maybe even spend five minutes drawing on some graph paper. First decide on the major regions of your app and use a frame or some other container for each (paned window, notebook, etc). Once you have those, do the same divide-and-conquer approach for each section. This lets you use different types of layout for different sections of your app. Toolbars get horizontal layout, forms might get vertical layout, etc.
I was initially misunderstanding how packing worked and didn't realise that the entire left side was being "claimed" when i did x.pack(side=LEFT). What I found after reading this and the answer by Alex here is that I was not really after having x packed to the left side at all, but rather having it anchored to the left, using anchor=W (W for West) instead of side=LEFT. My revised code snippet which does what I was after looks like this:
from tkinter import *
root = Tk()
w = Text(root)
w.pack()
x = Button(root, text="Hi there!")
x.pack(anchor=W)
y = Text(root)
y.pack(side=BOTTOM)
root.mainloop()
This way x is not "claiming" the left side anymore, it's just aligned to the left (or West) within its block of space.
Packing happens in the order the .pack methods are called, so once x has "claimed" the left side, that's it -- it will take up the left portion of its parent and everything else within its parent will be to its right. You need a Frame to "mediate", e.g....:
from Tkinter import *
root = Tk()
w = Button(root, text="Mysterious W")
w.pack()
f = Frame(root)
x = Button(f, text="Hi there!")
x.pack()
y = Button(f, text="I be Y")
y.pack(side=BOTTOM)
f.pack(side=LEFT)
root.mainloop()
(changed Texts to Buttons for more immediate visibility of layout only -- the Tkinter on this Mac doesn't show Texts clearly until they have focus, but Buttons are quite clear;-).
Do it the same way that WebView does using the Mosaic Canvas Widget Sets internals(which are very similar to Tk). The trick is that the second identical named Frame Object works as a Block Level Float(inline:block;) for everything placed after it and everything that calls "fr" already will automatically begin over inside of it.
You can have many doing this of TOP aligned widgets and simply add another identical named Frame where you want to break between side=LEFT's. Works after Bottom also.
fr=Frame(root)
fr.pack(fill=X, side=TOP)
block1=Label(fr)
block1.pack(side=LEFT)
block2=Label(fr)
block2.pack(side=LEFT)
block3=Button(fr)
block3.pack(side=LEFT)
# NAME IT THE SAME ID NAME AS THE FIRST MAIN FRAME...
fr=Frame(root)
fr.pack(fill=X, side=TOP)
# These NOW jump into the second Frame breaking the side=LEFT in new Frame
block4=Label(fr)
block4.pack(side=LEFT)
block5=Label(fr)
block5.pack(side=LEFT)
# AND THEY CONTINUE GOING side=LEFT AFTERWARDS.