6

We are making a GUI using PyQt and Qt Designer. Now we need that an image(pixmap) placed in a QLabel rescales nicely keeping ratio when the window is resized.

I've been reading other questions/answers but all of them use extended classes. As we are making constant changes in our UI, and it's created with Qt Creator, the .ui and (corresponding).py files are automatically generated so, if I'm not wrong, using a class-solution is not a good option for us because we should manually change the name of the class each time we update the ui.

Is there any option to autoresize the pixmap in a QLAbel keeping the ratio and avoiding using extended clases?

Thanks.

ekhumoro
  • 115,249
  • 20
  • 229
  • 336
user3061177
  • 111
  • 1
  • 8

4 Answers4

14

There are a couple of ways to do this.

Firstly, you can promote your QLabel in Qt Designer to a custom subclass that is written in python. Right-click the QLabel and select "Promote to...", then give the class a name (e.g. "ScaledLabel") and set the header file to the python module that the custom subclass class will be imported from (e.g. 'mylib.classes').

The custom subclass would then re-implement the resizeEvent like this:

class ScaledLabel(QtGui.QLabel):
    def __init__(self, *args, **kwargs):
        QtGui.QLabel.__init__(self)
        self._pixmap = QtGui.QPixmap(self.pixmap())

    def resizeEvent(self, event):
        self.setPixmap(self._pixmap.scaled(
            self.width(), self.height(),
            QtCore.Qt.KeepAspectRatio))

For this to work properly, the QLabel should have its size policy set to expanding or minimumExpanding, and the minimum size should be set to a small, non-zero value (so the image can be scaled down).

The second method avoids using a subclass and uses an event-filter to handle the resize events:

class MainWindow(QtGui.QMainWindow):
    def __init__(self):
        ...
        self._pixmap = QtGui.QPixmap(self.label.pixmap())
        self.label.installEventFilter(self)

    def eventFilter(self, widget, event):
        if (event.type() == QtCore.QEvent.Resize and
            widget is self.label):
            self.label.setPixmap(self._pixmap.scaled(
                self.label.width(), self.label.height(),
                QtCore.Qt.KeepAspectRatio))
            return True
        return QtGui.QMainWindow.eventFilter(self, widget, event)
ekhumoro
  • 115,249
  • 20
  • 229
  • 336
  • I'm having problems trying to implement any of both solutions. One question. The "Scaled contents" property should be activated or deactivated? – user3061177 Jan 13 '14 at 11:49
  • 1
    @user3061177. The `scaledContents` property should be set to false, and you need to set the size-policy and minimum-size as per my answer. I could post a working demo, but there's not much to add to what is already in my answer. What exactly is it that you're having trouble with? – ekhumoro Jan 13 '14 at 17:22
2

Set background-image:, background-repeat: and background-position QSS properties for your label. You may do it via Forms editor or in code QWidget::setStyleSheet.

A good starting point for QSS (with examples) - http://doc.qt.io/qt-5/stylesheet-reference.html

jesterjunk
  • 2,342
  • 22
  • 18
Dmitry Sazonov
  • 8,801
  • 1
  • 35
  • 61
1

One way is to create a QWidget/QLabel subclass and reimplement the resizeEvent.

void QWidget::resizeEvent(QResizeEvent * event) [virtual protected]

This event handler can be reimplemented in a subclass to receive widget resize events which are passed in the event parameter. When resizeEvent() is called, the widget already has its new geometry. The old size is accessible through QResizeEvent::oldSize().

The widget will be erased and receive a paint event immediately after processing the resize event. No drawing need be (or should be) done inside this handler.

This would need to be done in C++ though, not PyQt.

Having that done, you could add your custom widget to the QtDesigner as follows:

Using Custom Widgets with Qt Designer

Community
  • 1
  • 1
László Papp
  • 51,870
  • 39
  • 111
  • 135
  • Laszlo, so Do I need to have py and cpp files mixed in the same project? Or the cpp code is written directly in QtCreator? – user3061177 Jan 10 '14 at 11:35
  • 1
    @user3061177: you need to have the class available for your generated ui files, so yes, you need to have it either in your project, or a separate library to link against (with some modifications). – László Papp Jan 10 '14 at 11:37
  • Ok, and side question. Why can I not use the subclass in Python? Is a limitation in Qt Creator or PyQT? – user3061177 Jan 10 '14 at 14:53
  • @user3061177: to be honest, ekhumoro knows this lotta better than me, I think. – László Papp Jan 13 '14 at 17:49
1

Incredibly, after seven years @ekhumoro's excellent answer is still pretty much the only working Python implementation that can be found around; everyone else tells what to do, but nobody gives the actual code.

In spite of this, it did not work at first for me, because I happened to have the pixmap generation somewhere else in the code - specifically, my pixmap was generated inside a function which was only activated when clicking on a button, so not during the window intialization.

After figuring out how @ekhumoro's second method worked, I edited it in order to accomodate this difference. In pratice I generalised the original code, also because I did not like (for efficiency reasons) how it added a new _pixmap attribute to the label, which seemed to be nothing more than a copy of the original pixmap.

The following his is my version; mind that I have not fully tested it, but since it is a shorter version of my original working code, it too should work just fine (corrections are welcome, though):

class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()

        # Initialize stuff here; mind that no pixmap is added to the label at this point

    def eventFilter(self, widget, event):
        if event.type() == QEvent.Resize and widget is self.label:
            self.label.setPixmap(self.label.pixmap.scaled(self.label.width(), self.label.height(), aspectRatioMode=Qt.KeepAspectRatio, transformMode=Qt.SmoothTransformation))
            return True
        return QMainWindow.eventFilter(self, widget, event)

    def apply_pixelmap(self, image):  # This is where the pixmap is added. For simplicity, suppose that you pass a QImage as an argument to this function; however, you can obtain this in any way you like
        pixmap = QPixmap.fromImage(image).scaled(new_w, new_h, aspectRatioMode=Qt.KeepAspectRatio, transformMode=Qt.SmoothTransformation)

        self.label.setPixmap(pixmap)
        self.label.pixmap = QPixmap(pixmap)  # I am aware that this line looks like a redundancy, but without it the program does not work; I could not figure out why, so I will gladly listen to anyone who knows it
        self.label.installEventFilter(self)

        return

This works by setting the ScaledContents property to False and the SizePolicy to either Expanding or Ignored. Note that it might not work if the label containing the image is not set as the central widget (self.setCentralWidget(self.label), where self refers to MainWindow).

Zelethil
  • 11
  • 4