Pyqt5 - grid layout misbehaving - python

So I'm trying to get a grip on Qt (more specifically, Pyqt), and I want to create a simple feedback form. It should have
a title
a name ('author')
a message
a send and a cancel button
Let's try without the buttons, first (the App class just provides a button to create a popup. the question concerns the Form class below it):
import sys
from PyQt5.QtWidgets import QApplication, QWidget, QMainWindow, QDesktopWidget,\
QHBoxLayout, QVBoxLayout, QGridLayout,\
QPushButton, QLabel,QLineEdit, QTextEdit,\
qApp
from PyQt5.QtGui import QIcon
class App(QMainWindow):
def __init__(self):
super().__init__()
self.title = 'PyQt5 Layout Demo'
self.popup = None
self.initUI()
def initUI(self):
self.setWindowTitle(self.title)
self.setWindowIcon(QIcon('imgs/python3.png'))
formButton = QPushButton("show form")
formButton.clicked.connect(self.showPopup)
formBox = QHBoxLayout()
formBox.addWidget(formButton)
formBox.addStretch(1)
vbox = QVBoxLayout()
vbox.addLayout(formBox)
vbox.addStretch(1)
# self.setLayout(vbox) # would work if this was a QWidget
# instead, define new central widget
window = QWidget()
window.setLayout(vbox)
self.setCentralWidget(window)
self.center(self)
self.show()
#staticmethod
def center(w: QWidget):
qr = w.frameGeometry() # get a rectangle for the entire window
# center point = center of screen resolution
cp = QDesktopWidget().availableGeometry().center()
qr.moveCenter(cp) # move center of rectangle to cp
w.move(qr.topLeft()) # move top-left point of window to top-let point of rectangle
def showPopup(self):
if self.popup is None:
self.popup = Form(self)
self.popup.setGeometry(10, 10, 300, 400)
self.center(self.popup)
self.popup.show()
class Form(QWidget):
def __init__(self, main):
super().__init__()
self.initUI()
self.main = main
def initUI(self):
self.setWindowTitle('Feedback')
self.setWindowIcon(QIcon('imgs/python3.png'))
title = QLabel('Title')
author = QLabel('Author')
message = QLabel('Message')
titleEdit = QLineEdit()
authorEdit = QLineEdit()
messageEdit = QTextEdit()
grid = QGridLayout()
grid.setSpacing(10)
grid.addWidget(title, 1, 0)
grid.addWidget(titleEdit,1, 1)
grid.addWidget(author, 2, 0)
grid.addWidget(authorEdit,2, 1)
grid.addWidget(message, 3, 0)
grid.addWidget(messageEdit, 4, 0, 6, 0)
self.setLayout(grid)
# probably should delegate to self.main, but bear with me
def send(self):
self.main.popup = None
self.hide()
def cancel(self):
self.hide()
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = App()
sys.exit(app.exec_())
Ok, looks about right. There's a bit too much spacing in-between the line edits and the text edit, but since I want to add some buttons below it, that should be be a problem.
So I add:
sendBtn = QPushButton("send")
cancelBtn = QPushButton("cancel")
sendBtn.clicked.connect(self.send)
cancelBtn.clicked.connect(self.cancel)
grid.addWidget(sendBtn, 7, 1)
grid.addWidget(cancelBtn, 7, 2)
which yields
Now obviously, I forgot to stretch the title and author line edits to the newly introduced column 2. Easy enough to fix but what really bothers me is the placement of the buttons.
WHY do they show up in the middle of the text edit? I can see how Qt chooses the column size, and why that would lead to the buttons' being of different size, but since the tutorial doesn't actually add buttons to the form, I have no idea how to fix that.
I could, of course, simply add boxes:
sendBtn = QPushButton("send")
cancelBtn = QPushButton("cancel")
sendBtn.clicked.connect(self.send)
cancelBtn.clicked.connect(self.cancel)
btns = QHBoxLayout()
btns.addStretch(1)
btns.addWidget(sendBtn)
btns.addWidget(cancelBtn)
l = QVBoxLayout()
l.addLayout(grid)
l.addLayout(btns)
self.setLayout(l)
With which the popup then actually starts looking closer to something acceptable:
But is there a way to fix this within the grid layout, instead?

