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.
Related
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 can set the background of the main window, and I don't need to worry about the window trying to fit the background image or the image shoving the widgets out of the way.
However when I try to do the same with a frame it becomes a huge mess...
I have tried a few things but everything seams to be related to resizing the image itself and I don't want to warp the image. I want the image to be in the frame and NOT have the frame resize to fit the image.
Is there a way to place the image into the background of the frame without the frame changing in size?
EDIT: Note: The image I am using is large enough to fill the screen so if there is resizing going on by the user the image will cover all the extra space.
So this is how I add a background image to the main window:
bgImage=PhotoImage(file="./Colors/bgImage.png")
bgLable = Label(root,image = bgImage)
bgLable.image = bgImage
bgLable.grid(row=0,column=0,columnspan=3,rowspan=8)
However when I try to do the same thing with a frame it resizes the frame as well:
FrameTL = Frame(root, width = 100)
FrameTL.grid(row = 0, column = 0, rowspan = 20, columnspan = 1, sticky = W+N+S)
TLbg = Label(FrameTL,image = bgImage)
TLbg.image = bgImage
TLbg.grid(row=0,column=0)
There were some other things I tried but they would warp the image to fit the window/frame and I do not want to warp the image.
EDIT:
I have found a work around of sorts. Instead of using frames I used .grid for all my widgets except for the widgets I needed to be on my side menu. The side menu widgets were not aliening to the top left of the window so I was trying to use frames to fix the problem (using .place for now). The frames would work but I could not find a way to make frames transparent so I can keep my background from my root window. And that is why I got stuck on trying to a background image to my frame but without the frame resizing to fit the image.
Is there a way to place the image into the background of the frame without the frame changing in size?
Yes. Use place with relative coordinates. The following will place the label with your image in the center of the root window, and will not affect any other widgets.
some_frame = tk.Frame(root, borderwidth=2, relief="raised", width=200, height=200)
...
background = tk.Label(some_frame, text="I am in the center", background="pink")
background.place(relx=.5, rely=.5, anchor="c")
This is one of the few times when I think place is superior to using grid or pack. This is what the official documentation says about place:
Unlike many other geometry managers (such as the packer) the placer does not make any attempt to manipulate the geometry of the master windows or the parents of slave windows (i.e. it does not set their requested sizes). To control the sizes of these windows, make them windows like frames and canvases that provide configuration options for this purpose.
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.
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.