pythonqtpyqtresizerubber-band

Forcing QRubberBand to a specific aspect ratio


I have a resizable rubber-band with drag handles that is acting funny. I want to force it to a 4x3 selection rectangle. It works, but there are clipping artefacts, and the grips don't stay in the corners.

Code:

class Resizable_rubber_band(QWidget):
    def __init__(self, parent=None):
        super(Resizable_rubber_band, self).__init__(parent)
        self.setWindowFlags(Qt.SubWindow)
        self.layout = QHBoxLayout(self)
        self.layout.setContentsMargins(0, 0, 0, 0)
        self.grip1 = QSizeGrip(self)
        self.grip2 = QSizeGrip(self)
        self.layout.addWidget(self.grip1, 0, Qt.AlignLeft | Qt.AlignTop)
        self.layout.addWidget(self.grip2, 0, Qt.AlignRight | Qt.AlignBottom)
        self.rubberband = QRubberBand(QRubberBand.Rectangle, self)
        self.rubberband.move(0, 0)
        self.rubberband.show()
        self.show()

    def resizeEvent(self, event):
        force 4h x 3w aspect ratio using QSize
        if width < height: height = 1.333 * width
        if width > height: width = height / 1.333
        width = self.width()
        height = self.height()
        if(width < height):
            height = int(width * 1.333)
        else:
            width = int(height / 1.333)
        self.rubberband.resize(QSize(width, height))

Solution

  • You need to scale and resize both the rubber-band and its container.

    Here is a demo based on your example:

    import sys
    from PyQt5 import QtCore, QtGui, QtWidgets
    
    class ResizableRubberBand(QtWidgets.QWidget):
        def __init__(self, parent=None):
            super(ResizableRubberBand, self).__init__(parent)
            self.setWindowFlags(QtCore.Qt.SubWindow)
            layout = QtWidgets.QHBoxLayout(self)
            layout.setContentsMargins(0, 0, 0, 0)
            layout.addWidget(
                QtWidgets.QSizeGrip(self), 0,
                QtCore.Qt.AlignLeft | QtCore.Qt.AlignTop)
            layout.addWidget(
                QtWidgets.QSizeGrip(self), 0,
                QtCore.Qt.AlignRight | QtCore.Qt.AlignBottom)
            self._band = QtWidgets.QRubberBand(
                QtWidgets.QRubberBand.Rectangle, self)
            self._band.show()
            self.show()
    
        def resizeEvent(self, event):
            size = QtCore.QSize(3, 4)
            size.scale(self.size(), QtCore.Qt.KeepAspectRatio)
            self.resize(size)
            self._band.resize(self.size())
    
    class Window(QtWidgets.QWidget):
        def __init__(self):
            super(Window, self).__init__()
            self.button = QtWidgets.QPushButton('Show Rubber Band')
            self.button.clicked.connect(self.handleButton)
            self.label = QtWidgets.QLabel()
            self.label.setScaledContents(True)
            self.label.setPixmap(QtGui.QPixmap('image.jpg'))
            layout = QtWidgets.QVBoxLayout(self)
            layout.addWidget(self.label)
            layout.addWidget(self.button)
    
        def handleButton(self):
            self.band = ResizableRubberBand(self.label)
            self.band.setGeometry(50, 50, 150, 300)
    
    if __name__ == '__main__':
    
        app = QtWidgets.QApplication(sys.argv)
        window = Window()
        window.setGeometry(800, 100, 600, 500)
        window.show()
        sys.exit(app.exec_())