I just got started using Tkinter and I'm facing a weird problem, basically, I want my app to look something like this
So in order to do that, I created two Frames, one for menu items and the other one to display the content. Strange thing, when I initialize the frames with the given width and height the program seems to work as expected, but when I put some widgets inside the window resizes, it looks like this
Could somebody please explain this weird behaviour to me? What am I missing? Also, I would like to mention that when I add the buttons to the menu frame the width changes to fit the button width, not vice versa as I would like
app = tk.Tk()
app.resizable(False, False)
menu_frame_users = tk.Frame(app, width=200, background='red')
content_frame = tk.Frame(app, height=600, width=600, background='blue')
hello_label = tk.Label(menu_frame_users, text='Hello, User').grid(column=0, row=0, sticky='we')
view_profile_button = tk.Button(menu_frame_users, text="View Profile").grid(column=0, row=1, sticky='we')
invoices_button = tk.Button(menu_frame_users, text="Invoices").grid(column=0, row=2, sticky='we')
bookings_button = tk.Button(menu_frame_users, text="View bookings").grid(column=0, row=3, sticky='we')
tools_button = tk.Button(menu_frame_users, text="Search tools").grid(column=0, row=4, sticky='we')
test_label = tk.Label(content_frame, text='View profile')
test_label.grid(column=0, row=0, sticky='we')
menu_frame_users.grid(column=0, row=0, sticky='nswe')
content_frame.grid(column=1, row=0, sticky='nswe')
The frames in tkinter will only be as big as the widgets contained within unless you add weight to row and columns to make them expand.
You can get the frame to expand by setting the size of the window and then adding weight to the appropriate row and column.
app.geometry('500x400')
app.grid_columnconfigure(1, weight=1)
app.grid_rowconfigure(0, weight=1)
You can play around with the size of the window and then resize and position your buttons until you get the layout you want.
You can also use:
app.grid_columnconfigure(1, minsize=300)
However this only applies when the column contains a widget.
I'm not sure if I fully understand the question, but maybe this will help. Note I removed the explicit frame dimensions for the sake of the example.
import tkinter as tk
app = tk.Tk()
app.resizable(False, False)
menu_frame_users = tk.Frame(app,background='red')
content_frame = tk.Frame(app, background='blue')
app.geometry("500x500")
app.rowconfigure(0, weight=1)
app.columnconfigure(0, weight=1)
menu_frame_users.columnconfigure(0, weight=1)
hello_label = tk.Label(menu_frame_users, text='Hello, User').grid(column=0, row=0, sticky='we')
view_profile_button = tk.Button(menu_frame_users, text="View Profile").grid(column=0, row=1, sticky='we')
invoices_button = tk.Button(menu_frame_users, text="Invoices").grid(column=0, row=2, sticky='we')
bookings_button = tk.Button(menu_frame_users, text="View bookings").grid(column=0, row=3, sticky='we')
tools_button = tk.Button(menu_frame_users, text="Search tools").grid(column=0, row=4, sticky='we')
test_label = tk.Label(content_frame, text='View profile')
test_label.grid(column=0, row=0, sticky='we', padx=20, pady=20)
menu_frame_users.grid(column=0, row=0, sticky='nswe')
content_frame.grid(column=1, row=0, sticky='nswe')
app.mainloop()
Documentation for tkinter is a bit limited but there's some great info regarding grid configuration on this page.
Generally speaking, the widgets in a given container are what give the container it's dimensions, unless explicitly coded otherwise. (In other words, the frame will grow as you add things into it, not the other way around)
In your example, I added an arbitrary window size (you can also specify an offset in that string argument). My guess is you're looking for rowconfigure() and columnconfigure(). Also, you can add some padding to space things out with .grid()
I almost exclusively use the grid geometry manager, but sometimes you might find it more pragmatic to use pack() or place(), just make sure you don't use both at the same time.
Cheers.
Related
I currently have this grid
https://imgur.com/a/ZELwflE
I want to make it like this:
https://imgur.com/a/SnBHW2S
Would it be possible? Heres my code:
import random
from tkinter import *
root = Tk()
root.title("TOMBOLA")
root.resizable(0, 0)
root.config(bg="WHITE")
entry= Entry(root, width= 40)
entry.focus_set()
entry.grid(column=1, row=0, padx=25)
texto=Label(root, text="Introduce el nombre para agregar a la tómbola:", font=("Courier")).grid(column=0, row=0, padx=(10,0))
boton_ok=Button(root, text="OK", width=10, height=2)
boton_ok.grid(row=0, column=3, padx=10, pady=(25,0))
listaindic = Label(root, text="Lista: ")
listaindic.grid(row=1, column=1, sticky=W)
display = Label(root, text="")
display.grid(row=1, column=2, columnspan=1, sticky=E)
boton_sig=Button(root, text="INICIAR TOMBOLA").grid(column=0, row=1, columnspan=1, pady=(0,15))
root.mainloop()
Thank you all so much
I tried making columnspan bigger, but didnt end well.
Tkinter relies on cells and rows to display widgets, without these there is no functionality. This being the case there is really no way of doing what you are asking in the first image. Everything has to be in a cell. So how I would try to fix your problem is by creating a tkinter.Frame and insert it into the cell you are trying to configure (1, 1). This creates another grid inside the frame so we can choose what to do with it. e.g: increasing the weight on the second column to make it seem much bigger.
newFrame = Frame(master=root)
newFrame.grid_rowconfigure(0, weight=1)
newFrame.grid_columnconfigure(0, weight=1)
newFrame.grid_columnconfigure(1, weight=3)
listaindic = Label(newFrame, text="Lista: ")
listaindic.grid(row=0, column=0, sticky=W)
This shows you how to create a basic frame, and putting the Label into the first cell.
I want to display images on a Canvas within a Frame. For that I want the Canvas to have a Scrollbar. So I created a LabelFrame and assigned a Canvas for it:
wrapper1 = LabelFrame(self.root)
wrapper1.grid(row=1, columnspan=6, sticky="WE")
image_canvas = Canvas(wrapper1)
image_canvas.grid(sticky="NS")
Then I tried to add the Scrollbar:
yscrollbar = Scrollbar(wrapper1, orient="vertical", command=image_canvas.yview)
yscrollbar.grid(sticky="SNE", row=0, column=6)
image_canvas.configure(yscrollcommand=yscrollbar.set)
image_canvas.bind("<Configure>", lambda e: image_canvas.configure(scrollregion=image_canvas.bbox("all")))
All of it works apart from the fact that the Scrollbar doesn't properly stick to the right edge of the Frame:
As you can see it's rather in the middle. I also tried the same thing with buttons to no avail. Prior to this I also set up some buttons on self.root. I don't know if they mess up the grid somehow, as they are not in the LabelFrame:
BrowseButton = Button(self.root, text="Test1", command=self.browse)
BrowseButton.grid(row=3, column=0, sticky="WE")
ConvertButton = Button(self.root, text="Test2", command=self.search)
ConvertButton.grid(row=3, column=5, sticky="WE")
SimilarImages = Button(self.root, text="Test3", command=self.compare)
SimilarImages.grid(row=2, column=5, sticky="WE")
How do you make the Scrollbar stick correctly to the right side, while also filling out the whole Frame vertically?
I want my widgets of my gui to get managed when the size of the GUI Window gets increase or decrease according to the monitor size.
With my current code this window looks like this in my monitor ---
Look of the gui when open in my pc
And when the same GUI program is open in another monitor screen of same size, the some part of the window gets cut off --
Look of the gui window in another pc monitor
But I want my GUI Window to adjust all its widgets with the monitor screen.
Please help me with this and please suggest me what should I add to my code to make it responsive !!
Note - I have used pack() method to place the Label Frames
My some line of code ---
class Application:
def __init__(self, root):
self.root = root
self.root.title("Auto-Garage Management Application")
self.screen_width = self.root.winfo_screenwidth()
self.screen_height = self.root.winfo_screenheight()
self.root.geometry("%dx%d+-10+0" % (self.screen_width, self.screen_height))
bg_color1 = "#ff704d"
bg_color2 = "powder blue"
# =============== Fonts
frame_label_font = Font(family="Poppins", size=20, weight="bold")
entry_label_font1 = Font(family="Roboto", size=17, weight="bold")
entry_label_font2 = Font(family="Roboto", size=15, weight="bold")
entry_font = Font(family="Noto Sans", size=15, weight="bold")
btn_font = Font(family="Noto Sans", size=15, weight="bold")
# ================ Variables
# Customer Details
self.customer_name = StringVar()
self.customer_phone = StringVar()
self.customer_telno = StringVar()
self.customer_address_1 = StringVar()
self.customer_address_2 = StringVar()
self.customer_address_3 = StringVar()
# ========== Customer Details
customer_details_frame = LabelFrame(self.root, text="Customer Details", bg=bg_color1, fg="black",
font=frame_label_font, bd=6, relief=GROOVE)
customer_details_frame.pack(fill=BOTH)
# Customer Name
customer_name = Label(customer_details_frame, text="Customer Name", bg=bg_color1, fg="yellow", font=entry_label_font1)
customer_name.grid(row=0, column=0, padx=20, pady=5)
customer_name_txt = Entry(customer_details_frame, textvariable=self.customer_name, width=22, font=entry_font, bd=5, relief=SUNKEN)
customer_name_txt.grid(row=0, column=1, padx=10)
# Customer Phone Number
customer_phone = Label(customer_details_frame, text="Customer Phone", bg=bg_color1, fg="yellow", font=entry_label_font1)
customer_phone.grid(row=0, column=2, padx=20, pady=5)
customer_phone_txt = Entry(customer_details_frame, textvariable=self.customer_phone, width=22, font=entry_font, bd=5, relief=SUNKEN)
customer_phone_txt.grid(row=0, column=3, padx=10)
customer_phone2 = Label(customer_details_frame, text="Customer Tel No.", bg=bg_color1, fg="yellow", font=entry_label_font1)
customer_phone2.grid(row=1, column=2, padx=10)
customer_phone2_txt = Entry(customer_details_frame, textvariable=self.customer_telno, width=22, font=entry_font, bd=5, relief=SUNKEN)
customer_phone2_txt.grid(row=1, column=3, padx=10)
# Customer Address
customer_address = Label(customer_details_frame, text="Customer Address", bg=bg_color1, fg="yellow", font=entry_label_font1)
customer_address.grid(row=0, column=4, padx=20, pady=5)
customer_address_txt1 = Entry(customer_details_frame, textvariable=self.customer_address_1, width=22, font=entry_font, bd=5, relief=SUNKEN)
customer_address_txt1.grid(row=0, column=5, padx=10)
customer_address_txt2 = Entry(customer_details_frame, textvariable=self.customer_address_2, width=22, font=entry_font, bd=5, relief=SUNKEN)
customer_address_txt2.grid(row=1, column=5, padx=10)
customer_address_txt3 = Entry(customer_details_frame, textvariable=self.customer_address_3, width=22, font=entry_font, bd=5, relief=SUNKEN)
customer_address_txt3.grid(row=2, column=5, padx=10, pady=5)
app = Tk()
software = Application(app)
app.mainloop()
Thanks For Any Help !!!
pack and grid both have options that help you define where extra space goes, and how widgets grow, shrink, and align in the space that is given. Creating a layout that is somewhat responsive simply requires careful use of those options.
It appears you're also using a hard-coded font size. It's possible to get the size of the window, and then choose a font size that is appropriate for the window size.
Laying out the widgets.
While it would take far too much code to simulate the entire UI, here's how I would create the top panel. Notice how weight is given to the columns with entry widgets so that they grow if there is any extra space. I also use the sticky option to make sure they fill the space given to them.
class CustomerDetails(tk.LabelFrame):
def __init__(self, parent, *args, **kwargs):
super().__init__(parent, *args, **kwargs)
self.name = tk.Entry(self)
self.phone = tk.Entry(self)
self.telno = tk.Entry(self)
self.addr1 = tk.Entry(self)
self.addr2 = tk.Entry(self)
self.addr3 = tk.Entry(self)
name_label = tk.Label(self, text="Customer Name")
phone_label = tk.Label(self, text="Customer Phone")
telno_label = tk.Label(self, text="Customer Tel No.")
addr_label = tk.Label(self, text="Customer Address")
self.grid_columnconfigure((1,3,5), weight=1)
name_label.grid(row=0, column=0, sticky="e")
phone_label.grid(row=0, column=2, sticky="e")
telno_label.grid(row=1, column=2, sticky="e")
addr_label.grid(row=0, column=4, sticky="e")
self.name.grid(row=0, column=1, sticky="ew")
self.phone.grid(row=0, column=3, sticky="ew")
self.telno.grid(row=1, column=3, sticky="ew")
self.addr1.grid(row=0, column=5, sticky="ew")
self.addr2.grid(row=1, column=5, sticky="ew")
self.addr3.grid(row=2, column=5, sticky="ew")
You can then add this widget to the root window using pack like in the following example. Notice how it defines both the fill and expand options to control how it behaves when it doesn't exactly fit the window.
root = tk.Tk()
details = CustomerDetails(root, text="Customer Details")
details.pack(side="top", fill="x", expand=False)
If you run this code you'll notice that the widgets all fit in both a very narrow and very wide window. Of course, if the window is too small then the entry widgets become unusuable.
Dynamically changing font sizes.
By default, labels will use a font named TkDefaultFont and entry and text widgets use TkTextFont. If you rely on these defaults, you can change the fonts and all of the widgets will change accordingly. You can then either get the size of the display and pick the correct size before the UI is created, or you can set it up so that as the window is resized, the fonts change automatically.
To get a reference to a font you can use the nameToFont method from the font module. From there you can change any attribute of the font you want: family, size, etc. Here's a simple example of a function that sets the size of the default fonts based on the screen size:
def initialize_fonts():
font_size = 12 if root.winfo_screenwidth() <= 1200 else 32
for font_name in ("TkDefaultFont", "TkTextFont"):
font = tkFont.nametofont(font_name)
font.configure(size=font_size)
If you call this function before creating any widgets, they will all be created with the new size.
root = tk.Tk()
initialize_fonts()
details = CustomerDetails(root, text="Customer Details")
details.pack(side="top", fill="x", expand=False)
I have a suggestion.
instead of building a big and lengthy GUI which does all work in one screen, you can divide it in parts and make the GUI small. (from 1000x800 to 600x400 i guess monitors cant be smmaler than this)
Like:
Take customer details first in small GUI.
then Vehicle details and so on
or:
Use HTML, CSS, JS to create a responsive GUI(as its much easier to make responsive GUI using HTML, CSS and JS than in tkinter
So i have some tkinter checkboxes in a frame widget, which should be aligned to each other. I thought with grid and then using sticky='w' should align them to most east as possible of the column. So to say, to have the checkbox-squares aligned. Instead this happens (the relief is just to see the boundaries of the checkbutton widgets):
Image of GUI
The documentation of the widget - in my opinion - does not provide a function to set this. With the sticky of grid i am not sure whether it should even be the function. Actually the relief shows its sticky in that column...
Here is my code example:
def init_checkboxes(self):
"""
Creates all checkboxes for this particular frame subclass.
"""
self.bvar_cbx_showplots = tk.BooleanVar(value=False)
self.cbx_showplots = tk.Checkbutton(self.lblframe, width=20,
text='Show Plots after run',
variable=self.bvar_cbx_showplots,
relief='groove')
self.cbx_showplots.grid(row=1, column=2, sticky='W')
self.bvar_cbx_saveres = tk.BooleanVar(value=True)
self.cbx_saveres = tk.Checkbutton(self.lblframe, width=20,
text='Save simulation results',
variable=self.bvar_cbx_saveres,
relief='groove')
self.cbx_saveres.grid(row=2, column=2, sticky='W')
edit: Second attempt from Reblochon
Use anchor.
anchor=
Controls where in the button the text (or image) should be located. Use one of N, NE, E, SE, S, SW, W, NW, or CENTER. Default is CENTER. If you change this, it is probably a good idea to add some padding as well, using the padx and/or pady options. (anchor/Anchor)
import tkinter as tk
root = tk.Tk()
bvar_cbx_showplots = tk.BooleanVar(value=False)
cbx_showplots = tk.Checkbutton(root, width=20,
text='Show Plots after run',
variable=bvar_cbx_showplots,
relief='groove', anchor='w')
cbx_showplots.grid(row=1, column=2, sticky='W')
bvar_cbx_saveres = tk.BooleanVar(value=True)
cbx_saveres = tk.Checkbutton(root, width=20,
text='Save simulation results',
variable=bvar_cbx_saveres,
relief='groove', anchor='w')
cbx_saveres.grid(row=2, column=2, sticky='W')
root.mainloop()
If you uncomment the options_frame_title you will see that it does not behave properly. Am I missing something? That section was just copied and pasted from the preview_frame_title and that seems to have no issues.
from tkinter import *
blank_app = Tk()
blank_app.geometry('750x500+250+100')
blank_app.resizable(width=False, height=False)
main_frame = Frame(blank_app, width=750, height=500, bg='gray22')
main_frame.grid(row=0, column=0, sticky=NSEW)
main_title = Label(main_frame, text='App Builder', bg='gray', fg='red', font='Times 12 bold', relief=RIDGE)
main_title.grid(row=0, column=0, padx=2, pady=2, sticky=NSEW, columnspan=2)
preview_frame = Frame(main_frame, width=70, height=465, bg='red', highlightcolor='white', highlightthickness=2)
preview_frame.grid(row=1, column=0, padx=2, pady=2, sticky=NSEW)
preview_frame_title = Label(preview_frame, text='Preview Window', width=70, bg='gray', fg='blue', relief=RIDGE)
preview_frame_title.grid(row=0, column=0, sticky=NSEW)
options_frame = Frame(main_frame, width=240, height=465, bg='blue', highlightcolor='white', highlightthickness=2)
options_frame.grid(row=1, column=1, padx=2, pady=2, sticky=NSEW)
options_frame_title = Label(options_frame, text='Widget Options', width=20, bg='gray', fg='blue', anchor=CENTER, relief=RIDGE)
options_frame_title.grid(row=0, column=0, sticky=NSEW)
blank_app.mainloop()
I don't understand what you mean by "behaving properly". It seems to be behaving as it's designed to behave.
By default, tkinter frames are designed to shrink (or grow) to fit their child widgets. When you comment out options_frame_title.grid(...), the frame has no visible children so it says the fixed size that you gave it. When you uncomment that line, it causes a label to be placed in the widget which then causes the frame to shrink to fit.
To further complicate the matters for you, grid will by default give any extra space to rows and columns that have a non-zero weight. Since you haven't given any rows or columns any weight, they don't get any extra space.
Part of the problem is that you are trying to solve too many problems at once. When first starting out you need to be more methodical. Also, you should consider using pack when you're putting a single widget into another widget. It only takes one line of code to get it to fill its parent rather than three with grid.
pro-tip: it really helps if you separate widget creation from widget layout. Your code, even though it's only a couple dozen lines long, is really hard to read.
For example, the first thing you should do is start by creating your top-most frames, and get them to fill and expand/shrink properly before putting any widgets in them.
Starting from scratch
Step 0: don't remove the ability to resize the window
User's don't like having control taken away from them. Remove this line:
blank_app.resizable(width=False, height=False)
Your users will thank you, and during development it's much easier to play with the window to make sure everything is filling, growing, and shrinking as necessary.
Step 1: main_frame
Since it appears this is designed to contain everything, it makes sense to use pack since it is the only widget directly in blank_app.
main_frame = Frame(blank_app, width=750, height=500, bg='gray22')
main_frame.pack(fill="both", expand=True)
With just that (plus the first couple of lines where you create the root window, along with the final call to mainloop), notice how the window is the right size, and the main frame fills the window. You can resize the window all you want and the main frame will continue to fill the whole window.
Step 2: widgets inside main_frame
As I mentioned earlier, it's best to separate widget creation and widget layout. Also, when using grid a good rule of thumb is to always give at least one row and one column a weight. It appears you want the right frame to be about 3x as wide as the left frame. This is where you can use weights.
# widgets in the main frame
main_title = Label(main_frame, text='App Builder', bg='gray', fg='red', font='Times 12 bold', relief=RIDGE)
preview_frame = Frame(main_frame, width=70, height=465, bg='red', highlightcolor='white', highlightthickness=2)
options_frame = Frame(main_frame, width=240, height=465, bg='blue', highlightcolor='white', highlightthickness=2)
# laying out the main frame
main_frame.grid_rowconfigure(1, weight=1)
main_frame.grid_columnconfigure(0, weight=1)
main_frame.grid_columnconfigure(1, weight=3)
main_title.grid(row=0, column=0, padx=2, pady=2, sticky="nsew", columnspan=2)
preview_frame.grid(row=1, column=0, padx=2, pady=2, sticky="nsew")
options_frame.grid(row=1, column=1, padx=2, pady=2, sticky="nsew")
Once again, run the code and notice that as you resize the main window everything still continues to fill the window, and resize properly, and keep the proper proportions. If you don't like the proportions, just change the weights. They can be any number you want. For example, you could use 70 and 240 if you want.
Step 3: preview frame
The preview frame has a label, and I presume you will be putting other stuff under the label. We'll continue to use grid, and just give the row below the label a weight so that it gets all of the extra space. When you add more widgets, you might need to adjust accordingly.
# widgets in the preview frame
preview_frame_title = Label(preview_frame, text="Preview Window", bg='gray', fg='blue', relief=RIDGE)
# laying out the preview frame
preview_frame.grid_rowconfigure(1, weight=1)
preview_frame.grid_columnconfigure(0, weight=1)
preview_frame_title.grid(row=0, column=0, sticky="nsew")
Step 4: the options frame
This is just like the preview frame: a label at the top, and all of the extra space is given to the empty row number 1.
# widgets in the options frame
options_frame_title = Label(options_frame, text='Widget Options', bg='gray', fg='blue', anchor=CENTER, relief=RIDGE)
# laying out the options frame
options_frame.grid_rowconfigure(1, weight=1)
options_frame.grid_columnconfigure(0, weight=1)
options_frame_title.grid(row=0, column=0, sticky="new")
Final thoughs
Notice that you don't need to worry about propagation, which is somewhat of an advanced topic. You also don't have to worry about the size of frames since we're using column weights to give relative sizes, and you don't have to give sizes to their labels.
We have removed the propagation code, removed the non-resizable behavior, and removed some hard-coded widths, giving us less code but more functionality.
Ok after some digging I realized the problem was not with your options_frame_title but with your frames the Label was being placed in.
Of you un-comment options_frame_title and comment out preview_frame_title you will see the exact same problem. What is happening is the frame has a set size and the main window is conforming to that frame size. And when you decide to place a label into the frame then the frame will conform to the label size.
What you want to do to achieve the look you are going for is do something a little different with the .grid_propagate(0) than what you are currently doing.
We also need to add some weights to the correct frames so the widgets will fill properly.
Take a look at this code.
from tkinter import *
blank_app = Tk()
main_frame = Frame(blank_app,width=700, height=300, bg='gray22')
main_frame.grid(row=0, column=0, sticky=NSEW)
main_frame.grid_propagate(0) #the only place you need to use propagate(0) Thought there are better ways
main_frame.columnconfigure(0, weight = 1) #using weights to manage frames properly helps a lot here
main_frame.columnconfigure(1, weight = 1)
main_frame.rowconfigure(0, weight = 0)
main_frame.rowconfigure(1, weight = 1)
main_title = Label(main_frame, text='App Builder', bg='gray', fg='red', font='Times 12 bold', relief=RIDGE)
main_title.grid(row=0, column=0, columnspan=2, padx=2, pady=2, sticky=NSEW)
preview_frame = Frame(main_frame, bg='red', highlightcolor='white', highlightthickness=2)
preview_frame.grid(row=1, column=0, padx=2, pady=2, sticky=NSEW)
preview_frame.columnconfigure(0, weight = 1)# using weights to manage frames properly helps a lot here
preview_frame_title = Label(preview_frame, text='Preview Window', bg='gray', fg='blue', relief=RIDGE)
preview_frame_title.grid(row=0, column=0, sticky=NSEW)
options_frame = Frame(main_frame, bg='blue', highlightcolor='white', highlightthickness=2)
options_frame.grid(row=1, column=1, padx=2, pady=2, sticky=NSEW)
options_frame.columnconfigure(0, weight = 1) #using weights to manage frames properly helps a lot here
options_frame_title = Label(options_frame, text='Widget Options', bg='gray', fg='blue', anchor=CENTER, relief=RIDGE)
options_frame_title.grid(row=0, column=0, sticky=NSEW)
blank_app.mainloop()