Tkinter Button Placement to stretch vertically - python-3.x

I have a tkinter pop-up with four objects and a button.
I need them to be placed in this way (showing using excel):
I am placing the labels like this:
lb1.grid(row=1,column=1)
lb3.grid(row=2,column=1)
lb3.grid(row=3,column=1,sticky='w')
lb4.grid(row=3,column=1,sticky='e')
However, when I put button using following, it doesn't stretch across all rows. Instead it stays as a row 4 item.
bt.grid(rowspan=3,column=2) #have even tried adding sticky = 'ns' to this but doesn't stretch
Basically, I want the button to be a thin vertical line on the right border of the widget. I will also want to remove the bevel of the button, text will be blank, and color will be same as label background to make it difficult to see the button. But it should be clickable across the whole east side border of the widget across all the labels.
Can we do that?

Your problem is that you are placing the lb4 label on the lb3 label. You also should specify a columnspan = 2 for lb1 and lb2 as they span 2 columns (column 1, where lb3 sits, and column 2 where lb4 sits). bt will be in column 3 starting from row 1 with a rowspan = 3 therefor reaching till row = 3; Code:
from tkinter import *
root = Tk()
root.geometry("400x400")
lb1 = Label(root, text="Obj1")
lb2 = Label(root, text="Obj2")
lb3 = Label(root, text="Obj3")
lb4 = Label(root, text="Obj4")
lb1.grid(row=1,column=1, columnspan=2)
lb2.grid(row=2,column=1, columnspan=2)
lb3.grid(row=3,column=1,sticky='w')
lb4.grid(row=3,column=2,sticky='e')
bt = Button(root, text="Button")
bt.grid(rowspan=3, row=1, column=3, sticky="ns")
root.mainloop()
also note that rows and columns actually start at 0 (like most things in python), but because i wasn't sure if you already had some widgets in row = 0 / column = 0, i used your column numbers / row numbers.

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()

How to change just one side border width of a ttk.Entry?

I am trying to display a sudoku layout with tkinter grid and ttk entries (maybe, I am not using the right approach to achieve it). I would like to know if the style option of ttk entry has any way to change the border of just one side. I have applied this function:
def grid_layout(parent):
s = ttk.Style()
s.configure('border.TEntry',borderwidth=3)
for row in range(1,10):
for column in range(1,10):
entry = None
if column % 3 == 0 or row % 3 == 0:
entry = ttk.Entry(parent, width=3,justify='center'
,style='border.TEntry')
else:
entry = ttk.Entry(parent,width=3,justify='center')
entry.grid(column= column, row = row)
Which produces the following view:
I just need to change the border width shared by colum 3-4, 6-7 and row 3-4 and 6-7 ,as the typical sudoku-like layout. Any recommendations would be appreciated.
As stated in my comment before, I'd use a stacked layout with the boxes in frames and the entries in these boxes. So there is no need for special styling. Access to the values within the ttk.Entries gives a dictionary that contains tk.StringVar objects, the keys are uppercase letters 'A'..'I' combined with numbers '1'..'9' like in spreadsheet applications:
import tkinter as tk
from tkinter import ttk
mainWin = tk.Tk()
mainWin.title('Sudoku solver')
mainFrame = ttk.Frame(mainWin, borderwidth=10)
mainFrame.grid(column=1, row=1)
# access to entries (of type tk.StringVar)
values = {}
for box_col in range(3):
for box_row in range(3):
box = ttk.Frame(mainFrame, borderwidth=1, relief='sunken')
box.grid(column=box_col+1, row=box_row+1)
for cell_col in range(3):
for cell_row in range(3):
v = tk.StringVar()
# build spreadsheet key from overall column and row
col = 'ABCDEFGHI'[3*box_col+cell_col]
row = '123456789'[3*box_row+cell_row]
key = col+row
values[key] = v
entry = ttk.Entry(box, width=3, justify='center', textvariable=v)
entry.grid(column=cell_col+1, row=cell_row+1)
# example for accessing the entries
values['A1'].set(1)
values['G3'].set(7)
values['E5'].set(int(values['A1'].get())+4)
values['I9'].set(int(values['E5'].get())+4)
mainWin.mainloop()
Under Windows 8.1, this will look this:

Tkinter label font size interfere with the frame structure below

