How to make QGridLayout resize cells the exact same size? - python

Whenever I resize the window (a QDialog), Reference Viewer and Selected Viewer (subclasses of QScrollArea) should have the exact same size at all time, even after a resize event.
However, once out of twice, I get a size 1 pixel smaller for the Selected Viewer (QScrollArea widget on the right). By once out of twice, I mean every odd pixel count.
It seems that the QGridLayout is forcing the right-most panel to that smaller size, probably due to rounding down the value of the space still available.
I use a QGridLayout because I need the toolbar to stay aligned in the center between the panels and it works well.
Here is a screencast demonstrating the problem: you can see the scrollbar showing up every-time the Selected Viewer (panel on the right) is resized one pixel shorter in width compared to the panel on the left.
Here is mostly what I'm doing:
verticalLayout = QVBoxLayout(self)
verticalLayout.setSpacing(0)
verticalLayout.setContentsMargins(0, 0, 0, 0)
gridLayout = QGridLayout()
# Minimum width for the toolbar in the middle:
gridLayout.setColumnMinimumWidth(1, 30)
gridLayout.setColumnStretch(0,1)
gridLayout.setColumnStretch(1,0)
gridLayout.setColumnStretch(2,1)
gridLayout.setSpacing(3)
selectedImageViewer = ScrollAreaImageViewer(self)
gridLayout.addWidget(selectedImageViewer, 0, 0, 3, 1)
verticalToolBar = QToolBar(self)
verticalToolBar.setOrientation(Qt.Orientation(Qt.Vertical))
gridLayout.addWidget(verticalToolBar, 1, 1, 1, 1, Qt.AlignCenter)
referenceImageViewer = ScrollAreaImageViewer(self)
gridLayout.addWidget(referenceImageViewer, 0, 2, 3, 1)
verticalLayout.addLayout(gridLayout)
I add another widget below in the QVBoxLayout but it's irrelevant here.
I have tried adding spacers but it doesn't seem to change anything:
gridLayout.addItem(QSpacerItem(5,0, QSizePolicy.Minimum), 1, 3, 1, 1, Qt.Alignment(Qt.AlignCenter))
Is there a way to ensure both Viewers get the same size without using resize() on them on every resizeEvent()?
Or should this actually be considered a bug in Qt?
I have tried the following which works around the scrollbar flickering issue:
def resizeEvent(self, event):
self.gridLayout.setColumnMinimumWidth(0, self.selectedImageViewer.size().width())
self.gridLayout.setColumnMinimumWidth(2, self.selectedImageViewer.size().width())
But the sizes still differ by one pixel once out of twice.
Edit: here is a minimal reproducible example
from PyQt5.QtCore import QSize, Qt
from PyQt5.QtWidgets import (QDialog, QLayout, QVBoxLayout,
QLabel, QSizePolicy, QToolBar, QGridLayout,
QWidget, QApplication )
class MyWidget(QWidget):
def __init__(self, parent):
super().__init__(parent)
self.label = QLabel(self)
def resizeEvent(self, event):
self.label.setText(f"{self.size()}")
class MyDialog(QDialog):
def __init__(self, parent):
super().__init__(parent)
self.setMinimumSize(QSize(500, 100))
self.verticalLayout = QVBoxLayout(self)
self.verticalLayout.setSpacing(0)
self.verticalLayout.setContentsMargins(0, 0, 0, 0)
self.gridLayout = QGridLayout()
self.gridLayout.setColumnMinimumWidth(1, 30)
self.gridLayout.setColumnStretch(0,1)
self.gridLayout.setColumnStretch(1,0)
self.gridLayout.setColumnStretch(2,1)
self.gridLayout.setSpacing(3)
self.selectedImageViewer = MyWidget(self)
self.gridLayout.addWidget(self.selectedImageViewer, 0, 0, 3, 1)
self.verticalToolBar = QToolBar(self)
self.verticalToolBar.setOrientation(Qt.Orientation(Qt.Vertical))
self.gridLayout.addWidget(self.verticalToolBar, 1, 1, 1, 1, Qt.AlignCenter)
self.referenceImageViewer = MyWidget(self)
self.gridLayout.addWidget(self.referenceImageViewer, 0, 2, 3, 1)
self.verticalLayout.addLayout(self.gridLayout)
def main():
app = QApplication([()])
window = QWidget()
dialog = MyDialog(window)
dialog.show()
return app.exec()
if __name__ == "__main__":
main()

