我使用 QLabel 向用户显示更大、动态变化的 QPixmap 的内容。根据可用空间使这个标签更小/更大会很好。屏幕尺寸并不总是像 QPixmap 一样大。
如何修改 QLabel 的 QSizePolicy
和 sizeHint()
以调整 QPixmap 的大小,同时保持原始 QPixmap 的纵横比?
我无法修改 QLabel 的 sizeHint()
,将 minimumSize()
设置为零无济于事。在 QLabel 上设置 hasScaledContents()
允许增长,但会破坏纵横比...
子类化 QLabel 确实有帮助,但是这个解决方案为一个简单的问题添加了太多代码......
任何聪明的提示如何在没有子类化的情况下完成这个?
QLabel
的尺寸。 QPixmap
应保持其大小、内容和维度。此外,如果“自动”发生大小调整(实际上是缩小)以填充可用空间——最大为原始 QPixmap
的大小,那就太好了。所有这些都是通过子类化完成的......
为了更改标签尺寸,您可以为标签选择适当的尺寸策略,例如展开或最小展开。
您可以通过在每次更改时保持其纵横比来缩放像素图:
QPixmap p; // load pixmap
// get label dimensions
int w = label->width();
int h = label->height();
// set a scaled pixmap to a w x h window keeping its aspect ratio
label->setPixmap(p.scaled(w,h,Qt::KeepAspectRatio));
您应该在两个地方添加此代码:
当像素图更新时
在包含标签的小部件的 resizeEvent 中
我已经完善了这个缺少的 QLabel
子类。它很棒而且效果很好。
aspectratiopixmaplabel.h
#ifndef ASPECTRATIOPIXMAPLABEL_H
#define ASPECTRATIOPIXMAPLABEL_H
#include <QLabel>
#include <QPixmap>
#include <QResizeEvent>
class AspectRatioPixmapLabel : public QLabel
{
Q_OBJECT
public:
explicit AspectRatioPixmapLabel(QWidget *parent = 0);
virtual int heightForWidth( int width ) const;
virtual QSize sizeHint() const;
QPixmap scaledPixmap() const;
public slots:
void setPixmap ( const QPixmap & );
void resizeEvent(QResizeEvent *);
private:
QPixmap pix;
};
#endif // ASPECTRATIOPIXMAPLABEL_H
aspectratiopixmaplabel.cpp
#include "aspectratiopixmaplabel.h"
//#include <QDebug>
AspectRatioPixmapLabel::AspectRatioPixmapLabel(QWidget *parent) :
QLabel(parent)
{
this->setMinimumSize(1,1);
setScaledContents(false);
}
void AspectRatioPixmapLabel::setPixmap ( const QPixmap & p)
{
pix = p;
QLabel::setPixmap(scaledPixmap());
}
int AspectRatioPixmapLabel::heightForWidth( int width ) const
{
return pix.isNull() ? this->height() : ((qreal)pix.height()*width)/pix.width();
}
QSize AspectRatioPixmapLabel::sizeHint() const
{
int w = this->width();
return QSize( w, heightForWidth(w) );
}
QPixmap AspectRatioPixmapLabel::scaledPixmap() const
{
return pix.scaled(this->size(), Qt::KeepAspectRatio, Qt::SmoothTransformation);
}
void AspectRatioPixmapLabel::resizeEvent(QResizeEvent * e)
{
if(!pix.isNull())
QLabel::setPixmap(scaledPixmap());
}
希望有帮助! (根据@dmzl 的回答更新了 resizeEvent
)
QLabel::setPixmap(pix.scaled(this->size(), Qt::KeepAspectRatio, Qt::SmoothTransformation));
添加到 setPixmap()
方法。
this->resize(width(), height());
放在 setPixmap
函数的末尾。
auto scaled = pix.scaled(this->size() * devicePixelRatioF(), Qt::KeepAspectRatio, Qt::SmoothTransformation); scaled.setDevicePixelRatio(devicePixelRatioF()); return scaled;
这也适用于正常缩放的屏幕。
我只是使用 contentsMargin
来修复纵横比。
#pragma once
#include <QLabel>
class AspectRatioLabel : public QLabel
{
public:
explicit AspectRatioLabel(QWidget* parent = nullptr, Qt::WindowFlags f = Qt::WindowFlags());
~AspectRatioLabel();
public slots:
void setPixmap(const QPixmap& pm);
protected:
void resizeEvent(QResizeEvent* event) override;
private:
void updateMargins();
int pixmapWidth = 0;
int pixmapHeight = 0;
};
#include "AspectRatioLabel.h"
AspectRatioLabel::AspectRatioLabel(QWidget* parent, Qt::WindowFlags f) : QLabel(parent, f)
{
}
AspectRatioLabel::~AspectRatioLabel()
{
}
void AspectRatioLabel::setPixmap(const QPixmap& pm)
{
pixmapWidth = pm.width();
pixmapHeight = pm.height();
updateMargins();
QLabel::setPixmap(pm);
}
void AspectRatioLabel::resizeEvent(QResizeEvent* event)
{
updateMargins();
QLabel::resizeEvent(event);
}
void AspectRatioLabel::updateMargins()
{
if (pixmapWidth <= 0 || pixmapHeight <= 0)
return;
int w = this->width();
int h = this->height();
if (w <= 0 || h <= 0)
return;
if (w * pixmapHeight > h * pixmapWidth)
{
int m = (w - (pixmapWidth * h / pixmapHeight)) / 2;
setContentsMargins(m, 0, m, 0);
}
else
{
int m = (h - (pixmapHeight * w / pixmapWidth)) / 2;
setContentsMargins(0, m, 0, m);
}
}
到目前为止对我来说非常有效。别客气。
QSize
而不是 ...Width
和 ...Height
不是更有意义吗?如果不出意外,那将使您的提前返回检查成为一个简单的 QSize::isEmpty
调用。 QPixmap
和 QWidget
都有 size
方法将宽度和高度检索为 QSize
。
从 Timmmm 改编为 PYQT5
from PyQt5.QtGui import QPixmap
from PyQt5.QtGui import QResizeEvent
from PyQt5.QtWidgets import QLabel
class Label(QLabel):
def __init__(self):
super(Label, self).__init__()
self.pixmap_width: int = 1
self.pixmapHeight: int = 1
def setPixmap(self, pm: QPixmap) -> None:
self.pixmap_width = pm.width()
self.pixmapHeight = pm.height()
self.updateMargins()
super(Label, self).setPixmap(pm)
def resizeEvent(self, a0: QResizeEvent) -> None:
self.updateMargins()
super(Label, self).resizeEvent(a0)
def updateMargins(self):
if self.pixmap() is None:
return
pixmapWidth = self.pixmap().width()
pixmapHeight = self.pixmap().height()
if pixmapWidth <= 0 or pixmapHeight <= 0:
return
w, h = self.width(), self.height()
if w <= 0 or h <= 0:
return
if w * pixmapHeight > h * pixmapWidth:
m = int((w - (pixmapWidth * h / pixmapHeight)) / 2)
self.setContentsMargins(m, 0, m, 0)
else:
m = int((h - (pixmapHeight * w / pixmapWidth)) / 2)
self.setContentsMargins(0, m, 0, m)
我尝试使用 phyatt 的 AspectRatioPixmapLabel
类,但遇到了一些问题:
有时我的应用程序进入了一个无限循环的调整大小事件。我将此追溯到 resizeEvent 方法中对 QLabel::setPixmap(...) 的调用,因为 QLabel 实际上在 setPixmap 中调用 updateGeometry,这可能会触发调整大小事件...
包含小部件(在我的情况下为 QScrollArea)似乎忽略了 heightForWidth ,直到我开始为标签设置大小策略,显式调用 policy.setHeightForWidth(true)
我希望标签永远不会超过原始像素图大小
QLabel 的 minimumSizeHint() 实现对包含文本的标签有一些魔力,但总是将大小策略重置为默认值,所以我不得不覆盖它
也就是说,这是我的解决方案。我发现我可以只使用 setScaledContents(true)
并让 QLabel
处理调整大小。当然,这取决于遵守 heightForWidth
的包含小部件/布局。
aspectratiopixmaplabel.h
#ifndef ASPECTRATIOPIXMAPLABEL_H
#define ASPECTRATIOPIXMAPLABEL_H
#include <QLabel>
#include <QPixmap>
class AspectRatioPixmapLabel : public QLabel
{
Q_OBJECT
public:
explicit AspectRatioPixmapLabel(const QPixmap &pixmap, QWidget *parent = 0);
virtual int heightForWidth(int width) const;
virtual bool hasHeightForWidth() { return true; }
virtual QSize sizeHint() const { return pixmap()->size(); }
virtual QSize minimumSizeHint() const { return QSize(0, 0); }
};
#endif // ASPECTRATIOPIXMAPLABEL_H
aspectratiopixmaplabel.cpp
#include "aspectratiopixmaplabel.h"
AspectRatioPixmapLabel::AspectRatioPixmapLabel(const QPixmap &pixmap, QWidget *parent) :
QLabel(parent)
{
QLabel::setPixmap(pixmap);
setScaledContents(true);
QSizePolicy policy(QSizePolicy::Maximum, QSizePolicy::Maximum);
policy.setHeightForWidth(true);
this->setSizePolicy(policy);
}
int AspectRatioPixmapLabel::heightForWidth(int width) const
{
if (width > pixmap()->width()) {
return pixmap()->height();
} else {
return ((qreal)pixmap()->height()*width)/pixmap()->width();
}
}
heightForWidth
属性的边缘情况更可取,但对于包含此标签的父窗口小部件和/或布局不的一般情况,此答案失败i> 尊重 heightForWidth
属性。不幸的是,这个答案比 phyatt 的 long-standing answer 更可取。
如果您的图像是资源或文件,则不需要对任何内容进行子类化;只需在标签的样式表中设置 image
;它将被缩放以适应标签,同时保持其纵横比,并将跟踪对标签所做的任何大小更改。您可以选择使用 image-position
将图像移动到边缘之一。
它不适合 OP 的动态更新像素图的情况(我的意思是,您可以随时设置不同的资源,但它们仍然必须是资源),但如果您使用来自资源的像素图,这是一个好方法。
样式表示例:
image: url(:/resource/path);
image-position: right center; /* optional: default is centered. */
在代码中(例如):
QString stylesheet = "image:url(%1);image-position:right center;";
existingLabel->setStyleSheet(stylesheet.arg(":/resource/path"));
或者您可以在 Designer 中设置样式表属性:
https://i.stack.imgur.com/4VHal.gif
需要注意的是,它不会将图像缩放得更大,只会更小,因此如果您希望图像变大,请确保图像大于您的尺寸范围(请注意,它可以支持 SVG,这可以提高质量)。
标签的大小可以像往常一样控制:要么使用样式表中的大小元素,要么使用标准布局和大小策略策略。
有关详细信息,请参阅 the documentation。
这种风格从 Qt 早期就已经存在(位置是在 2007 年左右的 4.3 中添加的,但图像在此之前就已经存在)。
Qt 文档有一个 Image Viewer example,它演示了如何在 QLabel
中处理调整图像大小。基本思想是使用 QScrollArea
作为 QLabel
的容器,如果需要,使用 label.setScaledContents(bool)
和 scrollarea.setWidgetResizable(bool)
来填充可用空间和/或确保内部的 QLabel 可调整大小。此外,要在尊重纵横比的同时调整 QLabel 的大小,请使用:
label.setPixmap(pixmap.scaled(width, height, Qt::KeepAspectRatio, Qt::FastTransformation));
width
和 height
可以基于 scrollarea.width()
和 scrollarea.height()
设置。这样就不需要子类化 QLabel。
我终于让它按预期工作了。必须覆盖 sizeHint
和 resizeEvent
,并设置最小尺寸和尺寸政策。当控件与图像的纵横比不同时,setAlignment
用于将控件中的图像水平或垂直居中。
class ImageDisplayWidget(QLabel):
def __init__(self, max_enlargement=2.0):
super().__init__()
self.max_enlargement = max_enlargement
self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
self.setAlignment(Qt.AlignCenter)
self.setMinimumSize(1, 1)
self.__image = None
def setImage(self, image):
self.__image = image
self.resize(self.sizeHint())
self.update()
def sizeHint(self):
if self.__image:
return self.__image.size() * self.max_enlargement
else:
return QSize(1, 1)
def resizeEvent(self, event):
if self.__image:
pixmap = QPixmap.fromImage(self.__image)
scaled = pixmap.scaled(event.size(), Qt.KeepAspectRatio)
self.setPixmap(scaled)
super().resizeEvent(event)
这里真的没有什么新鲜事。
我将接受的回复 https://stackoverflow.com/a/8212120/11413792 和使用 setContentsMargins
的 https://stackoverflow.com/a/43936590/11413792 混合在一起,但只是用我自己的方式对其进行了编码。
/**
* @brief calcMargins Calculate the margins when a rectangle of one size is centred inside another
* @param outside - the size of the surrounding rectanle
* @param inside - the size of the surrounded rectangle
* @return the size of the four margins, as a QMargins
*/
QMargins calcMargins(QSize const outside, QSize const inside)
{
int left = (outside.width()-inside.width())/2;
int top = (outside.height()-inside.height())/2;
int right = outside.width()-(inside.width()+left);
int bottom = outside.height()-(inside.height()+top);
QMargins margins(left, top, right, bottom);
return margins;
}
一个函数计算将一个矩形居中另一个矩形所需的边距。它是一个非常通用的函数,虽然我不知道是什么,但它可以用于很多事情。
然后 setContentsMargins
变得易于使用,只需添加几行,许多人会将它们合并为一条。
QPixmap scaled = p.scaled(this->size(), Qt::KeepAspectRatio);
QMargins margins = calcMargins(this->size(), scaled.size());
this->setContentsMargins(margins);
setPixmap(scaled);
有人可能会感兴趣...我需要处理 mousePressEvent
并知道我在图像中的位置。
void MyClass::mousePressEvent(QMouseEvent *ev)
{
QMargins margins = contentsMargins();
QPoint labelCoordinateClickPos = ev->pos();
QPoint pixmapCoordinateClickedPos = labelCoordinateClickPos - QPoint(margins.left(),margins.top());
... more stuff here
}
我的大图像来自相机,我通过除以像素图的宽度然后乘以原始图像的宽度来获得相对坐标 [0, 1)。
这是@phyatt 类到 PySide2 的端口。
除了移植之外,我在 resizeEvent 中添加了一个额外的对齐,以便使新调整大小的图像在可用空间中正确定位。
from typing import Union
from PySide2.QtCore import QSize, Qt
from PySide2.QtGui import QPixmap, QResizeEvent
from PySide2.QtWidgets import QLabel, QWidget
class QResizingPixmapLabel(QLabel):
def __init__(self, parent: Union[QWidget, None] = ...):
super().__init__(parent)
self.setMinimumSize(1,1)
self.setScaledContents(False)
self._pixmap: Union[QPixmap, None] = None
def heightForWidth(self, width:int) -> int:
if self._pixmap is None:
return self.height()
else:
return self._pixmap.height() * width / self._pixmap.width()
def scaledPixmap(self) -> QPixmap:
scaled = self._pixmap.scaled(
self.size() * self.devicePixelRatioF(),
Qt.KeepAspectRatio,
Qt.SmoothTransformation
)
scaled.setDevicePixelRatio(self.devicePixelRatioF());
return scaled;
def setPixmap(self, pixmap: QPixmap) -> None:
self._pixmap = pixmap
super().setPixmap(pixmap)
def sizeHint(self) -> QSize:
width = self.width()
return QSize(width, self.heightForWidth(width))
def resizeEvent(self, event: QResizeEvent) -> None:
if self._pixmap is not None:
super().setPixmap(self.scaledPixmap())
self.setAlignment(Qt.AlignCenter)
不定期副业成功案例分享
QLabel
进行子类化时的核心。但我认为这个用例(在任意大小的小部件中显示任意大小的图像)足够普遍,可以通过现有代码实现类似的东西......QLabel
。否则,您可以在每次像素图更改时调用的插槽/函数中使用我的答案代码。QLabel
根据用户调整QMainWindow
的大小和可用空间而自动扩展,所以我不能使用信号/插槽解决方案 - 我不能模拟 扩展 以这种方式制定政策。label->setMinimumSize(1, 1)