【问题标题】:QPaint with mouse Event panning带鼠标事件平移的 QPaint
【发布时间】:2018-11-21 18:56:38
【问题描述】:

我创建了一个绘画小部件,我想用手形图标工具实现一个平移事件,这通常在许多软件中看到。当用户按下鼠标按钮并按住它然后在Qpainter canvas 内移动时,绘图跟随鼠标移动。我不知道如何在 PyQt5 中做到这一点。

视觉示例

当前画面:

所需的屏幕事件:

守则:

import sys
from PyQt5 import QtCore, QtGui, QtWidgets

class Foo(QtWidgets.QWidget):
    def __init__(self, parent=None):
        super(Foo, self).__init__(parent)
        self.setGeometry(QtCore.QRect(200, 100, 1200, 600))        
        self.paint = Paint()
        self.sizeHint()
        self.lay = QtWidgets.QVBoxLayout()
        self.lay.addWidget(self.paint)
        self.setLayout(self.lay)

class Paint(QtWidgets.QWidget):
    def __init__(self, parent=None):
        super(Paint, self).__init__(parent)
        self.setBackgroundRole(QtGui.QPalette.Base)     
        self.setAutoFillBackground(True)

        self._width = 350
        self._height = 250

    def paintEvent(self, event):
        painter = QtGui.QPainter(self)
        painter.setRenderHint(QtGui.QPainter.Antialiasing)
        painter.setBrush(QtGui.QBrush( QtCore.Qt.cyan))
        painter.setPen(QtCore.Qt.darkCyan)

        r = QtCore.QRect(QtCore.QPoint(), QtCore.QSize(self._width, self._height))
        r.moveCenter(self.rect().center())
        painter.drawRect(r)


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

感谢您的帮助,并在此先感谢您。

代码更新:

可视化:

嗯,当我在我的中心代码中实现更新的代码时会发生这种情况。 这是当用户单击并从圆形中选择部分然后在小部件中绘制时,在单击并从矩形中选择另一个部分后立即进行。小部件应更新并删除以前的绘图。但它不会那样做。在我之前的代码中,剖面图直接出现在小部件的中心,现在它粘在画布的左上角。

部分代码:

from PyQt5 import QtCore, QtGui, QtWidgets

class Foo(QtWidgets.QWidget):
    def __init__(self, parent=None):
        super(Foo, self).__init__(parent)
        self.setGeometry(QtCore.QRect(200, 100, 800, 800))

        self.button = Button()
        self.paint = Createpaintwidget()
        self.button.valuesChanged.connect(self.paint.set_size_squares)
        self.button.valueChanged.connect(self.paint.set_size_round)

        self.lay = QtWidgets.QVBoxLayout(self)
        self.lay.addWidget(self.paint)
        self.lay.addWidget(self.button)

