#qt #pyqt #qlabel
#qt #pyqt #qlabel
Вопрос:
Я хотел бы создать небольшую базовую постоянную QLabel для QStatusBar, которая также имеет маленький значок в зависимости от высоты шрифта. Значок на самом деле является base64
встроенным <img>
, так что я могу использовать общий движок Qt rich text QLabel вместо создания составного виджета.
Размер изображения основан на метриках шрифта, поэтому технически он должен соответствовать минимальному размеру метки. Если метрика шрифта возвращает высоту 16 пикселей, добавление встроенного изображения с высотой 16 пикселей не должно изменять подсказку метки. К сожалению, это не так.
Как только изображение добавляется к метке, высота увеличивается, даже если высота изображения равна высоте метрик шрифта, и оно всегда выровнено по вертикали до верха; попытка установить выравнивание, похоже, не очень помогает, что, вероятно, связано с этим qt-сообщение на форуме.
Использование таблиц HTML частично решает проблему: вертикальное выравнивание соблюдается, но добавленный запас все еще присутствует.
Я знаю, что мы говорим всего лишь о нескольких пикселях, но мне действительно не нравится текущее поведение: переключение между текстом, в котором есть изображение, и другим, в котором его нет, приводит к изменению всего макета (и, возможно, размера родительского виджета, который явно являетсяпроблема, особенно если метка должна использоваться в QStatusBar).
Хотя есть возможность добавить «призрачное» изображение ( width=0
) всякий раз, когда изображение не должно отображаться, мне все еще интересно понять, почему это происходит и можно ли это переопределить.
Я знаю, что можно было бы как-то обойти проблему, обратившись к макету QTextDocument, но, поскольку QLabel использует QTextDocument только в частном порядке, это невозможный подход.
Я также знаю, что я мог бы просто проигнорировать все это и создать подкласс QWidget, правильно переопределить sizeHint
paintEvent
и согласиться со всем этим, но дело не в этом.
Хотя документация Qt rich text подразумевает, что свойства выравнивания поддерживаются, выравнивание по вертикали, похоже, игнорируется практически в любом случае для изображений, за исключением "middle"
, который фактически выравнивает изображение по верхней части (возможно) следующей строки, и это то, что для меня не имеет большого смысла.
Чтобы лучше понять проблему, вот базовая демонстрация, которая показывает мою точку зрения.
Метки выровнены по макету и имеют рамку, поэтому вы можете четко видеть прямоугольную границу каждого элемента: всякий раз, когда добавляется изображение, добавляется некоторое поле (размер зависит от операционной системы и стиля).
Код основан на PyQt, но я знаю, что проблема связана с Qt:
import sys
from PyQt5 import QtCore, QtGui, QtWidgets
StyleSheet = 'QLabel { border: 1px solid darkGray; }'
BaseText = '<img {align} src="data:image/png;base64,{img};"> {label}'
TableText = '<table><tr><td {align}><img src="data:image/png;base64,{img};"></td><td>{label}</td></tr></table>'
class LabelTest(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
central = QtWidgets.QWidget()
self.setCentralWidget(central)
layout = QtWidgets.QVBoxLayout(central)
top = QtWidgets.QHBoxLayout()
layout.addLayout(top)
boldFont = self.font()
boldFont.setBold(True)
top.addWidget(QtWidgets.QLabel('Icon theme:'))
self.iconCombo = QtWidgets.QComboBox()
top.addWidget(self.iconCombo)
currentTheme = QtGui.QIcon.themeName().lower()
themes = []
for iconPath in QtGui.QIcon.themeSearchPaths():
it = QtCore.QDirIterator(iconPath, ['*'], QtCore.QDir.Dirs|QtCore.QDir.NoDotAndDotDot)
while it.hasNext():
if QtCore.QDir(it.next()).exists('index.theme'):
themeName = it.fileName()
if themeName.lower() in themes:
continue
themes.append(themeName.lower())
if themeName.lower() == currentTheme:
index = self.iconCombo.count()
self.iconCombo.addItem(themeName '*', themeName)
self.iconCombo.model().setData(
self.iconCombo.model().index(index, 0),
boldFont, QtCore.Qt.FontRole)
self.iconCombo.setCurrentIndex(index)
else:
self.iconCombo.addItem(themeName, themeName)
top.addWidget(QtWidgets.QLabel('Style'))
self.styleCombo = QtWidgets.QComboBox()
top.addWidget(self.styleCombo)
currentStyle = self.style().objectName().lower()
for i, styleName in enumerate(QtWidgets.QStyleFactory.keys()):
if styleName.lower() == currentStyle:
# automatically select the current style
self.styleCombo.addItem(styleName '*', styleName)
self.styleCombo.model().setData(
self.styleCombo.model().index(i, 0),
boldFont, QtCore.Qt.FontRole)
self.styleCombo.setCurrentIndex(i)
else:
self.styleCombo.addItem(styleName, styleName)
self.boundingRectCheck = QtWidgets.QCheckBox('Bounding rect')
top.addWidget(self.boundingRectCheck)
top.addStretch()
mid = QtWidgets.QHBoxLayout()
layout.addLayout(mid)
self.alignCombo = QtWidgets.QComboBox()
mid.addWidget(self.alignCombo)
for alignment in ('', 'top', 'super', 'middle', 'baseline', 'sub', 'bottom'):
if alignment:
self.alignCombo.addItem(alignment.title(), alignment)
else:
self.alignCombo.addItem('No alignment')
self.tableCheck = QtWidgets.QCheckBox('Table')
mid.addWidget(self.tableCheck)
self.labelIconCheck = QtWidgets.QCheckBox('Status icon')
mid.addWidget(self.labelIconCheck)
self.statusCombo = QtWidgets.QComboBox()
mid.addWidget(self.statusCombo)
frameLayout = QtWidgets.QGridLayout()
layout.addLayout(frameLayout)
frameLayout.setColumnStretch(3, 1)
self.labelData = []
for label in ('Information', 'Warning', 'Critical', 'Question'):
row = frameLayout.rowCount()
self.statusCombo.addItem(label)
pixmapLabel = QtWidgets.QLabel(styleSheet=StyleSheet)
frameLayout.addWidget(pixmapLabel,
row, 0, alignment=QtCore.Qt.AlignCenter)
frameLayout.addWidget(QtWidgets.QLabel(label, styleSheet=StyleSheet),
row, 1, alignment=QtCore.Qt.AlignVCenter)
formattedLabel = QtWidgets.QLabel(styleSheet=StyleSheet)
frameLayout.addWidget(formattedLabel,
row, 2, alignment=QtCore.Qt.AlignVCenter)
self.labelData.append((label, pixmapLabel, formattedLabel))
mid.addStretch()
self.editor = QtWidgets.QTextEdit(readOnly=True)
self.editor.setMinimumHeight(1)
frameLayout.addWidget(self.editor, 1, 3, frameLayout.rowCount(), 1)
self.statusLabel = QtWidgets.QLabel(styleSheet=StyleSheet)
self.statusBar().addPermanentWidget(self.statusLabel)
self.iconCombo.currentIndexChanged.connect(self.setStatus)
self.styleCombo.currentIndexChanged.connect(self.updateStyle)
self.alignCombo.currentIndexChanged.connect(self.setStatus)
self.boundingRectCheck.toggled.connect(self.setStatus)
self.tableCheck.toggled.connect(self.setStatus)
self.statusCombo.currentIndexChanged.connect(self.setStatus)
self.labelIconCheck.toggled.connect(self.setStatus)
self.setStatus()
def setStatus(self):
self.editor.clear()
align = self.alignCombo.currentData()
if self.tableCheck.isChecked():
baseText = TableText
if align:
align = 'style="vertical-align: {}"'.format(align)
else:
baseText = BaseText
if align:
align = 'align="{}"'.format(align)
statusIcon = self.labelIconCheck.isChecked()
if not statusIcon:
self.statusLabel.setText(self.statusCombo.currentText())
boundingRect = self.boundingRectCheck.isChecked()
pen1 = QtGui.QPen(QtCore.Qt.black)
pen1.setDashPattern([1, 1])
pen2 = QtGui.QPen(QtCore.Qt.white)
pen2.setDashPattern([1, 1])
pen2.setDashOffset(1)
# create pixmaps from the icon theme, with size based on the font metrics
QtGui.QIcon.setThemeName(self.iconCombo.currentData())
iconSize = self.fontMetrics().height()
for i, (label, pixmapLabel, formattedLabel) in enumerate(self.labelData):
enum = getattr(QtWidgets.QStyle, 'SP_MessageBox' label)
icon = self.style().standardIcon(enum)
pixmap = icon.pixmap(iconSize)
pixmapLabel.setPixmap(pixmap)
if boundingRect and not pixmap.isNull():
qp = QtGui.QPainter(pixmap)
qp.setPen(pen1)
qp.drawRect(pixmap.rect().adjusted(0, 0, -1, -1))
qp.setPen(pen2)
qp.drawRect(pixmap.rect().adjusted(0, 0, -1, -1))
qp.end()
# create a QByteArray of the resized icon so that we can use the
# embedded base64 data for the HTML image
byteArray = QtCore.QByteArray()
buffer = QtCore.QBuffer(byteArray)
buffer.open(buffer.WriteOnly)
pixmap.save(buffer, 'png')
imageData = byteArray.toBase64().data().decode()
embedText = baseText.format(
img=imageData,
label=label,
align=align
)
formattedLabel.setText(embedText)
if statusIcon:
if i == self.statusCombo.currentIndex():
self.statusLabel.setText(formattedLabel.text())
self.editor.append(embedText)
else:
self.editor.append(label)
QtCore.QTimer.singleShot(50, lambda: self.resize(self.minimumSizeHint()))
def updateStyle(self):
QtWidgets.QApplication.setStyle(self.styleCombo.currentData())
QtCore.QTimer.singleShot(50, lambda: self.resize(self.minimumSizeHint()))
app = QtWidgets.QApplication(sys.argv)
w = LabelTest()
w.show()
app.exec()
Это то, что в основном показывает приведенный выше код:
Комментарии:
1. Для меня выравнивание по верхнему краю работает отлично, то есть высота правой метки точно такая же, как и у левой метки (а высота метки строки состояния не меняется). Казалось бы, это имеет смысл, учитывая, как определено вертикальное выравнивание для CSS2 , т.Е. Если высота изображения совпадает с высотой строки, выравнивание их верхних краев должно привести к их точному перекрытию. Выравнивание по середине выглядит довольно сложно, учитывая, что оно основано на x-height.
2. PS: одна вещь, которая не совсем понятна из вашего скриншота, — это форма значков. Выровнены ли видимые пиксели по центру и заполняют ли они всю область изображения?
3. @ekhumoro Спасибо за ваш вклад! Я улучшил тестовый код (см. Обновление), чтобы показать размер изображения, и тем временем у меня была возможность провести дальнейшее тестирование. Оказывается, что с более новой версией Qt и улучшенным
top
выравниванием кода действительно соблюдается заданная высота. Однако выравнивание по-прежнему представляет некоторые проблемы: по какой-то причинеmiddle
изображение помещается даже ниже, чемbottom
, и нет абсолютно никакой разницы междуbaseline
,sub
илиbottom
. Возможно, это зависит от соображений производительности механизма компоновки текста (напримерmiddle
), но я просто предполагаю.4. @ekhumoro Тем не менее, я не могу отследить, когда произошло изменение (где-то между 5.7 и 5.13), что меня все еще интересует. Хотя я знаю, что 5.7 довольно старый, и я не должен его так сильно рассматривать, я все же хотел бы знать, где (и когда / как) произошло изменение. В любом случае, я добавлю ответ в ближайшие дни после дальнейших исследований. Еще раз спасибо.
5. Середина частично соответствует высоте x (которая зависит от шрифта), тогда как нижняя часть просто выравнивается по нижнему краю строки. Учитывая это, имеет смысл, что оно может быть отображено ниже. Для sub и super текст автоматически отображается меньшим шрифтом и выравнивается по строке, поэтому неудивительно, что изображения обрабатываются по-разному. Однако, кроме этого, общее поведение на самом деле более согласовано для изображений, чем для текста (т. Е. По сравнению с современным браузером). Для изображений на самом деле не работает только sub (он должен вести себя так же, как bottom).
Ответ №1:
Вот некоторые результаты тестирования для Firefox, Chrome и QLabel:
AFAICS, единственное явно неправильное поведение с QLabel — это text / middle (который должен выравнивать текст ниже в пределах строки) и image / sub (который должен выравнивать строку так же, как и внизу). Поведение двух браузеров не совсем идентично, но я не знаю, какой из них более правильный.
Тестовая страница HTML:
<html>
<head><title>Test</title>
</head>
<body style="font: 12pt dejavu sans">
<br>
<hr>
:: xX<span style="background-color: red">Text</span>Xx
jyf xX<img src="red-small.png">Xx Default
<hr>
:: xX<span style="background-color: red; vertical-align: top">Text</span>Xx
jyf xX<img style="vertical-align: top" src="red-small.png">Xx Top
<hr>
:: xX<span style="background-color: red; vertical-align: bottom">Text</span>Xx
jyf xX<img style="vertical-align: bottom" src="red-small.png">Xx Bottom
<hr>
:: xX<span style="background-color: red; vertical-align: middle">Text</span>Xx
jyf xX<img style="vertical-align: middle" src="red-small.png">Xx Middle
<hr>
:: xX<span style="background-color: red; vertical-align: sub">Text</span>Xx
jyf xX<img style="vertical-align: sub" src="red-small.png">Xx Sub
<hr>
:: xX<span style="background-color: red; vertical-align: super">Text</span>Xx
jyf xX<img style="vertical-align: super" src="red-small.png">Xx Super
<hr>
:: xX<span style="background-color: red; vertical-align: baseline">Text</span>Xx
jyf xX<img style="vertical-align: baseline" src="red-small.png">Xx Baseline
<hr>
</body>
</html>
red-small.png: