Qt: thay đổi kích thước của QLabel có chứa QPixmap trong khi vẫn giữ nguyên tỷ lệ co của nó


81

Tôi sử dụng QLabel để hiển thị nội dung của một QPixmap lớn hơn, thay đổi động cho người dùng. Sẽ rất tốt nếu bạn làm cho nhãn này nhỏ hơn / lớn hơn tùy thuộc vào không gian có sẵn. Kích thước màn hình không phải lúc nào cũng lớn như QPixmap.

Làm cách nào để sửa đổi QSizePolicysizeHint()của QLabel để thay đổi kích thước của QPixmap trong khi vẫn giữ nguyên tỷ lệ co của QPixmap ban đầu?

Tôi không thể sửa đổi sizeHint()QLabel, việc đặt minimumSize()thành 0 không giúp ích được gì. Đặt hasScaledContents()trên QLabel cho phép phát triển, nhưng phá vỡ tỷ lệ khung hình ...

Phân lớp QLabel đã giúp ích, nhưng giải pháp này thêm quá nhiều mã chỉ cho một vấn đề đơn giản ...

Bất kỳ gợi ý thông minh nào về cách thực hiện điều này mà không cần phân lớp?


Bằng cách thay đổi động, ý bạn là dữ liệu pixel hay kích thước?
r_ahlskog

Ý tôi là các kích thước của QLabelbố cục hiện tại. Các QPixmapnên giữ kích thước, nội dung và kích thước của nó. Ngoài ra, sẽ rất tuyệt khi việc thay đổi kích thước (thu nhỏ trong thực tế) xảy ra "tự động" để lấp đầy không gian có sẵn - lên đến kích thước của bản gốc QPixmap. Tất cả điều này được thực hiện thông qua subclassing ...
marvin2k

Câu trả lời:


98

Để thay đổi kích thước nhãn, bạn có thể chọn chính sách kích thước thích hợp cho nhãn như mở rộng hoặc mở rộng tối thiểu.

Bạn có thể chia tỷ lệ ảnh pixmap bằng cách giữ nguyên tỷ lệ co của nó mỗi khi nó thay đổi:

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

Có hai nơi bạn nên thêm mã này:

  • Khi bản đồ ảnh được cập nhật
  • Trong resizeEventtiện ích con có chứa nhãn

hm vâng, đây về cơ bản là cốt lõi khi tôi phân lớp QLabel. Nhưng tôi nghĩ trường hợp này sử dụng (hiển thị hình ảnh với kích thước tùy ý trong Widgets kích thước tùy ý) sẽ đủ phổ biến để có một cái gì đó giống như nó implementable qua mã hiện ...
marvin2k

AFAIK chức năng này không được cung cấp theo mặc định. Cách thanh lịch nhất để đạt được những gì bạn muốn là phân lớp QLabel. Nếu không, bạn có thể sử dụng mã câu trả lời của tôi trong một vị trí / chức năng sẽ được gọi mỗi khi bản đồ ảnh thay đổi.
pnezis

1
vì tôi muốn QLabeltự động mở rộng dựa trên việc người dùng thay đổi kích thước của QMainWindowvà không gian có sẵn, tôi không thể sử dụng giải pháp tín hiệu / vị trí - tôi không thể lập mô hình chính sách mở rộng theo cách này.
marvin2k

21
Để có thể giảm quy mô là tốt, bạn cần phải thêm cuộc gọi này:label->setMinimumSize(1, 1)
Pieter-Jan Busschaert

1
Điều này không hữu ích lắm nếu tôi muốn duy trì tỷ lệ khung hình ngay cả khi người dùng thay đổi kích thước của nhãn.
Tomáš Zato - Phục hồi Monica

33

Tôi đã đánh bóng lớp con bị thiếu này của QLabel. Nó thật tuyệt vời và hoạt động tốt.

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

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

Hy vọng rằng sẽ giúp! (Đã cập nhật resizeEvent, theo câu trả lời của @ dmzl)


1
Cảm ơn, hoạt động tuyệt vời. Tôi cũng sẽ thêm QLabel::setPixmap(pix.scaled(this->size(), Qt::KeepAspectRatio, Qt::SmoothTransformation));vào setPixmap()phương pháp.
Hyndrix

Bạn đúng rồi. Tôi đã đưa ra giả định rằng bạn muốn lưu trữ phiên bản chất lượng cao nhất của pixmap và bạn gọi setPixmap trước khi thay đổi kích thước / neo nhãn. Để giảm sự trùng lặp mã, có lẽ tôi nên đặt this->resize(width(), height());ở cuối setPixmaphàm.
phyatt

Cảm ơn vì đã chia sẻ điều này. Bạn có gợi ý nào về cách tôi có thể đặt kích thước "ưa thích" cho QPixmap để nó không chiếm độ phân giải tối đa trong lần khởi chạy ứng dụng đầu tiên không?
Julien M

Sử dụng bố cục và quy tắc kéo dài.
phyatt

3
Câu trả lời chính xác! Đối với bất kỳ ai cần làm việc trên màn hình DPI cao, chỉ cần thay đổi scaledPixmap () để làm: auto scaled = pix.scaled(this->size() * devicePixelRatioF(), Qt::KeepAspectRatio, Qt::SmoothTransformation); scaled.setDevicePixelRatio(devicePixelRatioF()); return scaled;Điều này cũng hoạt động trên màn hình được chia tỷ lệ bình thường.
Saul

