Tkinter - How to center a Widget (Python) - python-3.x

I started with the GUI in Python and have a problem.
I've added widgets to my frame, but they're always on the left side.
I have tried some examples from the internet, but I did not manage it .
I tried .place, but it does not work for me. Can one show me how to place the widgets in the middle?
Code:
import tkinter as tk
def site_open(frame):
frame.tkraise()
window = tk.Tk()
window.title('Test')
window.geometry('500x300')
StartPage = tk.Frame(window)
FirstPage = tk.Frame(window)
for frame in (StartPage, FirstPage):
frame.grid(row=0, column=0, sticky='news')
lab = tk.Label(StartPage, text='Welcome to the Assistant').pack()
lab1 = tk.Label(StartPage, text='\n We show you helpful information about you').pack()
lab2 = tk.Label(StartPage, text='\n \n Name:').pack()
ent = tk.Entry(StartPage).pack()
but = tk.Button(StartPage, text='Press', command=lambda:site_open(FirstPage)).pack()
lab1 = tk.Label(FirstPage, text='1Page').pack()
but1 = tk.Button(FirstPage, text='Press', command=lambda:site_open(StartPage)).pack()
site_open(StartPage)
window.mainloop()

After you have created window, add:
window.columnconfigure(0, weight=1)
More at The Grid Geometry Manager

You are mixing two different Layout Managers. I suggest you either use The Grid Geometry Manager or The Pack Geometry Manager.
Once you have decided which one you would like to use, it is easier to help you :)
For example you could use the Grid Geometry Manager with two rows and two columns and place the widgets like so:
label1 = Label(start_page, text='Welcome to the Assistant')
# we place the label in the page as the fist element on the very left
# and allow it to span over two columns
label1.grid(row=0, column=0, sticky='w', columnspan=2)
button1 = Button(start_page, text='Button1', command=self.button_clicked)
button1.grid(row=1, column=0)
button2 = Button(start_page, text='Button2', command=self.button_clicked)
button2.grid(row=1, column=1)
This will lead to having the label in the first row and below the two buttons next to each other.

Related

Why is Python Tkinter grid method separating the buttons

I am trying to make a game using python 3.8, where I have 1 canvas/frame and 2 buttons. I want the buttons to be right next to each other but whenever I run the code, there is always white space in between the 2 buttons. I tried using pack() but when you click on the buttons, it makes the buttons go above the canvas/frame instead of to the right of it.
My code:
from tkinter import *
# windows, canvas, and frames
root = Tk()
WatchRun = Canvas(root, bg="green", width=600, height=500)
WatchRun.grid(row=0, column=0, rowspan=25)
Upgrade = Frame(root, bg="yellow", width=600, height=500)
Upgrade.grid_forget()
# button functions
def show_upgrade(widget, widget2):
global upgradeBtn
global WatchRunBtn
widget.grid_forget()
widget2.grid(row=0, column=0, rowspan=25)
def show_watchrun(widget, widget2):
global upgradeBtn
global WatchRunBtn
widget.grid_forget()
widget2.grid(row=0, column=0, rowspan=25)
# variables and buttons
distance = 0
started = 0
money = 0
startImage = PhotoImage(file='start.png')
stopImage = PhotoImage(file='stop.png')
upgradeBtn = Button(root, text="Upgrades", width=9, command=lambda: show_upgrade(WatchRun, Upgrade))
upgradeBtn.grid(row=0, column=1)
WatchRunBtn = Button(root, text="Watch run", width=9, command=lambda: show_watchrun(Upgrade, WatchRun))
WatchRunBtn.grid(row=1, column=1)
#loop
root.mainloop()
It appears you're trying to use rowspan to try to force the widgets apart. That's not a good solution.
While there are multiple ways to solve the problem of the buttons not being together, the one I recommend in this specific case is to treat your GUI as if it had three rows (or maybe four, depending on what you want to happen when you resize the window).
Row 0 holds the first button, row 1 holds the second, and row 2 takes up the rest of the GUI. Then, the canvas can span all three rows.
So, start by adding a non-zero weight to the third row so that all unallocated space goes to it.
root.grid_rowconfigure(2, weight=1)
Next, add your buttons to rows 0 and 1:
upgradeBtn.grid(row=0, column=1)
WatchRunBtn.grid(row=1, column=1)
And finally, have your canvas span all three rows. It will force the window to grow larger, with all extra space being given to the third row. This allows the first two rows to retain their natural small height.
WatchRun.grid(row=0, column=0, rowspan=3)
An arguably better solution involves using pack and an extra frame or two. The UI seems to clearly have two logical sections to it: a canvas on the left and a column of buttons on the right. So, you can create one frame for the left and one for the right. Then, put the buttons in the right frame and the canvas in the left frame.
See my comments that indicates the changes in the code. If you have questions left on it, let me know. You may find this Q&A helpfull as well. PEP 8
import tkinter as tk #no wildcard imports
#free functions under imports to ensure function is defined
def show_frame(widget, widget2):
'''common function for upgrade_btn and watchrun_btn
- packs the appropiated widget in the left_frame'''
widget.pack_forget()
widget2.pack()
distance = 0
started = 0
money = 0
#variable names lowercase
root = tk.Tk()
#split window in two containers/master/frames
left_frame = tk.Frame(root)
right_frame= tk.Frame(root)
#leftframe content
watchrun = tk.Canvas(left_frame, bg="green", width=600, height=500)
upgrade = tk.Frame(left_frame, bg="yellow", width=600, height=500)
#rightframe content
upgrade_btn = tk.Button(right_frame, text="Upgrades", width=9, command=lambda: show_frame(watchrun, upgrade))
watchrun_btn = tk.Button(right_frame, text="Watch run", width=9, command=lambda: show_frame(upgrade, watchrun))
#geometry management
left_frame.pack(side=tk.LEFT)
right_frame.pack(side=tk.RIGHT,fill=tk.Y)
watchrun.pack()
upgrade_btn.pack()
watchrun_btn.pack()
root.mainloop()

