Stacked Layout causing alignment to top problem - python

I have a vertical layout with alignment set to "top" that contains what I want to be a fixed height "header" bar contained in a QHBoxLayout, with a QTextEdit window underneath it. One of the sections of this header bar contains a QStackedLayout. If you run the sample code below, when resizing the window the QTextEdit window begins pulling away from the "header" bar instead of staying anchored just underneath the "header" bar. The intended behavior would be for the QTextEdit window to remain anchored just underneath the header bar and expand and contract along its bottom margin. How do I fix the size of the QStackedLayout element to achieve this behavior?
from PySide2 import QtWidgets, QtCore, QtGui
from PySide2.QtWidgets import QApplication
import sys
class Demo(QtWidgets.QDialog):
def __init__(self, parent=None):
super(Demo, self).__init__(parent)
self.main_layout = QtWidgets.QVBoxLayout(self)
self.main_layout.setAlignment(QtCore.Qt.AlignTop)
# Header with combobox anchored to the top of the layout
self.data_results_layout = QtWidgets.QHBoxLayout()
self.results_stacked_layout = QtWidgets.QStackedLayout()
self.data_results_layout.addLayout(self.results_stacked_layout)
self.combobox_results_widget = QtWidgets.QWidget()
self.combobox_results_widget.setFixedHeight(25)
self.combobox_results_layout = QtWidgets.QVBoxLayout(self.combobox_results_widget)
self.combobox_results_layout.setContentsMargins(0,0,0,0)
self.directory_combobox = QtWidgets.QComboBox()
self.directory_combobox.setFixedHeight(25)
self.combobox_results_layout.addWidget(self.directory_combobox)
self.results_stacked_layout.addWidget(self.combobox_results_widget)
self.main_layout.addLayout(self.data_results_layout)
# QTextEdit Window
self.asset_data_layout = QtWidgets.QVBoxLayout()
self.asset_data_window = QtWidgets.QTextEdit('Ready.')
self.asset_data_layout.addWidget(self.asset_data_window)
self.main_layout.addLayout(self.asset_data_layout)
if __name__ == '__main__':
app = QApplication(sys.argv)
demo = Demo()
demo.show()
sys.exit(app.exec_())

Setting the alignment on a layout does not set the alignment of the widgets it manages, but only the alignment that that layout will have when added to another layout manager (you can read a more deeper explanation in this answer).
Basically, the following:
layout.setAlignment(QtCore.Qt.AlignTop)
is almost the same as this:
otherLayout.addLayout(layout, alignment=QtCore.Qt.AlignTop)
So, not only that alignment won't affect the children alignment, but since you're using that layout as main layout for the widget, that alignment is completely ignored.
In order to achieve what you want you can set a stretch for the lower alignment (as suggested in the comment by S.Nick):
self.main_layout.addLayout(self.asset_data_layout, stretch=1)
On the other hand, consider that QStackedLayout should only used for custom widgets or dedicated behavior, and in most cases it's better to use its convenience class QStackedWidget, on which you can set an appropriate size policy:
self.results_stacked_layout = QtWidgets.QStackedWidget()
self.data_results_layout.addWidget(self.results_stacked_layout)
self.results_stacked_layout.setSizePolicy(
QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Maximum)
With the above, the stacked widget will resize itself horizontally according to its contents and will only use the minimum vertical space required by it: since the combobox expands horizontally as much as possible, it will try to occupy all the horizontal available space, and since the vertical policy of the combobox is fixed, it will only use the vertical space required, leaving all the remaining vertical space to the other widgets in the parent layout.
Note that the size policy definitions might seem a bit counterintuitive; the rule is that it's always referred to the sizeHint of the widget: Maximum means that the size hint of the contents will be used as maximum size, and it cannot be larger than that.

Related

Python PyQt5 I wanna set the position of my Grid Layout

