How to horizontally center a widget using grid()? - python

I am using grid() to place widgets in a tkinter window. I am trying to put a label on the horizontal center of a window and have it stay there, even if the window is resized. How could I go about doing this?
I don't want to use pack(), by the way. I would like to keep using grid().

There's no trick -- the widget is centered in the area allocated to it by default. Simply place a label in a cell without any sticky attributes and it will be centered.
Now, the other question is, how to get the area it is allocated to be centered. That depends on many other factors, such as what other widgets are there, how they are arranged, etc.
Here's a simple example showing a single centered label. It does this by making sure the row and column it is in takes up all extra space. Notice that the label stays centered no matter how big you make the window.
import Tkinter as tk
class Example(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent)
label = tk.Label(self, text="This should be centered")
label.grid(row=1, column=1)
self.grid_rowconfigure(1, weight=1)
self.grid_columnconfigure(1, weight=1)
if __name__ == "__main__":
root = tk.Tk()
Example(root).grid(sticky="nsew")
root.grid_rowconfigure(0, weight=1)
root.grid_columnconfigure(0, weight=1)
root.mainloop()
You can get a similar effect by giving a weight to all rows and columns except the one with the label.
import Tkinter as tk
class Example(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent)
label = tk.Label(self, text="This should be centered")
label.grid(row=1, column=1)
self.grid_rowconfigure(0, weight=1)
self.grid_rowconfigure(2, weight=1)
self.grid_columnconfigure(0, weight=1)
self.grid_columnconfigure(2, weight=1)
if __name__ == "__main__":
root = tk.Tk()
Example(root).grid(sticky="nsew")
root.grid_rowconfigure(0, weight=1)
root.grid_columnconfigure(0, weight=1)
root.mainloop()

There is nothing special required. A widget will be in the middle of it's parent automatically. What is required to to tell the parent to fill all available space.
from tkinter import *
root = Tk()
root.geometry("500x500+0+0")
frmMain = Frame(root,bg="blue")
startbutton = Button(frmMain, text="Start",height=1,width=4)
startbutton.grid()
#Configure the row/col of our frame and root window to be resizable and fill all available space
frmMain.grid(row=0, column=0, sticky="NESW")
frmMain.grid_rowconfigure(0, weight=1)
frmMain.grid_columnconfigure(0, weight=1)
root.grid_rowconfigure(0, weight=1)
root.grid_columnconfigure(0, weight=1)
root.mainloop()
This uses grid rather than pack to place the widgets and the grid is configured to fill the entire size of the window. The button will appear in the centre regardless of the size of the window.

This worked for me:
lbl1 = Label(self, text='some text')
lbl1.grid(row=1, column=1, sticky='e') # right aligned
# sticky='w' # left aligned
# sticky='' # centered (or no sticky)

There is another way although it is not .grid it is .place. I found that when using .grid and sticky to centre, the widget would not centre if you were only using one column.
label1 = Label(loginWindow, text="Please Log In", font=("Arial", 25))
label1.place(anchor = CENTER, relx = .5, rely = .2)
Where relx and rely is the % of the screen it is located, and anchor=CENTER centres all the widgets.

Related

Scrollbar in Tkinter