18

Tôi chỉ sử dụng contentsMarginđể sửa tỷ lệ khung hình.

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

Hoạt động hoàn hảo cho tôi cho đến nay. Không có gì.


4
Chỉ cần sử dụng nó và nó hoạt động như một sự quyến rũ! Ngoài ra, sử dụng khá thông minh của trình quản lý bố cục. Nên là câu trả lời được chấp nhận vì tất cả những cái khác đều có sai sót trong các trường hợp góc.
thokra

2
Mặc dù không thông minh bằng trực giác, nhưng câu trả lời này giải quyết một câu hỏi khác về cơ bản : "Chúng ta nên thêm bao nhiêu phần đệm bên trong giữa một nhãn có kích thước đã nổi tiếng và ảnh pixmap có trong nhãn đó để bảo toàn tỷ lệ co của ảnh pixmap đó? " Mọi câu trả lời khác đều giải quyết được câu hỏi ban đầu: "Chúng ta nên thay đổi kích thước nhãn chứa ảnh pixmap để bảo toàn tỷ lệ co của ảnh pixmap đó ở kích thước nào?" Câu trả lời này yêu cầu kích thước của nhãn phải được xác định trước bằng cách nào đó (ví dụ: với chính sách kích thước cố định), điều này là không mong muốn hoặc thậm chí không khả thi trong nhiều trường hợp sử dụng.
Cecil Curry,

1
Đó là cách để sử dụng màn hình HiResolution (hay còn gọi là "retina") - nó tốt hơn nhiều so với việc giảm tỷ lệ QPixmap.
jvb 19/02/19

Có lẽ tôi hơi quá tập trung vào việc làm cho mã diễn đạt ý nghĩa cấp cao vì lợi ích của khả năng bảo trì nhưng sẽ có ý nghĩa hơn khi sử dụng QSizethay vì ...Width...Height? Nếu không có gì khác, điều đó sẽ làm cho việc kiểm tra việc trả lại sớm của bạn trở thành một QSize::isEmptycuộc gọi đơn giản . QPixmapQWidgetcả hai đều có sizecác phương thức để lấy chiều rộng và chiều cao là a QSize.
ssokolow

@ssokolow Vâng, điều đó nghe hay hơn - vui lòng chỉnh sửa câu trả lời.
Timmmm

5

Tôi đã thử sử dụng AspectRatioPixmapLabellớp của phyatt , nhưng gặp một số vấn đề:

  • Đôi khi ứng dụng của tôi đi vào một vòng lặp vô hạn các sự kiện thay đổi kích thước. Tôi lần theo dấu vết này để gọi QLabel::setPixmap(...)bên trong phương thức resizeEvent, bởi vì QLabelthực sự các lệnh gọi updateGeometrybên trong setPixmap, có thể kích hoạt các sự kiện thay đổi kích thước ...
  • heightForWidthdường như bị bỏ qua bởi tiện ích con chứa ( QScrollAreatrong trường hợp của tôi) cho đến khi tôi bắt đầu thiết lập chính sách kích thước cho nhãn, gọi một cách rõ ràngpolicy.setHeightForWidth(true)
  • Tôi muốn nhãn không bao giờ lớn hơn kích thước bản đồ gốc
  • QLabelViệc triển khai thực hiện minimumSizeHint()một số điều kỳ diệu đối với các nhãn chứa văn bản, nhưng luôn đặt lại chính sách kích thước về chính sách mặc định, vì vậy tôi đã phải ghi đè nó

Điều đó nói rằng, đây là giải pháp của tôi. Tôi thấy rằng tôi chỉ có thể sử dụng setScaledContents(true)và để QLabelxử lý việc thay đổi kích thước. Tất nhiên, điều này phụ thuộc vào widget / bố cục chứa heightForWidth.

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

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

Mặc dù thích hợp cho các trường hợp cạnh trong đó tiện ích mẹ và / hoặc bố cục chứa nhãn này tôn trọng thuộc heightForWidthtính, nhưng câu trả lời này không phù hợp với trường hợp chung trong đó tiện ích con và / hoặc bố cục chứa nhãn này không tôn trọng thuộc heightForWidthtính. Thật không may, vì câu trả lời này có vẻ thích hợp hơn câu trả lời lâu đời của phyatt .
Cecil Curry

3

Chuyển thể từ Timmmm sang 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)

0

Tài liệu Qt có ví dụ về Trình xem Hình ảnh minh họa việc xử lý các hình ảnh thay đổi kích thước bên trong a QLabel. Ý tưởng cơ bản là sử dụng QScrollAreanhư một thùng chứa để QLabelsử dụng label.setScaledContents(bool)và nếu cần và scrollarea.setWidgetResizable(bool)để lấp đầy không gian có sẵn và / hoặc đảm bảo QLabel bên trong có thể thay đổi kích thước. Ngoài ra, để thay đổi kích thước QLabel trong khi tôn trọng việc sử dụng tỷ lệ khung hình:

Các widthheightcó thể được thiết lập dựa trên scrollarea.width()scrollarea.height(). Theo cách này, không cần phân lớp QLabel.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.