Qt: Ändern der Größe eines QLabels, das eine QPixmap enthält, unter Beibehaltung des Seitenverhältnisses

80

Ich verwende ein QLabel, um dem Benutzer den Inhalt einer größeren, sich dynamisch ändernden QPixmap anzuzeigen. Es wäre schön, dieses Etikett je nach verfügbarem Platz kleiner / größer zu machen. Die Bildschirmgröße ist nicht immer so groß wie die QPixmap.

Wie kann ich das QSizePolicyund sizeHint()des QLabels ändern, um die Größe der QPixmap zu ändern, während das Seitenverhältnis der ursprünglichen QPixmap beibehalten wird?

Ich kann sizeHint()das QLabel nicht ändern , das Setzen minimumSize()auf Null hilft nicht. Das Einstellen hasScaledContents()auf dem QLabel ermöglicht das Wachsen, bricht aber das Seitenverhältnis ...

Die Unterklasse von QLabel hat geholfen, aber diese Lösung fügt zu viel Code für nur ein einfaches Problem hinzu ...

Irgendwelche intelligenten Hinweise, wie dies ohne Unterklassen erreicht werden kann?

marvin2k
quelle
Mit dynamischer Änderung meinen Sie die Pixeldaten oder die Abmessungen?
r_ahlskog
Ich meine die Abmessungen der QLabelim aktuellen Layout. Der QPixmapsollte seine Größe, Inhalt und Dimension behalten. Es wäre auch schön, wenn die Größenänderung (in der Realität schrumpfen) "automatisch" erfolgt, um den verfügbaren Speicherplatz auszufüllen - bis zur Größe des Originals QPixmap. All dies wurde über Unterklassen gemacht ...
marvin2k

Antworten:

98

Um die Etikettengröße zu ändern, können Sie eine geeignete Größenrichtlinie für das Etikett auswählen, z. B. das Erweitern oder das minimale Erweitern.

Sie können die Pixmap skalieren, indem Sie das Seitenverhältnis bei jeder Änderung beibehalten:

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));

Es gibt zwei Stellen, an denen Sie diesen Code hinzufügen sollten:

  • Wenn die Pixmap aktualisiert wird
  • Im resizeEventWidget, das die Beschriftung enthält
pnezis
quelle
hm ja, das war im Grunde der Kern, als ich unterklassifizierte QLabel. Aber ich dachte, dieser Anwendungsfall (Bilder mit beliebiger Größe in Widgets beliebiger Größe
anzeigen
AFAIK Diese Funktionalität wird standardmäßig nicht bereitgestellt. Der eleganteste Weg, um das zu erreichen, was Sie wollen, ist die Unterklasse QLabel. Andernfalls können Sie den Code meiner Antwort in einem Slot / einer Funktion verwenden, der jedes Mal aufgerufen wird, wenn sich die Pixmap ändert.
Pnezis
1
Da ich möchte, dass das QLabelSystem basierend auf der Größe des Benutzers QMainWindowund dem verfügbaren Speicherplatz automatisch erweitert wird, kann ich die Signal- / Slot-Lösung nicht verwenden. Auf diese Weise kann ich keine Erweiterungsrichtlinie modellieren .
marvin2k
21
Um auch verkleinern zu können, müssen Sie diesen Aufruf hinzufügen:label->setMinimumSize(1, 1)
Pieter-Jan Busschaert
1
Dies ist nicht sehr nützlich, wenn ich das Seitenverhältnis beibehalten möchte, auch wenn der Benutzer die Größe des Etiketts ändert.
Tomáš Zato - Wiedereinsetzung Monica
33

Ich habe diese fehlende Unterklasse von poliert QLabel. Es ist großartig und funktioniert gut.

aspektratiopixmaplabel.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

aspektratiopixmaplabel.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());
}

Hoffentlich hilft das! (Aktualisiert resizeEventgemäß der Antwort von @ dmzl)

Phyatt
quelle
1
Danke, funktioniert super. Ich würde auch QLabel::setPixmap(pix.scaled(this->size(), Qt::KeepAspectRatio, Qt::SmoothTransformation));zur setPixmap()Methode hinzufügen .
Hyndrix
Du hast recht. Ich ging davon aus, dass Sie die qualitativ hochwertigste Version der Pixmap speichern möchten und dass Sie setPixmap aufrufen, bevor Sie die Größe des Labels ändern / verankern. Um die Codeduplizierung zu reduzieren, sollte ich wahrscheinlich this->resize(width(), height());das Ende der setPixmapFunktion einfügen .
Phyatt
Vielen Dank für das Teilen. Haben Sie Vorschläge, wie ich eine "bevorzugte" Größe für die QPixmap festlegen kann, damit beim ersten Start der Anwendung nicht die maximale Auflösung erreicht wird?
Julien M
Verwenden Sie Layouts und Stretch-Regeln.
Phyatt
3
Gute Antwort! Wenn Sie an hochauflösenden Bildschirmen arbeiten möchten, ändern Sie einfach scaledPixmap (), um Folgendes zu tun: auto scaled = pix.scaled(this->size() * devicePixelRatioF(), Qt::KeepAspectRatio, Qt::SmoothTransformation); scaled.setDevicePixelRatio(devicePixelRatioF()); return scaled;Dies funktioniert auch auf normal skalierten Bildschirmen.
Saul
17

