python - 当固定纵横比 QWidget 无法填满整个窗口时创建填充
问题描述
自定义小部件(类名 MyLabel,继承 QLabel)具有固定的纵横比 16:9。
当我调整窗口大小时,标签是左上对齐的,除非窗口恰好是 16:9,在这种情况下它会完美地填充窗口。
如何让标签居中?我已经查看了尺寸政策、对齐方式、使用空间项和拉伸,但我似乎无法让它按预期工作。
这是一个最小的可重现示例:
import sys
from PyQt5.QtWidgets import QApplication, QLabel, QMainWindow
from PyQt5.QtCore import QSize, Qt
from PyQt5.Qt import QVBoxLayout, QWidget
class MyLabel(QLabel):
def __init__(self, text, parent=None):
super().__init__(text, parent)
self.setStyleSheet("background-color: lightgreen") # Just for visibility
def resizeEvent(self, event):
# Size of 16:9 and scale it to the new size maintaining aspect ratio.
new_size = QSize(16, 9)
new_size.scale(event.size(), Qt.KeepAspectRatio)
self.resize(new_size)
class MainWindow(QMainWindow):
def __init__(self):
super().__init__(None)
# Main widget and layout, and set as centralWidget
self.main_layout = QVBoxLayout()
self.main_widget = QWidget()
self.main_widget.setLayout(self.main_layout)
self.setCentralWidget(self.main_widget)
# Add button to main_layout
label = MyLabel("Hello World")
self.main_layout.addWidget(label)
self.show()
app = QApplication(sys.argv)
ex = MainWindow()
sys.exit(app.exec_())
期望结果的例子:
实际结果示例:
解决方案
不幸的是,Qt 没有为需要固定纵横比的小部件提供直接的解决方案。
旧文档中有一些痕迹,但主要问题是:
- 所有与小部件、布局和大小策略的纵横比(
hasHeightForWidth()
等)相关的功能都只考虑大小提示,因此如果小部件由布局手动调整大小,则没有可用的约束; - 正如文档报告
moveEvent()
的那样,在or中更改小部件的几何形状resizeEvent()
可能会导致无限递归; - 在保持纵横比的同时(正确)控制尺寸增长或缩小是不可能的;
为了完整起见,这里是这个问题的部分解决方案,但请注意 QLabel 是一个非常特殊的小部件,它有一些与其文本表示相关的约束(最重要的是,具有富文本和/或自动换行)。
class MyLabel(QLabel):
lastRect = None
isResizing = False
def __init__(self, text, parent=None):
super().__init__(text, parent)
self.setStyleSheet("background-color: lightgreen")
self.setScaledContents(True)
def restoreRatio(self, lastRect=None):
if self.isResizing:
return
rect = QRect(QPoint(),
QSize(16, 9).scaled(self.size(), Qt.KeepAspectRatio))
if not lastRect:
lastRect = self.geometry()
rect.moveCenter(lastRect.center())
if rect != lastRect:
self.isResizing = True
self.setGeometry(rect)
self.isResizing = False
self.lastRect = None
def hasHeightForWidth(self):
return True
def heightForWidth(self, width):
if self.pixmap():
return width * self.pixmap().height() / self.pixmap().width()
return width * 9 / 16
def sizeHint(self):
if self.pixmap():
return self.pixmap().size()
return QSize(160, 90)
def moveEvent(self, event):
self.lastRect = self.geometry()
def resizeEvent(self, event):
self.restoreRatio(self.lastRect)
由于目的是显示图像,另一种可能性是自己手动绘制所有内容,您根本不需要 QLabel,您可以覆盖paintEvent
QWidget 的,但出于性能目的,它可能会稍微最好使用带有子 QLabel 的容器小部件:理论上这会使事情变得更快,因为所有计算都完全在 Qt 中完成:
class ParentedLabel(QWidget):
def __init__(self, pixmap=None):
super().__init__()
self.child = QLabel(self, scaledContents=True)
if pixmap:
self.child.setPixmap(pixmap)
def setPixmap(self, pixmap):
self.child.setPixmap(pixmap)
self.updateGeometry()
def updateChild(self):
if self.child.pixmap():
r = self.child.pixmap().rect()
size = self.child.pixmap().size().scaled(
self.size(), Qt.KeepAspectRatio)
r = QRect(QPoint(), size)
r.moveCenter(self.rect().center())
self.child.setGeometry(r)
def hasHeightForWidth(self):
return bool(self.child.pixmap())
def heightForWidth(self, width):
return width * self.child.pixmap().height() / self.child.pixmap().width()
def sizeHint(self):
if self.child.pixmap():
return self.child.pixmap().size()
return QSize(160, 90)
def moveEvent(self, event):
self.updateChild()
def resizeEvent(self, event):
self.updateChild()
最后,另一种可能性是使用 QGraphicsView,这可能是所有方法中更快的方法,但有一个小缺点:基于给定尺寸提示显示的图像可能会比原始图像略小(几个像素),与结果,由于调整大小,它看起来有点“失焦”。
class ViewLabel(QGraphicsView):
def __init__(self, pixmap=None):
super().__init__()
self.setStyleSheet('ViewLabel { border: 0px solid none; }')
self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
scene = QGraphicsScene()
self.setScene(scene)
self.pixmapItem = QGraphicsPixmapItem(pixmap)
self.pixmapItem.setTransformationMode(Qt.SmoothTransformation)
scene.addItem(self.pixmapItem)
def setPixmap(self, pixmap):
self.pixmapItem.setPixmap(pixmap)
self.updateGeometry()
self.updateScene()
def updateScene(self):
self.fitInView(self.pixmapItem, Qt.KeepAspectRatio)
def hasHeightForWidth(self):
return not bool(self.pixmapItem.pixmap().isNull())
def heightForWidth(self, width):
return width * self.pixmapItem.pixmap().height() / self.pixmapItem.pixmap().width()
def sizeHint(self):
if not self.pixmapItem.pixmap().isNull():
return self.pixmapItem.pixmap().size()
return QSize(160, 90)
def resizeEvent(self, event):
self.updateScene()
推荐阅读
- zlib - 在 zlib 中,当字母的霍夫曼代码长度超过最大代码长度(15)时会发生什么?
- python-3.x - 如何将多字标题绑定到它的值从 csv 作为字典
- c++ - Qt 中的 GMock 和未定义的引用错误
- java - 在 recyclerView 制作的图像滑块中添加下一个,上一个按钮
- flask - Flask Limiter 在共享主机中不起作用
- osgi - 当我将我的功能安装到 odl 时,出现问题 aboule:zodiac.springsource.com: Name or service not known
- c# - 选择不发布所选项目
- javascript - 什么是hermes引擎(react-native)
- amazon-web-services - 将新密钥传递到 AWS CLI
- django - 将我的后端数据传输到前端(Django)时遇到问题