class Createpaintwidget(QtWidgets.QWidget):
    def __init__(self):
        super().__init__()
        self.sizeHint()        
        self.setBackgroundRole(QtGui.QPalette.Base)     
        self.setAutoFillBackground(True)

        self._size = QtCore.QSizeF()
        self._path = QtGui.QPainterPath()
        self._rect = QtCore.QRectF()
        self._type = QtGui.QRegion.Rectangle
        self._factor = 1.0

        self._pos = QtCore.QPointF()
        self._initial_flag = False
        fnt = self.font() 
        fnt.setPointSize(20) 
        self.setFont(fnt) 

    def showEvent(self, event):
        if not self._initial_flag:
            self._pos = self.rect().center()
            self._initial_flag = True

    @QtCore.pyqtSlot(int,int)
    def set_size_squares(self, w, h):
        self._size = QtCore.QSizeF(w, h)
        self._type = QtGui.QRegion.Rectangle
        self.updatePath()

    @QtCore.pyqtSlot(int)
    def set_size_round(self, v):
        self._size = QtCore.QSizeF(v, v)
        self._type = QtGui.QRegion.Ellipse
        self.updatePath()

    def paintEvent(self, event):
        pen = QtGui.QPen()
        brush = QtGui.QBrush(QtCore.Qt.black)
        painter = QtGui.QPainter(self)
        painter.setRenderHint(QtGui.QPainter.Antialiasing)
        painter.setPen(pen)
        painter.setBrush(brush)

        painter.translate(self.rect().center())
        painter.scale(self._factor, self._factor)
        painter.translate(-self.rect().center())

        painter.translate(self._pos)
        painter.drawPath(self._path)
        if self._type == QtGui.QRegion.Rectangle:
            painter.fillRect(self._rect, QtGui.QBrush(QtCore.Qt.gray, QtCore.Qt.Dense7Pattern))
            painter.setBrush(QtGui.QBrush(QtCore.Qt.NoBrush))
            painter.drawRect(self._rect)
        elif self._type == QtGui.QRegion.Ellipse:
            painter.setBrush(QtGui.QBrush(QtCore.Qt.gray, QtCore.Qt.Dense7Pattern))
            painter.drawEllipse(self._rect)

    def mousePressEvent(self, event):
        QtWidgets.QApplication.setOverrideCursor(QtGui.QCursor(QtCore.Qt.OpenHandCursor))
        self._initial_pos = event.pos()
        super().mousePressEvent(event)

    def mouseMoveEvent(self, event):
        delta = event.pos() - self._initial_pos
        self._path.translate(delta)
        self._rect.translate(delta)
        self.update()
        self._initial_pos = event.pos()
        super().mouseMoveEvent(event)

    def mouseReleaseEvent(self, event):
        QtWidgets.QApplication.restoreOverrideCursor()
        super().mouseReleaseEvent(event)

    def updatePath(self):
        fm = QtGui.QFontMetrics(self.font())
        r = QtCore.QRectF(QtCore.QPointF(), self._size)
        r.moveCenter(QtCore.QPointF())
        self._rect = QtCore.QRectF(r)
        self._path.moveTo(QtCore.QPointF())
        p = QtCore.QPointF(self._size.width()/2 + 75 ,0)
        self._path.lineTo(p)
        self._path.lineTo(p + QtCore.QPoint(-16, -16))
        self._path.lineTo(p)
        self._path.moveTo(p)
        self._path.lineTo(p + QtCore.QPoint(-16, 16))
        self._path.lineTo(p)
        self._path.addText(p + QtCore.QPoint(16, 0) , self.font(), "x")

        self._path.moveTo(QtCore.QPointF())
        p = QtCore.QPointF(0, -self._size.height()/2 - 75)
        self._path.lineTo(p)
        self._path.lineTo(p + QtCore.QPoint(16, 16))
        self._path.lineTo(p)
        self._path.moveTo(p)
        self._path.lineTo(p + QtCore.QPoint(-16, 16))
        self._path.addText(p + QtCore.QPoint(0, -16) , self.font(), "y")

        if self._type == QtGui.QRegion.Rectangle:
            pl = r.bottomLeft() + QtCore.QPointF(0, 75)
            pr = r.bottomRight() + QtCore.QPointF(0, 75)
            self._path.moveTo(pl)
            self._path.lineTo(pr)
            for p in (pl, pr):
                self._path.moveTo(p+ QtCore.QPoint(0, -40))
                self._path.lineTo(p+ QtCore.QPoint(0, 20))
                self._path.moveTo(p+ QtCore.QPoint(10, -10))
                self._path.lineTo(p+ QtCore.QPoint(-10, 10))
            word = "{}".format(self._size.width())
            p = QtCore.QPointF(r.center().x() - 0.5*fm.width(word) , r.bottom() +  100)
            self._path.addText(p , self.font(), word)

            pt = r.topLeft() + QtCore.QPointF(-75, 0)
            pb = r.bottomLeft() + QtCore.QPointF(-75, 0)
            self._path.moveTo(pt)
            self._path.lineTo(pb)
            for p in (pt, pb):
                self._path.moveTo(p+ QtCore.QPoint(40, 0))
                self._path.lineTo(p+ QtCore.QPoint(-20, 0))
                self._path.moveTo(p+ QtCore.QPoint(10, -10))
                self._path.lineTo(p+ QtCore.QPoint(-10, 10))
            word = "{}".format(self._size.height())
            p = QtCore.QPointF(r.left() -80 - fm.width(word) , r.center().y() + 0.5*fm.height())
            self._path.addText(p , self.font(), word)
        if self._type == QtGui.QRegion.Ellipse:
            pl = r.bottomLeft() + QtCore.QPointF(0, 75)
            pr = r.bottomRight() + QtCore.QPointF(0, 75)
            self._path.moveTo(pl)
            self._path.lineTo(pr)
            for p in (pl, pr):
                self._path.moveTo(p+ QtCore.QPoint(0, -self._size.height()/2 - 20))
                self._path.lineTo(p+ QtCore.QPoint(0, 10))
                self._path.moveTo(p+ QtCore.QPoint(10, -10))
                self._path.lineTo(p+ QtCore.QPoint(-10, 10))
            word = "{}".format(self._size.width())
            p = QtCore.QPointF(r.center().x() - 0.5*fm.width(word) , r.bottom() +  100)
            self._path.addText(p , self.font(), word)
        self.update()

    def wheelEvent(self, event):
        self._factor *= 1.01**(event.angleDelta().y()/15.0)
        self.update()
        super().wheelEvent(event)

