Search code examples
pythonpysideqtstylesheetspyside2

QSS not applied to first QSizeGrip in (composite widget) in QVBoxLayout


As shown below, the widget TestWidget contains a QFrame and a CSS-styled QSizeGrip. Several TestWidget instances are placed in a QVBoxLayout

from PySide import QtGui, QtCore
import sys

class TestWidget(QtGui.QWidget):
    def __init__(self , parent=None):
        super(TestWidget , self).__init__(parent)

        layout = QtGui.QVBoxLayout()
        layout.setContentsMargins( 0 , 0 , 0 , 0 )

        frame = QtGui.QFrame()
        frame.setFrameShape(QtGui.QFrame.StyledPanel)
        frame.setMinimumHeight( 100 )

        grip = QtGui.QSizeGrip(self)
        grip.setStyleSheet( "QSizeGrip { image: url(dots.png); }")
        grip.setCursor(QtCore.Qt.SplitVCursor)

        layout.addWidget(frame)
        layout.addWidget( grip , 0 , QtCore.Qt.AlignBottom | QtCore.Qt.AlignRight )

        self.setLayout(layout)

class TestApp(QtGui.QMainWindow):
    def __init__(self, parent=None):
        super(TestApp, self).__init__(parent)

        track1 = TestWidget()
        track2 = TestWidget()
        track3 = TestWidget()

        centralWidget = QtGui.QWidget()
        layout = QtGui.QVBoxLayout(centralWidget)

        layout.addWidget(track1)
        layout.addWidget(track2)
        layout.addWidget(track3)

        self.setCentralWidget(centralWidget)
        self.show() 

if __name__=="__main__":
    app=QtGui.QApplication(sys.argv)
    myapp = TestApp();
    sys.exit(app.exec_())   

As shown below, the size grip of the first TestWidget in the QVBoxLayout appears only if the TestWidget is the only element in the layout.

enter image description here enter image description here enter image description here

Qt version 4.8.7

PySide version 1.2.2


The PySide2 version of the program (below) has the same issue

from PySide2 import QtCore
from PySide2.QtWidgets import QApplication, QWidget , QMainWindow , QGraphicsView , QVBoxLayout , QFrame , QSizeGrip , QWidget

import sys

class TestWidget(QWidget):
    def __init__(self , parent=None):
        super(TestWidget , self).__init__(parent)

        layout = QVBoxLayout()
        layout.setContentsMargins( 0 , 0 , 0 , 0 )

        frame = QFrame()
        frame.setFrameShape(QFrame.StyledPanel)
        frame.setMinimumHeight( 100 )

        grip = QSizeGrip(self)
        grip.setStyleSheet( "QSizeGrip { image: url(dots.png); }")
        grip.setCursor(QtCore.Qt.SplitVCursor)

        layout.addWidget(frame)
        layout.addWidget( grip , 0 , QtCore.Qt.AlignBottom | QtCore.Qt.AlignRight )

        self.setLayout(layout)

class TestApp(QMainWindow):
    def __init__(self, parent=None):
        super(TestApp, self).__init__(parent)

        track1 = TestWidget()
        track2 = TestWidget()
        track3 = TestWidget()

        centralWidget = QWidget()
        layout = QVBoxLayout(centralWidget)

        layout.addWidget(track1)
        layout.addWidget(track2)
        layout.addWidget(track3)

        self.setCentralWidget(centralWidget)
        self.show() 

if __name__=="__main__":
    app = QApplication(sys.argv)
    myapp = TestApp();
    sys.exit(app.exec_())   

PySide2 version 5.12.1