I need to set the position of my Grid Layout. It has to be exactly where I want it to be. I've tried some methods like ".setGeometry, .setAlignment" and I couldn't unfortunately have the result exactly I wanted.
As you can see from this photo, I want it to be in the bottom right but I guess there's no method for it. (I need to say that the interface in the photo below is a different interface. I don't need this interface. Because of I wanted to show you exactly what kind of stuff I wanted.)
You can achieve this using layouts and their setStretch methods.
Here is a runnable example:
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
class MainWindow(QMainWindow):
def __init__(self, parent=None) -> None:
super().__init__(parent)
self.central = QWidget()
# create a vertical layout and add stretch so it is the first
# item of the layout and everything that is inserted after
# is pushed down to the bottom
self.layout = QVBoxLayout(self.central)
self.layout.addStretch()
self.btn1 = QPushButton("PushButton", self)
self.btn2 = QPushButton("PushButton", self)
self.btn3 = QPushButton("PushButton", self)
# create a horizontal layout and add stretch so everything is
# pushed to the right
self.hlayout = QHBoxLayout()
self.hlayout.addStretch()
# add buttons after the stretch so they will be pushed to the
# right
self.hlayout.addWidget(self.btn1)
self.hlayout.addWidget(self.btn2)
self.hlayout.addWidget(self.btn3)
# add horizontal layout to vertical layout which will be pushed
# to the bottom from the vertical layouts stretch
self.layout.addLayout(self.hlayout)
self.setCentralWidget(self.central)
app = QApplication([])
window = MainWindow()
window.show()
app.exec_()
The alternative would be to use the QHBoxLayout.setgeometry(QRect(x, y, w, h)) but this will force the layout into a specific position that will not adjust dynamically if you resize the window, or for any other reason.
This is essentially what is happening in the code above. The blue lines would represent the stretch. the stretch can be thought of as invisible pressure that can be inserted into any layout, before, after, or between any widgets, It can work in either direction.
This same process can be done using QGridWidget.

PyQt5 resize tabs to its content