Adding a background image to a root window and placing labels on the top of the background image

I'm trying to add a background image to my root window but it doesn't seem to be working for me. This is my code. I would like the background image to cover the whole window and place the labels on top of the background image.
from tkinter import *
from tkinter import messagebox
top = Tk()
textButton = Frame(top)
textButton.pack()
img = PhotoImage(file="bk.gif")
img = img.subsample(1, 1)
background = Label(top, image = img, bd=0)
background.pack()
background.image = img
name_label = Label(textButton, text="Username")
name_label.grid(row=1, sticky=W)
name_entry = Entry(textButton)## the Entry will let the user entre text inside the text box
name_entry.grid(row=1, column=1)
password_label = Label(textButton, text="Password")
password_label.grid(row=2, sticky=W)
password_entry = Entry(textButton, show="*")
password_entry.grid(row=2, column=1)
top.mainloop
You can use place to use an image as a background for other widgets. place doesn't affect the geometry of other widgets so you can use it with grid or pack.
The main think to keep in mind is that you should do this before creating other widgets so that it is lowest in the stacking order. That, or be sure to call lower on it, otherwise it will overlay the other widgets rather than underlay them.
With your code, just remove background.pack() and replace it with these two lines:
background.place(relx=.5, rely=.5, anchor="center")
background.lower()
You don't need to change anything else. The above centers the image. If you instead want the image to start in the upper-left corner you can do it like this:
background.place(x=0, y=0, anchor="nw")
You have to use background as parent for widgets to put them inside Label with background.
I remove Frame to make it simpler. And now I can use weight to automatically resize empty rows and columns around widgets so they will be in the center.
import tkinter as tk
top = tk.Tk()
top.geometry('250x250')
img = tk.PhotoImage(file="hal_9000.gif")
img = img.subsample(1, 1)
background = tk.Label(top, image=img, bd=0)
background.pack(fill='both', expand=True)
background.image = img
# resize empty rows, columns to put other elements in center
background.rowconfigure(0, weight=100)
background.rowconfigure(3, weight=100)
background.columnconfigure(0, weight=100)
background.columnconfigure(3, weight=100)
name_label = tk.Label(background, text="Username")
name_label.grid(row=1, column=1, sticky='news')
name_entry = tk.Entry(background)## the Entry will let the user entre text inside the text box
name_entry.grid(row=1, column=2)
password_label = tk.Label(background, text="Password")
password_label.grid(row=2, column=1, sticky='news')
password_entry = tk.Entry(background, show="*")
password_entry.grid(row=2, column=2)
top.mainloop()
Result:
As you see widgets have gray background which you can't remove. If you need text without gray background then you have to use Canvas with create_text() (and create_window() to put Entry)
Gif file (with HAL 9000) to test code:

Nesting grids and frames in tkinter and python

