通过拖动pyqt5中的边缘来调整自定义小部件的大小

Resizing custom widget by dragging the edges in pyqt5

我制作了一个类似于 QPushbutton 或 label 的自定义小部件。我想让用户在鼠标悬停在小部件边缘时调整小部件的大小。我该怎么做?

(注意:我不是在寻找拆分器window)

一个图像编辑软件,你有一个图像专用的“space”,用户可以在space的范围内自由she/he做任何想做的事情。当一个小部件被放置在一个布局管理的容器中(通常应该如此)时,它可以代表多个问题。您不仅必须实现整个鼠标交互来调整小部件的大小,而且还需要将调整大小通知可能的父小部件。

也就是说,您想要实现的目标可以完成,但有一些注意事项。

以下是标准 QWidget 的一个非常基本的实现,它能够调整自身大小,同时将大小提示修改通知其父窗口小部件。请注意,这 完整,并且它的行为不会正确响应发生在小部件顶部或左侧边缘的鼠标移动。此外,虽然它(可以)在增加其大小时正确调整父小部件的大小,但在缩小时不会发生调整大小。理论上这可以通过设置 minimumSize() 并手动调用 adjustSize() 来实现,但是,为了正确提供类似概念所需的所有可能功能,您需要自己完成整个实现。

from PyQt5 import QtCore, QtGui, QtWidgets

Left, Right = 1, 2
Top, Bottom = 4, 8
TopLeft = Top|Left
TopRight = Top|Right
BottomRight = Bottom|Right
BottomLeft = Bottom|Left

class ResizableLabel(QtWidgets.QWidget):
    resizeMargin = 4
    # note that the Left, Top, Right, Bottom constants cannot be used as class
    # attributes if you want to use list comprehension for better performance,
    # and that's due to the variable scope behavior on Python 3
    sections = [x|y for x in (Left, Right) for y in (Top, Bottom)]
    cursors = {
        Left: QtCore.Qt.SizeHorCursor, 
        Top|Left: QtCore.Qt.SizeFDiagCursor, 
        Top: QtCore.Qt.SizeVerCursor, 
        Top|Right: QtCore.Qt.SizeBDiagCursor, 
        Right: QtCore.Qt.SizeHorCursor, 
        Bottom|Right: QtCore.Qt.SizeFDiagCursor, 
        Bottom: QtCore.Qt.SizeVerCursor, 
        Bottom|Left: QtCore.Qt.SizeBDiagCursor, 
    }
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.startPos = self.section = None
        self.rects = {section:QtCore.QRect() for section in self.sections}

        # mandatory for cursor updates
        self.setMouseTracking(True)

        # just for demonstration purposes
        background = QtGui.QPixmap(3, 3)
        background.fill(QtCore.Qt.transparent)
        qp = QtGui.QPainter(background)
        pen = QtGui.QPen(QtCore.Qt.darkGray, .5)
        qp.setPen(pen)
        qp.drawLine(0, 2, 2, 0)
        qp.end()
        self.background = QtGui.QBrush(background)

    def updateCursor(self, pos):
        for section, rect in self.rects.items():
            if pos in rect:
                self.setCursor(self.cursors[section])
                self.section = section
                return section
        self.unsetCursor()

    def adjustSize(self):
        del self._sizeHint
        super().adjustSize()

    def minimumSizeHint(self):
        try:
            return self._sizeHint
        except:
            return super().minimumSizeHint()

    def mousePressEvent(self, event):
        if event.button() == QtCore.Qt.LeftButton:
            if self.updateCursor(event.pos()):
                self.startPos = event.pos()
                return
        super().mousePressEvent(event)

    def mouseMoveEvent(self, event):
        if self.startPos is not None:
            delta = event.pos() - self.startPos
            if self.section & Left:
                delta.setX(-delta.x())
            elif not self.section & (Left|Right):
                delta.setX(0)
            if self.section & Top:
                delta.setY(-delta.y())
            elif not self.section & (Top|Bottom):
                delta.setY(0)
            newSize = QtCore.QSize(self.width() + delta.x(), self.height() + delta.y())
            self._sizeHint = newSize
            self.startPos = event.pos()
            self.updateGeometry()
        elif not event.buttons():
            self.updateCursor(event.pos())
        super().mouseMoveEvent(event)
        self.update()

    def mouseReleaseEvent(self, event):
        super().mouseReleaseEvent(event)
        self.updateCursor(event.pos())
        self.startPos = self.section = None
        self.setMinimumSize(0, 0)

    def resizeEvent(self, event):
        super().resizeEvent(event)
        outRect = self.rect()
        inRect = self.rect().adjusted(self.resizeMargin, self.resizeMargin, -self.resizeMargin, -self.resizeMargin)
        self.rects[Left] = QtCore.QRect(outRect.left(), inRect.top(), self.resizeMargin, inRect.height())
        self.rects[TopLeft] = QtCore.QRect(outRect.topLeft(), inRect.topLeft())
        self.rects[Top] = QtCore.QRect(inRect.left(), outRect.top(), inRect.width(), self.resizeMargin)
        self.rects[TopRight] = QtCore.QRect(inRect.right(), outRect.top(), self.resizeMargin, self.resizeMargin)
        self.rects[Right] = QtCore.QRect(inRect.right(), self.resizeMargin, self.resizeMargin, inRect.height())
        self.rects[BottomRight] = QtCore.QRect(inRect.bottomRight(), outRect.bottomRight())
        self.rects[Bottom] = QtCore.QRect(inRect.left(), inRect.bottom(), inRect.width(), self.resizeMargin)
        self.rects[BottomLeft] = QtCore.QRect(outRect.bottomLeft(), inRect.bottomLeft()).normalized()

    # ---- optional, mostly for demonstration purposes ----

    def paintEvent(self, event):
        super().paintEvent(event)
        qp = QtGui.QPainter(self)
        if self.underMouse() and self.section:
            qp.save()
            qp.setPen(QtCore.Qt.lightGray)
            qp.setBrush(self.background)
            qp.drawRect(self.rect().adjusted(0, 0, -1, -1))
            qp.restore()
        qp.drawText(self.rect(), QtCore.Qt.AlignCenter, '{}x{}'.format(self.width(), self.height()))

    def enterEvent(self, event):
        self.update()

    def leaveEvent(self, event):
        self.update()


class Test(QtWidgets.QWidget):
    def __init__(self):
        super().__init__()
        layout = QtWidgets.QGridLayout(self)

        for row in range(3):
            for column in range(3):
                if (row, column) == (1, 1):
                    continue
                layout.addWidget(QtWidgets.QPushButton(), row, column)

        label = ResizableLabel()
        layout.addWidget(label, 1, 1)

if __name__ == '__main__':
    import sys
    app = QtWidgets.QApplication(sys.argv)
    w = Test()
    w.show()
    sys.exit(app.exec_())