I tried many things to do 2 things :
center my content (horizontal)
have a scrollbar right and bottom
Here my code :
# encoding: utf8
from tkinter import *
from tkinter import ttk
class Program:
def __init__(self):
# Tk.__init__(self)
# Fill the content of the window
self.window = Tk()
self.window.geometry("1080x720")
self.createFrameWithScrollbar()
self.content()
def createFrameWithScrollbar(self):
# Create a Main Frame
self.mainFrame = Frame(self.window)
self.mainFrame.pack(fill=BOTH, expand=True)
# Create a canvas
self.canvas = Canvas(self.mainFrame)
self.canvas.pack(side=LEFT, fill=BOTH, expand=True)
# Add a scrobar
yScrollbar = ttk.Scrollbar(self.mainFrame, orient=VERTICAL, command=self.canvas.yview)
yScrollbar.pack(side=RIGHT, fill=Y)
xScrollbar = ttk.Scrollbar(self.mainFrame, orient=HORIZONTAL, command=self.canvas.xview)
xScrollbar.pack(side=BOTTOM, fill=X)
# Configure the canvas
self.canvas.configure(yscrollcommand=yScrollbar.set)
self.canvas.configure(xscrollcommand=xScrollbar.set)
self.canvas.bind('<Configure>', lambda e: self.canvas.configure(scrollregion = self.canvas.bbox("all")))
self.frame = Frame(self.canvas)
self.canvas.create_window((0,0), window=self.frame, anchor="nw")
self.currentFrame = Frame(self.frame)
self.currentFrame.configure(bg="red")
self.currentFrame.pack(fill=BOTH, expand=1)
def content(self):
label_title = Label(self.currentFrame, text="Title")
label_title.grid(column=0, row=0, sticky=NSEW)
label_description = Label(self.currentFrame, text="Title")
label_description.grid(column=0, row=1, sticky=NSEW)
label_version = Label(self.currentFrame, text="0.2 beta [Novembre 2020]")
label_version.grid(column=0, row=2, sticky=NSEW)
# Lauch the program
app = Program()
app.window.mainloop()
So the result is the following :
So any suggestion ?
The bottom sidebar is so small ! I don't know why.
Are regarding the text, it's not center at all ;(
I want to have it center and changing position if I rezize the window
Thanks a lot
pack works by allocating space along an entire empty side of the master, so the order of when you call pack matters. When you pack the canvas before packing the scrollbar, the canvas takes up the entire left side from top to bottom.
The simple solution is to pack the scrollbars before you pack the canvas.
yScrollbar.pack(side=RIGHT, fill=Y)
xScrollbar.pack(side=BOTTOM, fill=X)
self.canvas.pack(side=LEFT, fill=BOTH, expand=True)
To make the UI look a bit more professional, grid is usually a better choice when laying out scrollbars. With pack, either one edge of the horizontal scrollbar will be below the vertical scrollbar, or the edge of the vertical scrollbar will be directly beside the horizontal one. That, or you have to add a little bit of padding so the scrollbars don't seem to overlap.
When I have a scrollable widget, I almost always put it and the one or two scrollbars together in a frame using grid. Then, I can treat them all as if they were a single widget when adding them to the rest of the UI.
In a comment you ask about how to do it in a grid. You simply need to place the widgets in the appropriate rows and columns, and make sure that the row and column with the scrollable widget has a non-zero weight so any extra space is allocated to it.
self.mainFrame.grid_rowconfigure(0, weight=1)
self.mainFrame.grid_columnconfigure(0, weight=1)
yScrollbar.grid(row=0, column=1, sticky="ns")
xScrollbar.grid(row=1, column=0, sticky="ew")
self.canvas.grid(row=0, column=0, sticky="nsew")

Expand one widget vertically while locking another with Tkinter/ttk

