PyQt4: set size of QGridLayout depending on size of QMainWindow - python

I'm writing a little Qt application with Python. I've created QMainWindow, which have a QGridLayout. In each grid I'm adding QTextBrowser Widget. I want left side of my grid to be not bigger than 25% of window. So I'll have two QTextBrowsers: one is 25% of window's width, and another is 75% of window's width. How can I do it? Thanks!

You can specify relative width by giving each cell a stretch with setStretch(). They will get sizes proportional to the given stretches. Here is a simple example that makes the right widget 3 times greater than the left widget.
import sys
from PyQt4 import QtGui
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
Q = QtGui.QWidget()
H = QtGui.QHBoxLayout()
H.addWidget(QtGui.QTextBrowser())
H.setStretch(0,1)
H.addWidget(QtGui.QTextBrowser())
H.setStretch(1,3)
Q.setLayout(H)
Q.show()
app.exec_()
But, bear in mind that widgets have minimum sizes by default. So they can shrink below that. If you want to change that behavior also, consider setting minimum sizes to your liking.

Related

Stacked Layout causing alignment to top problem

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.

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;

Set minimal reasonable width for a QScrollArea

I am relatively new to Qt, which I access through PySide.
I have a longish list of content that I want to make vertically scrollable.
The horizontal size is not an issue. I tried using QScrollArea for that. Here is a minimal example:
import sys
import PySide.QtGui as gui
application = gui.QApplication(sys.argv)
window = gui.QScrollArea()
list = gui.QWidget()
layout = gui.QVBoxLayout(list)
for i in range(100):
layout.addWidget(gui.QLabel(
"A something longish, slightly convoluted example text."))
window.setWidget(list)
window.show()
sys.exit(application.exec_())
What happens:
The scroll-area sets its horizontal size to the size needed for the labels
It notices that the vertical space is insufficient, so it adds a vertical scrollbar.
Due to the vertical scrollbar, the horizontal space is now insufficient as well, and so the horizontal scrollbar is also shown.
I can make the horizontal scrollbar go away with setHorizontalScrollBarPolicy, but the main problem persists: the vertical scrollbar obscures part of the labels.
How can I set the width of the scroll-area to the minimal value which does not need a horizontal scrollbar?
Your example is somewhat unrealistic, because the scroll-area is made the top-level window. More typically, it would be a child widget, and its initial size would be determined by a layout, and would be indirectly constrained by the sizes of other widgets and/or layouts. As a top-level window, the constraints are different, and partly under the influence of the window-manager (the exact behaviour of which can vary between platforms).
To ensure that the scroll-area has the correct size, you must set a minimum width for it, based on its contents, and also allowing for the vertical scrollbar and the frame. It is also probably best to set the widgetResizable property to True and add an expandable spacer to the end of the contents layout.
In the example below, I have changed the background colour of the labels to make it easier to see what is going on. I have also allowed resizing the window smaller than its initial size, by resetting the minimum width after it is shown - but that is optional.
import sys
import PySide.QtGui as gui
application = gui.QApplication(sys.argv)
window = gui.QScrollArea()
window.setWidgetResizable(True)
list = gui.QWidget()
layout = gui.QVBoxLayout(list)
# just for testing
window.setStyleSheet('QLabel {background-color: red}')
for i in range(30):
layout.addWidget(gui.QLabel(
"A something longish, slightly convoluted example text."))
layout.addStretch()
window.setWidget(list)
window.setMinimumWidth(
list.sizeHint().width() +
2 * window.frameWidth() +
window.verticalScrollBar().sizeHint().width())
window.show()
# allow resizing smaller
window.setMinimumWidth(1)
sys.exit(application.exec_())
Try to use sizeHint() in the widgets and use the result to set the minimum width of the QScrollArea.
Some extra info here: http://doc.qt.io/qt-5/layout.html

Generate PyQt Legend

I'm trying to build myself a simple graphics calculator, as a way of teaching myself PyQt. I'd like the calculator to have a pane which lists all of the equations plotted and shows the line style used.
I began with a QListWidget to achieve these ends, the pane contains a scrollable list of equations, but does not show the line style because the QListWidget only allows strings or icons.
So I tried using a QAbstractScrollArea widget instead. For each equation I use QHBoxLayout to produce two Widgets, a label containing the equation string, and a QWidget within which I draw the line style. Then I stack all of the equation QHBoxLayouts within the ScrollArea using QVBoxLayout.
The problem is that QVBoxLayout uses all of the space available to it. So if I have only three equations, they are spread throughout the pane and not listed at the top as I'd like them to be, while if I have too many equations to fit in the pane they are stacked on top of one another rather than causing the area to be scrollable.
This is how the calculator appears with too many equations...
And this is how it looks with too few...
Does anyone have any suggestions of better ways to get around these issues? One idea is to generate icons programmatically to have the properties of the lines and to use these in the listwidget, is that possible?
It sounds like what you want is a QListWidget with multiple columns - and a QTreeWidget can be adapted to do exactly that.
Here's a simple demo:
from PyQt4 import QtGui, QtCore
class Window(QtGui.QWidget):
def __init__(self):
QtGui.QWidget.__init__(self)
self.list = QtGui.QTreeWidget(self)
self.list.setRootIsDecorated(False)
self.list.setUniformRowHeights(True)
self.list.setAllColumnsShowFocus(True)
self.list.setItemsExpandable(False)
self.list.header().hide()
self.list.setColumnCount(2)
for label, color in (
('v=x**2', 'red'),
('v=x/2', 'blue'),
('v=2*x', 'green'),
('v=3*2', 'orange'),
('v=5-x', 'purple'),
):
item = QtGui.QTreeWidgetItem([label, '------------'])
item.setForeground(1, QtGui.QColor(color))
self.list.addTopLevelItem(item)
layout = QtGui.QVBoxLayout(self)
layout.addWidget(self.list)
if __name__ == '__main__':
import sys
app = QtGui.QApplication(sys.argv)
window = Window()
window.setGeometry(500, 300, 300, 200)
window.show()
sys.exit(app.exec_())

Categories