You seem to have misunderstood the signature of addWidget. The second and third arguments specify the row and column that the widget is placed in, whilst the third and fourth specify the row-span and column-span.
In your example, the problems start here:
grid.addWidget(message, 3, 0)
grid.addWidget(messageEdit, 4, 0, 6, 0)
where you make the text-edit span six rows and zero columns - which I doubt is what you intended. Instead, you probably want this:
grid.addWidget(message, 3, 0, 1, 2)
grid.addWidget(messageEdit, 4, 0, 1, 2)
which will make the message label and text-edit span the two columns created by the title and author fields above.
Now when you add the buttons, they must have a layout of their own, since the top two rows are already determining the width of the two columns. If you added the buttons directly to the grid, they would be forced to have the same widths as the widgets in the top two rows (or vice versa). So the buttons should be added like this:
hbox = QHBoxLayout()
sendBtn = QPushButton("send")
cancelBtn = QPushButton("cancel")
sendBtn.clicked.connect(self.send)
cancelBtn.clicked.connect(self.cancel)
hbox.addStretch()
hbox.addWidget(sendBtn)
hbox.addWidget(cancelBtn)
grid.addLayout(hbox, 5, 0, 1, 2)

Related

Problem with GridLayout widget stretching/sizing

I'm trying to make a simple image editor GUI using the GridLayout. However, I am coming across a problem where the ratios between the image and the side panels are not what I want them to be. Currently, my code is:
import sys
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
class Window(QMainWindow):
def __init__(self, parent = None):
super().__init__(parent)
self.setWindowTitle("PyEditor")
self.setGeometry(100, 100, 500, 300)
self.centralWidget = QLabel()
self.setCentralWidget(self.centralWidget)
self.gridLayout = QGridLayout(self.centralWidget)
self.createImagePanel()
self.createDrawPanel()
self.createLayerPanel()
def createImagePanel(self):
imageLabel = QLabel(self)
pixmap = QPixmap('amongus.png')
imageLabel.setPixmap(pixmap)
self.gridLayout.addWidget(imageLabel, 0, 0, 3, 4)
def createDrawPanel(self):
drawPanel = QLabel(self)
drawLayout = QVBoxLayout()
drawPanel.setLayout(drawLayout)
tabs = QTabWidget()
filterTab = QWidget()
drawTab = QWidget()
tabs.addTab(filterTab, "Filter")
tabs.addTab(drawTab, "Draw")
drawLayout.addWidget(tabs)
self.gridLayout.addWidget(drawPanel, 0, 4, 1, 1)
def createLayerPanel(self):
layerPanel = QLabel(self)
layerLayout = QVBoxLayout()
layerPanel.setLayout(layerLayout)
tab = QTabWidget()
layerTab = QWidget()
tab.addTab(layerTab, "Layers")
layerLayout.addWidget(tab)
self.gridLayout.addWidget(layerPanel, 1, 4, 1, 1)
if __name__ == "__main__":
app = QApplication(sys.argv)
window = Window()
window.show()
sys.exit(app.exec_())
This gives me the following window:
When I resize the window, only the filter/draw and layer panels are stretching, and not the image panel. I want to image panel to stretch as well and take up the majority of the window instead.
While theoretically every Qt widget could be used as a container, some widgets should not be used for such a purpose, as their size hints, size policies and resizing have different and specific behavior depending on their nature.
QLabel is intended as a display widget, not as a container. Everything related to its size is based on the content (text, image or animation), so the possible layout set for it will have no result in size related matters and will also create some inconsistencies in displaying the widgets added to that layout.
If a basic container is required, then basic QWidget is the most logical choice.
Then, if stretching is also a requirement, that should be applied using the widget or layout stretch factors. For QGridLayout, this is achieved by using setColumnStretch() or setRowStretch().
Trying to use the row or column span is not correct for this purpose, as the spanning only indicates how many grid "cells" a certain layout item will use, which only makes sense whenever there are widgets that should occupy more than one "cell", exactly like the spanning of a table.
So, the following changes are required to achieve the wanted behavior:
change all QLabel to QWidget (except for the label that shows the image, obviously);
use the proper row/column spans; the imageLabel should be added with only one column span (unless otherwise required):
self.gridLayout.addWidget(imageLabel, 0, 0, 3, 1, alignment=Qt.AlignCenter)
set a column stretch of (at least) 1 for the first column:
self.gridLayout.setColumnStretch(0, 1)
if you want the image to be center aligned in the available space, set the alignment on the widget (not when adding it to the layout):
imageLabel = QLabel(self, alignment=Qt.AlignCenter)
Note that all the above will not scale the image whenever the available size is greater than that of the image. While you can set the scaledContents to True, the result will be that the image will be stretched to fill the whole available space, and unfortunately QLabel doesn't provide the ability to keep the aspect ratio. If you need that, then it's usually easier to subclass QWidget and provide proper implementation for size hint and paint event.
class ImageViewer(QWidget):
_pixmap = None
def __init__(self, pixmap=None, parent=None):
super().__init__(parent)
self.setPixmap(pixmap)
def setPixmap(self, pixmap):
if self._pixmap != pixmap:
self._pixmap = pixmap
self.updateGeometry()
def sizeHint(self):
if self._pixmap and not self._pixmap.isNull():
return self._pixmap.size()
return super().sizeHint()
def paintEvent(self, event):
if self._pixmap and not self._pixmap.isNull():
qp = QPainter(self)
scaled = self._pixmap.scaled(self.width(), self.height(),
Qt.KeepAspectRatio, Qt.SmoothTransformation)
rect = scaled.rect()
rect.moveCenter(self.rect().center())
qp.drawPixmap(rect, scaled)

