当固定纵横比 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 无法填满整个窗口时创建填充的主要内容,如果未能解决你的问题,请参考以下文章

在调整大小期间保持子类 QWidget 的纵横比

保持高度固定的图像纵横比

如何在 Qt 中保持小部件的纵横比?

如何使用 CSS 拉伸图像以适应屏幕并保持纵横比?

固定缩略图比例大小和保持纵横比?

固定纵横比视图