Ich benutze nur contentsMargin, um das Seitenverhältnis zu korrigieren.

#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);
    }
}

Funktioniert bisher perfekt für mich. Bitte.

Timmmm
quelle
4
Ich habe es gerade benutzt und es funktioniert wie ein Zauber! Auch ziemlich clevere Verwendung des Layout-Managers. Sollte die akzeptierte Antwort sein, da alle anderen Fehler in Eckfällen haben.
Thokra
2
Diese Antwort ist zwar nicht intuitiv clever, löst jedoch eine grundlegend andere Frage: "Wie viel interne Auffüllung sollten wir zwischen einem bereits bekannten Etikett und der in diesem Etikett enthaltenen Pixmap hinzufügen, um das Seitenverhältnis dieser Pixmap beizubehalten?" "" Jede andere Antwort löst die ursprüngliche Frage: "Auf welche Größe sollten wir die Größe eines Etiketts mit einer Pixmap ändern, um das Seitenverhältnis dieser Pixmap beizubehalten?" Diese Antwort erfordert, dass die Größe des Etiketts irgendwie vorbestimmt ist (z. B. mit einer Richtlinie für feste Größen), was in vielen Anwendungsfällen unerwünscht oder sogar nicht durchführbar ist.
Cecil Curry
1
Das ist der richtige Weg für HiResolution-Displays (auch bekannt als "Retina") - es ist viel besser, als die QPixmap zu verkleinern.
JVB
Vielleicht bin ich ein bisschen zu sehr darauf konzentriert, dass Code aus Gründen der Wartbarkeit eine hohe Bedeutung ausdrückt, aber wäre es nicht sinnvoller, QSizeanstelle von ...Widthund zu verwenden ...Height? Wenn nichts anderes, würde dies Ihre frühzeitige Rückgabe zu einem einfachen QSize::isEmptyAnruf machen. QPixmapund QWidgetbeide haben sizeMethoden, um die Breite und Höhe als abzurufen QSize.
ssokolow
@ssokolow Ja, das klingt besser - Sie können die Antwort jederzeit bearbeiten.
Timmmm
5

Ich habe versucht, Phyatt's AspectRatioPixmapLabelKlasse zu verwenden, hatte aber einige Probleme:

  • Manchmal trat meine App in eine Endlosschleife von Größenänderungsereignissen ein. Ich habe dies auf den Aufruf von QLabel::setPixmap(...)inside der resizeEvent-Methode zurückgeführt, da QLabeltatsächlich updateGeometryinside aufgerufen wird setPixmap, was Größenänderungsereignisse auslösen kann ...
  • heightForWidthschien vom enthaltenen Widget ( QScrollAreain meinem Fall a) ignoriert zu werden, bis ich anfing, eine Größenrichtlinie für das Etikett festzulegen und explizit aufzurufenpolicy.setHeightForWidth(true)
  • Ich möchte, dass das Etikett nie größer wird als die ursprüngliche Pixmap-Größe
  • QLabelDie Implementierung von minimumSizeHint()bewirkt etwas Magie für Beschriftungen, die Text enthalten, setzt jedoch die Größenrichtlinie immer auf die Standardrichtlinie zurück, sodass ich sie überschreiben musste

Das heißt, hier ist meine Lösung. Ich stellte fest, dass ich die Größenänderung einfach verwenden setScaledContents(true)und durchführen lassen konnte QLabel. Dies hängt natürlich davon ab, welches Widget / Layout das enthält heightForWidth.

aspektratiopixmaplabel.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

aspektratiopixmaplabel.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();
    }
}
Alexander Schlüter
quelle
Während dies für Randfälle vorzuziehen ist, in denen das übergeordnete Widget und / oder Layout, das diese Bezeichnung enthält, die heightForWidthEigenschaft respektieren , schlägt diese Antwort für den allgemeinen Fall fehl, in dem das übergeordnete Widget und / oder Layout, das diese Bezeichnung enthält, die Eigenschaft nicht respektieren heightForWidth. Welche bedauerlich ist, da diese Antwort sonst vorzuziehen ist phyatt ‚s Antwort langjährige .
Cecil Curry
2

Angepasst von Timmmm an 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)
Derek R.
quelle
0

Die Qt-Dokumentationen enthalten ein Image Viewer-Beispiel, das die Handhabung der Größenänderung von Bildern in a demonstriert QLabel. Die Grundidee besteht darin, QScrollAreaals Container für die QLabelund bei Bedarf zu verwenden label.setScaledContents(bool)und scrollarea.setWidgetResizable(bool)den verfügbaren Platz zu füllen und / oder sicherzustellen, dass die Größe von QLabel im Inneren geändert werden kann. Um die Größe von QLabel zu ändern und gleichzeitig das Seitenverhältnis zu berücksichtigen, verwenden Sie außerdem:

label.setPixmap(pixmap.scaled(width, height, Qt::KeepAspectRatio, Qt::FastTransformation));

Das widthund heightkann basierend auf scrollarea.width()und eingestellt werden scrollarea.height(). Auf diese Weise muss QLabel nicht in Unterklassen unterteilt werden.

Omid
quelle