I assume that problem is with ScrollArea being used for referenceImageViewer, so in each resize event actual referenceImageViewer is trying to add a horizontal scrollbar to itself.
As a solution you can
Set referenceImageViewer's adjust policy to (QAbstractScrollArea.AdjustIgnored or QAbstractScrollArea.AdjustToContents).
Try to use Widgets instead of ScrollAreaImageViewer instances in gridLayout, and then adding ScrollAreaImageViewer inside that widgets.
Edited.
There must be difference between width of 1st and 3rd widgets as long
as ToolBar's width is fixed. E.g when window width is 501 and toolbar
width is fixed at 20 auto alignment can't equally divide remaining 481
pixels in a half..
As a solution your toolbar must be resizable too.
For reducing ToolBar width changes you can increase 1st and 3rd column
stretch in GridLayout for example to value 8, and set 2nd column
stretch to 1, so layout will automatically adjust width of each
column.

Related

How to prevent window and widgets in a pyqt5 application from changing size when the visibility of one widget is altered

I want to create a dialog, in which the user should first select one item in a drop down, and for some choices specify an additional parameter. For the sake of the example let's say that the possible choices are A and B and for B the user has to enter a text. The text field should not be visible when A is selected.
Here is a MWE:
#!/usr/bin/env python
import sys
from PyQt5.QtWidgets import QApplication, QComboBox, QDialog, QGridLayout, QLineEdit
class Example(QDialog) :
def __init__(self, parent=None) :
super(QDialog, self).__init__(parent)
self.mainLayout = QGridLayout()
self.setLayout(self.mainLayout)
self.comboBox = QComboBox()
self.comboBox.addItems(['A', 'B'])
self.mainLayout.addWidget(self.comboBox, 0, 0)
self.lineEdit = QLineEdit('')
self.lineEdit.setMinimumWidth(50)
self.mainLayout.addWidget(self.lineEdit, 0, 1)
self.comboBox.activated[str].connect(self.update)
self.update(str(self.comboBox.currentText()))
def update(self, choice) :
if 'B' in choice :
self.lineEdit.setVisible(True)
else :
self.lineEdit.setVisible(False)
if __name__ == '__main__':
app = QApplication(sys.argv)
example = Example()
example.show()
sys.exit(app.exec_())
The problem is, that when initially choice A is presented, the size of the dialog is just enough for the comboBox. When option B is selected, the window is expanded and everything is as it should be. However, when option A is selected again, the comboBox' width increases, taking up all of the avalaible space, instead of leaving empty space to the right.
How can I have space allocated for the text field, no matter if visible or not? What am I missing here?
EDIT The answer by S.Nick solves the problem of the MWE in a way, but not the way I was hoping for: As soon as the scenario is more complex, widgets get reallocated again, e.g. if a QLabel is added in front of the comboBox
self.label = QLabel('label')
self.mainLayout.addWidget(self.label, 0, 0)
self.comboBox = QComboBox()
self.comboBox.addItems(['A', 'B'])
self.mainLayout.addWidget(self.comboBox, 0, 1, alignment=Qt.AlignLeft)
self.lineEdit = QLineEdit('', self)
self.lineEdit.setMinimumWidth(50)
self.mainLayout.addWidget(self.lineEdit, 0, 2)
then the comboBox is flipped around when changing the selection. What I want is that, once in the beginning space and position is allocated for each widget and that the space and position is permanent no matter if any widget is visible or not.
You could try something like this:
def __init__(self, parent=None) :
super(QDialog, self).__init__(parent)
self.mainLayout = QGridLayout()
self.setLayout(self.mainLayout)
self.label = QLabel('label')
self.mainLayout.addWidget(self.label, 0, 0)
self.comboBox = QComboBox()
self.comboBox.addItems(['A', 'B'])
self.mainLayout.addWidget(self.comboBox, 0, 1)
self.lineEdit = QLineEdit('', self)
self.lineEdit.setMinimumWidth(200)
self.mainLayout.addWidget(self.lineEdit, 0, 2)
self.comboBox.activated[str].connect(self.update)
self.mainLayout.setColumnStretch(2,1)
self.adjustSize()
self.update(str(self.comboBox.currentText()))
self.mainLayout.setColumnStretch(2,1) will make sure that the last column will take up all the extra horizontal space even when the line edit widget is hidden.
self.adjustSize() adjusts the size of the main window to the sum of the sizes of all its child widgets. Since at this point the line edit widget is still visible, its size is taken into account as well when the size of the main window is adjusted.
Screenshots
Initial window:
After selecting B:

