当固定纵横比 QWidget 无法填满整个窗口时创建填充
Posted
技术标签:
【中文标题】当固定纵横比 QWidget 无法填满整个窗口时创建填充【英文标题】:Create padding when fixed aspect ratio QWidget cannot fill entire window 【发布时间】:2021-02-16 14:06:05 【问题描述】:自定义小部件(类名 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 上正确实现一个可调整大小的小部件以保持纵横比并不容易(实际上,有时几乎不可能)。还要记住,在resizeEvent()
中更改大小是highly discouraged,因为它可能导致无限递归。问题是:您是否仅出于示例目的使用 QLabel 并且您实际上将使用自定义小部件,或者您绝对需要该类?还是您想显示一个保持纵横比的缩放图像?
感谢您评论@musicamante。在我的实际问题中,我使用 opencv 在确实继承 qlabel 的小部件中显示视频。我对这里的任何其他建议持开放态度。我有一个实现,我实际上对 resizeevent 进行了调整,以便整个主窗口保持纵横比,但这不是很好,并且在捕捉到侧面、顶部或底部时变得有点奇怪。
嗯。这可能会变得很棘手,因为它取决于 opencv 实际使用 QLabel 的方式。除了定位问题,您的实施是否正常?我的意思是,视频是否正确显示(位置和大小),还是被裁剪(可能在其左侧/底部)?
其实,我读了一个帧,把它放在一个像素图中,所以没问题。如果我们解决了这个问题中的示例,我们就可以开始了。
【参考方案1】:
遗憾的是,Qt 没有为需要固定纵横比的小部件提供直接的解决方案。
旧文档中有一些痕迹,但主要问题是:
所有与小部件、布局和大小策略的纵横比 (hasHeightForWidth()
等) 相关的函数都只考虑大小提示,因此如果布局手动调整小部件的大小,则没有任何约束;
因为documentation reports 在moveEvent()
或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,您可以覆盖 QWidget 的 paintEvent
,但是对于出于性能目的,使用带有子 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()
【讨论】:
以上是关于当固定纵横比 QWidget 无法填满整个窗口时创建填充的主要内容,如果未能解决你的问题,请参考以下文章