I have a treeview inside of a frame that sits on top of another frame containing buttons. I would like the top frame to expand when I resize the window but keep the button frame from doing the same.
Code in Python 2.7.5:
class MyWindow(Tk.Toplevel, object):
def __init__(self, master=None, other_stuff=None):
super(MyWindow, self).__init__(master)
self.other_stuff = other_stuff
self.master = master
self.resizable(True, True)
self.grid_columnconfigure(0, weight=1)
self.grid_rowconfigure(0, weight=1)
# Top Frame
top_frame = ttk.Frame(self)
top_frame.grid(row=0, column=0, sticky=Tk.NSEW)
top_frame.grid_columnconfigure(0, weight=1)
top_frame.grid_rowconfigure(0, weight=1)
top_frame.grid_rowconfigure(1, weight=1)
# Treeview
self.tree = ttk.Treeview(top_frame, columns=('Value'))
self.tree.grid(row=0, column=0, sticky=Tk.NSEW)
self.tree.column("Value", width=100, anchor=Tk.CENTER)
self.tree.heading("#0", text="Name")
self.tree.heading("Value", text="Value")
# Button Frame
button_frame = ttk.Frame(self)
button_frame.grid(row=1, column=0, sticky=Tk.NSEW)
button_frame.grid_columnconfigure(0, weight=1)
button_frame.grid_rowconfigure(0, weight=1)
# Send Button
send_button = ttk.Button(button_frame, text="Send",
command=self.on_send)
send_button.grid(row=1, column=0, sticky=Tk.SW)
send_button.grid_columnconfigure(0, weight=1)
# Close Button
close_button = ttk.Button(button_frame, text="Close",
command=self.on_close)
close_button.grid(row=1, column=0, sticky=Tk.SE)
close_button.grid_columnconfigure(0, weight=1)
I make the instance elsewhere like this:
window = MyWindow(master=self, other_stuff=self._other_stuff)
What I have tried:
Tried locking resizability which only made the buttons disappear. I also tried changing weights around but my current configuration is the only way everything shows up on screen.
What it should always look like no matter how long the height:
What I want to prevent:
Thanks in advance.
The problem isn't that the button frame is growing, it's that the top frame is growing but isn't using all of it's space. This is because you are giving row 1 of top_frame a weight of 1 but you don't put anything in row 1. Extra space is being allocated to row 1 because of its weight, but row 1 is empty.
An easy way to visualize this is to change top_frame to a tk (rather than ttk) frame, and temporarily give it a distinctive background color. You will see that when you resize the window, top_frame fills the window as a whole, but that it is partially empty.
Create top_frame like this:
top_frame = Tk.Frame(self, background="pink")
... yields a screen like the following image when you resize the window. Note that the pink top_frame is showing through, and that button_frame remains its preferred size.
You can fix this by simply removing this one line of code:
top_frame.grid_rowconfigure(1, weight=1)

Tkinter window in canvas doesn't fill its parent in height