Making Highly Customized, Hoverable, Overlappable, Widgets

I want to up my game in UI design using PyQt5. I feel like the resources for UI customization in PyQt5 are not easy to find. It is possible to try and make personalized widget, but the overall method seems non-standardized.
I need to build an arrow widget that is hoverable, overlappable with other widgets and highly customized. As I read in this tutorial and some other posts, it possible to do exactly what you need using paintEvent. Thus that is what I tried, but overall, I feel like the method is quite messy, and I'd like some guidelines on building complex Customized, general widget. Here's what I have:
Customized Shape: I built my code based on this
Hoverable property: I read everywhere that modifying the projects styleSheet is usually the way to go, especially if you want to make your Widget general and adapt to colors, the problem is that I wasn't able to find how to use properly self.palette to fetch the current colors of the QApplication styleSheet. I feel like i's have to maybe use enterEvent and leaveEvent, but I tried to redraw the whole widget with a painter in those functions and it said
QPainter::begin: Painter already active
QWidget::paintEngine: Should no longer be called
QPainter::begin: Paint device returned engine == 0, type: 1
QPainter::setRenderHint: Painter must be active to set rendering hints
Overlappable Property: I found a previous post which seemed to have found a solution: create a second widget that is children of the main widget, in order to be able to move the children around. I tried that but it seems that it doesn't want to move, no matter the position I give the widget.
Here is my code:
import sys
from PyQt5.QtWidgets import QWidget, QHBoxLayout, QGraphicsDropShadowEffect, QApplication, QFrame, QPushButton
from PyQt5.QtCore import Qt, QPoint, QLine
from PyQt5.QtGui import QPainter, QPen, QColor, QPalette
class MainWidget(QWidget):
def __init__(self):
super(MainWidget, self).__init__()
self.resize(500, 500)
self.layout = QHBoxLayout()
self.setLayout(self.layout)
self.myPush = QPushButton()
self.layout.addWidget(self.myPush)
self.arrow = ArrowWidget(self)
position = QPoint(-40, 0)
self.layout.addWidget(self.arrow)
self.arrow.move(position)
class ArrowWidget(QWidget):
def __init__(self, parent=None):
super(ArrowWidget, self).__init__(parent)
self.setWindowFlag(Qt.FramelessWindowHint)
self.setAttribute(Qt.WA_TranslucentBackground)
self.w = 200
self.h = 200
self.blurRadius = 20
self.xO = 0
self.yO = 20
self.resize(self.w, self.h)
self.layout = QHBoxLayout()
# myFrame = QFrame()
# self.layout.addWidget(myFrame)
self.setLayout(self.layout)
self.setStyleSheet("QWidget:hover{border-color: rgb(255,0,0);background-color: rgb(255,50,0);}")
shadow = QGraphicsDropShadowEffect(blurRadius=self.blurRadius, xOffset=self.xO, yOffset=self.yO)
self.setGraphicsEffect(shadow)
def paintEvent(self, event):
painter = QPainter(self)
painter.setRenderHint(QPainter.Antialiasing)
painter.begin(self)
# painter.setBrush(self.palette().window())
# painter.setPen(QPen(QPalette, 5))
ok = self.frameGeometry().width()/2-self.blurRadius/2-self.xO/2
oky = self.frameGeometry().height()/2-self.blurRadius/2-self.yO/2
painter.drawEllipse(QPoint(self.frameGeometry().width()/2-self.blurRadius/2-self.xO/2, self.frameGeometry().height()/2-self.blurRadius/2-self.yO/2), self.w/2-self.blurRadius/2-self.yO/2-self.xO/2, self.h/2-self.blurRadius/2-self.yO/2-self.xO/2)
painter.drawLines(QLine(ok-25, oky-50, ok+25, oky), QLine(ok+25, oky, ok-25, oky+50))
painter.end()
if __name__ == '__main__':
app = QApplication(sys.argv)
testWidget = MainWidget()
testWidget.show()
sys.exit(app.exec_())
If someone could help me make this work and explain along the way to help us better understand the structure of customized widgets and explain a better method that isn't messy like this one, I believe it would be a plus to the beginners like me using PyQt5 as a main Framework for UI making.
There is no "standard" method for custom widgets, but usually paintEvent overriding is required.
There are different issues in your example, I'll try and address to them.
Overlapping
If you want a widget to be "overlappable", it must not be added to a layout. Adding a widget to a layout will mean that it will have its "slot" within the layout, which in turn will try to compute its sizes (based on the widgets it contains); also, normally a layout has only one widget per "layout slot", making it almost impossible to make widget overlap; the QGridLayout is a special case which allows (by code only, not using Designer) to add more widget to the same slot(s), or make some overlap others. Finally, once a widget is part of a layout, it cannot be freely moved nor resized (unless you set a fixedSize).
The only real solution to this is to create the widget with a parent. This will make it possible to use move() and resize(), but only within the boundaries of the parent.
Hovering
While it's true that most widgets can use the :hover selector in the stylesheet, it only works for standard widgets, which do most of their painting by themself (through QStyle functions). About this, while it's possible to do some custom painting with stylesheets, it's generally used for very specific cases, and even in this case there is no easy way to access to the stylesheet properties.
In your case, there's no need to use stylesheets, but just override enterEvent and leaveEvent, set there any color you need for painting and then call self.update() at the end.
Painting
The reason you're getting those warnings is because you are calling begin after declaring the QPainter with the paint device as an argument: once it's created it automatically calls begin with the device argument. Also, it usually is not required to call end(), as it is automatically called when the QPainter is destroyed, which happens when the paintEvent returns since it's a local variable.
Example
I created a small example based on your question. It creates a window with a button and a label within a QGridLayout, and also uses a QFrame set under them (since it's been added first), showing the "overlapping" layout I wrote about before. Then there's your arrow widget, created with the main window as parent, and that can be moved around by clicking on it and dragging it.
import sys
from PyQt5 import QtCore, QtGui, QtWidgets
class ArrowWidget(QtWidgets.QWidget):
def __init__(self, parent=None):
super().__init__(parent)
# since the widget will not be added to a layout, ensure
# that it has a fixed size (otherwise it'll use QWidget default size)
self.setFixedSize(200, 200)
self.blurRadius = 20
self.xO = 0
self.yO = 20
shadow = QtWidgets.QGraphicsDropShadowEffect(blurRadius=self.blurRadius, xOffset=self.xO, yOffset=self.yO)
self.setGraphicsEffect(shadow)
# create pen and brush colors for painting
self.currentPen = self.normalPen = QtGui.QPen(QtCore.Qt.black)
self.hoverPen = QtGui.QPen(QtCore.Qt.darkGray)
self.currentBrush = self.normalBrush = QtGui.QColor(QtCore.Qt.transparent)
self.hoverBrush = QtGui.QColor(128, 192, 192, 128)
def mousePressEvent(self, event):
if event.buttons() == QtCore.Qt.LeftButton:
self.mousePos = event.pos()
def mouseMoveEvent(self, event):
# move the widget based on its position and "delta" of the coordinates
# where it was clicked. Be careful to use button*s* and not button
# within mouseMoveEvent
if event.buttons() == QtCore.Qt.LeftButton:
self.move(self.pos() + event.pos() - self.mousePos)
def enterEvent(self, event):
self.currentPen = self.hoverPen
self.currentBrush = self.hoverBrush
self.update()
def leaveEvent(self, event):
self.currentPen = self.normalPen
self.currentBrush = self.normalBrush
self.update()
def paintEvent(self, event):
qp = QtGui.QPainter(self)
qp.setRenderHints(qp.Antialiasing)
# painting is not based on "pixels", to get accurate results
# translation of .5 is required, expecially when using 1 pixel lines
qp.translate(.5, .5)
# painting rectangle is always 1px smaller than the actual size
rect = self.rect().adjusted(0, 0, -1, -1)
qp.setPen(self.currentPen)
qp.setBrush(self.currentBrush)
# draw an ellipse smaller than the widget
qp.drawEllipse(rect.adjusted(25, 25, -25, -25))
# draw arrow lines based on the center; since a QRect center is a QPoint
# we can add or subtract another QPoint to get the new positions for
# top-left, right and bottom left corners
qp.drawLine(rect.center() + QtCore.QPoint(-25, -50), rect.center() + QtCore.QPoint(25, 0))
qp.drawLine(rect.center() + QtCore.QPoint(25, 0), rect.center() + QtCore.QPoint(-25, 50))
class MainWidget(QtWidgets.QWidget):
def __init__(self):
super().__init__()
layout = QtWidgets.QGridLayout()
self.setLayout(layout)
self.button = QtWidgets.QPushButton('button')
layout.addWidget(self.button, 0, 0)
self.label = QtWidgets.QLabel('label')
self.label.setAlignment(QtCore.Qt.AlignCenter)
layout.addWidget(self.label, 0, 1)
# create a frame that uses as much space as possible
self.frame = QtWidgets.QFrame()
self.frame.setFrameShape(self.frame.StyledPanel|self.frame.Raised)
self.frame.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
# add it to the layout, ensuring it spans all rows and columns
layout.addWidget(self.frame, 0, 0, layout.rowCount(), layout.columnCount())
# "lower" the frame to the bottom of the widget's stack, otherwise
# it will be "over" the other widgets, preventing them to receive
# mouse events
self.frame.lower()
self.resize(640, 480)
# finally, create your widget with a parent, *without* adding to a layout
self.arrowWidget = ArrowWidget(self)
# now you can place it wherever you want
self.arrowWidget.move(220, 140)
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
testWidget = MainWidget()
testWidget.show()
sys.exit(app.exec_())

PyQt Image(pixmap) gets cropped when other content changes width in a widget

I'm making a table-like widget that displays an image, the file name, and two box-selection areas. I have two objects 'grid_row' & 'grid_table' (both using QGridLayout), grid_row being a single row and grid_table containing x number of grid_rows (I'm designing it like this because it's simply easier to keep track of my custom properties).
The tool looks like this
The final layout is a QVBoxLayout, then from top to bottom, I have QHBoxLayout(the one with a label and combobox), grid_row(for the headers 1,2,3), a scroll_area that contains the grid_table with each one being grid_rows. Lastly another QHBoxLayout for the buttons.
Each grid_row contains a 'image-widget', and two region labels(QLabel). The image widget contains a label(I used setPixmap for display) and a pushbutton. Here are my grid_row and image_widget classes:
class grid_row(QWidget):
def __init__(self, parent=None):
super().__init__()
#self.frame = frame_main()
self.grid_layout = QGridLayout()
self.grid_layout.setSpacing(50)
self.image_widget = image_widget()
self.grid_layout.addWidget(self.image_widget, 0, 0, 1, 1, Qt.AlignHCenter)
self.region_2 = QLabel('null')
self.grid_layout.addWidget(self.region_2, 0, 2, 1, 1, Qt.AlignHCenter)
self.setLayout(self.grid_layout)
self.region_1 = QLabel('null')
self.grid_layout.addWidget(self.region_1, 0, 1, 1, 1, Qt.AlignHCenter)
class image_widget(QWidget):
def __init__(self, parent=None):
super().__init__()
self.initUI()
def initUI(self):
self.setAcceptDrops(True)
self.image_widget_layout = QHBoxLayout()
self.image_widget_label = QLabel()
self.image_widget_label.setPixmap(QPixmap('default.png').scaled(96, 54))
self.image_widget_layout.addWidget(self.image_widget_label)
self.img_btn = QPushButton()
self.img_btn.setEnabled(False)
self.img_btn.setText('Drag Here!')
self.image_widget_layout.addWidget(self.img_btn)
self.setLayout(self.image_widget_layout)
if __name__ == '__main__':
app = QApplication(sys.argv)
widget = QWidget()
layout = QVBoxLayout()
grid_row = grid_row()
layout.addWidget(grid_row)
btn = QPushButton('press')
btn.clicked.connect(lambda: grid_row.region_1.setText('[0,0,1920,1080]'))
layout.addWidget(btn)
widget.setLayout(layout)
scroll_area = QScrollArea()
scroll_area.setWidget(widget)
scroll_area.show()
sys.exit(app.exec_())
So currently, I've implemented events that allow me to drag images into the image_widget and click the push button to modify the two regions that are framed (format: [x1, y1, x2, y2]). The problem is that when I do that(e.g. region values go from 'null' to say '[20,20, 500, 500]', the image gets squished because now the labels are taking up more width.
I realize that some size policy needs to be set (and maybe other properties) but I don't know which property to use and on which widget. I want the image to remain the same. Maybe stretch out the width of each column for the grid_row?
To clarify, I want the label containing the pixmap to remain the same size (always 96*54) and fully displayed(not cropped or stretched) at all times.
I've provided the a simplified executable code to display my problem, the classes are the same as my code, I just only put grid_row inside the scroll_area and added a button to change one of the values of the region to simulate the situation. Can provide additional code if needed. Thanks in advance!
Wow sometimes the answer is really one extra line of code...
So the documentation mentions that QScrollArea by default honors the size of its widget. Which is why when I changed the region (to a value that's wider/ more text) the widget does not auto adjust.
I needed to add
scroll_area.setWidgetResizable(True)
to allow the widget to resize wider thus prompting the scroll bars to appear. This way my pixmap image doesn't get cropped from not having enough space.
The easiest way would be to add size constraints to the label before adding to the layout
self.image_widget_label.adjustSize()
self.image_widget_label.setFixedSize(self.image_widget_label.size())
self.image_widget_layout.addWidget(self.image_widget_label)
adjustSize would resize the label depending on the contents.
The more difficult way is to answer the questions :
"when I change the size of the overall window, how do I want this
particular item to behave? When the window is at its minimal size,
which items do I want hidden or out of view? When the window is full
size, where do I want empty spots?"
To answer these better read a bit on Qt Layout management

How to reset the column span of a widget in a QGridLayout?

Is possible to set the column span of a QLineEdit box after it has been added to the layout? I have two QLineEdit boxes in a QGridLayout that are right next to each other horizontally. During the execution of some of my code, one of these boxes gets hidden. I would like to increase the column span where the hidden one was to avoid a weird gap at the end, and reduce it when needed.
I couldn't really find anything in the Qt documentation for this type of change beyond making the adjustment prior to adding the widget to the layout.
There no method for resetting the row- or column-span after a widget has been added. However, addWidget can be called again on the same widget to achieve the same affect, because re-adding a widget to the same layout always implicitly removes it first. So something like this should work:
index = layout.indexOf(widget)
row, column = layout.getItemPosition(index)[:2]
layout.addWidget(widget, row, column, rowspan, colspan)
Here is a simple demo script:
import sys
from PyQt5 import QtCore, QtWidgets
class Window(QtWidgets.QWidget):
def __init__(self):
super(Window, self).__init__()
self.button = QtWidgets.QPushButton('Toggle Edit')
self.button.clicked.connect(self.handleButton)
self.edit1 = QtWidgets.QLineEdit()
self.edit1.setPlaceholderText('One')
self.edit2 = QtWidgets.QLineEdit()
self.edit2.setPlaceholderText('Two')
layout = QtWidgets.QGridLayout(self)
layout.addWidget(self.edit1, 0, 0)
layout.addWidget(self.edit2, 0, 1)
layout.addWidget(self.button, 1, 0)
def handleButton(self):
if self.edit2.isHidden():
self.setWidgetSpan(self.edit1, 1, 1)
self.edit2.setHidden(False)
else:
self.edit2.setHidden(True)
self.setWidgetSpan(self.edit1, 1, 2)
def setWidgetSpan(self, widget, rowspan=1, colspan=1):
layout = self.layout()
index = layout.indexOf(widget)
row, column = layout.getItemPosition(index)[:2]
layout.addWidget(widget, row, column, rowspan, colspan)
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
window = Window()
window.setGeometry(600, 100, 300, 100)
window.show()
sys.exit(app.exec_())
Try as said below.
When you add two QLineEdit, The QGridLayout dynamically creates two columns.
If you want extra columns for spanning use QSpacerItem and get extra columns.
And you have to use addItem(..,..,..) of QGridLayout to add the spacer item to layout.
Between your two QLineEdit add spacers as shown below
spacer = QtGui.QSpacerItem(20, 20)
layout.addItem(spacer,0,1)
Now if you add totak 4 spacers (for example) ,
spacer1 = QtGui.QSpacerItem(20, 20)
layout.addItem(spacer1,0,2)
spacer2 = QtGui.QSpacerItem(20, 20)
layout.addItem(spacer2,0,3)
spacer3 = QtGui.QSpacerItem(20, 20)
layout.addItem(spacer3,0,4)
Now you have total 6 columns ------- with first QLineEdit (column 0), 4 spacers, last QLineEdit (column 5)
Now you can use setColumnStretch(column,stretch) to set the span of any line edit. Here I am trying to set span for last QLineEdit..as shown below. Spanned for 3 columns...
layout.setColumnStretch(5,3)
Hope this helps...

Why does my window layout change when I change my selection?

If you look at my two images here, you'll notice that switching between the two selections changes the size of the QListWidget on the right.
I have it laid out using a QGridLayout. Here is the relevant code snippet, with pn_list being the relevant widget, but you can see the full code here:
def initUI(self):
# Layouts
grid = QtGui.QGridLayout()
ov_main = QtGui.QHBoxLayout()
ov_col1 = QtGui.QFormLayout()
ov_col2 = QtGui.QVBoxLayout()
ov_custs = QtGui.QFormLayout()
# Main window widgets
self.pn_input = QtGui.QLineEdit()
overviewBox = QtGui.QGroupBox('Overview')
self.pn_list = MyListWidget()
# Main window layout
grid.addWidget(QtGui.QLabel('Part Number'), 1, 1)
grid.addWidget(self.pn_input, 1, 2)
grid.addWidget(overviewBox, 2, 1, 1, 2)
grid.addWidget(self.pn_list, 1, 3, 2, 1)
Update: Also, as a follow-on question, what can I do to prevent this from happening? Finally, I would like the "Description" field to wrap when the line-length exceeds a pre-determined character limit - I currently have my presenter truncating the "Description" when it exceeds this length. How would I have the QLabel wrap at a given character width?
Figured it out. On the MyListWidget (which extends QListWidget) I overwrote the sizeHint method to return the width, in pixels, that I want my widget to be:
class MyListWidget(QtGui.QListWidget):
def sizeHint(self):
hint = QtCore.QSize()
hint.setWidth(120)
return hint
Then, in my initUI method, I added a line to the #Main window layout section:
# Main window layout
grid.addWidget(QtGui.QLabel('Part Number'), 1, 1)
grid.addWidget(self.pn_input, 1, 2)
grid.addWidget(overviewBox, 2, 1, 1, 2)
grid.addWidget(self.pn_list, 1, 3, 2, 1)
This kept the widget on the right a constant width, however I still had problems with varying length descriptions messing things up. I added the following lines to my initUI method to similarly deal with the description QLabel widget and now all is well:
myQLabel.sizeHint = lambda: QtCore.QSize(150, -1)
myQLabel.setSizePolicy(QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Preferred)
myQLabel.setWordWrap(True)
The lambda statement is essentially a quicker way of achieving what I did with the QListWidget. In the QListWidget case, I was already re-implementing the class in order to add a custom signal, so I added the sizeHint code there.

Categories

Resources