I have a window with two main frames. One frame in row=0 called frame-A is used for a Title. The other frame in row=1 called frame-B is structured in several sub-frames with data. This frame-B has a label at the top (row = 0). It contains also several sub-frames in rows 1-3. If I use a font size of 15 for the label on frame-B, there are no problems. If I increase the font size= 20, the sub-frames in the frame-B become separated. I am trying to understand how the font size is creating problems with the frames in rows 1-3.
Here is my code:
import tkinter as tk
window = tk.Tk()
window.geometry("1200x1200")
#1-Main text
fr_A = tk.Frame(window,width=50, height=50, bd= 1,highlightbackground="green", highlightcolor="green", highlightthickness=1)
tk.Label(fr_A,text="My title",font=("Courier", 30,"bold")).grid(row=0)
fr_A.grid(row=0)
#2-initial configuration
fr_B = tk.Frame(window,width=300, height=300, bd= 1,highlightbackground="red",highlightcolor="red", highlightthickness=1)
fr_B.grid(row=1,column=0,ipady=80)
tk.Label(fr_B,text="Init data",font="helvetica 20",height=2).grid(row=0,column=0) #>>>>>>>font size problem
fr_list = []
for cr in ((1,0),(1,1),(1,2),(2,0),(2,1),(2,2),(3,0),(3,1),(3,2)):
frame_in=tk.Frame(fr_B, highlightbackground="black", highlightcolor="black", highlightthickness=1,bd= 1)
frame_in.grid(row=cr[0],column=cr[1])
fr_list.append(frame_in)
cnt = -1
for fr in fr_list:
for cr in ((0,0),(0,1),(0,2),(1,0),(1,1),(1,2),(2,0),(2,1),(2,2)):
cnt += 1
tk.Label(fr,text=cnt,width=3,height =1).grid(row=cr[0],column=cr[1],sticky="nsew")
window.mainloop()
You need to add columnspan to the title label.
tk.Label(fr_B, text="Init data",font="helvetica 20",height=2).grid(row=0,column=0, columnspan=3) #>>>>>>>font size problem

Multiple tkinter labels in notebook tab are not expanding to full length of window

I'm trying to equally distribute three objects/widgets across one row in a ttk notebook tab, but the three objects only expand half of the window.
I'm not sure what controls the number of columns within a tab since columnsspan in tab.grid(row=0, columnspan=3) doesn't appear to change anything. I've also tried various values for rows and columns for every object with .grid. This is only an issue for notebook tabs, rather than a single window.
#!/usr/bin/env python3
from tkinter import ttk
from tkinter import *
root = Tk()
root.title('Title')
root.resizable(width=FALSE, height=FALSE)
root.geometry('{}x{}'.format(750, 750))
nb = ttk.Notebook(root)
nb.grid(row=0, column=0)
# Add first tab
tab1 = ttk.Frame(nb)
#tab1.grid(row=0, column=0)
nb.add(tab1, text='Setup')
# Add row label
lb1 = ttk.Label(tab1, text = 'Parent Directory:')
lb1.grid(row = 1, column = 1)
# Add text entry
txt1 = ttk.Entry(tab1)
txt1.grid(row = 1, column = 2)
# Add selection button
btn1 = ttk.Button(tab1, text="Select")
btn1.grid(row=1, column=3)
root.mainloop()
I'm expecting the columns to span the full length of the window, instead of half the length of the window.
In order to do this using grid you need to use the Frame.columnconfigure([column#], minsize=[minsize]) function.
If you want the text box and button to stretch to fill the space, use the sticky option. (Sticky doesn't really do anything with the label)
Code:
#!/usr/bin/env python3
from tkinter import ttk
from tkinter import *
root = Tk()
root.title('Title')
root.resizable(width=FALSE, height=FALSE)
root.geometry('{}x{}'.format(750, 750))
nb = ttk.Notebook(root, width=750)
nb.grid(row=0, column=0)
# Add first tab
tab1 = ttk.Frame(nb)
#tab1.grid(row=0, column=0)
nb.add(tab1, text='Setup')
# Change the sizes of the columns equally
tab1.columnconfigure(1, minsize=250)
tab1.columnconfigure(2, minsize=250)
tab1.columnconfigure(3, minsize=250)
# Add row label
lb1 = ttk.Label(tab1, text = 'Parent Directory:')
lb1.grid(row = 1, column = 1,sticky=(E,W))
# Add text entry
txt1 = ttk.Entry(tab1)
txt1.grid(row = 1, column = 2,sticky=(E,W))
# Add selection button
btn1 = ttk.Button(tab1, text="Select")
btn1.grid(row=1, column=3,sticky=(E,W))
root.mainloop()
Image of result

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()

Resources