whenever I run this script of mine, the layout of my elements are somewhat in the correct area but the spacing in the right column (where the labels/button/line edit resides) makes it very unsightly with weird spacing in between them.
Any ideas how I can kill it off? Or perhaps is the using of gridLayout not a wise choice?
class createUI(QFrame):
def __init__(self, parent =None):
QFrame.__init__(self, parent)
self.initUI()
def initUI(self):
self.objLbl = QLabel('Name of Item')
self.objTxt = QLineEdit()
self.objTxt.setMaximumWidth(100)
self.renameLbl = QLabel('Rename')
self.renameTxt = QLineEdit()
self.renameTxt.setMaximumWidth(100)
self.itemLbl = QLabel('Item Lists')
self.itemList = QListWidget()
self.okButton = QPushButton('OK')
self.okButton.setMaximumWidth(100)
gridLayout = QGridLayout()
gridLayout.addWidget(self.itemLbl,1,0)
gridLayout.addWidget(self.itemList,2,0,6,1)
gridLayout.addWidget(self.objLbl,2,1)
gridLayout.addWidget(self.objTxt,3,1)
gridLayout.addWidget(self.renameLbl,4,1)
gridLayout.addWidget(self.renameTxt,5,1)
gridLayout.addWidget(self.okButton,7,1)
self.setLayout(gridLayout)
self.setWindowTitle("Testing")
Insert a spacer with vertical strech above the OK button:
gridLayout.addWidget(self.renameTxt,5,1)
gridLayout.addItem(QSpacerItem(
0, 0, QSizePolicy.Minimum, QSizePolicy.Expanding), 6, 1)
gridLayout.addWidget(self.okButton,7,1)
Related
I've recently started with PyQt5 and am now trying to write an interface for an application.
I want to have a sidebar with different tools and essentially an I/O panel at the bottom of the window.
This I/O panel should contain only 2 QPushButtons - Ok and Cancel.
When I change the size of the application window, the right button (Ok) is supposed to stay in the bottom right-hand corner, while the left button (Cancel) - in the bottom left-hand corner, right next to (but not below) the sidebar. To achieve that, I've added a horizontal spacer between the buttons.
However, both buttons always remain in the bottom right-hand corner of the window. I've played with QSizePolicy parameters a bit, but it doesn't seem to do anything. The only way I've been able to get what I want was by setting the horizontal spacer width in the constructor at a non-zero value, but I'm not satisfied with this solution. What should I change in the code below to get what I want?
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from PyQt5.QtCore import *
class Sidebar(QWidget):
def __init__(self, parent=None):
super().__init__()
layout = QVBoxLayout()
layout.addWidget(QPushButton(maximumWidth = 200, minimumHeight = 50))
layout.addWidget(QPushButton(maximumWidth = 200, minimumHeight = 50))
layout.addWidget(QPushButton(maximumWidth = 200, minimumHeight = 50))
layout.addWidget(QPushButton(maximumWidth = 200, minimumHeight = 50))
layout.addWidget(QPushButton(maximumWidth = 200, minimumHeight = 50))
layout.addWidget(QPushButton(maximumWidth = 200, minimumHeight = 50))
layout.addItem(QSpacerItem(200, 0, QSizePolicy.Maximum, QSizePolicy.Expanding))
self.setLayout(layout)
self.setMaximumWidth(200)
sizePolicy = QSizePolicy(QSizePolicy.Maximum, QSizePolicy.Expanding)
self.setSizePolicy(sizePolicy)
layout.setSpacing(0)
layout.setContentsMargins(0, 0, 0, 0)
class IOPanel(QWidget):
def __init__(self, parent=None):
super().__init__()
layout = QHBoxLayout()
layout.addWidget(QPushButton("Cancel", maximumWidth = 100, maximumHeight = 30), 0, Qt.AlignLeft)
layout.addItem(QSpacerItem(0, 30, QSizePolicy.Expanding, QSizePolicy.Maximum))
layout.addWidget(QPushButton("Ok", maximumWidth = 100, minimumHeight = 30), 0, Qt.AlignRight)
self.setLayout(layout)
sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Maximum)
self.setSizePolicy(sizePolicy)
class Canvas(QWidget):
def __init__(self, parent=None):
super().__init__()
layout = QVBoxLayout()
panel = IOPanel()
layout.addWidget(QWidget(), 0, Qt.AlignTop)
layout.addWidget(panel, 0, Qt.AlignBottom)
self.setLayout(layout)
sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
self.setSizePolicy(sizePolicy)
class Interface(QWidget):
def __init__(self, parent=None):
super().__init__()
layout = QHBoxLayout()
layout.addWidget(Sidebar(), 0, Qt.AlignLeft)
layout.addWidget(Canvas(), 0, Qt.AlignRight)
layout.setContentsMargins(0, 0, 0, 0)
self.setLayout(layout)
sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
self.setSizePolicy(sizePolicy)
class MainWindow(QMainWindow):
def __init__(self, parent=None):
super().__init__()
interface = Interface()
self.setCentralWidget(interface)
def main():
import sys
app = QApplication(sys.argv)
w = MainWindow()
w.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()
You can set the layout to QGridLayout in your Interface widget. You can play around with number of rows and columns to suit your setup/goal.
....
class Canvas(QWidget):
def __init__(self, parent=None):
super().__init__()
layout = QVBoxLayout()
#panel = IOPanel() # Remove IOPanel from Canvas widget add it to Main Interface
layout.addWidget(QWidget(), 0, Qt.AlignTop)
#layout.addWidget(panel, 0, Qt.AlignBottom)
self.setLayout(layout)
sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
self.setSizePolicy(sizePolicy)
class Interface(QWidget):
def __init__(self, parent=None):
super().__init__()
layout = QGridLayout()
layout.addWidget(Sidebar(), 0, 0, 4, 1)
layout.addWidget(Canvas(), 0, 1, 3, 3)
layout.addWidget(IOPanel(), 3, 1, 3, 3)
layout.setContentsMargins(0, 0, 0, 0)
self.setLayout(layout)
sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
self.setSizePolicy(sizePolicy)
The problem is not in the way the layouts are set, but due to the usage of the alignment argument for addWidget() (emphasis mine):
The alignment is specified by alignment. The default alignment is 0, which means that the widget fills the entire cell.
This means that specifying an alignment will not make the widget fill the entire cell, but only its preferred size (what's returned by sizeHint()). Moreover, since you are already adding a spacer to the sidebar, and the buttons will be on bottom of the vertical layout of the canvas, specifying the alignment is unnecessary anyway.
So, the solution is simple: remove all unrequired arguments from addWidget() (including the stretch, which already defaults to 0):
class Interface(QWidget):
def __init__(self, parent=None):
# ...
layout.addWidget(Sidebar())
layout.addWidget(Canvas())
Note that you might want to do the same for all addWidget() in Canvas too.
Other considerations:
QPushButton already tries to expand as long as the layout on which it's added allows it; since you already set a maximum width for the panel, setting it also for the buttons is pointless;
boxed layouts already provide convenience functions for spacers (addStretch() and addSpacing()), which internally create QSpacerItems on their own: you can use layout.addStretch() between the buttons of IOPanel;
unless you need to set advanced aspects in the size policy, there's no use in creating a new object: you can directly use self.setSizePolicy(<horizontal policy>, <vertical policy>) instead;
multiple levels of widgets and their layouts increase the amount of margins between objects (that's why there is a lot of space around the IO buttons) unless margins are explicitly removed for all involved layouts; unless you go for a grid layout, consider using less container widgets and use nested layouts by using addLayout(); another option is to override QStyle's pixelMetric and return 0 for all PM_Layout*Margin metrics, but that would default all layout margins to 0 unless explicitly set;
most of the size related code set for the panel is a bit boilerplate; consider using simpler concepts and avoiding unnecessary calls: remove the spacer item and add a stretch, use stylesheet min-size properties and properly implement sizeHint(); as you can see, the resulting code is much cleaner and easier to read:
class Sidebar(QWidget):
def __init__(self, parent=None):
super().__init__()
layout = QVBoxLayout(self)
layout.setSpacing(0)
layout.setContentsMargins(0, 0, 0, 0)
layout.addWidget(QPushButton())
layout.addWidget(QPushButton())
layout.addWidget(QPushButton())
layout.addWidget(QPushButton())
layout.addWidget(QPushButton())
layout.addWidget(QPushButton())
layout.addStretch()
self.setStyleSheet('''
Sidebar > QPushButton { min-height: 50; }
''')
self.setMaximumWidth(200)
def sizeHint(self):
return QSize(self.maximumWidth(), super().sizeHint().height())
I have made a browser in pyqt5. It has a search bar and a button. I want to add a back button and a forward button, just like the ones in regular browsers. Can you also explain how you added it, if you added it, so that I can add my own widgets in the future:) this is the picture of my browser
so I want the back button and the forward button on the left side of the search bar. Here's the code:
class StealthBrowser(QMainWindow):
def __init__(self):
super(StealthBrowser, self).__init__()
self.setGeometry(200, 200, 1000, 700)
self.setWindowTitle("StealthBrowser")
self.search = QtWidgets.QPushButton("Search")
self.textbox = QLineEdit()
self.browser = QWebEngineView()
self.backbutton = QtWidgets.QPushButton("Back")
self.forwardbutton = QtWidgets.QPushButton("Front")
self.initUI()
def initUI(self):
central_widget = QWidget()
self.setCentralWidget(central_widget)
lay = QGridLayout(central_widget)
lay.addWidget(self.textbox, 0, 0)
lay.addWidget(self.search, 0, 1)
lay.addWidget(self.browser, 1, 0, 1, 2)
self.search.clicked.connect(self.search_button_clicked)
In this case you have to use the layout (in SO there are hundreds of examples in addition to the official examples like https://doc.qt.io/qt-5/layout.html).
You must also use the triggerAction() method of QWebEnginePage to perform the forward and backward actions.
class StealthBrowser(QMainWindow):
def __init__(self):
super(StealthBrowser, self).__init__()
self.setGeometry(200, 200, 1000, 700)
self.setWindowTitle("StealthBrowser")
self.search = QtWidgets.QPushButton("Search")
self.textbox = QLineEdit()
self.browser = QWebEngineView()
self.backbutton = QtWidgets.QPushButton("Back")
self.forwardbutton = QtWidgets.QPushButton("Front")
self.initUI()
def initUI(self):
central_widget = QWidget()
self.setCentralWidget(central_widget)
lay = QGridLayout(central_widget)
lay.addWidget(self.backbutton, 0, 0)
lay.addWidget(self.forwardbutton, 0, 1)
lay.addWidget(self.textbox, 0, 2)
lay.addWidget(self.search, 0, 3)
lay.addWidget(self.browser, 1, 0, 1, 4)
self.search.clicked.connect(self.search_button_clicked)
self.backbutton.clicked.connect(self.backward)
self.forwardbutton.clicked.connect(self.forward)
def backward(self):
self.browser.page().triggerAction(QWebEnginePage.Back)
def forward(self):
self.browser.page().triggerAction(QWebEnginePage.Forward)
Note: A browser implementation with Qt already exists so you should check it out as an example: https://doc.qt.io/qt-5/qtwebengine-webenginewidgets-simplebrowser-example.html, which is also written for PySide2 so the conversion to PyQt5 is trivial: https://code.qt.io/cgit/pyside/pyside-setup.git/tree/examples/webenginewidgets/simplebrowser/simplebrowser.py
You need to add self.backbutton and self.forwardbutton at positions (0,0) and (0,1), respectively, and move all the other widgets over:
def initUI(self):
central_widget = QWidget()
self.setCentralWidget(central_widget)
lay = QGridLayout(central_widget)
lay.addWidget(self.backbutton, 0, 0)
lay.addWidget(self.forwardbutton, 0, 1)
lay.addWidget(self.textbox, 0, 2)
lay.addWidget(self.search, 0, 3)
# gridlayout is now 4 widgets wide, so the span of self.browser needs to be increased to 4
lay.addWidget(self.browser, 1, 0, 1, 4)
self.search.clicked.connect(self.search_button_clicked)
Alternatively you could opt for a combination of a QHBoxLayout and QVBoxLayout which would allow you to add widgets without having to specify their position in the layout:
def initUI2(self):
central_widget = QWidget()
self.setCentralWidget(central_widget)
# create horizontal layout and add buttons and line edit
hlayout = QHBoxLayout()
hlayout.setContentsMargins(0,0,0,0)
hlayout.addWidget(self.backbutton)
hlayout.addWidget(self.forwardbutton)
hlayout.addWidget(self.textbox)
hlayout.addWidget(self.search)
# create vertical layout for central widget and add horizontal layout with buttons and the web engine view
vlayout = QVBoxLayout(central_widget)
vlayout.addLayout(hlayout)
vlayout.addWidget(self.browser)
self.search.clicked.connect(self.search_button_clicked)
I have a widget which contains three labels and three lineedits. I would like all lineedits to be aligned vertically directly after the longest label.
Here is my class:
class ScaleDisplayWidget(QWidget):
def __init__(self, parent=None):
super(ScaleDisplayWidget, self).__init__(parent)
self.setFixedSize(400, 200)
self.initUI()
self.update(0, 0, 0)
def initUI(self):
'''
Setup GUI elements of scale window
'''
mainLayout = QVBoxLayout()
hLayout = QHBoxLayout()
hLayout.setSpacing(0)
self.dx_label = QLabel('DX:')
self.dx_label.setFixedWidth(80)
self.dx_edit = QLineEdit()
self.dx_edit.setReadOnly(True)
self.dx_edit.setFocus(True)
self.dx_edit.setFixedWidth(150)
hLayout.addWidget(self.dx_label)
hLayout.addWidget(self.dx_edit)
h2Layout = QHBoxLayout()
h2Layout.setSpacing(0)
self.dy_label = QLabel('DY:')
self.dy_label.setFixedWidth(80)
self.dy_edit = QLineEdit()
self.dy_edit.setReadOnly(True)
self.dy_edit.setFocus(True)
self.dy_edit.setFixedWidth(150)
h2Layout.addWidget(self.dy_label)
h2Layout.addWidget(self.dy_edit)
h3Layout = QHBoxLayout()
h3Layout.setSpacing(0)
self.dist_label = QLabel('Distance:')
self.dist_label.setFixedWidth(80)
self.dist_edit = QLineEdit()
self.dist_edit.setReadOnly(True)
self.dist_edit.setFocus(True)
self.dist_edit.setFixedWidth(150)
h3Layout.addWidget(self.dist_label)
h3Layout.addWidget(self.dist_edit)
mainLayout.addLayout(hLayout)
mainLayout.addLayout(h2Layout)
mainLayout.addLayout(h3Layout)
self.setLayout(mainLayout)
self.show()
def update(self, dx, dy, dist):
self.dx_edit.setText(str(dx))
self.dy_edit.setText(str(dy))
self.dist_edit.setText(str(dist))
In this case I'm aiming to have all lineedits aligned directly after the distance label (maybe add 5 pixels or something small of padding). I have tried using setContentsMargins(0,0,0,0) on all the layouts but it hasn't changed anything.
Use a QFormLayout instead:
self.dx_edit = QLineEdit()
self.dx_edit.setReadOnly(True)
self.dx_edit.setFocus(True)
self.dx_edit.setFixedWidth(150)
self.dy_edit = QLineEdit()
self.dy_edit.setReadOnly(True)
self.dy_edit.setFocus(True)
self.dy_edit.setFixedWidth(150)
self.dist_edit = QLineEdit()
self.dist_edit.setReadOnly(True)
self.dist_edit.setFocus(True)
self.dist_edit.setFixedWidth(150)
layout = QFormLayout(self)
layout.addRow("DX", self.dx_edit)
layout.addRow("DY", self.dy_edit)
layout.addRow("Distance", self.dist_edit)
You might need to set the label align to left with layout.setLabelAlignment(Qt.AlignLeft)
I am an experienced (if rather rusty) Windows programmer. I am trying to create a partial logic simulation of a computer (Ferranti Argus 400) I designed in 1963 (yes I am that old!
I am however new to Python programming and am having a formatting problem using a QTableWidget.
I want to display this on the right alongside a set of controls for entering test data for a simulated function (and to use the QTableWidget to display the steps the simulation will run through to) for example show the working of the multiplier bit by bit.
I am trying to use a QHBoxLayout() as my outer container with two embedded Boxes. The left hand QVBox will contain my data entry for the simulation and the right hand box will contain a QTablewidget object with scrolling display of the state of the computer registers.
The QTableWidget works (the one below is a simplification) correctly until I add a Vbox on the left which contains the controls (one set is included here). The Table shrinks from its full width and positions itself at the right of the remaining space. I would like it on the left next to the other controls and for it to scroll if there is insufficient room (the target machine has 32*24 bit registers to display although I will be testing initially with 8 bits)
Screen with a group on left
enter image description here
class App(QMainWindow):
def __init__(self):
super().__init__()
self.title = 'PyQt5 simple window'
self.left = 10
self.top = 50
self.width = 800
self.height = 480
self.initUI()
def initUI(self):
self.setWindowTitle(self.title)
self.setGeometry(self.left, self.top, self.width, self.height)
# Parent layout
self.central_Widget = QWidget(self)
self.setCentralWidget(self.central_Widget)
self.central_Layout = QHBoxLayout()
# Configure left panel as Vbox
self.leftpanel_Layout = QVBoxLayout()
#
self.leftpanel_Layout.addWidget(DataSelectionGroup("Data entry mode"))
#
# Add stretch to bottom of panel
self.leftpanel_Layout.addStretch(0)
self.rightpanel_Layout = QVBoxLayout()
self.myLogTable = LogTablex()
self.rightpanel_Layout.addWidget(self.myLogTable)
self.setLayout(self.rightpanel_Layout)
# Add left panel layout to central layout
# uncomment these lines to see the unwanted behaviour
#self.central_Layout.addLayout(self.leftpanel_Layout)
#self.central_Layout.addStretch(0)
self.setLayout(self.central_Layout)
self.central_Layout.addLayout(self.rightpanel_Layout)
self.setLayout(self.central_Layout)
self.central_Widget.setLayout(self.central_Layout)
self.setWindowTitle("Demo")
self.show()
class DataSelectionGroup(QGroupBox):
""" Create a group of Radio Buttons to choose type of data entry """
def __init__(self, title):
super().__init__(title)
self.buttonLayout = QVBoxLayout()
# add radio buttons to choose which mode of data entry to use
self.radio1 = QRadioButton("&Binary")
self.buttonLayout.addWidget(self.radio1)
self.radio1.setChecked(True)
self.radio2 = QRadioButton("Decimal &Fraction")
self.buttonLayout.addWidget(self.radio2)
self.radio3 = QRadioButton("Decimal &Integer")
self.buttonLayout.addWidget(self.radio3)
self.setLayout(self.buttonLayout)
class LogTablex(QTableWidget):
def __init__(self, WordLength:int = 24):
super().__init__()
self.WordLength = WordLength
self.initTable(["Note", "X", "Q", "N", "C", "O"])
def initTable(self, headerlist):
font = QFont()
font.setFamily = "Courier New"
font.setPointSize(8)
self.setFont(font)
self.horizontalHeader().setStyleSheet("QHeaderView::section { background-color:lightgrey; }")
self.setColumnCount(len(headerlist))
self.setHorizontalHeaderLabels(headerlist)
self.setRowCount(0)
self.start_newrow()
self.str = '0' * self.WordLength + ' '
self.setCellWidget(0, 0, QLabel("note"))
self.setCellWidget(0, 1, QLabel(self.str))
self.setCellWidget(0, 2, QLabel(self.str))
self.setCellWidget(0, 3, QLabel(self.str))
self.setCellWidget(0, 4, QLabel("1"))
self.setCellWidget(0, 5, QLabel("1"))
self.verticalHeader().setDefaultSectionSize(22)
self.horizontalHeader().setSectionResizeMode(3)
self.resizeColumnsToContents()
def start_newrow(self, note:str = "new note "):
self.insertRow(self.rowCount())
self.setCellWidget(self.rowCount() - 1, 0, QLabel(note))
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = App()
sys.exit(app.exec_())
This version produces the first image. If you uncomment the two lines shown it produces the second version. I have battled with this for a while and I am unable to fathom how to get the right hand table to behave. I am hoping that someone can shed light on it.
I am using PyQt5 with the latest version of Python downloaded about 1 week ago. I am running on Windows 10.
This is my first post here. I hope it is formatted correctly
First of all I recommend you execute a script in the CMD or terminal because many times the IDEs hide us errors like for example if I execute your code I get the following message:
QWidget::setLayout: Attempting to set QLayout "" on App "", which already has a layout
QWidget::setLayout: Attempting to set QLayout "" on App "", which already has a layout
QWidget::setLayout: Attempting to set QLayout "" on App "", which already has a layout
And that error because QMainWindow is a special widget that has a preset layout as shown in the following image:
And in your case you are trying to replace it, the correct thing is to set the layout to the centralwidget.
Going to the problem it seems that you are adding layouts repeatedly and that causes the problem, so I have improved your code to establish the positions (I recommend not to make the layouts attributes of the class since they are generally not reused)
from PyQt5 import QtCore, QtGui, QtWidgets
class App(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
self.title = 'PyQt5 simple window'
self.left, self.top, self.width, self.height = 10, 50, 800, 480
self.initUI()
def initUI(self):
self.setWindowTitle(self.title)
self.setGeometry(self.left, self.top, self.width, self.height)
# Parent layout
self.central_Widget = QtWidgets.QWidget()
self.setCentralWidget(self.central_Widget)
central_Layout = QtWidgets.QHBoxLayout(self.central_Widget)
leftpanel_Layout = QtWidgets.QVBoxLayout()
leftpanel_Layout.addWidget(DataSelectionGroup("Data entry mode"))
leftpanel_Layout.addStretch()
rightpanel_Layout = QtWidgets.QVBoxLayout()
self.myLogTable = LogTablex()
rightpanel_Layout.addWidget(self.myLogTable)
central_Layout.addLayout(leftpanel_Layout)
central_Layout.addLayout(rightpanel_Layout)
self.setWindowTitle("Demo")
self.show()
class DataSelectionGroup(QtWidgets.QGroupBox):
""" Create a group of Radio Buttons to choose type of data entry """
def __init__(self, title):
super().__init__(title)
buttonLayout = QtWidgets.QVBoxLayout(self)
# add radio buttons to choose which mode of data entry to use
self.radio1 = QtWidgets.QRadioButton("&Binary")
buttonLayout.addWidget(self.radio1)
self.radio1.setChecked(True)
self.radio2 = QtWidgets.QRadioButton("Decimal &Fraction")
buttonLayout.addWidget(self.radio2)
self.radio3 = QtWidgets.QRadioButton("Decimal &Integer")
buttonLayout.addWidget(self.radio3)
class LogTablex(QtWidgets.QTableWidget):
def __init__(self, WordLength:int = 24):
super().__init__()
self.WordLength = WordLength
self.initTable(["Note", "X", "Q", "N", "C", "O"])
def initTable(self, headerlist):
font = QtGui.QFont("Courier New", 8)
self.setFont(font)
self.horizontalHeader().setStyleSheet("QHeaderView::section { background-color:lightgrey; }")
self.setColumnCount(len(headerlist))
self.setHorizontalHeaderLabels(headerlist)
self.setRowCount(0)
self.start_newrow()
text = '0' * self.WordLength + ' '
for i, val in enumerate(("note", text, text, text, "1", "1",)):
self.setCellWidget(0, i, QtWidgets.QLabel(val))
self.verticalHeader().setDefaultSectionSize(22)
self.horizontalHeader().setSectionResizeMode(3)
self.resizeColumnsToContents()
def start_newrow(self, note: str = "new note "):
self.insertRow(self.rowCount())
self.setCellWidget(self.rowCount() - 1, 0, QtWidgets.QLabel(note))
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
ex = App()
sys.exit(app.exec_())
Update:
For the first layout to occupy the minimum space and the other one to expand, you must add a stretch of 1 when you add the rightpanel_Layout, so change it to:
central_Layout.addLayout(leftpanel_Layout)
central_Layout.addLayout(rightpanel_Layout, stretch=1) # <---
Also it is not necessary to establish fixed anchors, the layouts will do the work for you:
class DataEntryGroupLayout(QtWidgets.QGroupBox):
def __init__(self, title):
super().__init__(title)
_form = QtWidgets.QFormLayout(self)
# Add the form controls to the group box = list required
_pairlist = ["x value", "n value"]
for _pair in _pairlist:
_label = QtWidgets.QLabel(_pair)
_value = QtWidgets.QLineEdit()
_form.addRow(_label, _value)
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)