Search code examples
pythonpyqtpyqt4qlistwidget

Custom QSizeGrip to resize a QListWidget


I want to make a QListWidget with a resize handle at the bottom (similar to text fields you see on webpages like this one). I've seen a few people asking the same question out there but did not find a complete example.

I have some code which is nearly there, but it flickers during resize so I'm guessing there is something I am missing about resize policies or layouts or something...

Here's my "working" example. The theory is pretty simple, you just measure the distance of the mouse move in the widget's mousePressEvent and resize/reposition accordingly. Unfortunately I'm missing something basic and I don't know what:

from PyQt4 import QtGui
import sys

class Grip(QtGui.QLabel):
    def __init__(self, parent, move_widget):
        super(Grip, self).__init__(parent)
        self.move_widget = move_widget
        self.setText("+")
        self.min_height = 50

        self.mouse_start = None
        self.height_start = self.move_widget.height()
        self.resizing = False
        self.setMouseTracking(True)


    def showEvent(self, event):
        super(Grip, self).showEvent(event)
        self.reposition()

    def mousePressEvent(self, event):
        super(Grip, self).mousePressEvent(event)
        self.resizing = True
        self.height_start = self.move_widget.height()
        self.mouse_start = event.pos()

    def mouseMoveEvent(self, event):
        super(Grip, self).mouseMoveEvent(event)
        if self.resizing:
            delta = event.pos() - self.mouse_start
            height = self.height_start + delta.y()
            if height > self.min_height:
                self.move_widget.setFixedHeight(height)
            else:
                self.move_widget.setFixedHeight(self.min_height)

            self.reposition()

    def mouseReleaseEvent(self, event):
        super(Grip, self).mouseReleaseEvent(event)
        self.resizing = False

    def reposition(self):
        rect = self.move_widget.geometry()
        self.move(rect.right(), rect.bottom())


class Dialog(QtGui.QDialog):
    def __init__(self):
        super(Dialog, self).__init__()
        layout = QtGui.QVBoxLayout()
        self.setLayout(layout)
        list_widget = QtGui.QListWidget()
        layout.addWidget(list_widget)
        gripper = Grip(self, list_widget)

        layout.addWidget(QtGui.QLabel("Test"))

        self.setGeometry(200, 500, 200, 500)

Solution

  • Ok turns out I was really damn close. I post an answer here for anyone else looking to solve a similar problem!

    All I really needed to change from my original code was to reference the globalPos() instead of the local pos(). Thanks for the help, and S. Nick in particular for spotting that the move event was causing the issue.

    from PyQt4 import QtGui
    import sys
    
    class Grip(QtGui.QLabel):
        def __init__(self, parent, move_widget):
            super(Grip, self).__init__(parent)
            self.move_widget = move_widget
            self.setText("+")
            self.min_height = 50
    
            self.mouse_start = None
            self.height_start = self.move_widget.height()
            self.resizing = False
            self.setMouseTracking(True)
    
            self.setCursor(QtCore.Q.SizeVerCursor)
    
    
        def showEvent(self, event):
            super(Grip, self).showEvent(event)
            self.reposition()
    
        def mousePressEvent(self, event):
            super(Grip, self).mousePressEvent(event)
            self.resizing = True
            self.height_start = self.move_widget.height()
            self.mouse_start = event.globalPos()
    
        def mouseMoveEvent(self, event):
            super(Grip, self).mouseMoveEvent(event)
            if self.resizing:
                delta = event.globalPos() - self.mouse_start
                height = self.height_start + delta.y()
                if height > self.min_height:
                    self.move_widget.setFixedHeight(height)
                else:
                    self.move_widget.setFixedHeight(self.min_height)
    
                self.reposition()
    
        def mouseReleaseEvent(self, event):
            super(Grip, self).mouseReleaseEvent(event)
            self.resizing = False
    
        def reposition(self):
            rect = self.move_widget.geometry()
            self.move(rect.right(), rect.bottom())
    
    
    class Dialog(QtGui.QDialog):
        def __init__(self):
            super(Dialog, self).__init__()
            layout = QtGui.QVBoxLayout()
            self.setLayout(layout)
            list_widget = QtGui.QListWidget()
            layout.addWidget(list_widget)
            gripper = Grip(self, list_widget)
    
            layout.addWidget(QtGui.QLabel("Test"))
    
            self.setGeometry(200, 500, 200, 500)