Consider this example. I want to make a program where the main window is divided into three parts which can be resized. In the middle I want to have two widgets placed vertially, the bottom one is QTabWidget, where users can change certain properties. Currently I have only one tab and one property there can be more.
I saw similar questions (here and here) but I can't seem to fathom how all the different parts related to size and layout even work together in the first place + they were C++ questions.
Please help me resize QTabWidget to its minimum necessary size to show the contents of the current tab.
As side note you can point me to some understandable docs for a beginner in GUI and PyQt5.
import sys
from PyQt5 import QtWidgets
from PyQt5.QtWidgets import QLineEdit, QLabel, QSplitter, QWidget, QListWidget, QApplication, QTabWidget, QGroupBox, \
QFormLayout, QSizePolicy, QLayout
from PyQt5.QtCore import Qt
class Example(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
self.init_tabs()
self.main_splitter = QSplitter(Qt.Horizontal)
some_left_widget = QWidget()
some_right_widget = QWidget()
mid = QSplitter(Qt.Vertical)
mid.addWidget(QListWidget())
mid.addWidget(self.tabs)
self.main_splitter.addWidget(some_left_widget)
self.main_splitter.addWidget(mid)
self.main_splitter.addWidget(some_right_widget)
self.setCentralWidget(self.main_splitter)
self.showMaximized()
def init_tabs(self):
self.properties_dict = {}
self.properties_dict['Text'] = QLineEdit()
self.tabs = QTabWidget()
self.properties_groupbox = QGroupBox("Overview")
layout = QFormLayout()
for k, v in self.properties_dict.items():
layout.addRow(QLabel(k + ':'), v)
self.properties_groupbox.setLayout(layout)
self.tabs.addTab(self.properties_groupbox, 'Properties')
# I have no idea how these work
self.properties_groupbox.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)
self.properties_groupbox.resize(self.properties_groupbox.minimumSizeHint())
self.properties_groupbox.adjustSize()
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
Left one is now, right one is desired
A QSplitter uses complex computation to evaluate the sizes it assigns to each of its child widgets, especially when resizing (which is something that also happens as soon as it's first shown, like any other widget).
The most important aspects it takes into account are the widgets size hints (what the widget suggests it would be the preferable size) and size policy (how the widget can be resized and how it will behave if there's more or less available space).
To achieve what you want, you'll need to set the size policy stretch (which is the proportion of available space in the layout the widget will try to use).
Just add the following lines after adding the widgets to the splitter:
mid.setStretchFactor(0, 1)
mid.setStretchFactor(1, 0)
The first line indicates that the first widget (the list) will use a stretch factor of 1, while the second (the tab widget) will be 0. The stretch factor is computed based on the sum of all the stretch factors of the widgets.
In this way the list will try to uccupy the maximum available space (since 1 is the maximum of 1 + 0), while the tab the least.
Remember that stretch factor also consider the size hints of the widget, so if you set 2 to the list and 1 to the tab, you will not get a list with a height twice than that of the tab.
Also, as soon as the splitter is resized, the new proportions will be used when the splitter is resized, ignoring the previously set stretch factors.

Move pyqt button out of list of buttons

I have a list of pyqt4 push button and want to move the position. Since it is troublesome it make lots of buttons variable I create them through a list. The code below
import sys
from PyQt5.QtWidgets import QApplication, QPushButton, QWidget, QVBoxLayout
class Window(QWidget):
def __init__(self):
QWidget.__init__(self)
layout = QVBoxLayout(self)
self.buttons = []
for i in range(3):
self.buttons.append(QPushButton('',self))
self.buttons[-1].setFixedWidth(50)
self.buttons[-1].setFixedHeight(50)
self.buttons[-1].move(70*i+50,300)
layout.addWidget(self.buttons[-1])
if __name__ == '__main__':
import sys
app = QApplication(sys.argv)
window = Window()
window.resize(500,500)
window.show()
sys.exit(app.exec_())
don't work for specify the position
but
class Window(QWidget):
def __init__(self):
QWidget.__init__(self)
layout = QVBoxLayout(self)
self.button1 = QPushButton('',self)
self.button1.setFixedWidth(50)
self.button1.setFixedHeight(50)
self.button1.move(50,300)
self.button2 = QPushButton('',self)
self.button2.setFixedWidth(50)
self.button2.setFixedHeight(50)
self.button2.move(120,300)
self.button3 = QPushButton('',self)
self.button3.setFixedWidth(50)
self.button3.setFixedHeight(50)
self.button3.move(190,300)
layout = QVBoxLayout(self)
layout.addWidget(self.button1)
layout.addWidget(self.button2)
layout.addWidget(self.button3)
works fine.
What's the reason behind?
If you want to manually specify the geometry (position and size) of widgets, you should not add them to a layout.
Your second example "works" just because you already created and set a layout to the top-level widget (the Window class), and since a layout already exists the second one is not "installed". Running it from the console will shows this error:
StdErr: QLayout: Attempting to add QLayout "" to Window "", which already has a layout
When a widget is added to a layout, the ownership of the "layout item" (an abstract item used by layouts to manage its widgets) is transferred to the layout, and the widget is reparented to the widget that uses that layout.
Since the second layout cannot be set, it will not manage the geometries of the items you tried to add, and the result is that they will keep the geometries you set before.
The same result can be obtained if you remove all the last lines referencing the other layout, which is exactly what you need.
Also, note that in order to add a widget that is not managed by a layout, a parent is required. Your last example also works because you specified the window as the parent while instantiating them; if you don't do that the buttons will not be shown, but if you do show them (with show() or setVisible(True)) they will each appear in separate windows, as they will become their own top level windows.
If you don't have other widgets that should be managed by a layout, you can also avoid creating the first layout at all (but, still, the parent is required).
Finally, let me tell you that using manual geometries is generally discouraged, and there are very few and specific cases for which it's a good idea to go with.
The main reason behind this is that widgets tend to show very differently from device to device, and that depends on various aspects:
different OS and OS versions draw widgets differently (sometimes dramatically), and this involves varying the widget size and behavior; while this might not be a major issue for simple widgets, it can be a problem for things like item views, spinboxes, etc;
different OS and systems use different styles, which also includes things like internal widget content margins and minimum size;
specific system settings (most importantly, the default font) could make widgets mostly unreadable, specifically with text being clipped within the widget margins if the font size is too big;
depending on the OS, you might face issues if the system uses High DPI displays, and you could end up with very tiny widgets that are almost impossible to interact with;
fixed geometries force you (and the user) to have a fixed widget/window size, and (along with the DPI issue above) this can be a problem: the same widget could look too big or too small;

How to add scrollbars in PyQt4 WITHOUT with absolute positioning

I'm creating an interface in PyQt4, and I am in need of scrollbars. My interface uses absolute positioning. I have looked a other treads for adding scrollbars, but the only answers given are to those interfaces without a layout (such as VBoxLayout, Grid Layout, etc).
Please take a look at my code. How could I add a scrollbars (horizontal and vertical) to this interface?
The full code wouldn't format properly on here, so I'll link to this pastebin
http://pastebin.com/hEH4R534
Here is the base of the interface (a 1500px by 1000px window)
class Example(QtGui.QWidget):
def __init__(self):
super(Example, self).__init__()
self.initUI()
def initUI(self):
self.setGeometry(100,100,1500,1000)
def main():
import sys
app = QtGui.QApplication(sys.argv)
window = Example()
window.show()
sys.exit(app.exec_())
main()
The question is... How would I modify the code above so I have horizontal and vertical scrollbars?
Only widgets that inherit from QAbstractScrollArea can have scroll bars. You could place one of those widgets inside your widget, and then place other widgets inside that widget. Or, just have your widget inherit from QScrollArea instead of QWidget.
By default, scroll bars will only appear when necessary to display hidden child widgets. You can force scroll bars to appear by setting the scroll bar policy for the widget.
from PyQt4.QtCore import Qt
...
def initUI(self):
self.setGeometry(100, 100, 1500, 1000)
self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOn)
self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn)