Using QScrollArea collapses children widgets

I am trying to create a dynamic GUI with multiple Groupbox objects in a QVBoxLayout. As there are a lot of them, I will be needing a scroll area to make them available to the end user.
So I tried to change to top widget of this tab from a QWidget to a QScrollArea.
Before the change:
This is the kind of result I want but with a scroll bar because the window is too high.
After the change to QScrollArea:
My GroupBoxs are now "collapsed" and there is not scrollbar. I tried setting their size but it is not adequate because they are not fixed. I searched the documentation and tried to use WidgetResizable or I tried to set a fixed height or the sizehint but nothing worked as I wanted.
After creating the the Groupbox, the sizeHint for my QScrollArea is already very low (around 150px of height) so I think I'm missing a parameter.
It would be complicated to provide code as it is intricate. If necessary I could recreate the problem in a simpler way.
How to reproduce:
from PyQt5 import QtWidgets, QtGui, QtCore
from PyQt5.QtWidgets import *
import sys
class Example(QMainWindow):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
v_layout = QVBoxLayout()
scroll_area = QScrollArea()
self.layout().addWidget(scroll_area)
scroll_area.setLayout(v_layout)
# v_layout.setSizeConstraint(QLayout.SetMinimumSize)
for i in range(50):
box = QGroupBox()
grid = QGridLayout()
box.setLayout(grid)
grid.addWidget(QLabel("totototo"), 0, 0)
grid.addWidget(QLineEdit(), 1, 0)
grid.addWidget(QPushButton(), 2, 0)
v_layout.addWidget(box)
self.show()
app = QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
Uncommenting # v_layout.setSizeConstraint(QLayout.SetMinimumSize) allows the content of the group boxes to deploy and fixes the first part of the issue. But there is still not scroll bar.
You have 2 errors:
A widget should not be added to the layout of a QMainWindow, but the setCentralWidget method should be used.
You should not add the layout to the QScrollArea but use a widget as a container for the other widgets, also if you use layouts then you have to activate the widgetResizable property.
Considering the above, the solution is:
def initUI(self):
scroll_area = QScrollArea(widgetResizable=True)
self.setCentralWidget(scroll_area)
container = QWidget()
scroll_area.setWidget(container)
v_layout = QVBoxLayout(container)
for i in range(50):
box = QGroupBox()
grid = QGridLayout()
box.setLayout(grid)
grid.addWidget(QLabel("totototo"), 0, 0)
grid.addWidget(QLineEdit(), 1, 0)
grid.addWidget(QPushButton(), 2, 0)
v_layout.addWidget(box)
self.show()

