I am currently working on a Python Project and I would like to create the UI with PyQt5 (preferably with the help of PyQt5 Designer).
I have recently finished a Youtube tutorial about the basics of PyQt5, but I am still at beginner level.
I am currently trying to create the Main Menu of the program.
I would like the Main Menu to look like this (the background image would decorate the background but it's not today's problem):
enter image description here
But if possible, I hope there is a way to achieve it, I would like the buttons to scale with window size.
So for example when the user resizes the window, I would like it to look somehow ike this (not exactly, but something similar):enter image description here
So as you can see I would like the button width and height getting changed, as the user resizes the window (and keeping their ratio within the window).
I hope there is a way to solve this problem.
Thank you for the help in advance.
I tried to right click on QWidget and then clicking on the last option (might be Alignment in English, I am not native) and then clicking on that option (Maybe align as Grid in English)
enter image description here
After doing this, the layout this expanded to the window size, the button got resized as well.
enter image description here
enter image description here
But the button width corresponds to the layout size (not just for example 1/3 of it as I would like) and the height does not change that greatly, just the width.
Premise: there are various questions on the matter here on StackOverflow; while they mostly point out to documentation or answer to specific issues, I've not found a comprehensive answer that could be considered as a "main reference" which can be used as duplicate pointer yet, especially when the developer wants to have big margins and leave some empty space within the UI. The following may be considered as a generic answer thanks to the simple nature of this question.
You are on the right track: layout managers are the solution, and while it's not forbidden to use explicit geometries, that practice its frowned upon[1].
Layout management basics
Know more about layout managers
Qt provides 2 basic layout types:
QBoxLayout, a "direction based" layout, that aligns widget along a specified direction: horizontally or vertically; Qt provides two convenience classes for those directions: respectively, QHBoxLayout and QVBoxLayout;
QGridLayout, a layout that aligns widget using a grid, similarly to a table or a spreadsheet;
There are also more complex layout managers:
form layout (QFormLayout), providing a simple "form style" layout, normally shown with a label on the left, and some widget on the right, with "items" shown from the top to the bottom;
stacked layout (QStackedLayout), which instead switches between visible widgets as they were "pages" (see below);
Additionally, there are complex widgets that implement custom layouts on their own:
QTabWidget is based on the convenience QStackedWidget class (using the above mentioned QStackedLayout) with the addition of a tab bar to allow the user to switch between them;
QSplitter, that can split the available space horizontally or vertically, with handles that allow resizing the contents within the available area;
QToolBox that behaves similarly to QTabWidget, with the difference that the "pages" are put in a column, similarly to a file cabinet;
Finally, in Qt, layout managers use layout items, which are abstract object that represent physical geometries that are being shown on the screen. Those items may be widgets, spacers or even nested layouts.
In this answer, I will only cover the basic (grid and boxed) layout managers.
Set a main layout
The first step is to ensure that the parent widget (the one that contains a group of child widgets) has a layout manager: in Designer (as the documentation explains), you have to open the context menu of the "container widget" by right clicking on an empty area inside it, and choose an appropriate item in the "Lay out" submenu.
Using code, that's normally done like this:
widget = QWidget()
layout = QVBoxLayout()
widget.setLayout(layout)
# alternatively, just use the target widget as argument in the constructor
# layout = QVBoxLayout(widget)
# the above line automatically calls setLayout(widget) internally
# ...
layout.addWidget(someWidget)
The issue of "responsiveness" and spacing
Modern UIs may often have lots of free space with relatively small controls. For instance, a login interface takes some amount of screen space, even if its input fields are quite small, allowing some space to show a fancy background or, even, just to better capture attention from the user.
With nowadays devices, we normally have a lot of available (and actually readable) screen size, but that wasn't the case until 10-20 years ago, when it was still normal to have "big" screens with a very small resolution (19" CRT screens only showing 1280x960 pixels if not less... heck, that's one of my screens!): you had about 90-100 pixels for inch, while High DPI screens or modern mobile devices can show about 10 times more in the same size. Less resolution means that it's more difficult to distinguish objects on the screen, especially when dealing with text; it's like having some light form of visual impairment: you may still be able to read text and set shapes apart, but it's difficult, you need to be more focused on what you're trying to see, and after some time that can be annoying and create fatigue.
The QtWidget module, similarly to other namespaces of other "old" common toolkits (eg. Gtk), was born on those concepts, meaning that "widgets" (UI elements that are used as human interface) have some requirements based on the available pixels, also considering the capabilities of the system to show text.
Long story short: widgets normally take as much space as it's possible in order to show their context, based on the principles above, but also considering previous, existing conventions.
Some widgets use space weirdly
Let's create two buttons and manually set their geometries:
And then set a vertical layout as explained above:
As you can see, they take all the available horizontal space, but do not extend vertically. The previous convention says that buttons do not expand vertically.
In the early days, UIs were simple: there were few buttons that were normally shown at the bottom of some dialog, and they used as much horizontal space as possible. Screen resolutions were actually small (640x480, or even less when using simple ASCII characters to display UI elements), there was no point in having "big" buttons, not to mention tall buttons.
Other widgets require different space usage
Let's add a QLineEdit to the top of the above layout: QLineEdit is a simple input field, it normally requires a simple string consisting of a single line; meaning that there is no point in requiring more vertical space, so the result is quite the same:
Now, we need a more complex text input, allowing text that may have multiple lines; let's add a QTextEdit widget:
Whoa, what's happened? Since QTextEdit is normally capable of showing multiple lines, it's important to take all the available space. Since the other widgets don't really need all that space, it will take advantage of it.
Size hints and policies
Layout managers have to decide how to set the geometry (position and size) of all the items they manage. In order to do that, they query all the items they manage (consider that layouts may be nested) and ask them about their size hints (the size that the item prefers), their constraints (minimum/maximum or fixed size) and their policy.
All Qt widgets provide a size policy, which tells the layout that manages them how they could be eventually resized and positioned. QPushButton has a fixed vertical policy (meaning that they usually have a predefined fixed height[2], as shown in the second image), while all scroll areas have an expanding vertical and horizontal policy, so they will try to take advantage of all the available space (and the layout will decide how much, based on all the other items).
Stretch factors
Basic layout managers support stretch factors, which can also be used as spacers: each layout item has a stretch factor that is considered as a ratio (computed with the total sum of all stretch factors of the layout).
Consider the following layout, using 2 QTextEdits:
Now, select the container widget in Designer, and scroll to the bottom of the Property Editor until we get the container layout properties; change the layoutStretch property to 1, 2, meaning that the available vertical space will be split in 3, with the first QTextEdit using the result of that value, and the second using twice (height / sum_of_stretches * stretch_of_item):
Margins and spaces
Considering the above, we still want to have a fancy UI that uses a lot of the huge available resolution and show very tiny widgets.
QBoxLayout classes provide the helper functions addStretch() and insertStretch(), that allow to create "empty" spaces (using QSpacerItem objects). This can also be done in Designer, by dragging and dropping the "Horizontal" or "Vertical" spacer item from the widget box; in the following image, I've added a vertical spacer to the top, and changed again the layoutStretch property to 1, 1, 2, meaning that there will be an empty space on top that is tall as much as the first QTextEdit, and both will be half of the second:
Complex layout management
As said above, QLayout subclasses manage their items, including nested layouts[3]. Let's restart from the basic two buttons and add spacers above and below:
That's still not fine. Let's set stretch factors properly; for instance, let the buttons take 1/5 of the available space each, with 2/5 of the space above and a remaining fifth below:
But that still doesn't work as expected. Remember about the size policy explained above. That property can be changed also in Designer (or by code by using setSizePolicy()); select those buttons and change their vertical policies to Preferred [4]:
That's better. But still not close enough: there's still a lot of unnecessarily used horizontal space.
We could change the maximumWidth properties of those buttons, but that would be fixed; we don't like that: the buttons will always have the same width, even if the window is very wide.
Enter QGridLayout
One of the benefits (and falls) of QGridLayout is that it always has a static amount of rows and columns [5], even if no layout item exists at that row/column cell position. This means that we can use its setRowStretch() and setColumnStretch() functions even when the layout has no widget for such row or column amount.
In Designer, we can change the layout type to a grid by right clicking on an empty area of the container, then it's just a matter of setting proper stretch factors, but if you already had a layout set it's better to select the Break Layout item and manually set an hypothetical layout by hand, then select Grid Layout from the submenu.
Let's restart again from scratch, as shown in the first image; break the layout and reposition/resize the buttons:
Then select a grid layout from the context menu (the following shows buttons for which a Preferred vertical size policy was already set):
Now add horizontal and vertical spacers:
Assuming that the vertical size policy of those buttons is set to Preferred as explained above, finally update the parent layout's layoutRowStretch and layoutColumnStretch factors to 2, 1, 1, 1 and 1, 1, 1 respectively; the vertical space will be split in 5, with an empty area occupying twice the resulting value, while the buttons and the bottom spacer occupying that fifth each; horizontally, left and right spacers will be as wide as the buttons:
If we resize the form or its preview, the button sizes are more responsive:
Note that in order to do that by code you must previously consider cell positions: the buttons will be added to rows 1 and 2, and column 1, then you have to properly call setRowStretch() and setColumnStretch() with appropriate indexes and factors.
This is one of the reasons for which QGridLayout may not always be the proper choice, especially for dynamic layouts for which the row/column cell count might not be known at first.
Layout management seems difficult, is it required?
The simple answer is "no", but reality is quite more complex.
As mentioned above, widgets must always ensure that they are always visible and usable. Modern systems use HighDPI screens, meaning that the physical pixels are never the same as logical pixels: a line that has a width of 1 "pixel" may actually be 10 pixels wide. Text may also depend on the font scaling.
Some visually impaired users (including people just having "simple" presbyopia) may set up their computers to have high font scale ratios (150, 200 or even more) in order to be able to read text more easily; some people may just use a peculiar default font that, at the default size, requires much more space to be shown, vertically and/or horizontally. Since Qt always tries to fully show a widget containing text considering the required space of its font, the result is that you may have overlapping widgets, because you didn't consider that another widget on its left or top may require more space than you thought.
The rule of thumb is: what you see on your device(s) is never what others will see.
Qt layout management (including QStyle functions, specifically those related to QSize such as QStyle.sizeFromContents()) always consider these factors, and, besides some unexpected behavior/bug, you can normally expect a resulting UI that is properly shown to the user.
99.9% of the times somebody wants to use fixed geometries, they are doing something wrong: they are probably trying to do something for the wrong reason (normally, lack of experience), and, in any case, they are not considering the above aspects.
As a reference, you've probably browsed some website on a mobile device that is simply "not responsive": you have to scroll a lot, and navigation is really annoying. That is because those website obviously didn't consider modern devices; and that's as annoying as a "not layout managed" UI might look. Luckily, even if the QtWidgets module is "old", it considers these modern aspects, and, even considering some glitches and inconsistencies, it normally allows proper geometry management as long as layout managers are properly used.
[1]: there is theoretically nothing wrong in explicitly setting geometries, as long as it's done with awareness; 99% of the times, it isn't: object require a certain size in order to be properly shown and used, which requires being aware of system settings: most importantly screen DPI and font scaling; Qt is quite careful about these aspects and tries to ensure that all widgets are properly displayed and usable; if you're getting issues with font or widget display, avoiding layout managers is not the solution;
[2]: Qt uses QStyle functions to decide how wide or tall a widget should or could be, based on the detected system configuration; you should normally trust it;
[3]: See the following posts: 1, 2 and 3;
[4]: It's possible to set properties to multiple widgets at once, as long as those properties are common; since the sizePolicy property is common to all widgets, we can select both buttons (using Ctrl) and the property change will be applied to both of them;
[5]: See this related post;
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've always found the pack() geometry manager quite ambiguous in terms of how it acts when widgets are added.
Here I have a simple code for creating a new frame within a much bigger parent frame. The frame size has been set to 300x300. The problem is that if I create a label with the pack() geometry manager within this frame, it will suppress the original frame size. Basically the frame will become as big as is the label.
If I use the place() geometry manager, then there is no problem and the frame stays at the original 300x300 size.
The question is - why does packing a label within the frame affects its size? And then what is the best way to avoid this problem and have everything fixed at the size as they are set?
class MainRightFrame(Frame):
def __init__(self, parent):
Frame.__init__(self, parent)
self.place(x=600, y=0)
self.config(height=300, width=300, bg='green')
label = Label(self, text='Left Frame')
label.place(x=10, y=10) # OPTION 1
# label.pack() # OPTION 2
why does packing a label within the frame affects its size?
Because that is how the packer is designed to work. It will shrink or grow to fit its contents, which is what you want 99.99% of the time.
For the canonical documentation for how pack works, see the official tcl/tk documentation here:
The packer algorithm
And then what is the best way to avoid this problem and have everything fixed at the size as they are set?
The best wait to avoid this "problem" is to use place. However, the way both pack and grid works makes it much easier then using place to create a responsive UI that can handle changes in font size, resolution, and the user manually resizing the window.
In over a couple decades of writing GUIs with python/tkinter and tcl/tk, I have never used place except for extremely special circumstances. Its simply too difficult to use for must common layouts.
If you absolutely insist on using pack or grid without this "shrink to fit" behavior, you can pass a false value to the pack_propagate or grid_propagate method of the containing frame (eg: self.pack_propagate(False)). In my experience this is very rarely the right solution.
To fix this, add the following line after the line beginning: self.config(...:
self.pack_propagate(0)
See here for a documentary explaining this.
As #KārlisRieksts noted, this approach does not work however if the frame (or other parent widget) is packed with place() geometry manager. The child widgets will then affect the size of the parent.
Are there any best practice tips regarding when one should use pack vs. grid for their layouts?
From what I've been reading via google, the concencus seems to be that grid can handle any pack scenario but not vice-versa.
To start the conversation, it appears that one use case that favors grid vs. pack is when one wants to show/hide widgets.
Neither is intrinsically better than the other. Each have strengths and weaknesses. Learn what those are and the choice of which to use becomes obvious.
grid is considerably easier to use if you need to lay things out in a grid. pack is generally easier to use if all you need to do is put some widgets in a single row or single column. There's a whole lot of gray area in-between where neither is necessarily better than the other.
The other thing to consider is what you said in your question: if you want to show and hide widgets at run-time, grid is probably the best choice because of the grid_remove method which remembers the values of all of the configured attributes in case you want to re-add the widget.
Personally, my first choice is always to use pack because I first learned Tk back when there was no grid command. If I can't do it easily in pack, or if I'm very clearly laying things out in a grid, I'll use grid.
I always recommend grid over pack for polished applications. There are only a few edge cases where pack is easier and fits the bill (everything in one row or col). grid has better "composability" (e.g. megawidgets or gridding elements of gridded elements). The reasons to prefer grid are the extra fine-tuning options that it provides. The use of weight (which effects growing and shrinking btw), minsize and maxsize, as well as convenience features like enforcing uniform rows/columns.
A fully gridded app of any size will use (significantly) fewer frames than an equivalent packed app, and have better shrink/expand control over inner elements.
BTW, both pack and grid can show/hide sub-elements, though the syntax differs slightly between the two. Grid is just slightly better because 'remove' (rather than 'forget') will remember the grid options on the slave widget.
I personally just think grid is a lot easier to work with, so I would use that. Of course, you've probably read the one thing you should never do is try to use both at the same time in the same container. Thank you Bryan Oakley for making that distinction.
What is the best way to have transparency of specific widgets in a PyGTK application? I do not want to use themes because the transparency of each of the widgets will be changing through animation.
The only thing I can find is to use cairo to draw widgets with an Alpha, but I can't figure out how to do this. Is there perhaps a better way to do this as well?
Thanks!
Assuming that your program runs under composition manager, you could get per-widget transparency by manipulating widget's X window. Look at gtk.gdk.Window.set_opacity().
Note, it is not gtk.Window; you can get this object by getting its window property (buttonWidget.window), but only when widget is realized and only when widget does handle events -- gtk.Label does not have its own X window for instance.
If you need to work also when you don't have composition manager, drawing your widgets by yourself is the only option -- but you don't necessarily have to use cairo; drawing pixel by pixel on the bare X window will also work.