Solution

  • What is observed is a predetermined behavior but not documented, if the source code is revised it will be observed:

    Qt::Corner QSizeGripPrivate::corner() const
    {
        Q_Q(const QSizeGrip);
        QWidget *tlw = qt_sizegrip_topLevelWidget(const_cast<QSizeGrip *>(q));
        const QPoint sizeGripPos = q->mapTo(tlw, QPoint(0, 0));
        bool isAtBottom = sizeGripPos.y() >= tlw->height() / 2;
        bool isAtLeft = sizeGripPos.x() <= tlw->width() / 2;
        if (isAtLeft)
            return isAtBottom ? Qt::BottomLeftCorner : Qt::TopLeftCorner;
        else
            return isAtBottom ? Qt::BottomRightCorner : Qt::TopRightCorner;
    }
    

    Where it is observed that the sizeGrip is placed in the upper part if it is in the upper part of the window, and this is the cause of the behavior that it observes.

    The workaround is to overwrite the paintEvent method of QSizeGrip:

    PySide2:

    import sys
    from PySide2 import QtCore, QtGui, QtWidgets
    
    class SizeGrip(QtWidgets.QSizeGrip):
        def paintEvent(self, event):
            painter = QtGui.QPainter(self)
            opt = QtWidgets.QStyleOptionSizeGrip()
            opt.initFrom(self)
            opt.corner = QtCore.Qt.BottomRightCorner
            self.style().drawControl(QtWidgets.QStyle.CE_SizeGrip, opt, painter, self)
    
    class TestWidget(QtWidgets.QWidget):
        def __init__(self , parent=None):
            super(TestWidget , self).__init__(parent)
            layout = QtWidgets.QVBoxLayout(self)
            layout.setContentsMargins( 0 , 0 , 0 , 0 )
            frame = QtWidgets.QFrame()
            frame.setFrameShape(QtWidgets.QFrame.StyledPanel)
            frame.setMinimumHeight( 100 )
            grip = SizeGrip(self)
            grip.setStyleSheet('''QSizeGrip { 
                image: url(dots.png);
            }''')
            layout.addWidget(frame)
            layout.addWidget(grip , 0 , QtCore.Qt.AlignBottom | QtCore.Qt.AlignRight )
    
    class TestApp(QtWidgets.QMainWindow):
        def __init__(self, parent=None):
            super(TestApp, self).__init__(parent)
            centralWidget = QtWidgets.QWidget()
            layout = QtWidgets.QVBoxLayout(centralWidget)
            for _ in range(3):
                layout.addWidget(TestWidget())
            self.setCentralWidget(centralWidget)
            self.show()
    
    if __name__=="__main__":
        app = QtWidgets.QApplication(sys.argv)
        myapp = TestApp();
        sys.exit(app.exec_())   
    

    PySide:

    import sys
    from PySide import QtCore, QtGui
    
    class SizeGrip(QtGui.QSizeGrip):
        def paintEvent(self, event):
            painter = QtGui.QPainter(self)
            opt = QtGui.QStyleOptionSizeGrip()
            opt.initFrom(self)
            opt.corner = QtCore.Qt.BottomRightCorner
            self.style().drawControl(QtGui.QStyle.CE_SizeGrip, opt, painter, self)
    
    class TestWidget(QtGui.QWidget):
        def __init__(self , parent=None):
            super(TestWidget , self).__init__(parent)
            layout = QtGui.QVBoxLayout(self)
            layout.setContentsMargins( 0 , 0 , 0 , 0 )
            frame = QtGui.QFrame()
            frame.setFrameShape(QtGui.QFrame.StyledPanel)
            frame.setMinimumHeight( 100 )
            grip = SizeGrip(self)
            grip.setStyleSheet('''QSizeGrip { 
                image: url(dots.png);
            }''')
            grip.setCursor(QtCore.Qt.SplitVCursor)
            layout.addWidget(frame)
            layout.addWidget(grip , 0 , QtCore.Qt.AlignBottom | QtCore.Qt.AlignRight )
    
    class TestApp(QtGui.QMainWindow):
        def __init__(self, parent=None):
            super(TestApp, self).__init__(parent)
            centralWidget = QtGui.QWidget()
            layout = QtGui.QVBoxLayout(centralWidget)
            for _ in range(3):
                layout.addWidget(TestWidget())
            self.setCentralWidget(centralWidget)
            self.show()
    
    if __name__=="__main__":
        app = QtGui.QApplication(sys.argv)
        myapp = TestApp();
        sys.exit(app.exec_())   
    

    enter image description here