How do I stop a custom widget from expanding in PyQt5?

I'm trying to make a widget which contains many other widgets and I keep having problems with resizing the window: the widget keeps expanding even if I "tell" it not to. Here is my minimal example:
from PyQt5 import QtWidgets, QtGui, QtCore
class CustomWidget(QtWidgets.QWidget):
def __init__(self):
super().__init__()
self.layout = QtWidgets.QGridLayout()
self.button1 = QtWidgets.QPushButton("Button A")
self.button2 = QtWidgets.QPushButton("Button B")
self.label1 = QtWidgets.QLabel("Long label that can span multiple columns")
self.layout.addWidget(self.button1, 0, 0)
self.layout.addWidget(self.button2, 0, 1)
self.layout.addWidget(self.label1, 1, 0, 1, 2)
self.setLayout(self.layout)
class App(QtWidgets.QWidget):
def __init__(self):
super().__init__()
self.cw = CustomWidget()
self.layout = QtWidgets.QVBoxLayout()
self.layout.addWidget(self.cw)
self.setLayout(self.layout)
self.show()
QtWidgets.QApplication.setStyle(QtWidgets.QStyleFactory.create("Fusion"))
app = QtWidgets.QApplication([])
win = App()
status = app.exec_()
This code does work however if I resize the window then all the buttons and labels get spread out over the screen which is not what I want.
I've tried:
Setting a fixed size: doesn't work because the label text can be different lengths and I want the widget to resize accordingly (but stay as small as possible)
self.setFixedSize(self.size()) doesn't do anything and sometimes makes it worse
self.setSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Minimum) or any other size policy seems to do nothing
TL;DR I want my widget to shrink even if there's empty space but I don't want to set a fixed size.
EDIT:
I have partially solved the problem by passing in alignment=QtCore.Qt.AlignLeft to all the self.layout.addWidget calls. It doesn't totally get rid of the problem however it may be a step in the right direction.
If you want to keep those widgets as small as possible, you can add a row and column stretch to the layout, set for a row/column index greater than the most bottom-right layout coordinate used.
In your case, you have two rows and two columns, so it's enough to set the stretch for the third row and column:
self.layout.setColumnStretch(2, 1)
self.layout.setRowStretch(2, 1)
Obviously, you can set it for a very high index, so that if you have to add more widgets you don't have to care about it.
If you want to keep those widgets in the center, just add them starting from the second row and column and set the row/column stretch for the first row/column too:
self.layout.setColumnStretch(0, 1)
self.layout.setRowStretch(0, 1)
self.layout.addWidget(self.button1, 1, 1)
self.layout.addWidget(self.button2, 1, 2)
self.layout.addWidget(self.label1, 2, 1, 1, 2)
self.layout.setColumnStretch(3, 1)
self.layout.setRowStretch(3, 1)
Note that you can also use the size policy, but you have to use Maximum, not Minimum, and you also need to add widgets with the correct alignment.
I got same issue. I resolved the issue by setting the values of minimum and maximum size to equal like both to 100
If you want to shrink the widget set minimum to 0 and set maximum value to the size you want start the display. It will not expand but shrink
From what you are describing it seems you just don't want to use a layout.
Make a widget with a layout to hold the buttons and label. Place this widget in the main window without using a layout, instead place with setGeometry.
Something like this,
from PyQt5 import QtWidgets, QtGui, QtCore
class CustomWidget(QtWidgets.QWidget):
def __init__(self):
super().__init__()
self.resize(400, 100)
# Create a widget to hold your buttons
self.fixwidget = QtWidgets.QWidget(self)
self.layout = QtWidgets.QGridLayout()
self.button1 = QtWidgets.QPushButton("Button A")
self.button2 = QtWidgets.QPushButton("Button B")
self.label1 = QtWidgets.QLabel("Long label that can span multiple columns")
self.layout.addWidget(self.button1, 0, 0)
self.layout.addWidget(self.button2, 0, 1)
self.layout.addWidget(self.label1, 1, 0, 1, 2)
# set the layout inside the widget
self.fixwidget.setLayout(self.layout)
# place the widget with setGeometry
height = self.fixwidget.sizeHint().height()
width = self.fixwidget.sizeHint().width()
xpos, ypos = 5, 5
self.fixwidget.setGeometry(QtCore.QRect(xpos, ypos, width, height))
QtWidgets.QApplication.setStyle(QtWidgets.QStyleFactory.create("Fusion"))
app = QtWidgets.QApplication([])
win = CustomWidget()
win.show()
status = app.exec_()
Notice that not using a layout is, in most cases, not advised as it makes managing the window harder.