I would like to have my scrollbar in the bottom of the frame and my text widgets filling the whole frame above the scrollbar. I found some solution about the width configuration here but when I try to replace width with height, it does not work correctly.
from tkinter import *
from tkinter import ttk
class MainView(Frame):
def FrameHeight(self, event):
canvas_height = event.height
self.canvas.itemconfig(self.canvas_frame, height=canvas_height)
def OnFrameConfigure(self, event):
self.canvas.config(scrollregion=self.canvas.bbox("all"))
def __init__(self, *args, **kwargs):
Frame.__init__(self, *args, **kwargs)
self.grid_columnconfigure(0, weight=1)
self.grid_rowconfigure(0, weight=1)
sensorsFrame = Frame(self)
sensorsFrame.grid(row=0, sticky="nsew")
sensorsFrame.grid_columnconfigure(0, weight=1)
sensorsFrame.grid_rowconfigure(0, weight=1)
self.canvas = Canvas(sensorsFrame)
self.sensorsStatsFrame = Frame(self.canvas)
self.canvas.grid_rowconfigure(0, weight=1)
self.sensorsStatsFrame.grid_rowconfigure(0, weight=1)
myscrollbar = Scrollbar(sensorsFrame,orient=HORIZONTAL,command=self.canvas.xview)
self.canvas.configure(xscrollcommand=myscrollbar.set)
self.canvas.pack(fill=BOTH, expand=1)
myscrollbar.pack(fill=X, expand=1)
test0 = Text(self.sensorsStatsFrame, state=DISABLED)
test1 = Text(self.sensorsStatsFrame, width=150)
test0.grid(column=0, row=0, sticky="nsew")
test1.grid(column=1, row=0, sticky="nsew")
self.canvas_frame = self.canvas.create_window((0,0),window=self.sensorsStatsFrame,anchor='nw')
self.sensorsStatsFrame.bind("<Configure>", self.OnFrameConfigure)
#When I try to use what i found
#self.canvas.bind('<Configure>', self.FrameHeight)
if __name__ == "__main__":
root = Tk()
main = MainView(root)
main.pack(fill="both", expand=1)
root.wm_geometry("1100x500")
root.wm_title("MongoDB Timed Sample Generator")
root.mainloop()
Step 1: Remove space above and below the scrollbar
The expand option determines how tkinter handles unallocated space. Extra space will be evenly allocated to all widgets where the value is 1 or True. Because it's set to 1 for the scrollbar, it is given some of the extra space, causing the padding above and below the widget.
What you want instead is for all of the space to be allocated only to the canvas. Do this by setting expand to zero on the scrollbar:
myscrollbar.pack(fill=X, expand=0)
Step 2: call a function when the canvas changes size
The next problem is that you want the inner frame to grow when the canvas changes size, so you need to bind to the <Configure> event of the canvas.
def OnCanvasConfigure(self, event):
<code to set the size of the inner frame>
...
self.canvas.bind("<Configure>", self.OnCanvasConfigure)
Step 3: let the canvas control the size of the inner frame
You can't just change the size of the inner frame in OnCanvasConfigure, because the default behavior of a frame is to shrink to fit its contents. In this case you want the contents to expand to fit the frame rather than the frame shrink to fit the contents.
There are a couple ways you can fix this. You can turn geometry propagation off for the inner frame, which will prevent the inner widgets from changing the size of the frame. Or, you can let the canvas force the size of the frame.
The second solution is the easiest. All we have to do is use the height of the canvas for the frame height, and the sum of the widths of the inner text widgets for the frame width.
def OnCanvasConfigure(self, event):
width = 0
for child in self.sensorsStatsFrame.grid_slaves():
width += child.winfo_reqwidth()
self.canvas.itemconfigure(self.canvas_frame, width=width, height=event.height)
Step 4: fix the scrollbar
There's still one more problem to solve. If you resize the window you'll notice that tkinter will chop off the scrollbar if the window gets too small. You can solve this by removing the ability to resize the window but your users will hate that.
A better solution is to cause the text widgets to shrink before the scrollbar is chopped off. You control this by the order in which you call pack.
When there isn't enough room to fit all of the widgets, tkinter will start reducing the size of widgets, starting with the last widget added to the window. In your code the scrollbar is the last widget, but if instead you make it the canvas, the scrollbar will remain untouched and the canvas will shrink instead (which in turn causes the frame to shrink, which causes the text widgets to shrink).
myscrollbar.pack(side="bottom", fill=X, expand=0)
self.canvas.pack(fill=BOTH, expand=1)
Changing pack layout to grid layout for self.canvas and myscrollbar makes it work.
from tkinter import *
from tkinter import ttk
class MainView(Frame):
def FrameHeight(self, event):
canvas_height = event.height
self.canvas.itemconfig(self.canvas_frame, height = canvas_height)
def OnFrameConfigure(self, event):
self.canvas.config(scrollregion=self.canvas.bbox("all"))
def __init__(self, *args, **kwargs):
Frame.__init__(self, *args, **kwargs)
self.grid_columnconfigure(0, weight=1)
self.grid_rowconfigure(0, weight=1)
sensorsFrame = Frame(self)
sensorsFrame.grid(row=0, sticky="nsew")
sensorsFrame.grid_rowconfigure(0, weight=1)
sensorsFrame.grid_columnconfigure(0, weight=1)
self.canvas = Canvas(sensorsFrame, bg="blue")
self.sensorsStatsFrame = Frame(self.canvas)
self.canvas.grid_rowconfigure(0, weight=1)
self.sensorsStatsFrame.grid_rowconfigure(0, weight=1)
myscrollbar = Scrollbar(sensorsFrame,orient=HORIZONTAL,command=self.canvas.xview)
self.canvas.configure(xscrollcommand=myscrollbar.set)
self.canvas.grid(row=0, sticky="nsew")
myscrollbar.grid(row=1, sticky="nsew")
test0 = Text(self.sensorsStatsFrame, state=DISABLED, bg="red")
test1 = Text(self.sensorsStatsFrame, width=150)
test0.grid(column=0, row=0, sticky="nsew")
test1.grid(column=1, row=0, sticky="nsew")
self.canvas_frame = self.canvas.create_window((0,0),window=self.sensorsStatsFrame,anchor='nw')
self.sensorsStatsFrame.bind("<Configure>", self.OnFrameConfigure)
self.canvas.bind('<Configure>', self.FrameHeight)
if __name__ == "__main__":
root = Tk()
main = MainView(root)
main.pack(fill="both", expand=1)
root.wm_geometry("1100x500")
root.wm_title("MongoDB Timed Sample Generator")
root.mainloop()

