Resizing a window with PyQT5 – how do I reduce the size of a widget to allow the window to be shrunk?

Question:

I’m trying to learn it by re-making an old command line C program I’ve got for working with pixel art.

At the moment, the main window starts as a single QLabel set to show a 300 x 300 scaled up version of a 10 x 10 white image.

I’m using the resizeEvent (I’ve also tried using paintEvent with the same problem) to rescale the image to fill the window as the window size is increased.

My question is, how do I rescale the image to fit in the window as the window size is decreased? As it stands, the window can’t be resized smaller than the widget displaying the image. Essentially, I can make the window (and image) bigger, but never smaller.

My code for this so far is below. As it stands it’s only working based on changes to window width, just to keep it simple while I’m working this out. Is there a way to allow the window to be resized to be smaller than the largest widget? Or is there a better way to approach this problem?

#Create white 10*10 image
image = QImage(10,10,QImage.Format.Format_ARGB32)
image_scaled = QImage()
image.fill(QColor(255,255,255)) 

class Window(QMainWindow):
    
    #scale image to change in window width (image is window width * window width square)
    def resizeEvent(self,event):
        if self.imageLabel.width()>self.imageLabel.height(): 
            self.image_scaled = image.scaled(self.imageLabel.width(),self.imageLabel.width()) 
            self.pixmap = QPixmap.fromImage(self.image_scaled)
            self.imageLabel.setPixmap(self.pixmap)  
        QWidget.resizeEvent(self, event)

    def __init__(self, parent=None):
        super().__init__(parent)
        self.setGeometry(100,100,300,300)   
        self.imageLabel = QLabel()
        self.setCentralWidget(self.imageLabel)
        
        self.image_scaled = image.scaled(self.imageLabel.width(),self.imageLabel.width()) 
        self.pixmap = QPixmap.fromImage(self.image_scaled)
        self.imageLabel.setPixmap(self.pixmap)  


app = QApplication(sys.argv)
win = Window()
win.show()
sys.exit(app.exec_())
Asked By: jasonisme84

||

Answers:

Found a solution. Turns out putting the image inside a QScrollArea widget allows the window to be made smaller than the image it contains even if the scroll bars are disabled. This then allows the image to be rescaled to fit the window as the window size is reduced.

class Window(QMainWindow):
    
    #scale image to change in window width (image is window width * window width square)
    def resizeEvent(self,event):
        self.image_scaled = image.scaled(self.scroll.width(),self.scroll.height()) 
        self.pixmap = QPixmap.fromImage(self.image_scaled)
        self.imageLabel.setPixmap(self.pixmap)  
        QMainWindow.resizeEvent(self, event)

    def __init__(self, parent=None):

        super().__init__(parent)
        self.setGeometry(100,100,200,200)   
        self.imageLabel = QLabel()
        self.scroll = QScrollArea()
        self.scroll.setWidget(self.imageLabel)
        self.setCentralWidget(self.scroll)

        self.scroll.setWidgetResizable(True)
        self.scroll.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
        self.scroll.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
      
        self.image_scaled = image.scaled(self.scroll.width(),self.scroll.width()) 
        self.pixmap = QPixmap.fromImage(self.image_scaled)
        self.imageLabel.setPixmap(self.pixmap)  

app = QApplication(sys.argv)
win = Window()
win.show()
sys.exit(app.exec_())
Answered By: jasonisme84

While the OP proposed solution might work, it has an important drawback: it uses a QScrollArea for the wrong purpose (since it’s never used for scrolling). That approach creates unnecessary overhead while resizing, as the view will need to compute lots of things about its contents before "finishing" the resize event (including scroll bar ranges and geometries) that, in the end, will never be actually used.

The main problem comes from the fact that QLabel doesn’t allow resizing to a size smaller than the original pixmap set. To work around this issue, the simplest solution is to create a custom QWidget subclass that draws the pixmap on its own.

class ImageViewer(QWidget):
    pixmap = None
    _sizeHint = QSize()
    ratio = Qt.KeepAspectRatio
    transformation = Qt.SmoothTransformation

    def __init__(self, pixmap=None):
        super().__init__()
        self.setPixmap(pixmap)

    def setPixmap(self, pixmap):
        if self.pixmap != pixmap:
            self.pixmap = pixmap
            if isinstance(pixmap, QPixmap):
                self._sizeHint = pixmap.size()
            else:
                self._sizeHint = QSize()
            self.updateGeometry()
            self.updateScaled()

    def setAspectRatio(self, ratio):
        if self.ratio != ratio:
            self.ratio = ratio
            self.updateScaled()

    def setTransformation(self, transformation):
        if self.transformation != transformation:
            self.transformation = transformation
            self.updateScaled()

    def updateScaled(self):
        if self.pixmap:
            self.scaled = self.pixmap.scaled(self.size(), self.ratio, self.transformation)
        self.update()

    def sizeHint(self):
        return self._sizeHint

    def resizeEvent(self, event):
        self.updateScaled()

    def paintEvent(self, event):
        if not self.pixmap:
            return
        qp = QPainter(self)
        r = self.scaled.rect()
        r.moveCenter(self.rect().center())
        qp.drawPixmap(r, self.scaled)


class Window(QMainWindow):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.imageLabel = ImageViewer(QPixmap.fromImage(image))
        self.setCentralWidget(self.imageLabel)
Answered By: musicamante

This is working for me perfectly:

self.imageLabel.setMinimumSize(1, 1)

Found it here
But didn’t know what this line does, so I deleted it, until I tried to size image down 🙂

Categories: questions Tags: , , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.