PyQt5 - Properly dynamically sizing and laying out components

I am trying to make a GUI that will display (and eventually let the user build) circuits. Below is a rough sketch of what the application is supposed to look like.
The bottom panel (currently a simple QToolBar) should be of constant height but span the width of the application and the side panels (IOPanels in the below code) should have a constant width and span the height of the application.
The main part of the application (Canvas, which is currently a QWidget with an overriden paintEvent method, but might eventually become a QGraphicsScene with a QGraphicsView or at least something scrollable) should then fill the remaining space.
This is my current code:
from PyQt5.QtWidgets import *
from PyQt5.QtCore import Qt, QSize
class MainWindow(QMainWindow):
def __init__(self, *args):
super().__init__(*args)
self._wire_ys = None
self._init_ui()
self.update_wire_ys()
def update_wire_ys(self):
self._wire_ys = [(i + 0.5) * self.panel.height() / 4 for i in range(4)]
self.input.update_field_positions()
self.output.update_field_positions()
def wire_ys(self):
return self._wire_ys
def _init_ui(self):
self.panel = QWidget(self)
self.canvas = Canvas(self, self.panel)
self.input = IOPanel(self, self.panel)
self.output = IOPanel(self, self.panel)
hbox = QHBoxLayout(self.panel)
hbox.addWidget(self.canvas, 1, Qt.AlignCenter)
hbox.addWidget(self.input, 0, Qt.AlignLeft)
hbox.addWidget(self.output, 0, Qt.AlignRight)
self.setCentralWidget(self.panel)
self.addToolBar(Qt.BottomToolBarArea, self._create_run_panel())
self.reset_placement()
def _create_run_panel(self):
# some other code to create the toolbar
return QToolBar(self)
def reset_placement(self):
g = QDesktopWidget().availableGeometry()
self.resize(0.4 * g.width(), 0.4 * g.height())
self.move(g.center().x() - self.width() / 2, g.center().y() - self.height() / 2)
def resizeEvent(self, *args, **kwargs):
super().resizeEvent(*args, **kwargs)
self.update_wire_ys()
class IOPanel(QWidget):
def __init__(self, main_window, *args):
super().__init__(*args)
self.main = main_window
self.io = [Field(self) for _ in range(4)]
def update_field_positions(self):
wire_ys = self.main.wire_ys()
for i in range(len(wire_ys)):
field = self.io[i]
field.move(self.width() - field.width() - 10, wire_ys[i] - field.height() / 2)
def sizeHint(self):
return QSize(40, self.main.height())
class Field(QLabel):
def __init__(self, *args):
super().__init__(*args)
self.setAlignment(Qt.AlignCenter)
self.setText(str(0))
self.resize(20, 20)
# This class is actually defined in another module and imported
class Canvas(QWidget):
def __init__(self, main_window, *args):
super().__init__(*args)
self.main = main_window
def paintEvent(self, e):
print("ASFD")
qp = QPainter()
qp.begin(self)
self._draw(qp)
qp.end()
def _draw(self, qp):
# Draw stuff
qp.drawLine(0, 0, 1, 1)
# __main__.py
def main():
import sys
app = QApplication(sys.argv)
w = MainWindow()
w.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
Running that code gives me the following:
Here I have coloured the components to better see them using code like this in their construction:
p = self.palette()
p.setColor(self.backgroundRole(), Qt.blue)
self.setPalette(p)
self.setAutoFillBackground(True)
Green is the central panel (MainWindow.panel), blue are the IOPanels, the Fields are supposed to be red, and the Canvas is supposed to be white.
Ignore the bottom toolbar, it's some extra code I didn't include above (to keep it as minimal and relevant as possible), but it does no resizing of anything and no layout management except for its own child QWidget. In fact, including the painting code in my above minimal example gave a similar result with thinner bottom toolbar without the Run button. I'm just including the toolbar here to show its expected behaviour (as the toolbar is working correctly) in the general layout.
This result has several problems.
Problem 1
The Fields do not show up, initially. However, they do show up (and are appropriately placed within their respective panels) once I resize the main window. Why is this? The only thing the main window's resizeEvent does is update_wire_ys and update_field_positions, and those are performed by the main window's __init__ as well.
Problem 2
The IOPanels are not properly aligned. The first one should be on the left side of the central panel. Changing the order of adding them fixes this, as so:
hbox.addWidget(self.input, 0, Qt.AlignLeft)
hbox.addWidget(self.canvas, 1, Qt.AlignCenter)
hbox.addWidget(self.output, 0, Qt.AlignRight)
However, shouldn't the Qt.AlignX already do this, regardless of the order they're added in? What if I later on wanted to add another panel to the left side, would I have to remove all the components, add the new panel and then re-add them?
Problem 3
The IOPanels are not properly sized. They need to span the entire height of the central panel and touch the left/right edge of the central panel. I'm not sure if this is an issue with the layout or my colouring of the panels. What am I doing wrong?
Problem 4
The Canvas does not show up at all and in fact its paintEvent is never called ("ASFD" never gets printed to the console). I have not overridden its sizeHint, because I want the central panel's layout to appropriately size the Canvas by itself. I was hoping the stretch factor of 1 when adding the component would accomplish that.
hbox.addWidget(self.canvas, 1, Qt.AlignCenter)
How do I get the canvas to actually show up and fill all the remaining space on the central panel?
This is the typical spaghetti code, where many elements are tangled, which is usually difficult to test, I have found many problems such as sizeEvent is only called when the layout containing the widget is called, another example is when you use the Function update_field_positions and update_wire_ys that handle each other object.
In this answer I will propose a simpler implementation:
IOPanel clas must contain a QVBoxLayout that handles the changes of image size.
In the MainWindow class we will use the layouts with the alignments but you must add them in order.
lay.addWidget(self.input, 0, Qt.AlignLeft)
lay.addWidget(self.canvas, 0, Qt.AlignCenter)
lay.addWidget(self.output, 0, Qt.AlignRight)
To place a minimum width for IOPanel we use QSizePolicy() and setMinimumSize()
Complete code:
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from PyQt5.QtCore import *
class Field(QLabel):
def __init__(self, text="0", parent=None):
super(Field, self).__init__(parent=parent)
self.setAlignment(Qt.AlignCenter)
self.setText(text)
class IOPanel(QWidget):
numbers_of_fields = 4
def __init__(self, parent=None):
super(IOPanel, self).__init__(parent=None)
lay = QVBoxLayout(self)
for _ in range(self.numbers_of_fields):
w = Field()
lay.addWidget(w)
self.setMinimumSize(QSize(40, 0))
sizePolicy = QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Preferred)
self.setSizePolicy(sizePolicy)
class Panel(QWidget):
def __init__(self, parent=None):
super(Panel, self).__init__(parent=None)
lay = QHBoxLayout(self)
self.input = IOPanel()
self.output = IOPanel()
self.canvas = QWidget()
lay.addWidget(self.input, 0, Qt.AlignLeft)
lay.addWidget(self.canvas, 0, Qt.AlignCenter)
lay.addWidget(self.output, 0, Qt.AlignRight)
class MainWindow(QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent=parent)
self.initUi()
self.reset_placement()
def initUi(self):
panel = Panel(self)
self.setCentralWidget(panel)
self.addToolBar(Qt.BottomToolBarArea, QToolBar(self))
def reset_placement(self):
g = QDesktopWidget().availableGeometry()
self.resize(0.4 * g.width(), 0.4 * g.height())
self.move(g.center().x() - self.width() / 2, g.center().y() - self.height() / 2)
def main():
import sys
app = QApplication(sys.argv)
w = MainWindow()
w.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
Screenshot:

Wrong widget order using vbox layout PyQt

I am trying to put a QLabel widget on top of (ie before) a QLineEdit widget edit.
But it keeps appearing after the QLineEdit widget. My code,
class CentralWidget(QtGui.QWidget):
def __init__(self, parent=None):
super(CentralWidget, self).__init__(parent)
# set layouts
self.layout = QtGui.QVBoxLayout(self)
# Flags
self.randFlag = False
self.sphereFlag = False
self.waterFlag = False
# Poly names
self.pNames = QtGui.QLabel("Import file name", self) # label concerned
self.polyNameInput = QtGui.QLineEdit(self) # line edit concerned
# Polytype selection
self.polyTypeName = QtGui.QLabel("Particle type", self)
polyType = QtGui.QComboBox(self)
polyType.addItem("")
polyType.addItem("Random polyhedra")
polyType.addItem("Spheres")
polyType.addItem("Waterman polyhedra")
polyType.activated[str].connect(self.onActivated)
self.layout.addWidget(self.pNames)
self.layout.addWidget(self.polyNameInput)
self.layout.addWidget(self.pNames)
self.layout.addWidget(self.polyTypeName)
self.layout.addWidget(polyType)
self.layout.addStretch()
def onActivated(self, text):
# Do loads of clever stuff that I'm not at liberty to share with you
class Polyhedra(QtGui.QMainWindow):
def __init__(self):
super(Polyhedra, self).__init__()
self.central_widget = CentralWidget(self)
self.setCentralWidget(self.central_widget)
# Set up window
self.setGeometry(500, 500, 300, 300)
self.setWindowTitle('Pyticle')
self.show()
# Combo box
def onActivated(self, text):
self.central_widget.onActivated(text)
def main():
app = QtGui.QApplication(sys.argv)
poly = Polyhedra()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
The window I get is below.
What am I missing? I thought QVbox allowed to stack things vertically in the order that you add the items to the main widget. (Are these sub-widget objects called widgets?)
The problem is because you are adding self.pNames label to layout twice.
#portion of your code
...
self.layout.addWidget(self.pNames) # here
self.layout.addWidget(self.polyNameInput)
self.layout.addWidget(self.pNames) # and here
self.layout.addWidget(self.polyTypeName)
self.layout.addWidget(polyType)
self.layout.addStretch()
...
The first time you add the QLabel, it gets added before the LineEdit and when you add it second time, it just moves to the bottom of LineEdit. This happens because there is only one object of QLabel which is self.pNames. It can be added to only one location. If you want to use two labels, consider creating two separate objects of QLabel

Categories