Tkinter tkSimpleDialog.Dialog resize

I am creating a class that derives from tkSimpleDialog.Dialog.
I have implemented the body(self, master) method, where I create some widgets (buttons, labels) and layout them using grid().
I set up the grid layout to stretch, but it never takes the full window. Furthermore, if I resize the window, the widgets will stay in place and not move.
Here's the initial layout:
And here's what happens when I resize the window:
Here's my code:
import Tkinter
import tkSimpleDialog
class Test(tkSimpleDialog.Dialog):
# ########################################
def __init__(self, parent):
tkSimpleDialog.Dialog.__init__(self, parent, "?")
# ########################################
def body(self, master):
Tkinter.Label(master, text="Hello").grid(row=0, column=0, columnspan=2, sticky="nsew")
Tkinter.Button(master, text="ONE").grid(row=1, column=0, sticky="nsew")
Tkinter.Button(master, text="TWO").grid(row=1, column=1, sticky="nsew")
master.columnconfigure(0, weight=1)
master.columnconfigure(1, weight=1)
master.rowconfigure(0, weight=1)
master.rowconfigure(1, weight=1)
self.resizable(height=True, width=True)
if __name__ == "__main__":
Test(Tkinter.Tk("test"))
The desired behavior is: buttons ONE and TWO will stretch to fill the window. Any suggestion?
Thanks!
This appears to be a bug in the Dialog class. In the code where it packs the main part of the dialog it does this:
body.pack(padx=5, pady=5)
Note that it doesn't include an expand or fill attribute, so the body isn't going to grow to fill the extra room in its parent.

How to add scrollbar to text widget?

How to add scrollbar to text widget if text-widget inside top-level widget and added to layout via grid geometry manager.
I mean i got this inside "toplevel" window/dialog:
ttk.Label(toplevel,text="Text Area").grid(row=8,sticky=E)
self.TextAreaCCOrder=Text(toplevel,height=10,width=50 ).grid(row=8,column=1)
PS: I'm noob :)
Here's an example that creates a frame with a scrollbar and a text widget:
import Tkinter as tk
class Example(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent)
# create the text and scrollbar widgets
text = tk.Text(self, wrap="word")
vsb = tk.Scrollbar(self, orient="vertical")
# connect them to each other
text.configure(yscrollcommand=vsb.set)
vsb.configure(command=text.yview)
# use grid to arrange the widgets (though pack is simpler if
# you only have a single scrollbar)
vsb.grid(row=0, column=1, sticky="ns")
text.grid(row=0, column=0, sticky="nsew")
# configure grid such that the cell containing the text
# widget grows and shrinks with the window
self.grid_rowconfigure(0, weight=1)
self.grid_columnconfigure(0, weight=1)
if __name__ == "__main__":
root = tk.Tk()
frame = Example(parent=root)
frame.pack(side="top", fill="both", expand=True)
root.mainloop()

Categories