How would i go on about placeing widgets on a frame, which is in the zoomed state, that cannot be resized while using .grid()?
I want to be able to place the buttons on the right most of the screen while placing a treeview on the left of the screen, with say an instance of 'whitespace' in the middle. I'm not sure if it can be done personally, and in my experience with tkinter i have only used smaller windows which are not 'Zoomed' or 'fullscreen' making .grid() placement really easy to use without this problem. Ive looked across the web for a solution but not yet found one.
I would like to be able to layout my tkinter window in the layout provided down below.
The frustrating thing about questions like this is a) there are at least half a dozen ways to accomplish what you want, and b) the best way depends on variables that you haven't defined. For example, how do you decide the proportions of each section -- are you hard-coding them, do you want tkinter to compute them based on what's inside? What behavior do you want if the user resizes the window? And so on.
Getting Started
Let's start with the import statements and the root window. I hate full screen windows so I'm going with a smaller fixed size. The layout will work no matter what size, you can resize it to whatever you want.
import tkinter as tk
root = tk.Tk()
root.geometry("900x600")
Add frames for each section
Next, lets create a frame for each section. Each frame will use the colors from your image to make it easier to visualize. I could use any widget, but frames are the most common widget to use as a container for other widgets (for example, a treeview or text widget with a scrollbar, a graph possibly with buttons, etc)
treeview_frame = tk.Frame(root, background="#FFF0C1", bd=1, relief="sunken")
graph_frame = tk.Frame(root, background="#D2E2FB", bd=1, relief="sunken")
text_frame = tk.Frame(root, background="#CCE4CA", bd=1, relief="sunken")
button_frame = tk.Frame(root, background="#F5C2C1", bd=1, relief="sunken")
Designing your layout
With that out of the way, we can lay the widgets out. We could use pack for this pretty easily, but grid can be a bit easier to understand when you're learning.
When I look at your image, it's pretty clear to me that you have two rows and three columns. It looks like the first column (column 0) is a smidge wider than the other columns. Similarly, the first row (row 0) looks to be a smidge taller than the other row. We'll use that information in a bit.
Laying out the widgets with grid
First, let's put everything in their respective rows and columns. It should be pretty obvious, so I'll let the code do the explaining.
treeview_frame.grid(row=0, column=0, sticky="nsew", padx=2, pady=2)
graph_frame.grid(row=1, column=0, sticky="nsew", padx=2, pady=2)
text_frame.grid(row=0, column=1, rowspan=2, sticky="nsew", padx=2, pady=2)
button_frame.grid(row=0, column=2, rowspan=2, sticky="nsew", padx=2, pady=2)
Configuring the grid as a whole
Next comes a very important step that almost all beginners forget: you need to give each row and column a weight. The weight determines how grid allocates extra space. Since you said you want a zoomed window, you'll probably have a bunch of extra space after adding in your actual widgets.
Here's where the relative widths and heights come in. The weight attribute is a value that specifies how extra space is allocated. For example, if you have a column with a weight of 1 (one), and another with a weight of 2, the one with the weight of 2 will get two times the extra space of the other. In For example, if tkinter calculates that there's 100 extra pixels it needs to fill, it will give 66 or 67 pixels to one, and 33 or 34 to the other.
Something to keep in mind is that rows and columns have a weight of 0 (zero). That means that they are not given any extra space.
A rule of thumb says that most of the weight should be given to the "main" widget on the screen. For a text editor that would be a text widget, for a drawing app it might be a canvas. For a reporting app it might be a treeview. Since you have all of that, I don't know exactly how you want the space allocated.
Configuring the rows
I'm going to guess, and say that row 0 where the treeview is should take up a little more than half the height. I'm going to give it a weight of 3 and the other a weight of 2. If you want both the treeview and graph to be the same size you would give them each a weight of 1. If the graph is a fixed size but the treeview should be as big as possible, give the graph a weight of 0 (zero) and the tree a weight of 1.
root.grid_rowconfigure(0, weight=3)
root.grid_rowconfigure(1, weight=2)
Configuring the columns
Now we need to go through the same exercise with the columns. It looks like the first column, column zero, takes up just a bit more room than the other columns. So, I'm going to give it a weight of 3 and the others a weight of 2. Like with the rows, you can put all the weight in one column, distribute it equally, or do it however you wish.
root.grid_columnconfigure(0, weight=3)
root.grid_columnconfigure(1, weight=2)
root.grid_columnconfigure(2, weight=2)
Final steps
That's it, for a basic layout.
If you call a call to root.mainloop() at the end of all that code you'll have a window that looks almost exactly like the picture you provided. Notice that it all scales beautifully as you resize the window.
Adding more widgets
Now you can start to add widgets to each frame, without having to worry too much about how they affect the rest of the window. Not only that, but you can use pack for some, and grid or place for others. For example, if you wanted the treeview to have a scrollbar, you might create it like this:
tree = ttk.Treeview(treeview_frame)
sb = ttk.Scrollbar(treeview_frame)
sb.pack(side="right", fill="y")
tree.pack(side="left", fill="both", expand=True)
The only thing that might surprise you is that if you put in a very large widget, it may mess up the proportions. The simplest solution is usually to set the width and height to something small, and let it grow to fit. If you make it large, it will want to be large, pushing other widgets away.
Related
This is the code that's giving me trouble.
f = Frame(root, width=1000, bg="blue")
f.pack(fill=X, expand=True)
l = Label(f, text="hi", width=10, bg="red", fg="white")
l.pack()
If I comment out the lines with the Label, the Frame displays with the right width. However, adding the Label seems to shrink the Frame down to the Label's size. Is there a way to prevent that from happening?
By default, both pack and grid shrink or grow a widget to fit its contents, which is what you want 99.9% of the time. The term that describes this feature is geometry propagation. There is a command to turn geometry propagation on or off when using pack (pack_propagate) and grid (grid_propagate).
Since you are using pack the syntax would be:
f.pack_propagate(0)
or maybe root.pack_propagate(0), depending on which widgets you actually want to affect. However, because you haven't given the frame height, its default height is one pixel so you still may not see the interior widgets. To get the full effect of what you want, you need to give the containing frame both a width and a height.
That being said, the vast majority of the time you should let Tkinter compute the size. When you turn geometry propagation off your GUI won't respond well to changes in resolution, changes in fonts, etc. Tkinter's geometry managers (pack, place and grid) are remarkably powerful. You should learn to take advantage of that power by using the right tool for the job.
I am new to Tkinter and still learning. My desktop resolution is 1366x768 and I am developing for an environment which is 2048x768. Right now I am setting the window resolution by detecting it like this -
w = main_container.winfo_screenwidth()
h = main_container.winfo_screenheight()
main_container.geometry(str(w) + "x" + str(h))
Now I have several screens and I cannot use grid on each and every screen. Like I have a Login Screen in which I have a Frame like this-
self.frame_background = tk.Frame(self.frame_parent, bg='#ffffff')
self.frame_background.pack(fill='both', expand=True, padx=100,
pady=100)
This is just an example of one widget. Since values for padx and pady are fixed with 100, if I am running my application on a 640x480 resolution device, the layout gets too stretched and almost nothing is visible inside this frame.
How can I set these padx and pady and other width and height values dynamically according to the screen size and resolutions?
Now I have several screens and I cannot use grid on each and every screen.
Why can't you? There's nothing preventing you from using grid on every screen. Though, it's actually quite common to mix and match pack and grid in different frames, and arguably a best practice.
Like I have a Login Screen in which I have a Label like this...
The code following that statement doesn't have a Label, so it's hard to know what you are trying to do.
if I am running my application on a 640x480...
First you say you have a desktop resolution of 1366x768, then say you are targeting 2048x768, and are now asking about 640x480. That's a pretty wide range of values. If you have to support something as low as 640x480 you definitely should not be hard-coding padding to such huge values. Typically padding is never more than one or two. Tkinter apps can be made to accommodate that wide range of screen sizes, but that relies on not hard-coding the size of widgets and padding as much as possible.
How can I set these padx and pady and other width and height values dynamically according to the screen size and resolutions?
Generally speaking, you shouldn't do that. The general rule of thumb with tkinter is that you make the widgets the smallest size they need to be and let the geometry managers worry about stretching or shrinking them to fit the available space. There are a few exceptions to that rule, but in general, that rule works quite well.
Unfortunately, geometry management questions are hard to answer without knowing more about what you're trying to do. For example, we need to know why you think you need padding of 100 pixels. That seems highly unusual. Plus, we need to know what else is in your frame, what your frame is in, and so on.
My advice is to remove the padding, or set them to very small values. Then, make sure you use all of the geometry manager options that are available to you, such as fill and expand with pack, and sticky and others for grid.
I want to eliminate strange extra space that seems to resist any size tweaking in my layout when using grid() alone, but calling in pack() sometimes make things worse: The GUI simply disappear entirely!
I read a few eye-opening layout answers from #Bryan Oakley such as:
When to use pack or grid layouts in tkinter?
and
Tkinter: grid or pack inside a grid?
but when I get down to write my own stuff, I still often have troubles.
My understanding:
I must have a Frame to fill the root window, otherwise there'd be no hope to fill the extra space in the window, however I tweak widgets alone.
For all the child widgets sitting inside a common parent Frame, I must use either pack() or grid() but not both.
When using grid() in a Frame, it's mandatory to specify Frame.grid_rowconfigure() and .grid_columnconfigure() with non-zero weight arguments. Otherwise, nothing would show up.
It's thus possible to have the main Frame using pack(), but its immediate child Frames all using grid(); Inside each of these child Frames on the grid, we could then pack() their own child widgets. In other words, we could interleave grid() and pack() by "regions" or container hierarchy levels, but never mix them in the same container: The only restriction.
By a careful weight design, I could fill a horizontal space in a parent Frame with a child Frame full of widgets laid out horizontally, e.g., all widgets use grid(sticky='nsew'), and the child Frame uses pack(side='top', fill='both', expand=True).
If my understanding was correct, then I could never figure out why #5 couldn't work for me, e.g., there is always unused extra space towards the right end of my horizontal child Frame inside the main Frame of the root window.
UPDATE 2
I figured it out. #5 didn't work for me because I forgot to specify .grid_columnconfigure(0, weight=1) in the main Frame before using grid(). My bad! Case closed.
UPDATE
I'm on macOS High Sierra, running python 3.6.4 Homebrew.
In what cases Tkinter's grid() cannot be mixed with pack()?
In all cases, you cannot use both grid and pack for widgets that have a common master. Within a master, all direct children must use the same geometry manager. Within an application as a whole, you can mix pack and grid all you want as long as you follow that one rule that you can't use them both for widgets that have the same parent.
I must have a Frame to fill the root window, otherwise there'd be no hope to fill the extra space in the window, however I tweak widgets alone.
This is not correct. You can easily fill all of the space in the root window without using a frame.
For all the child widgets sitting inside a common parent Frame, I must use either pack() or grid() but not both.
That is correct. The third option is to use place, though it's rarely the best choice.
When using grid() in a Frame, it's mandatory to specify Frame.grid_rowconfigure() and .grid_columnconfigure() with non-zero weight arguments. Otherwise, nothing would show up.
That is not true -- configuring rows and columns to have a non-zero weight isn't mandatory. It's usually a best practice, but it's not required in order for widgets to show up. The weight only applies to how grid manages extra space. Any widgets with a non-zero size should appear whether you use weights or not.
It's thus possible to have the main Frame using pack(), but its immediate child Frames all using grid()
Correct.
By a careful weight design, I could fill a horizontal space in a parent Frame with a child Frame full of widgets laid out horizontally, e.g., all widgets use grid(sticky='nsew'), and the child Frame uses pack(side='top', fill='both', expand=True).
That is correct.
I am trying to build a grid of frames in each there is a matplotlib figure.
When I resize the window the figure remain with fix size and are not resizing to fit the empty space.
Is there a way to make the figure change its size according to the canvas (hopefully that it does change with the window size)?
This is how I do the embedding in each frame:
self._figure = pyplot.figure(figsize=(4,4))
self._axes = self._figure.add_subplot(111)
self._canvas = FigureCanvasTkAgg(self._figure, master=self._root)
self._canvas.get_tk_widget().grid(row=1,column=1,rowspan = 4)
This is most likely related to this question. The gist is that there are two parts to making a Tk grid cell grow:
Use the sticky keyword when applying grid to your widget, e.g., widget.grid(..., sticky=Tkinter.NSEW (Python 2.7) to make widget be able to grow in all four directions. See this documentation for more details.
Tell the parent/master to make the respective column/row grow when resizing by calling parent.grid_columnconfigure(...) and/or parent.grid_rowconfigure(...) with the desired row, column, and weight keyword. E.g., parent.grid_columnconfigure(col=0, weight=1) makes the first column take all available space (as long as there are no other columns, or they have not been similary configured). See the grid_columnconfigure documentation and the grid_rowconfigure documentation for more details, e.g., about how the weights affect multiple columns/rows.
This page contains many more details about grid layouts.
I'm working on a Tkinter application using the Grid geometry manager (It's my first time using Grid, and I love it! :D) that contains a scrolling listbox that displays options whenever a user selects an option.
It's working well, but the window is small and ugly. When I maximize it, everything else resizes fine (thanks to columnconfigure) but the listbox stays the same height. Is there a simple way to fix this?
(I have seen this question but it's for Pack, not Grid)
Thanks in advance.
Code sample because one was asked for:
self.tasklist = Listbox(self.frame, exportselection=0)
self.tasklist.grid(row=1, sticky=W+E+N+S)
yscroll = Scrollbar(self.frame, command=self.tasklist.yview, orient=VERTICAL)
yscroll.grid(row=1, column=1, sticky=N+S)
Without seeing more of your code it's impossible to say. Most likely your listbox is expanding properly, but your self.frame is not. Though, I don't see you giving any weight to the row and column that the listbox is in, so that could be a factor.
An easy way to debug this is to give self.frame a garish color that will stick out (red, bright green, etc). Then it will be easy to see if the listbox is properly resizing inside the frame, and if the frame is properly resizing inside its parent.