QBoxLayouts with Python

Because of their high customizability I've been relying on using multiple GroupBoxes while building app GUIs. But it appears QGroupBoxes make a certain impact on how fast an interface builds.
Now with layout's .insertLayout() method I can build an entire graphics interface placing the widgets any where I want. The dialogs feel very lightweight and extremely fast to re-draw. Unfortunately I can't find a way to control their appearance. I would appreciate if you would give me some clue on how to control the layout visual properties. I am particularly interested in knowing:
How to draw layout border, how to control a border line width,
How to place a layout title (similar to what QGroupBox's .setTitle() does)
How to control the layout outside and inside margins.
How to make layout minimizable/size-restorable (So the user could click some minus/arrow icon to fold/unfold layout when they need or don't need certain widgets belonging to the same layout.
The example with three nested layouts is posted below. As it is seen on dialog screenshot there is no way to visually differentiate one layout from another since there are no border, no titles, no dividers and etc.
from PyQt4 import QtGui, QtCore
class Dialog_01(QtGui.QMainWindow):
def __init__(self):
super(QtGui.QMainWindow,self).__init__()
tabWidget = QtGui.QTabWidget()
tabGroupBox = QtGui.QGroupBox()
tabLayout = QtGui.QVBoxLayout()
tabLayout.setContentsMargins(0, 0, 0, 0)
tabLayout.setSpacing(0)
subLayoutA=QtGui.QVBoxLayout()
tabLayout.insertLayout(0, subLayoutA)
tabGroupBox.setLayout(tabLayout)
tabWidget.addTab(tabGroupBox,' Tab A ')
listWidgetA = QtGui.QListWidget()
for i in range(3):
QtGui.QListWidgetItem( 'Item '+str(i), listWidgetA )
subLayoutA.addWidget(listWidgetA)
subLayoutB=QtGui.QHBoxLayout()
tabLayout.insertLayout(1, subLayoutB)
subLayoutB.addWidget(QtGui.QLineEdit('LineEdit 1'))
subLayoutB.addWidget(QtGui.QLineEdit('LineEdit 2'))
subLayoutC=QtGui.QVBoxLayout()
tabLayout.insertLayout(2, subLayoutC)
subLayoutC.addWidget(QtGui.QPushButton('PushButton 1'))
subLayoutC.addWidget(QtGui.QPushButton('PushButton 2'))
self.setCentralWidget(tabWidget)
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
dialog_1 = Dialog_01()
dialog_1.show()
dialog_1.resize(480,320)
sys.exit(app.exec_())
EDITED LATER
I inserted two lines into an example code to implement one of the suggestions made by sebastian. A Spacing-Margins method combos can be effectively used to get some additional tweaks done. Here is a screenshot (still could not get rid of the spacing around pushButtons):
QLayout sublcasses don't have a visual representation, which becomes clear by the fact that QLayout classes do not inherit QWidget. They only calculate the positions of the widgets they are responsible for in the context of their "parent" widget.
So the answer to questions 1,2 and 4 is basically: You can't.
You'll always have to have a QWidget in combination with a QLayout.
E.g. to group your two buttons into a frame with a box use a QFrame:
subLayoutC=QtGui.QVBoxLayout()
buttonFrame = QtGui.QFrame()
buttonFrame.setFrameStyle(QtGui.QFrame.Plain |QtGui.QFrame.Box)
buttonFrame.setLayout(subLayoutC)
subLayoutC.addWidget(QtGui.QPushButton('PushButton 1'))
subLayoutC.addWidget(QtGui.QPushButton('PushButton 2'))
# now we add the QFrame widget - not subLayoutC to the tabLayout
tabLayout.addWidget(buttonFrame) # I think your suggested edit was correct here
self.setCentralWidget(tabWidget)
Concerning question 3, check the docs:
http://qt-project.org/doc/qt-4.8/qlayout.html#setContentsMargins
http://qt-project.org/doc/qt-4.8/qboxlayout.html#setSpacing

Categories