Issue
I want a simple widget which contains a QLineEdit, a QPushButton and a second QLineEdit to enter a directory and file name. I started by defining everything within the same QWidget, named CentralWidget
that I wanted to add as the central widget of a QMainWindow
. But putting everything in the same object impacts rapidly code readability, so for once, I wanted to split my GUI into multiple simpler widget that would be arranged together.
But when I do that, the alignment in the QFormLayout
is lost.
MWE: First, the version "working", but with everything in the same object.
from PyQt5.QtCore import QRegExp
from PyQt5.QtWidgets import (
QApplication,
QLineEdit,
QFormLayout,
QWidget,
QPushButton,
QGridLayout,
QStyle,
)
from PyQt5.QtGui import QRegExpValidator
class CentralWidget(QWidget):
def __init__(self):
super().__init__()
self.setObjectName("central_widget")
# create central widget layout
layout = QFormLayout()
layout_dir = QGridLayout()
line = QLineEdit(objectName="QLineEdit_dir")
button = QPushButton("", objectName="QPushButton_dir")
button.setIcon(self.style().standardIcon(QStyle.SP_DialogOpenButton))
layout_dir.addWidget(line, 0, 0, 1, 5)
layout_dir.addWidget(button, 0, 6, 1, 1)
layout.addRow("Directory:", layout_dir)
validator = QRegExpValidator(QRegExp(r"^[a-zA-Z0-9]{1,8}$"))
line = QLineEdit(maxLength=8, objectName="QLineEdit_fname")
line.setValidator(validator)
layout.addRow("File name:", line)
self.setLayout(layout)
app = QApplication([])
window = CentralWidget()
window.show()
app.exec()
And now the version with the directory part split in a second widget:
from pathlib import Path
from PyQt5.QtCore import QRegExp
from PyQt5.QtWidgets import (
QApplication,
QLineEdit,
QFormLayout,
QWidget,
QPushButton,
QGridLayout,
QStyle,
QFileDialog,
)
from PyQt5.QtGui import QRegExpValidator
class CentralWidget(QWidget):
def __init__(self):
super().__init__()
self.setObjectName("central_widget")
# create central widget layout
layout = QFormLayout()
layout.addRow("Directory:", DirectoryDialog())
validator = QRegExpValidator(QRegExp(r"^[a-zA-Z0-9]{1,8}$"))
line = QLineEdit(maxLength=8, objectName="QLineEdit_fname")
line.setValidator(validator)
layout.addRow("File name:", line)
self.setLayout(layout)
class DirectoryDialog(QWidget):
def __init__(self):
super().__init__()
self.line = QLineEdit()
layout = QGridLayout()
self.button = QPushButton("")
self.button.setIcon(self.style().standardIcon(QStyle.SP_DialogOpenButton))
self.button.clicked.connect(self.browse_path)
layout.addWidget(self.line, 0, 0, 1, 5)
layout.addWidget(self.button, 0, 6, 1, 1)
self.setLayout(layout)
def browse_path(self):
path = QFileDialog.getExistingDirectory(
self, "Select directory", str(Path.home()), QFileDialog.ShowDirsOnly
)
if len(path) != 0:
self.line.setText(path)
app = QApplication([])
window = CentralWidget()
window.show()
app.exec()
So, how can I fix the alignment, and more importantly, why did it mess up the alignment?
Solution
Qt layouts always have a default "undefined" margin upon creation.
When set as managers of a widget, those margins become explicitly defined, following the current QStyle, which is about 10-12 pixels on all sides (the exact values depends on the OS or custom style, see the related pixel metrics in QStyle).
When directly added as nested layouts to a parent one, those margins remain undefined, so they default to 0 when finally added to another layout.
What you see in the second case is the further margin of the inner widget, because its layout is set on that widget; that margin is absent in case a layout is actually added instead of a container widget (because of the above reasons).
Note that while you could use a layout subclass for that purpose, it's normally discouraged, and an actual widget is preferable in any case.
For such custom components, it's usually better to explicitly set the layout margins to 0, so that they can be properly aligned as "unique widgets" to a parent layout.
Note that if you only need a single line of objects, using a grid layout is pointless, as the purpose of that layout is to show widgets along both rows and columns.
Setting spans for unused "cells" is also pointless: rows and columns of a grid layout do not represent actual "space" used on the layout, but just their logical indexes; if those rows and columns are not actually occupied by other widgets that have starts and spans in different cells, using a column span of 5 and adding a widget at the sixth column is fundamentally the same as adding them to columns 0 and 1.
If what you need is to ensure that a widget occupies more space than others, you must properly use stretch factors, or eventually set the Expanding
size policy for that widget.
Here is an improved version of your code, which also provides a better interface to access or set the path, including a dedicated signal when it is changed.
class CentralWidget(QWidget):
def __init__(self):
super().__init__()
self.setObjectName("central_widget")
# create central widget layout
layout = QFormLayout(self)
layout.addRow("Directory:", DirectoryDialog())
validator = QRegExpValidator(QRegExp(r"^[a-zA-Z0-9]{1,8}$"))
line = QLineEdit(maxLength=8, objectName="QLineEdit_fname")
line.setValidator(validator)
layout.addRow("File name:", line)
class DirectoryDialog(QWidget):
pathChanged = pyqtSignal(str)
def __init__(self):
super().__init__()
self.line = QLineEdit()
self.button = QPushButton()
self.button.setIcon(self.style().standardIcon(QStyle.SP_DialogOpenButton))
self.button.clicked.connect(self.browse_path)
layout = QHBoxLayout(self)
layout.setContentsMargins(0, 0, 0, 0)
layout.addWidget(self.line, stretch=1)
layout.addWidget(self.button)
self.line.textChanged.connect(self.pathChanged)
def path(self):
return self.line.text()
def setPath(self, path):
self.line.setText(path)
def browse_path(self):
path = QFileDialog.getExistingDirectory(
self, "Select directory", str(Path.home()), QFileDialog.ShowDirsOnly
)
if path:
self.line.setText(path)
Answered By - musicamante
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.