I'm trying to set up a grid inside of a frame which is in a larger grid structure. I've tried to distill it to the simplest version of the problem.
from tkinter import Tk, Frame, Label, Entry
root = Tk()
root.geometry('800x800')
frame1 = Frame(root, width=400, height=400, background="Blue")
frame2 = Frame(root, width=400, height=400, background="Red")
frame1.grid(row=0, column=0)
frame2.grid(row=1, column=1)
label1 = Label(frame1,text='Label1')
label1.grid()
Instead of placing the label inside of frame1, the label replaces the frame in the overall grid:
I've looked at other examples, but I haven't been able to identify why they work and mine does not.
Using jasonharper's observation that the Frame was automatically resizing itself and a similar question posted here, I was able to update the code.
from tkinter import Tk, Frame, Label
root = Tk()
root.geometry('800x800')
root.grid_rowconfigure(0, weight=1)
root.grid_columnconfigure(0, weight=1)
root.grid_rowconfigure(1, weight=1)
root.grid_columnconfigure(1, weight=1)
frame1 = Frame(root, background="Blue")
frame2 = Frame(root, background="Red")
frame1.grid(row=0, column=0, sticky="nsew")
frame2.grid(row=1, column=1, sticky="nsew")
label1 = Label(frame1,text='Label1')
label1.grid()
The changes that I made to the code are:
I removed the height and width parameters from the Frame instances because they weren't doing anything anyway.
Added weighting to the rows and columns of root, the container for the frames, to equally space the frames in the available space
Add the 'sticky' parameter to the grid placements of the frames so that the frames would take up all of the space that they were allotted.
The result:
The width= and height= of a Frame normally only apply when it has no children. Once child widgets are added, it resizes itself to fit its contents. So frame1 is still there, it's just now exactly the same size as, and entirely covered by, the label.
To turn off this auto-resizing behavior, call .grid_propagate(0) on the Frame (or .pack_propagate(0), depending on the geometry manager being used for the Frame's children).

Can you fit multiple buttons in one grid cell in tkinter?

I'm making a table, and the grid of the table is going to be filled with buttons, Is it possible to fit more than one button in a grid space?
Yes you can. Put a frame inside the cell, and then you can put whatever you want inside the frame. Inside the frame you can use pack, place or grid since it is independent from the rest of the widgets.
For example:
import Tkinter as tk
root = tk.Tk()
l1 = tk.Label(root, text="hello")
l2 = tk.Label(root, text="world")
f1 = tk.Frame(root)
b1 = tk.Button(f1, text="One button")
b2 = tk.Button(f1, text="Another button")
l1.grid(row=0, column=0)
l2.grid(row=0, column=1)
f1.grid(row=1, column=1, sticky="nsew")
b1.pack(side="top")
b2.pack(side="top")
root.mainloop()
#jasonharper already provided the answer, but here's some code to go along with it.
This is just a random example with a bunch of buttons / frames using grid / pack. The pack for the buttons was arbitrary you could have used grid instead Each grid section has a random padx to show that it's in a different column and each different column within the grid contains multiple buttons
import tkinter as tk
root = tk.Tk()
#Now you want another frame
for i in range(5):
gridframe = tk.Frame(root)
for j in range(3):
tk.Button(gridframe, text="%d%d" % (i, j)).pack(side=tk.LEFT)
gridframe.grid(row=0, column=i, padx=20)
root.mainloop()

Raise Frame on top in PanedWindow in tkinter python 3.5

I have a frame in PanedWindow which i need on every tkinter GUI (in this case it's topFrame). Below it are many frames and I want to switch between those frame on button click (just like in any software where the top portion of screen is fixed and clicking on buttons the lower portion of GUI changes).
I know i need grid layout for it. But, it is not happening and i am not getting a solution anywhere.I have researched a lot on this topic everywhere but this solution is nowhere. Here is my code... i have written in comments those code which i feel are not working fine.
#python 3.5
from tkinter import *
#function to raise the frame on button click
def raiseFrame(frame):
frame.tkraise()
m = PanedWindow(height=500, width=1000, orient=VERTICAL)
m.pack(fill=BOTH, expand=1)
#to expand the column and row to fill the extra space
m.grid_columnconfigure(index=0, weight=1) #this code is not working as it should
m.grid_rowconfigure(index=0, weight=1) #this code is not working as it should
#top frame has two buttons which switches the bottom frames
topFrame = Frame(m, bg="blue")
m.add(topFrame)
button1 = Button(topFrame, text="Raise Frame 2", command=lambda: raiseFrame(frame2)) #raises frame 2 on clicking it
button1.pack(side=LEFT)
button2 = Button(topFrame, text="Raise Frame 1", command=lambda: raiseFrame(frame1)) #raises frame 1 on clicking it
button2.pack(side=LEFT)
#bottomframe acts as container for two other frames which i need to switch
bottomframe = Frame(m, bg="orange")
m.add(bottomframe)
frame1 = Frame(bottomframe, bg="yellow")
frame1.grid(row=0, column=0, sticky="news") ## sticky is not working ##
frame2 = Frame(bottomframe, bg="green")
frame2.grid(row=0, column=0, sticky="news") ## sticky is not working ##
label1 = Label(frame1, text="i should change")
label1.pack(padx=10, pady=10)
label2 = Label(frame2, text="i am changed !!")
label2.pack(padx=10, pady=10)
mainloop()
1)Please correct my code.
2)Explain me why in the "topFrame" even though i have not written
topFrame.pack(fill=BOTH, expand=True)
my code is showing the above property and it's expanding as well as filling both X and Y.
Same goes for the bottomFrame, it's orange colour is filling the entire space which does not happen in normal frames. So, is it some special feature of PanedWindow ?
You don't want to call topFrame.pack() and m.add(topFrame). You either pack/place/grid the window, or you add it to a paned window, you don't do both.
Also, if the frame is going to be in the paned window it needs to be a child of the paned window rather than a child of the root window.

Resources