I am trying to use grid() function to align the labels and option menu side by side. Here's the code which I used to create a simple GUI:
from Tkinter import *
win1 = Tk()
win1.title("Chumma")
#Option selection frame:
f3 = Frame(win1)
f3.grid(column=0,row=0)
f3.pack()
l1 = Label(f3, text="Select the function which you want to perform: ", bg = "yellow")
moduleList = StringVar(f3)
moduleList.set("Normal Walk") #to display the default module name
o1 = OptionMenu(f3, moduleList, "Normal Walk", "Brisk Walk", "Running", "Custom")
b3 = Button(f3, text="Execute the option", fg="blue")
b4 = Button(f3, text="Stop", fg="red")
#Packing the stuffs in required order:
l1.grid(row=0, column=0, sticky=W) #E means east
l1.grid_rowconfigure(0, weight=1)
l1.grid_columnconfigure(0, weight=1)
l1.pack(fill = X, padx = 5)
o1.grid(row=0,column=1)
o1.grid_rowconfigure(0, weight=1)
o1.grid_columnconfigure(1, weight=1)
o1.pack()
b4.pack()
win1.mainloop()
The result is:
I am expecting the option menu o1 to be at the right of the l1.
If I comment the pack command [ l1.pack() and o1.pack() ], the program is not displaying any GUI at all.
After you call grid, a couple of lines later you call pack which cancels out the use of grid. Use one or the other but not both for each widget. Sinc pack defaults to side='top', your widgets appear stacked on top of each other.
The reason you see nothing if you comment out those two calls to pack is because you are still calling b4.pack(), and you can't use both pack and grid for different widgets with the same parent.
Also, the calls to rowconfigure and columnconfigure need to be on the parent widget. Calling them on the label widget will only affect widgets you put inside the label (which is possible, but unusual)
I believe Tkinter does not allow mixing up packing schemes (grid, pack, place) in one frame. Here is example how to organize three widgets.
from Tkinter import *
root = Tk()
label = Label(root, text='blablabla')
someotherwidget = Entry(root)
button = Button(root, command=lambda: None, text='Boom')
label.grid(row=0, column=0)
someotherwidget.grid(row=0, column=1)
button.grid(row=1, column=0, columnspan=2)
root.mainloop()
Option 'columspan' is like how many columns you want to join to place the widget. We have 2 columns here so if we want to see button not below label but below of both label and someotherwidget we have to specify 'columnspan' option (obviously, analog for rows is 'rowspan')
Related
I'm using .place() in my tkinter program, so I wanted to remove references to grid(). So far my program works but for some reason there's a single .grid() line that makes my whole program turn blank if it's removed. This shouldn't happen, since I'm entirely using .place(). Here is that line:
AllFrames.grid(row=0, column=0, sticky='nsew')
And here is my full code:
from tkinter import *
root = Tk()
root.title("Account Signup")
DarkBlue = "#2460A7"
LightBlue = "#B3C7D6"
root.geometry('350x230')
Menu = Frame()
loginPage = Frame()
registerPage = Frame()
for AllFrames in (Menu, loginPage, registerPage):
AllFrames.grid(row=0, column=0, sticky='nsew')
AllFrames.configure(bg=LightBlue)
def show_frame(frame):
frame.tkraise()
root.grid_rowconfigure(0, weight=1)
root.grid_columnconfigure(0, weight=1)
show_frame(Menu)
# ============= Menu Page =========
menuTitle = Label(Menu, text="Menu", font=("Arial", 25), bg=LightBlue)
menuTitle.place(x=130, y=25)
loginButton1 = Button(Menu, width=25, text="Login", command=lambda: show_frame(loginPage))
loginButton1.place(x=85, y=85)
registerButton1 = Button(Menu, width=25, text="Register", command=lambda: show_frame(registerPage))
registerButton1.place(x=85, y=115)
# ======== Login Page ===========
loginUsernameL = Label(loginPage, text='Username').place(x=30, y=60)
loginUsernameE = Entry(loginPage).place(x=120, y=60)
loginPasswordL = Label(loginPage, text='Password').place(x=30, y=90)
loginPasswordE = Entry(loginPage).place(x=120, y=90)
backButton = Button(loginPage, text='Back', command=lambda: show_frame(Menu)).place(x=0, y=0)
loginButton = Button(loginPage, text='Login', width=20).place(x=100, y=150)
# ======== Register Page ===========
root.mainloop()
I've also noticed that changing anything in the parentheses causes the same result. For example, if I change sticky='nsew' to sticky='n' or row=0 to row=1 it will show a blank page.
How do I remove .grid() from my program without it turning blank?
The place() manager does not reserve any space, unless you tell it directly.
The grid(sticky='nsew') makes the widget expand to fill the entire available space, in this case the containing widget. The widgets inside all use place() which will not take any space. When you change to grid(sticky='n') you place the zero size widget at the top of the containing widget.
But, for your current problem you can assign a size to the widgets:
AllFrames.place(relwidth=1, relheight=1) # w/h relative to size of master
I would recommend using the grid() geometry manager if you are going to make any more complicated layouts.
For more info have a look at effbot on archive.org
import tkinter as tk
win = tk.Tk()
win.title('使用者登入')
win.geometry('300x200')
lb1 = tk.Label(win,text='使用者帳號資料',font=('微軟正黑體',16),fg='yellow',bg='black')
lb1.pack(fill='x')
lb2 = tk.Label(win,text='帳號 : ABCDEF',height=4,width=26,font=('標楷體',14),bg='lightblue')
lb2.pack(side='left',anchor='nw',fill='x')
lb3 = tk.Label(win,text='注意',height=50,width=3,font=('微軟正黑體',12),bg='pink')
lb3.pack(anchor='se',fill='y',expand=True)
lb4 = tk.Label(win,text='密碼 : 123456',height=4,width=15,font=('標楷體',14),bg='lightgreen')
lb4.pack(anchor='sw',side='left')
win.mainloop()
The variable lb4 could not be shown on the tkinter window even though I have used pack() method. I would like to put the lb4 widget below lb2 and I had tried using anchor='sw' still not showing so any possible methods to have that widget be shown properly?
The pack method works properly, check that:
import tkinter as tk
win = tk.Tk()
win.title('使用者登入')
win.geometry('300x200')
# lb1 = tk.Label(win,text='使用者帳號資料',font=('微軟正黑體',16),fg='yellow',bg='black')
# lb1.pack(fill='x')
#
# lb2 = tk.Label(win,text='帳號 : ABCDEF',height=4,width=26,font=('標楷體',14),bg='lightblue')
# lb2.pack(side='left',anchor='nw',fill='x')
#
# lb3 = tk.Label(win,text='注意',height=50,width=3,font=('微軟正黑體',12),bg='pink')
# lb3.pack(anchor='se',fill='y',expand=True)
lb4 = tk.Label(win,text='密碼 : 123456',height=4,width=15,font=('標楷體',14),bg='lightgreen')
lb4.pack(anchor='sw',side='left')
win.mainloop()
If it does show anything is because the pack method add widget to the right of the previous on if it's anchored to "S"(outh), even if the new widget it's anchored to 'w' and sided to 'left'.
Since lb3 is anchor at 'se', any other widget is displayed outside the windows.
You shoud use .grid to properly design your window:
import tkinter as tk
win = tk.Tk()
win.title('使用者登入')
lb1 = tk.Label(win, text='使用者帳號資料', font=('微軟正黑體', 16), fg='yellow',
bg='black')
lb1.grid(row=0, column=0, columnspan=3, sticky='WE')
lb2 = tk.Label(win, text='帳號 : ABCDEF', height=4, width=26, font=('標楷體', 14),
bg='lightblue')
lb2.grid(row=1, column=0, sticky='WE')
lb3 = tk.Label(win, text='注意', width=3, font=('微軟正黑體', 12), bg='pink')
lb3.grid(row=1, column=2, rowspan=2, sticky='NS')
#
lb4 = tk.Label(win, text='密碼 : 123456', height=4, width=15, font=('標楷體', 14),
bg='lightgreen')
lb4.grid(row=2, column=0, sticky='w')
win.mainloop()
It looks like this:
Here, I removed the height of lb3 since it's not required (sticky 'NS' allow us to extend the widget to top and down of the rowspan).
You can change column, row, columnspan, rowspan as you want to create the layout you desire.
Each time you use pack, it allocates an entire side for the widget. Thus, the order in which you call pack matters. For example, once you put widget along the top, you can no longer put something to the right.
This is usually much easier to visualize when you group all of your pack statements together for all children in the same parent widget. I also recommend always explicitly defining the side parameter so that your intentions are crystal clear.
This is the proper order to use pack to get lb4 below lb2, with lb1 along the top and lb3 along the right side:
lb1.pack(side="top", fill='x')
lb3.pack(side="right", fill='y')
lb2.pack(side="top", fill='x')
lb4.pack(side="top", fill='x')
I tried running your code and using lb4 before lb3 fixes the problem, but it won't display below lb2 as you have already used side='left' in lb2 so it acquires the whole left side, to pack it below lb2 remove the side='left' in lb2 and put it in lb4.
It doesn't display lb4 at first because it packs it outside of the TKinter window .
Instead of using .pack() use .grid() to better place the labels in the window.
This works:
import tkinter as tk
win = tk.Tk()
win.title('使用者登入')
win.geometry('300x200')
lb1 = tk.Label(win,text='使用者帳號資料',font=('微軟正黑體',16),fg='yellow',bg='black')
lb1.pack(fill='x')
lb2 = tk.Label(win,text='帳號 : ABCDEF',height=4,width=26,font=('標楷體',14),bg='lightblue')
lb2.pack(anchor='nw',fill='x')
lb4 = tk.Label(win,text='密碼 : 123456',height=4,width=15,font=('標楷體',14),bg='lightgreen')
lb4.pack(side='left',anchor='sw')
lb3 = tk.Label(win,text='注意',height=50,width=3,font=('微軟正黑體',12),bg='pink')
lb3.pack(anchor='se',fill='y',expand=True)
win.mainloop()
if I try to put a label on a canvas, the latter always stay on the top (see the image attached) but what abut if I want to put the canvas under the label?
below my example code:
from tkinter import *
from tkinter import ttk
def ChangeinItalian():
MyLabel.configure(text="Ciao Ciao Ciao Ciao:")
def ChangeinEnglish():
MyLabel.configure(text="Hi Hi Hi Hi:")
root = Tk()
root.geometry("550x350")
MyFrame=Frame(root, background="#ffffff", highlightbackground="#848a98", highlightthickness=1)
MyFrame.place(x=5, y=5, width=535, height=300)
MyFrame.grid_columnconfigure(0, minsize=16)
MyFrame.grid_columnconfigure(2, minsize=10)
MyFrame.grid_columnconfigure(3, weight=1)
MyFrame.grid_columnconfigure(4, minsize=10)
MyFrame.grid_columnconfigure(6, minsize=16)
MyFrame.grid_rowconfigure(0, minsize=16, pad=16)
MyFrame.grid_rowconfigure(2, minsize=16, pad=16)
ttk.Label(MyFrame, text="Introduction", background="#ffffff").grid(row=1, column=1, sticky="w")
# I change the grid method options to cover the label with the canvas.. but it just an example.. what I want to do is put the label on the Canvas and not vice versa.
Canvas(MyFrame, height=1, background="#a0a0a0", highlightthickness=0, highlightbackground="white").grid(row=1, column=1, columnspan=3, sticky="we")
MyLabel=ttk.Label(MyFrame, text="Hi Hi Hi Hi", background="#ffffff")
MyLabel.grid(row=3, column=1, sticky="w")
ttk.Entry(MyFrame, width=70).grid(row=3, column=3)
ttk.Button(MyFrame, text="...", width=5).grid(row=3, column=5, sticky="we")
ItalianButton=ttk.Button(MyFrame, text="Italiano", command=ChangeinItalian)
ItalianButton.place(x=16, y=150)
EnglishButton=ttk.Button(MyFrame, text="English", command=ChangeinEnglish)
EnglishButton.place(x=16, y=200)
root.mainloop()
Widgets have what's called a stacking order, which is initially defined by the order in which widgets are created. Widgets created later have a stacking order higher than widgets that are created earlier. When two or more widgets occupy the same space, tkinter uses the stacking order to determine which widget is on top.
In your case you're creating the label and then the canvas, meaning that the canvas has a higher stacking order and thus appears on top of the label.
If you want the canvas under the label, you can either create the canvas before the label, or use the lift or lower method to adjust the stacking order.
I'm pretty new to Tkinter and I build a little window with different widgets.
My Code looks like this:
import tkinter as tk
from tkinter import ttk
class Application(tk.Frame):
def __init__(self, master):
super().__init__(master)
self.master = master
self.master.geometry("800x600")
self.master.title("Tkinter Sandbox")
self.master.grid_rowconfigure(0, weight=1)
self.master.grid_columnconfigure(1, weight=1)
self._create_left_frame()
self._create_button_bar()
self._create_label_frame()
def _create_left_frame(self):
frame = tk.Frame(self.master, bg="red")
tree_view = ttk.Treeview(frame)
tree_view.column("#0", stretch=tk.NO)
tree_view.heading("#0", text="Treeview")
tree_view.pack(fill=tk.Y, expand=1)
frame.grid(row=0, column=0, rowspan=2, sticky=tk.N + tk.S)
def _create_button_bar(self):
frame = tk.Frame(self.master, bg="blue")
button_run_single = tk.Button(frame, text="Button 1")
button_run_all = tk.Button(frame, text="Button 2")
button_details = tk.Button(frame, text="Button 3")
button_run_single.grid(row=0, column=0)
button_run_all.grid(row=0, column=1, padx=(35, 35))
button_details.grid(row=0, column=2)
frame.grid(row=0, column=1, sticky=tk.N)
def _create_label_frame(self):
frame = tk.Frame(self.master, bg="blue")
name_label = tk.Label(frame, text="Label 1")
performance_label = tk.Label(frame, text="Label 2")
name_entry = tk.Entry(frame)
performance_entry = tk.Entry(frame)
name_label.grid(row=0, column=0)
name_entry.grid(row=0, column=1)
performance_label.grid(row=1, column=0)
performance_entry.grid(row=1, column=1)
frame.grid(row=1, column=1)
if __name__ == '__main__':
root = tk.Tk()
app = Application(root)
app.mainloop()
Between the three buttons and the label + entry frame is a huge space. I want the button and label + entry frame right under each other, without the huge space but the treeview should also expand vertically over the whole application window.
I think the problem might be my row and column configuration but I don't know how to solve this problem.
The way you've structured your code makes it hard to see the problem. As a good general rule of thumb, all calls to grid or pack for widgets within a single parent should be in one place. Otherwise, you create dependencies between functions that are hard to see and understand.
I recommend having each of your helper functions return the frame rather than calling grid on the frame. That way you give control to Application.__init__ for the layout of the main sections of the window.
For example:
left_frame = self._create_left_frame()
button_bar = self._create_button_bar()
label_frame = self._create_label_frame()
left_frame.pack(side="left", fill="y")
button_bar.pack(side="top", fill="x")
label_frame.pack(side="top", fill="both", expand=True)
I used pack here because it requires less code than grid for this type of layout. However, if you choose to switch to grid, or wish to add more widgets to the root window later, you only have to modify this one function rather than modify the grid calls in multiple functions.
Note: this requires that your functions each do return frame to pass the frame back to the __init__ method. You also need to remove frame.grid from each of your helper functions.
With just that simple change you end up with the button bar and label/entry combinations at the top of the section on the right. In the following screenshot I changed the background of the button_bar to green so you can see that it fills the top of the right side of the UI.
You need to change line
self.master.grid_rowconfigure(0, weight=1)
to
self.master.grid_rowconfigure(1, weight=1)
so that the second row takes all the space. Then you need to stick widgets from the label frame to its top by adding sticky parameter to the grid call in _create_label_frame:
frame.grid(row=1, column=1, sticky=tk.N)
I prefer to use the Pack Function since it gives a more open window - its easy to configure. When you use Pack() you can use labels with no text and just spaces to create a spacer, by doing this you won't run into the problem your facing.
Example
(mimics the relevant parts of the layout in my real code)
import Tkinter as tk
import ttk
# set up root
root = tk.Tk()
root.minsize(300, 50)
frame = ttk.Frame(root)
frame.grid(row=0, column=0, sticky=tk.EW)
# set up buttons that insert a short or a long string
textvar = tk.StringVar(value='foo')
def insert_short():
textvar.set('foo')
def insert_long():
textvar.set('foo'*30)
button_short = ttk.Button(frame, text='short', command=insert_short)
button_short.grid(row=0, column=0)
button_long = ttk.Button(frame, text='long', command=insert_long)
button_short.grid(row=0, column=0)
button_long.grid(row=0, column=1)
# set up label
# border for label to see its size
style = ttk.Style()
style.configure(
'Bordered.TLabel', foreground='black', borderwidth=1, relief='solid')
# make label extend to the right
frame.columnconfigure(2, weight=1)
# place label
label = ttk.Label(frame, textvariable=textvar, style='Bordered.TLabel')
label.grid(row=0, column=2, sticky=tk.EW)
# place some other widget under label to mimic my real code
ttk.Button(frame, text='some other widget').grid(row=1, column=2)
# TRIED, NOT WORKING:
#root.resizable(False, False)
#frame.propagate(False)
#frame.grid_propagate(False)
#label.propagate(False)
#label.grid_propagate(False)
root.mainloop()
Output
Question
How do I prevent label from extending the main window?
(Bonus question, but not important: is there a way to make the label scrollable if it gets too long?)
Attempts
I tried the following commands:
root.resizable(False, False)
frame.propagate(False)
frame.grid_propagate(False)
label.propagate(False)
label.grid_propagate(False)
You can create a scrollable label using an Entry in a read-only state and by using scrolling it will prevent the widget from extending the main window.
Try replacing your label definition with the following code:
child_frm = ttk.Frame(frame)
label = ttk.Entry(child_frm, textvariable=textvar, style='Bordered.TLabel', state='readonly')
scroll = ttk.Scrollbar(child_frm, orient='horizontal', command=label.xview)
label.config(xscrollcommand=scroll.set)
label.grid(row=0, sticky=tk.EW)
scroll.grid(row=1, sticky=tk.EW)
child_frm.grid(row=0, column=2)
By default, the width of a Label is calculated based on its contents. You can override this behavior by specifying a value for width when creating the Label.
label = ttk.Label(frame, textvariable=textvar, style='Bordered.TLabel', width=1)
Much to my surprise, when I update your code with this, the label doesn't shrink to a size suitable for displaying exactly one character. It appears that the sticky=tk.EW argument of your grid call ensures that the label stays as wide as the widest element in the column.