class Button(QtWidgets.QWidget):
    valueChanged = QtCore.pyqtSignal(int)
    valuesChanged = QtCore.pyqtSignal(int,int)
    def __init__(self, parent=None):
        super(Button, self).__init__(parent)
        roundbutton = QtWidgets.QPushButton('Round')
        squarebutton = QtWidgets.QPushButton('Square')
        Alay = QtWidgets.QVBoxLayout(self)
        Alay.addWidget(roundbutton)
        Alay.addWidget(squarebutton)
        self.value = QtWidgets.QLabel()
        roundbutton.clicked.connect(self.getbuttonfunc)
        squarebutton.clicked.connect(self.sqaurebuttonfunc)

    @QtCore.pyqtSlot()
    def getbuttonfunc(self):
        number, ok = QtWidgets.QInputDialog.getInt(self, self.tr("Set Number"),
                                         self.tr("Input:"), 1, 1)
        if ok:
            self.valueChanged.emit(number)

    @QtCore.pyqtSlot()
    def sqaurebuttonfunc(self):
        number, ok = QtWidgets.QInputDialog.getInt(self, self.tr("Set Number"),
                                         self.tr("Input:"), 1, 1)
        if ok:
            self.valuesChanged.emit(number, number)


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

【问题讨论】:

    标签: python pyqt pyqt5 qpainter pan


    【解决方案1】:

    要制作平底锅,您必须按照以下步骤操作:

    • 在mousePressEvent中获取初始位置

    • 在mouseMoveEvent中,用当前位置和初始位置的差值移动矩形,并用当前位置更新初始位置。

    实现如下:

    class Paint(QtWidgets.QWidget):
        def __init__(self, parent=None):
            super(Paint, self).__init__(parent)
            self.setBackgroundRole(QtGui.QPalette.Base)     
            self.setAutoFillBackground(True)
    
            self._width = 350
            self._height = 250
            self._rect = QtCore.QRect(QtCore.QPoint(), QtCore.QSize(self._width, self._height))
            self._initial_flag = False
            self._initial_pos = QtCore.QPoint()
    
        def showEvent(self, event):
            if not self._initial_flag:
                # set initial pos
                self._rect.moveCenter(self.rect().center())
                self._initial_flag = True
            super(Paint, self).showEvent(event)
    
        def paintEvent(self, event):
            painter = QtGui.QPainter(self)
            painter.setRenderHint(QtGui.QPainter.Antialiasing)
            painter.setBrush(QtGui.QBrush( QtCore.Qt.cyan))
            painter.setPen(QtCore.Qt.darkCyan)
            painter.drawRect(self._rect)
    
        def mousePressEvent(self, event):
            if self._rect.contains(event.pos()):
                QtWidgets.QApplication.setOverrideCursor(QtGui.QCursor(QtCore.Qt.OpenHandCursor))
                self._initial_pos = event.pos()
            super(Paint, self).mousePressEvent(event)
    
        def mouseMoveEvent(self, event):
            if self._rect.contains(event.pos()):
                delta = event.pos() - self._initial_pos
                self._rect.translate(delta)
                self.update()
                self._initial_pos = event.pos()
            super(Paint, self).mouseMoveEvent(event)
    
        def mouseReleaseEvent(self, event):
            QtWidgets.QApplication.restoreOverrideCursor()
            super(Paint, self).mouseReleaseEvent(event)
    

    但是 Qt 已经提供了类,这些类允许您以简单的方式完成您指出的任务,在这种情况下,它是使用 QGraphicsView 和 QGraphicsItem:

    class GraphicsRectItem(QtWidgets.QGraphicsRectItem):
        def mousePressEvent(self, event):
            QtWidgets.QApplication.setOverrideCursor(QtGui.QCursor(QtCore.Qt.OpenHandCursor))
            super(GraphicsRectItem, self).mousePressEvent(event)
    
        def mouseReleaseEvent(self, event):
            QtWidgets.QApplication.restoreOverrideCursor()
            super(GraphicsRectItem, self).mouseReleaseEvent(event)
    
    class Paint(QtWidgets.QGraphicsView):
        def __init__(self, parent=None):
            super(Paint, self).__init__(parent)
            self.setBackgroundRole(QtGui.QPalette.Base)     
            self.setAutoFillBackground(True)
    
            scene = QtWidgets.QGraphicsScene(self)
            self.setScene(scene)
            rect_item = GraphicsRectItem(0, 0, 350, 250)
            rect_item.setBrush(QtGui.QBrush( QtCore.Qt.cyan))
            rect_item.setPen(QtCore.Qt.darkCyan)
            rect_item.setFlag(QtWidgets.QGraphicsItem.ItemIsMovable)
            self.scene().addItem(rect_item)
    

    更新:

    class Paint(QtWidgets.QWidget):
        def __init__(self, parent=None):
            super(Paint, self).__init__(parent)
            self.setBackgroundRole(QtGui.QPalette.Base)     
            self.setAutoFillBackground(True)
    
            self._width = 350
            self._height = 250
            self._initial_flag = False
            self._initial_pos = QtCore.QPoint()
            self._pos = QtCore.QPoint()
    
        def showEvent(self, event):
            if not self._initial_flag:
                # set initial pos
                self._pos = self.rect().center()
                self._initial_flag = True
            super(Paint, self).showEvent(event)
    
        def paintEvent(self, event):
            painter = QtGui.QPainter(self)
            painter.setRenderHint(QtGui.QPainter.Antialiasing)
            painter.setBrush(QtGui.QBrush( QtCore.Qt.cyan))
            painter.setPen(QtCore.Qt.darkCyan)
            r = QtCore.QRect(QtCore.QPoint(), QtCore.QSize(self._width, self._height))
            r.moveCenter(self._pos)
            painter.drawRect(r)
    
        def mousePressEvent(self, event):
            r = QtCore.QRect(QtCore.QPoint(), QtCore.QSize(self._width, self._height))
            r.moveCenter(self._pos)
            if r.contains(event.pos()):
                QtWidgets.QApplication.setOverrideCursor(QtGui.QCursor(QtCore.Qt.OpenHandCursor))
                self._initial_pos = event.pos()
            super(Paint, self).mousePressEvent(event)
    
        def mouseMoveEvent(self, event):
            r = QtCore.QRect(QtCore.QPoint(), QtCore.QSize(self._width, self._height))
            r.moveCenter(self._pos)
            if r.contains(event.pos()):
                delta = event.pos() - self._initial_pos
                self._pos += delta 
                self._initial_pos = event.pos()
                self.update()
            super(Paint, self).mouseMoveEvent(event)
    
        def mouseReleaseEvent(self, event):
            QtWidgets.QApplication.restoreOverrideCursor()
            super(Paint, self).mouseReleaseEvent(event)
    

    更新:

    实现任务的一种方法是拥有一个元素或集合连接元素,在这种情况下它是一个 QPainterPath 加上一个 QRectF,第一个将绘制箭头、线条等,第二个将绘制矩形或中心圆。之后,根据情况移动这些元素就足够了。

    class Createpaintwidget(QtWidgets.QWidget):
        def __init__(self):
            super().__init__()
            self.sizeHint()        
            self.setBackgroundRole(QtGui.QPalette.Base)     
            self.setAutoFillBackground(True)
    
            self._size = QtCore.QSizeF()
            self._path = QtGui.QPainterPath()
            self._rect = QtCore.QRectF()
            self._type = QtGui.QRegion.Rectangle
            self._factor = 1.0
    
            self._pos = QtCore.QPointF()
            self._initial_flag = False
            fnt = self.font() 
            fnt.setPointSize(20) 
            self.setFont(fnt) 
    
        def showEvent(self, event):
            if not self._initial_flag:
                self._pos = self.rect().center()
                self._initial_flag = True
    
        @QtCore.pyqtSlot(int, int)
        def set_size_squares(self, w, h):
            self._path = QtGui.QPainterPath()
            self._size = QtCore.QSizeF(w, h)
            self._type = QtGui.QRegion.Rectangle
            self.updatePath()
    
        @QtCore.pyqtSlot(int)
        def set_size_round(self, v):
            self._path = QtGui.QPainterPath()
            self._size = QtCore.QSizeF(v, v)
            self._type = QtGui.QRegion.Ellipse
            self.updatePath()
    
        def paintEvent(self, event):
            pen = QtGui.QPen()
            brush = QtGui.QBrush(QtCore.Qt.black)
            painter = QtGui.QPainter(self)
            painter.setRenderHint(QtGui.QPainter.Antialiasing)
            painter.setPen(pen)
            painter.setBrush(brush)
    
            painter.translate(self.rect().center())
            painter.scale(self._factor, self._factor)
            painter.translate(-self.rect().center())
    
            painter.translate(self._pos)
            painter.drawPath(self._path)
            if self._type == QtGui.QRegion.Rectangle:
                painter.fillRect(self._rect, QtGui.QBrush(QtCore.Qt.gray, QtCore.Qt.Dense7Pattern))
                painter.setBrush(QtGui.QBrush(QtCore.Qt.NoBrush))
                painter.drawRect(self._rect)
            elif self._type == QtGui.QRegion.Ellipse:
                painter.setBrush(QtGui.QBrush(QtCore.Qt.gray, QtCore.Qt.Dense7Pattern))
                painter.drawEllipse(self._rect)
    
        def mousePressEvent(self, event):
            QtWidgets.QApplication.setOverrideCursor(QtGui.QCursor(QtCore.Qt.OpenHandCursor))
            self._initial_pos = event.pos()
            super().mousePressEvent(event)
    
        def mouseMoveEvent(self, event):
            delta = event.pos() - self._initial_pos
            self._path.translate(delta)
            self._rect.translate(delta)
            self.update()
            self._initial_pos = event.pos()
            super().mouseMoveEvent(event)
    
        def mouseReleaseEvent(self, event):
            QtWidgets.QApplication.restoreOverrideCursor()
            super().mouseReleaseEvent(event)
    
        def updatePath(self):
            fm = QtGui.QFontMetrics(self.font())
            r = QtCore.QRectF(QtCore.QPointF(), self._size)
            r.moveCenter(QtCore.QPointF())
            self._rect = QtCore.QRectF(r)
            self._path.moveTo(QtCore.QPointF())
            p = QtCore.QPointF(self._size.width()/2 + 75 ,0)
            self._path.lineTo(p)
            self._path.lineTo(p + QtCore.QPoint(-16, -16))
            self._path.lineTo(p)
            self._path.moveTo(p)
            self._path.lineTo(p + QtCore.QPoint(-16, 16))
            self._path.lineTo(p)
            self._path.addText(p + QtCore.QPoint(16, 0) , self.font(), "x")
    
            self._path.moveTo(QtCore.QPointF())
            p = QtCore.QPointF(0, -self._size.height()/2 - 75)
            self._path.lineTo(p)
            self._path.lineTo(p + QtCore.QPoint(16, 16))
            self._path.lineTo(p)
            self._path.moveTo(p)
            self._path.lineTo(p + QtCore.QPoint(-16, 16))
            self._path.addText(p + QtCore.QPoint(0, -16) , self.font(), "y")
    
            if self._type == QtGui.QRegion.Rectangle:
                pl = r.bottomLeft() + QtCore.QPointF(0, 75)
                pr = r.bottomRight() + QtCore.QPointF(0, 75)
                self._path.moveTo(pl)
                self._path.lineTo(pr)
                for p in (pl, pr):
                    self._path.moveTo(p+ QtCore.QPoint(0, -40))
                    self._path.lineTo(p+ QtCore.QPoint(0, 20))
                    self._path.moveTo(p+ QtCore.QPoint(10, -10))
                    self._path.lineTo(p+ QtCore.QPoint(-10, 10))
                word = "{}".format(self._size.width())
                p = QtCore.QPointF(r.center().x() - 0.5*fm.width(word) , r.bottom() +  100)
                self._path.addText(p , self.font(), word)
    
                pt = r.topLeft() + QtCore.QPointF(-75, 0)
                pb = r.bottomLeft() + QtCore.QPointF(-75, 0)
                self._path.moveTo(pt)
                self._path.lineTo(pb)
                for p in (pt, pb):
                    self._path.moveTo(p+ QtCore.QPoint(40, 0))
                    self._path.lineTo(p+ QtCore.QPoint(-20, 0))
                    self._path.moveTo(p+ QtCore.QPoint(10, -10))
                    self._path.lineTo(p+ QtCore.QPoint(-10, 10))
                word = "{}".format(self._size.height())
                p = QtCore.QPointF(r.left() -80 - fm.width(word) , r.center().y() + 0.5*fm.height())
                self._path.addText(p , self.font(), word)
            if self._type == QtGui.QRegion.Ellipse:
                pl = r.bottomLeft() + QtCore.QPointF(0, 75)
                pr = r.bottomRight() + QtCore.QPointF(0, 75)
                self._path.moveTo(pl)
                self._path.lineTo(pr)
                for p in (pl, pr):
                    self._path.moveTo(p+ QtCore.QPoint(0, -self._size.height()/2 - 20))
                    self._path.lineTo(p+ QtCore.QPoint(0, 10))
                    self._path.moveTo(p+ QtCore.QPoint(10, -10))
                    self._path.lineTo(p+ QtCore.QPoint(-10, 10))
                word = "{}".format(self._size.width())
                p = QtCore.QPointF(r.center().x() - 0.5*fm.width(word) , r.bottom() +  100)
                self._path.addText(p , self.font(), word)
            self.update()
    
        def wheelEvent(self, event):
            self._factor *= 1.01**(event.angleDelta().y()/15.0)
            self.update()
            super().wheelEvent(event)
    

    【讨论】:

    • 太好了,你确实很聪明。谢谢。我注意到当QGraphicsView 运行并出现自动滚动时,但Qpainter 使用时不是这种情况,可以滚动添加到Qpainter 吗?但是QPainterQGraphicsView 之间有什么区别。我看到QGraphicsView被广泛使用了吗?
    • @ZarKha QGraphicsView 是一个专门处理内部使用 QPainter 的项目的小部件,也就是说,它是一种高级绘画方式。出现滚动条是因为 QGraphiscView 旨在处理可能在屏幕上较大位置的项目,它可以被禁用。总之,QPainter 处于石器时代,它是手动绘制的,而使用 QGraphicsView,您使用的是 MS 油漆或类似工具
    • @ZarKha 如果我的回答对您有帮助,请不要忘记将其标记为正确,如果您不知道该怎么做,请查看tour,这是最好的感谢方式。跨度>
    • 根据你的回答我的最后一个问题,我有这个代码行self._rect = within def paint,在你的回答中你把它带到了函数之外,这确实给出了我在我的私人代码。活动打不开!这是一种将其保留在函数内部的方法吗?我真的需要解决这个问题吗?
    • @ZarKha 当然在旧答案中不要指出矩形是可移动的,因此矩形是固定的并在paintEvent中使用,但在这种情况下是动态的,尽管我认为它可以保持以前的逻辑,一次我会给出一个替代方案。另一方面,我不知道您的私有代码的实现,所以我不会想,因为您可能还有其他错误。
    猜你喜欢
    • 2023-01-22
    • 2019-04-02
    • 2019-04-03
    • 2013-04-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-05-17
    相关资源
    最近更新 更多