I'm re-doing from scratch a GUI for a program beacuse I realise that a new layout would make it easier to use but I'm getting very confused.
I've been looking for scripts resulting in similar layout, but the more I read about it, the less I understand.
The image below is the very basic structure of the GUI that I'm trying to make:
I'm aware that it is a very simple question, but the docs and previous questions are not making the understading process any easier. I believe that having just the code of the main layout would be a huge help to finally understand how to organise frames.
NOTE: The background color and text are there just to make the layout more clear. I'm only asking for the very basic frame's arrangement.
As always, thanks a lot to anyone who help.
Cheers
You can use pack, place, or grid. They all can produce this layout, though I personally recommend not using grid.
For me, pack is the natural choice. pack excels at layouts where widgets take up an entire side of a region. If that fits your design, pack requires fewer lines of code than grid, and fewer and less confusing options than place.
In this specific case the blue area clearly takes up the whole left side, and the yellow and red fill up the top and bottom of the right side, so pack is well suited to the task.
For the following examples, we'll start with this code:
import tkinter as tk
root = tk.Tk()
f1 = tk.Frame(root, bg='blue', width=200,height=400)
f2 = tk.Frame(root, bg='yellow', width=400, height=300)
f3 = tk.Frame(root, bg='red', width=400, height=100)
do_layout()
root.mainloop()
Using pack
pack works by placing widgets along a side of an empty area. In this case, the blue area is clearly taking up the left side. In the space that remains after adding the blue area, the yellow space takes up the top part of the remaining space and the red area takes the bottom.
def do_layout():
f1.pack(side="left", fill="both", expand=True)
f2.pack(side="top", fill="both", expand=True)
f3.pack(side="bottom", fill="both", expand=True)
Whether this is what you actually want or not is hard to say. It depends a lot on how you want the widgets to react when you add children or you resize the window. You may want to change the expand and/or fill options for some of the windows, though that depends on how you want the widgets to react when the user resizes the window.
Using grid
Grid is often the easiest technique to grasp. You specify positions within a row or column, and can decide if an item should span one or more rows or column.
In your case you clearly have two rows and two columns, and the blue area spans both rows.
def do_layout():
f1.grid(row=0, column=0, rowspan=2, sticky="nsew")
f2.grid(row=0, column=1, sticky="nsew")
f3.grid(row=1, column=1, sticky="nsew")
root.grid_rowconfigure(0, weight=1)
root.grid_columnconfigure(1, weight=1)
Like with the pack example, it's hard to say if this meets your actual needs. Again, it depends on how you want the UI to behave when you add widgets to the frames and when you resize the window.
Notice that grid requires a couple of extra lines of code. As a general rule of thumb you should always give at least one row and one column a non-default weight so that grid knows what to do with unallocated space.
Using place
place is arguably the weakest choice of the three for this type of layout. Nevertheless, you can achieve the same result as with the others.
def do_layout():
f1.place(x=0, y=0, relwidth=.3, relheight=1.0)
f2.place(relx=.3, y=0, relwidth=.7, relheight=.6)
f3.place(relx=.3, rely=.6, relwidth=.7, relheight=.4)
One of the significant differences between place and the other options is that the use of place will not cause the containing window to grow or shrink to fit contents. You are required to make sure that the containing widget (in this case, root) is the correct size.
Other options
Another option would be to use a paned widget, in the case where you want the user to be able to adjust the proportions of an area. For example, you could use a horizontal paned widget to allow the user to make the blue area widget or narrower. Likewise, you could use a vertical paned window if you want the user to be able to adjust the relative height of each area.
You can also mix and match. While you can't use both grid and pack directly within the root window, you could use pack to lay out a frame on the left and a frame on the right, and then use grid within the right to lay out one frame on top and one on bottom. Or visa versa.
Related
I tried results from other answers but that didn't solve my Problem.
Here is a screenshot of my App on which I am working:
There above, (the one with >myIDLE text) is my Text box in disabled state.
When I decrease the font and use sticky="nsew" method, it works fine. But, when I increase the font and use the same method, something like this happens:
How to permanently FIX the size?
Currently I am declaring size like this:
from tkinter import *
root = Tk()
root.state('zoomed')
root.title("myIDLE Window")
root.iconbitmap(".\\resources\\images\\myIDLE.ico")
root.resizable(0, 0)
swidth = root.winfo_screenwidth()
sheight = root.winfo_screenheight()
mainf = Frame(root)
mainf.grid(row=0, column=0, padx=5, pady=7)
display = Text(mainf, height=int(33*(sheight/864)), width=int(112*(swidth/1536)))
display.grid(row=0, column=0)
root.mainloop()
I read in one answer about a similar question to use .grid_propogate(False)
I used it with following result:
On root window: Nothing changed
On Frame (mainf): Screen became white
On Text Widget: Nothing Changed
Please tell a way so I fix this problem
Also I am sorry I cant share my full code, but pls feel free to ask any part of the code
If you want the text widget to be a specific size in pixels, the following technique works:
Create a frame with the size that you want
turn geometry propagation off for this frame
create a text widget with a width of 1 and a height of 1, using the frame as its master
add the text widget to the frame so that it fills the frame.
add the frame to your app however you want, using grid, pack, or place.
The following example will appear to create a text widget that is 400 pixels wide and tall. Changing the font will not change the size since it is the frame controlling the size of the text widget rather than the text widget controlling the size of the frame.
import tkinter as tk
...
text_frame = tk.Frame(root, width=400, height=400)
text_frame.pack_propagate(0)
text = tk.Text(text_frame, width=1, height=1)
text.pack(fill="both", expand=True, padx=20, pady=20)
...
Found this question through Google. Ended up finding an easy solution.
By using the root.geometry() method, increasing and decreasing font will no longer change the size of the window.
For example, you may do root.geometry("250x250").
I imagine some background Boolean gets ticked off upon its usage that says "the program specified a size for the window, so it probably doesn't want to be changed through external means."
I'm sure i'm missing something here, but I haven't been able to add a gap or space between two ttk.Labelframe objects when they have been placed next to each other using the Grid() geometry manager (ref pic below).
A simple representation of the structure of my code (pseudo) is as follows:
Root()
mainframe = ttk.Frame()
frame1 = ttk.Labelframe()
frame1.Grid(row=0, column=0)
frame2 = ttk.Labelframe()
frame2.Grid(row=0, column=1)
I have tried mainframe.rowconfigure(0,weight=1) and mainframe.columnconfigure((0,1), weight=1) to no avail (however this works for other widgets inside those frames). I have also tried adding more padding to mainframe but this only adds padding around the inside of mainframe. frame1 and frame2, not between them.
I have probably missed a keyword or property somewhere that will do the trick but i'm yet to find it. Remember, i'm using themed tkinter widgets (ttk) so not all keywords tk widgets have apply to the ttk widgets.
Thanks in advance all,
Isn't it just typical that you find the answer you're looking for only after posting the question . . .
It seems it was a keyword in mainframe.columnconfigure() that i missed. The keyword is pad funnily enough (-.-). i.e.: mainframe.columnconfigure((0,1), weight=1, pad=5) will configure columns 0 through 1 to each have a weighting of 1 (if stretched) and an additional padding of 5 pixels that will be added above the
largest cell in those columns, resulting in space between the frames.
EDIT: Alternatively, there is also a padx and pady option on the grid command itself: the_label.grid(row=0, column=1, padx=10) - Bryan Oakley
Hope this helps someone out.
I'm making a small PyQt gui software for some scripts I often use. Part of this gui has a list of buttons to the right, and the amount of buttons can vary from each run of the gui. For that reason I would like to have them in a scrollarea. This works sort of OK, but for some reason the scroll area will not expand the very last bit when the window size permits it (so no scroller would be shown).
It behaves as if a maximumheight has been set elsewhere, even if I set a maximumheight much higher than the window size?!?
If I set the minimumheight to more than widgetsize, the scroller is hidden as expected, but not if minimumsize is smaller so scroller will be used when window is smaller.
Cutout, left with minimumheight of 550, and right with minimumheight of 200
self.Pvbox = QtGui.QVBoxLayout()
self.syncButton=QtGui.QPushButton('Sync')
self.syncButton.setMaximumWidth(100)
self.Pvbox.addWidget(self.syncButton)
PbuttonWdg = QtGui.QWidget()
Pbuttonlayout = QtGui.QVBoxLayout()
self.nbuttons=[]
c=0
for n in main.Pnames:
self.nbuttons.append(QtGui.QPushButton(str(n)))
self.nbuttons[-1].setMaximumWidth(80)
Pbuttonlayout.addWidget(self.nbuttons[-1])
c+=1
PbuttonWdg.setLayout(Pbuttonlayout)
scroll=QtGui.QScrollArea()
scroll.setMaximumWidth(110)
scroll.setMinimumWidth(110)
scroll.setMinimumHeight(550)
scroll.setMaximumHeight(800)
scroll.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded)
scroll.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
scroll.setWidgetResizable(False)
scroll.setWidget(PbuttonWdg)
self.Pvbox.addWidget(scroll)
self.Pvbox.addStretch(1)
self.localButton=QtGui.QPushButton('Local')
self.localButton.setMaximumWidth(100)
self.Pvbox.addWidget(self.localButton)
Found the problem...
The "addStretch(1)" after the scrollarea, makes that stretch part start to stretch before the scrollarea is fully expanded for some reason. Removing that stretcher makes the scrollarea do all the expanding/stretching (if the sizepolicy permits).
So I consider this as a solution, eventhough the optimal for me would be that the scrollarea would expand fully before any stretch takes over...
I'm writing a simple Tkinter app that has a main frame with two smaller frames on the sides, like so:
root = tk.Tk()
root.title("MyApp")
root.rowconfigure(0,weight=1)
root.columnconfigure(0,weight=1)
root.columnconfigure(1,weight=4)
root.columnconfigure(2,weight=1)
frame1 = tk.Frame(root)
frame1.grid(row=0,column=0,sticky='NSEW')
button1 = tk.Button(frame1,relief=tk.FLAT,text="button")
button1.grid()
frame2 = tk.Frame(root)
frame2.grid(row=0,column=1,sticky='NSEW')
button2 = tk.Button(frame2,relief=tk.FLAT,text="button")
button2.grid()
frame3 = tk.Frame(root)
frame3.grid(column=2,row=0,sticky='NSEW')
button3 = tk.Button(frame3,relief=tk.FLAT,text="button")
button3.grid()
But if I add more widgets to one of these frames:
button4 = tk.Button(frame3,relief=tk.FLAT,text="button")
button4.grid(row=0,column=1)
button5 = tk.Button(frame3,relief=tk.FLAT,text="button")
button5.grid(row=0,column=2)
then frame3 enlarges itself (instead of shrinking, as I understand it should do), ignoring the 1-to-4 specification of the columnconfigure. I've been googling like crazy but found no answer, so any ideas will be more than welcome. Thanks.
Update:
Thanks for the answers. I wanted to add a screenshot but I don't have enough reputation to post an image. If weights don't prevent a column from being overexpanded, how can I make sure the frames will keep their initial proportions despite how many widgets I add to them. Should I turn grid_propagate off or is there a better way?
Each frame is initially the size it need to be to contain all the widgets. The weights determine the allocation of extra space if you stretch the window. They also determine the rate at which different widgets compress if you shring the window. In your case, the second frame looses space 4x faster.
columnconfigure affects how widgets use extra space, they aren't absolute column widths. A column with a weight of 4 can easily be larger or smaller or the same size as a column with a weight of 1 or 2.
From the official documentation for the grid command:
The -weight option (an integer value) sets the relative weight for
apportioning any extra spaces among columns. A weight of zero (0)
indicates the column will not deviate from its requested size. A
column whose weight is two will grow at twice the rate as a column of
weight one when extra space is allocated to the layout.
When you put items in a frame, by default the frame will attempt to resize to exactly fit its contents. That means that if you add a widget to a frame, that frame will grow unless there's already extra space inside the frame for the widget.
Is there any way to move widgets to the centre of the screen using .grid geometry in Python 3.3's tkinter? Even when changing the rows and coloumns everything is at the top left. (New to Python, stackoverflow and coding in general).
Thank you
Within the tkinter module, you can use the .grid method to be able to centre your widgets on the screen:
Label(master, text="First")
Label.grid(row=0, column=1)
Label2(master, text="Second")
Label2.grid(row=1, column=1)
However I would use the .place function; this will allow you to write the coordinates where you want the widgets to be.
Label(master, text="First")
Label..place(x=20, y=30)
Label2(master, text="Second")
Label2..place(x=20, y=50)
This .place method is much easier to be able to maintain, when moving widgets around.
If you're not bound to using the grid geometry manager, you can also try out pack.
Pack automatically places widgets in the center of the window, and places new widgets below the first one if you continue to 'pack' them without specifying any particular position.
If you want to specify where a widget goes, you can also do so in the .pack() call, but you're limited in options.
Here's an example:
Mywidget.pack(side=TOP)
Mywidget.pack(side=BOTTOM)
side = allows you only a few options though, such as TOP, BOTTOM, LEFT, and RIGHT
I don't know if this is too limiting for you, but if you're new to Python, pack is somewhat simpler than grid to use in my opinion. Although you may well need grid for more sophisticated placements of widgets, ect.
Grid (as well as place and pack) can be used to position widgets in the center of their parent. If you're asking about the center of the physical screen, the best you can do is use wm_geometry to move the window to the physical center.
If you really want to use grid, and you want to place a single widget in the center of its parent, place it in row 1, column 1, and give the surrounding rows and columns a weight of 1 (one). This will guarantee that the edges will grow and shrink as the window resizes.
Most likely what is happening is that you're trying to put a widget in some random grid cell, but the widget still shows up in the corner. This is because the other cells don't exist -- rows and columns by default have zero width and height, so even if you put something at row 10,000, if there is nothing in the other 9,999 rows, row 10,000 will appear at the top. Adding to the confusion is the fact that by default, rows and columns don't grow as the window grows.
This is really only a problem when experimenting. In practice, you'll typically have widgets in all your rows and columns so these rows and columns won't have zero width.
Here's a working example. Notice that as you resize the window that the label stays in the middle. The thing that makes this work is that I've given a weight to all rows and columns except the center one, so they take up all the extra space which leaves the sole widget in the center.
import tkinter as tk
class Example(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent)
self.label = tk.Label(self, text="I am centered!")
self.label.grid(row=1, column=1, sticky="nsew")
self.grid_rowconfigure(0, weight=1)
self.grid_rowconfigure(2, weight=1)
self.grid_columnconfigure(0, weight=1)
self.grid_columnconfigure(2, weight=1)
if __name__ == "__main__":
root = tk.Tk()
Example(root).pack(fill="both", expand=True)
root.mainloop()