I made an Excel-like grid for entering data that will be exported to another program. The top row is "frozen" so that it remains visible when you scroll down. I'm using a list ColWidths = [15, 15, 40] to define the widths of the widgets in the top row, and the widgets in the scroll-able rows beneath.
I read that if you don't give a unit, width defaults to pixels. Even so, I made sure to use the same font size in the top row and all the other rows.
Oddly the columns still appear to be slightly different widths. Any idea how to fix this?
MCVE CODE:
import tkinter as tk
from tkinter import simpledialog,filedialog,colorchooser,messagebox,Frame,Button
from PIL import ImageTk, Image
import textwrap
root = tk.Tk()
canv_1 = tk.Canvas(root, bg="blue")
canv_1.pack(side="top", fill="both")#, fill="both", expand=True)
canv_2 = tk.Canvas(root, bg="gray")
canv_2.pack(fill="both", expand=True)
def onFrameConfigure(canvas):
'''Reset the scroll region to encompass the inner frame'''
canvas.configure(scrollregion=canvas.bbox("all"))
frame = tk.Frame(canv_2)
# create, bind & position scrollbar
vsb = tk.Scrollbar(canv_2, orient="vertical", command=canv_2.yview)
canv_2.configure(yscrollcommand=vsb.set)
vsb.pack(side="right", fill="y")
canv_2.pack(side="left", fill="both", expand=True)
canv_2.create_window((0,0), window=frame, anchor="nw")
frame.bind("<Configure>", lambda event, canv_2=canv_2: onFrameConfigure(canv_2))
labels = ["Chapter Title", "Slide", "Instructions"]
ColWidths = [15, 15, 40]
root.label_wid = []
font1 = ("arial", 15, "bold")
load1 = Image.open("StartingImage.jpg")
root.render1 = ImageTk.PhotoImage(load1)
ch_text = []
for i in range(len(labels)):
root.label_wid.append(tk.Label(canv_1,
font=font1,
relief="raised",
text=labels[i],
width=ColWidths[i],
).grid(row=0, column=i, sticky="we"))
c1 = "#a9d08e"
c2 = "#8dd1bf"
Title_col = [
tk.Entry(
frame,
bg = c1,
width = ColWidths[0],
font = font1)
for y in range(0, 5)
]
Slide_col = [
tk.Entry(
frame,
bg = c2,
width = ColWidths[1],
font = font1)
for y in range(0, 5)
]
instruction_col = [
tk.Text(
frame,
bg="white",
wrap="word",
font=font1, width = ColWidths[2], height=10)
for y in range(0, 5)
]
for y in range(0, 5):
Title_col[y].grid(row=y + 1, column=0, sticky='news')
Slide_col[y].grid(row=y + 1, column=1, sticky='news')
instruction_col[y].grid(row=y+1, column=2, sticky='news')
bt1 = tk.Button(canv_1, text="Export", font=font1, bg="#f5b942")
bt1.grid(row=0, column=4)
load2 = Image.open("scroll-up-gray.png")
root.render2 = ImageTk.PhotoImage(load2)
load3 = Image.open("scroll-down.png")
root.render3 = ImageTk.PhotoImage(load3)
root.mainloop()
GUI SCREENSNIP:
Remove bold from your font or figure out how much to add to the image column (your original screen grab) to compensate. Seriously though, just remove bold from your font and watch it all magically work.
bold is adding extra pixels to all of your labels, and it isn't adding anything in all of those empty boxes. It took me like 2 hours to figure this out (lol)! I even rewrote your entire example much better and still couldn't get it to work ... until I realized that the instruction label was being forced out of the window bounds. Some kind of way that triggered the font format in my head. I simply removed bold and everything worked. I then re-copy/pasted your example and removed bold. It still worked.
I understand that you ultimately want to keep bold. You will have to use line-metrics or something more reliable than simply telling every column to be the same size. The main point is, at least now you know the culprit and why.
edit:
I also got it to work by doing the below instead. Unfortunately, I can't explain why this works. Obviously, I know that we are just adding an inner padding to each cell, but I don't understand why dividing the cell width by 2 works in every case. This may even cause problems later. I simply don't know, but in this example case it seems to work almost perfect.
for y in range(0, 5):
Title_col[y].grid(row=y + 1, column=0, sticky='news', ipadx=ColWidths[0]/2)
Slide_col[y].grid(row=y + 1, column=1, sticky='news', ipadx=ColWidths[1]/2)
instruction_col[y].grid(row=y+1, column=2, sticky='news', ipadx=ColWidths[2]/2)
edit 2:
If you want the results of the above code to appear absolutely perfect in line with the header columns, specify highlightthickness=0 in your canvas instances. This will remove the canvas borders, which are creating a tiny offset.
canv_1 = tk.Canvas(root, bg="blue", highlightthickness=0)
canv_1.pack(side="top", fill="both")#, fill="both", expand=True)
canv_2 = tk.Canvas(root, bg="gray", highlightthickness=0)
canv_2.pack(fill="both", expand=True)
Related
Im trying to get a tkinter gui that lets the user sign in im really new to tkinter. I made a frame and when i use grid to put another frame widget inside of the frame it does it based off of the root and not the inner_frame thats what I think is happening. In the code I made a grey box to demonstrate and I dont understand why it is below the blue frame and not inside the yellow frame under the "sign in" text. Thanks for the help.
from tkinter import *
root = Tk()
root.title("sighn in test")
#colors
background = "#273E47"
accent = "#d8973c"
red = "#bb4430"
white = "#edf2f4"
#this creates and places the background frame
main_buttons_frame = Frame(root, height = 500, width = 400, bg = background).grid(row = 0, column = 0)
#this creates and places the inner frame
inner_frame = Frame(main_buttons_frame, height = 450, width = 300, bg = accent).grid(row = 0, column = 0)
#this creates and places the "sighn in text"
top_text = Label(inner_frame, text = "sign in", font = ("helvitica", 30, "bold"), bg = accent, fg =
background).grid(row = 0, column = 0)
#this is a test to demonstrate
test_frame = Frame(inner_frame, bg = "grey", height = 100, width = 100).grid(row = 1, column = 0)
root.mainloop()
You have very common mistake of beginners
inner_frame = Frame(...).grid...()
It assigns None to inner_frame because grid()/pack()/place() gives None.
So later Frame(inner_frame, ..) means Frame(None, ..) and it adds to root
You have to do it in two steps
inner_frame = Frame(...)
inner_frame.grid(...)
And now you have Frame assigned to inner_frame
EDIT:
With correctly assigned widgets I get
and now gray box is inside yellow frame but image shows different problem - grid()/pack() automatically calculate position and size and external frame automatically change size to fit to child's size.
Using .grid_propagate(False) you can stop it
But it shows other problem - cells don't use full size of parent so yellow frame is moved to left top corner, not centered :) Empty cells have width = 0 and heigh = 0 so moving all to next row and next column will not change it - it will be still in left top corner :)
You need to assign weight to column and/or row which decide how to use free space. All columns/rows has weight=0 and when you set weight=1 then this row and/or column will use all free space - (this would need better explanation) - and then element inside cell will be centered.
main_buttons_frame.grid_columnconfigure(0, weight=1)
main_buttons_frame.grid_rowconfigure(0, weight=1)
import tkinter as tk # PEP8: `import *` is not preferred
# --- colors ---
background = "#273E47"
accent = "#d8973c"
red = "#bb4430"
white = "#edf2f4"
# --- main ---
root = tk.Tk()
root.title("sighn in test")
main_buttons_frame = tk.Frame(root, height=500, width=400, bg=background) # PEP8: without spaces around `=` inside `( )`
main_buttons_frame.grid(row=0, column=0)
#main_buttons_frame = None
main_buttons_frame.grid_propagate(False)
main_buttons_frame.grid_columnconfigure(0, weight=1)
main_buttons_frame.grid_rowconfigure(0, weight=1)
inner_frame = tk.Frame(main_buttons_frame, height=450, width=300, bg=accent)
inner_frame.grid(row=0, column=0)
#inner_frame = None
inner_frame.grid_propagate(False)
inner_frame.grid_columnconfigure(0, weight=1)
inner_frame.grid_rowconfigure(0, weight=1)
top_text = tk.Label(inner_frame, text="sign in", font=("helvitica", 30, "bold"), bg=accent, fg=background)
top_text.grid(row=0, column=0,)
#top_text = None
#top_text.grid_propagate(False)
#top_text.grid_columnconfigure(0, weight=1)
#top_text.grid_rowconfigure(0, weight=1)
test_frame = tk.Frame(inner_frame, bg="grey", height=100, width=100)
test_frame.grid(row=1, column=0)
#test_frame = None
#test_frame.grid_propagate(False)
#test_frame.grid_columnconfigure(1, weight=1)
#test_frame.grid_rowconfigure(0, weight=1)
root.mainloop()
BTW: PEP 8 -- Style Guide for Python Code
Is it possible to make tkinter window's contents adjust to screen size? Like if I had made a window according to a screen resolution of 1366x768, and then I run the same code on a system of 1024x768 resolution and make its contents resize dynamically? I know I can use grid structure as as described here and set the weight of cells accordingly. But is it possible with pack geometry structure and not grid? I am asking this since half of the project I am working on was made by my colleague and he didn't use grid at all. So instead of redoing all of the code in grid, I am looking for a way around it. Here is the sample code I need to resize -
def container(self):
frame_number_panel = tk.Frame(self.parent, width=round(Dimension.SCREEN_WIDTH*0.8), height=Dimension.SCREEN_HEIGHT, bg="#eeeeee")
frame_number_panel.pack(side="left", anchor=W)
frame_number_panel.pack_propagate(False)
self.main_container(frame_number_panel)
# right side pannel
frame_right_side_panel = tk.Frame(self.parent, bg="#101115", width=round(Dimension.SCREEN_WIDTH*0.2), height=Dimension.SCREEN_HEIGHT)
frame_right_side_panel.pack(side="right", anchor=NE)
frame_right_side_panel.pack_propagate(False)
frame_first_row = tk.Frame(frame_right_side_panel, bg="#101115")
frame_first_row.pack()
winning_claim_image = PhotoImage(file=ResourcePath.resource_path('images/icon_win_claim.png'))
label_winning_claim = tk.Label(frame_first_row, bg="#fcd116", text="WINNING\nCLAIM", image=winning_claim_image,
compound="left", fg="#231f20", font=("Roboto-Bold", 10, "bold"),
width=Dimension.label_winning_claim_width,
height=Dimension.label_winning_claim_height)
label_winning_claim.image = winning_claim_image
label_winning_claim.pack(side="left", padx=Dimension.label_winning_claim_padx)
reprint_image = PhotoImage(file=ResourcePath.resource_path('images/reprint_2.png'))
label_reprint = tk.Label(frame_first_row, bg="#8ac539", text="REPRINT", image=reprint_image,
compound="left", fg="#231f20", font=("Roboto-Bold", 10, "bold"),
width=Dimension.label_winning_claim_width, height=Dimension.label_winning_claim_height)
label_reprint.image = reprint_image
label_reprint.pack(side="left", padx=Dimension.label_winning_claim_padding)
as you can see all the widgets are placed using pack geometry manager.
You can achieve nearly the same with pack than with grid. Indeed, I usually prefer the packing, since it gives me more freedom.
For instance, take the following example:
from tkinter import Tk, Label, X, Frame, Y, LEFT, BOTH
root = Tk()
# Initialize frames
f1 = Frame(root, bg="grey")
f2 = Frame(root, bg="pink")
# Initialize labels
w1 = Label(f1, text="Red", bg="red", fg="white")
w2 = Label(f1, text="Green", bg="green", fg="white")
w3 = Label(f1, text="Blue", bg="blue", fg="white")
w1b = Label(f2, text="Red", bg="red", fg="white")
w2b = Label(f2, text="Green", bg="green", fg="white")
w3b = Label(f2, text="Blue", bg="blue", fg="white")
# Packing level 1
f1.pack(fill=X)
f2.pack(fill=BOTH, expand=True)
# Packing level 2
w1.pack(fill=X)
w2.pack(fill=X)
w3.pack(fill=X)
w1b.pack(side=LEFT, fill=BOTH, expand=True)
w2b.pack(side=LEFT, fill=BOTH, expand=True)
w3b.pack(side=LEFT, fill=BOTH, expand=True)
root.mainloop()
As you can see, by using the parameters fill and expand in the correct way, I have set the second frame to be expandable with the windows on both sides (X and Y), while the first frame is only expandable in X. All the labels inside the frames are also equally distributed.
See all the options of pack in the documentation: https://effbot.org/tkinterbook/pack.htm.
I want to use the same tree widget for different reports. To make this work I need to modify the header every time before inserting new data. But I can't get the widget to behave the way I want it to: whenever I change the header/width, an empty column will come out of nowhere. Is there something I can do to prevent that or I must destroy and recreate a new treeview everytime?
import tkinter as tk
from tkinter import ttk
root = tk.Tk()
tree = ttk.Treeview(root, selectmode='browse')
tree.grid(row=0,column=0)
tree_header = ("One", "Two", "Three", "Four", "Five")
tree_width = (100, 100, 100, 100, 100)
tree["columns"] = tree_header
tree['show'] = 'headings'
for i in range(len(tree_header)):
tree.column(tree_header[i],width=tree_width[i], anchor="w", stretch = False)
tree.heading(tree_header[i], text=tree_header[i], anchor='w')
tree.insert("",tk.END,text="",value=(1,2,3,4,5))
def click_me():
tree.delete(*tree.get_children())
new_header = ("Six","Seven","Eight","Nine","Ten")
new_width = (120, 80, 120, 80, 100)
tree["columns"] = new_header
tree['show'] = 'headings'
for i in range(len(new_header)):
tree.column(new_header[i],width=new_width[i],anchor="w", stretch = False)
tree.heading(new_header[i],text=new_header[i],anchor="w")
a_button.config(command=click_me_again)
tree.insert("", tk.END, text="", value=(6, 7, 8, 9, 10))
def click_me_again():
tree.delete(*tree.get_children())
tree["columns"] = tree_header
tree['show'] = 'headings'
for i in range(len(tree_header)):
tree.column(tree_header[i],width=tree_width[i], anchor="w", stretch = False)
tree.heading(tree_header[i], text=tree_header[i], anchor='w')
a_button.config(command=click_me)
tree.insert("", tk.END, text="", value=(1, 2, 3, 4, 5))
a_button = tk.Button(root,text="Click me",command=click_me)
a_button.grid(row=1,column=0)
root.mainloop()
This example changes yours a little bit. I think it's a
benefit for the user if the Treeview expands with its
container.
But that change doesn't solve the problem.
I found that shrinking the column widths
still leaves the Treeview widget with the
original width, hence the white space.
Then if I resize the window using
the right border, and it passes in the right
direction over the last column right border, it
will catch the header and the headers and window
will resize together again...
What I do below is to set the container (root window)
width to the calculated width of all the columns,
after a width change. I account also
for the column separator pixels
(otherwise a window width resize will have to
catch again as described above):
You can still detach the last header from the
window border manually, opening whitespace,
But you can also force a minwidth. And rewriting
headers fixes it. I think you can also capture
that event and force the window width to follow,
if that matters.
I don't know it there is a simpler way to fix this
behaviour, and I don't know also if it is expected
or more like a bug. If I have a pre-determined number
of columns and I am on 'headings' not 'tree headings' why the
extra whitespace?
import tkinter as tk
from tkinter import ttk
def change_headers():
global headers
headers = (headers[0]+'A', headers[1]+'B', headers[2]+'C')
widths = (50, 50, 50)
tree['columns'] = headers
for i, header in enumerate(headers):
tree.heading(header, text=header)
tree.column(header, width=widths[i])
w = sum(width for width in widths) + len(widths)-1
h = root.winfo_reqheight()
root.wm_geometry('{}x{}'.format(w,h))
root.update()
root = tk.Tk()
tree = ttk.Treeview(root)
button = ttk.Button(root, text='Change Headers', command=change_headers)
tree.grid(row=0, column=0, sticky=tk.NSEW)
button.grid(row=1, column=0, sticky=tk.EW)
root.grid_rowconfigure(0, weight=1)
root.grid_columnconfigure(0, weight=1)
headers = ('A', 'B', 'C')
widths = (150, 150, 150)
tree['show'] = 'headings'
tree['columns'] = headers
for i, header in enumerate(headers):
tree.heading(header, text=header)
tree.column(header, width=widths[i])
root.mainloop()
I am currently coding a program with will tie in with Discord's rich presence, and I am needing to create a GUI for it in Tkinter. The issue is, I cannot understand how to place elements correctly. It has been quite a pain. Here is what I am planning to have the app sort of look like: https://i.stack.imgur.com/K9Wox.jpg
However, with my current code, this is how abismal the GUI looks... https://i.stack.imgur.com/wGA9A.jpg
Here is my code:
root = tkinter.Tk()
root.title("Matter: A Discord Rich Presence Tool")
root.config(bg='#2C2F33')
root.geometry("560x300")
#root.overrideredirect(1)
# Load images
loadProfileImage = tkinter.PhotoImage(file="resources/loadprofile.png")
saveProfileImage = tkinter.PhotoImage(file="resources/saveprofile.png")
newProfileImage = tkinter.PhotoImage(file="resources/newprofile.png")
# GUI Hell starts here
topCanvas = tkinter.Canvas(root, width=600, height=150)
topCanvas.config(bd=0, highlightthickness=0, relief='ridge', background="#7289DA")
topTextFieldText = tkinter.StringVar(value='Sample top text')
topTextField = tkinter.Entry(root, textvariable=topTextFieldText)
topTextField.config(borderwidth=0, background="#7289DA")
bottomTextFieldText = tkinter.StringVar(value='Sample bottom text')
bottomTextField = tkinter.Entry(root, textvariable=bottomTextFieldText)
bottomTextField.config(borderwidth=0, background="#7289DA")
largeIconName = tkinter.StringVar()
largeIconNameField = tkinter.Entry(root, textvariable=largeIconName)
smallIconName = tkinter.StringVar()
smallIconNameField = tkinter.Entry(root, textvariable=smallIconName)
applicationIDFieldText = tkinter.StringVar()
applicationIDField = tkinter.Entry(root, textvariable=applicationIDFieldText)
applicationIDField.config(borderwidth=0, background="#23272A")
largeIconHoverText = tkinter.StringVar()
largeIconHoverTextField = tkinter.Entry(root, textvariable=largeIconHoverText)
largeIconHoverTextField.config(borderwidth=0, background="#23272A")
smallIconHoverText = tkinter.StringVar()
smallIconHoverTextField = tkinter.Entry(root, textvariable=smallIconHoverText)
smallIconHoverTextField.config(borderwidth=0, background="#23272A")
#greet_button = tkinter.Button(root, text="Run", command=run)
buttonFrame = tkinter.Frame(height=2, bd=0, relief=tkinter.SUNKEN)
newProfileButton = tkinter.Button(root, text="Save to profile", command=save)
newProfileButton.config(image=newProfileImage, borderwidth=0, background="#23272A")
saveButton = tkinter.Button(root, text="Save to profile", command=save)
saveButton.config(image=saveProfileImage, borderwidth=0, background="#23272A")
loadButton = tkinter.Button(root, command=load)
loadButton.config(image=loadProfileImage, borderwidth=0, background="#23272A")
# Grid stuff
topCanvas.grid(row=0, column=1)
applicationIDField.grid(row=3, column=1)
largeIconHoverTextField.grid(row=3, column=2)
smallIconHoverTextField.grid(row=3, column=3)
newProfileButton.grid(row=5, column=1, padx=(20, 5))
saveButton.grid(row=5, column=2, padx=(5, 5))
loadButton.grid(row=5, column=3, padx=(5, 20))
root.mainloop()
Any guidance would be greatly appreciated since I cannot seem to be able to figure out how to use Tkinter's grid to make a layout similar to the images above.
Do not try to put everything into a single grid. Divide your UI up into sections, and then use the right tool for each section.
Start at the root window
I see two major sections to your UI: a top section in blue that has some information, and a bottom section with a black background that has some buttons.
So, I would start by creating those two sections in the root window, and use pack to place one on top of the other:
topFrame = tk.Frame(root, background="#7289DA")
bottomFrame = tk.Frame(root, background="#2C2F33")
topFrame.pack(side="top", fill="both", expand=True)
bottomFrame.pack(side="bottom", fill="both", expand=True)
With that, you will now always have the root divided into two colored regions. The above code gives them an equal size. You may one to change the expand value to False for one or the other, depending on what you want to happen when the user resizes the window.
Don't worry too much about the size, though. It will change once you start adding widgets to each section.
Next, do the bottom section
The bottom also appears to be in two sections: one for inputs and one for buttons. You could use a single grid layout for this whole section, but to illustrate the concept of dividing the UI into sections we'll split the bottom into two. Plus, because everything isn't neatly lined up into rows and columns, this will make things a bit easier.
As I mentioned earlier, you may want to fiddle around with the expand option, depending on if you want these frames to resize equally or stay the same size when the user resizes the window.
inputFrame = tk.Frame(bottomFrame, background="#2C2F33")
buttonFrame = tk.Frame(bottomFrame, background="#2C2F33")
inputFrame.pack(side="top", fill="both", expand=True)
buttonFrame.pack(side="top", fill="both", expand=True)
Note: if you stop right here and try to run your program, you might not see these frames. During development it sometimes helps to give them distinctive colors to help you visualize. Once you get everything working, you can adjust the colors to their final values.
Add the entry widgets
Now we can add the entry widgets to the top half of the bottom section. We can use grid here, since everything is lined up neatly. An important step is to give the rows an equal weight so that they grow and shrink together, though you can make it so that only one column resizes if you wish.
I'll also point out that there's no need to use StringVar instance. You can, but it adds extra objects to keep track of, which in most cases is not necessary.
label1 = tk.Label(inputFrame, text="APPLICATION ID",
foreground="lightgray",
background="#2C2F33")
label2 = tk.Label(inputFrame, text="LARGE IMAGE HOVER",
foreground="lightgray",
background="#2C2F33")
label3 = tk.Label(inputFrame, text="SMALL IMAGE HOVER",
foreground="lightgray",
background="#2C2F33")
# columns should get extra space equally. Give any extra vertical space
# to an empty column below the entry widgets
inputFrame.grid_columnconfigure((0,1,2), weight=1)
inputFrame.grid_rowconfigure(2, weight=1)
appIdEntry = tk.Entry(inputFrame, borderwidth=0,
highlightthickness=0,
background="#23272A", bd=0)
largeImageEntry = tk.Entry(inputFrame,
highlightthickness=0,
background="#23272A", bd=0)
smallImageEntry = tk.Entry(inputFrame, borderwidth=0,
highlightthickness=0,
background="#23272A", bd=0)
label1.grid(row=0, column=0, sticky="w")
label2.grid(row=0, column=1, sticky="w", padx=10)
label3.grid(row=0, column=2, sticky="w")
appIdEntry.grid(row=1, column=0, sticky="ew")
largeImageEntry.grid(row=1, column=1, sticky="ew", padx=10)
smallImageEntry.grid(row=1, column=2, sticky="ew")
This gives us the following:
Notice that the top appears to have shrunk. That's only because it's empty. Tkinter is really good about expanding and shrinking things to fit what's inside. Don't worry too much about it. You can tweak things once you get everything working.
And so on...
I don't want to rewrite your whole program in this answer. The point here is that you should break your UI up into logical chunks, and make each chunk a frame. You are then free to use whatever geometry manager makes the most sense within that frame. Sometimes grid is best, sometimes pack. In either case, it's much easier to manage a few high level frames than it is to try to cram dozens of widgets into a single grid, especially when there are no clear cut rows and/or columns.
This solution also makes it pretty easy to create functions or classes for each section. For example, your main program might look like:
root = tkinter.Tk()
top = topSection(root)
bottom = bottomSection(root)
By doing so, if you decide to completely redesign one section of the UI, you can do so without worrying that you'll mess up the layout in the other sections, since each frame is mostly independent of any other frame.
First you have to explain to the grid geometry manager that you want the canvas to span all three columns:
topCanvas.grid(row=0, column=1, columnspan=3)
Widgets do not automatically expand to fill the entire column (or row) if you don't specify it and it will center itself in a cell if you dont specify where you want it with sticky:
newProfileButton.grid(row=5, column=1, padx=(20, 5), sticky='w')
saveButton.grid(row=5, column=2, padx=(5, 5), sticky='w')
loadButton.grid(row=5, column=3, padx=(5, 20), sticky='w')
This will hopefully give you something to play with although it's not a complete answer.
Here is a good tutorial: Getting Tkinter Grid Sizing Right the first time
I'm trying to make some UI in python with tkinter.
This is a sample of the code I'm using:
root = Tk()
root.geometry("1000x700x0x0")
canvas = Canvas(root, width = 700, height = 700, bg ='white').grid(row = 0, column = 0)
button1 = Button(root, text = "w/e", command = w/e).grid(row = 0, column = 1)
button2 = Button(root, text = "w/e", command = w/e).grid(row = 1, column = 1)
This is what i'm getting:
and this is what I want:
Any help on how can I do it?
Thanks!
Since your GUI seems to have two logical groups of widgets, I would organize it as such. Start by placing the canvas on the left and a frame on the right. You can use pack, place, grid, or a paned window to manage them. For a left-to-right orientation, pack is a good choice due to its simplicity
Note that you don't have to do it this way, but experience has taught me it makes layout problems much easier to solve.
In the following example I set expand to False for the button frame, which means that the canvas will grow and shrink when the user resizes (because it has expand=True), but the buttons will only take up exactly as much space as they need.
canvas = Canvas(root, ...)
buttonframe = Frame(root, ...)
canvas.pack(side="left", fill="both", expand=True)
buttonframe.pack(side="right", fill="both", expand=False)
Next, you can put all of the buttons in the right side without having to worry how their placement might affect objects on the left.
The important thing to remember when using grid is that you should designate at least one row and at least one column to be given any extra space. This can be a row and/or column that contains widgets, or it can be an empty row and column on an edge.
button1 = Button(buttonframe, ...)
button2 = Button(buttonframe, ...)
button3 = Button(buttonframe, ...)
...
button1.grid(row=0, column=0)
button2.grid(row=0, column=1)
button3.grid(row=1, column=0)
...
buttonframe.grid_rowconfigure(100, weight=1)
buttonframe.grid_columnconfigure(2, weight=1)
note: if you need to keep a reference to a widget, you must create the widget and call grid (or pack or place) on two separate lines. This is because Button(...).grid(...) returns the value of the last function call, and grid(...) returns None