I have created a paint widget, that I want to implement a pan event with hand icon tool, which usually sees in many softwares. it is when user press on mouse button and hold it then move over within Qpainter canvas
, the drawing follows the mouse movement. I can't find out how to do it in PyQt5.
VISUAL EXAMPLE
The current screen:
The desired screen event:
The Code:
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_())
I appreciate any help and thanks in advance.
Update of code:
Visualization:
Well,that's what happens when I implement the updated code in my central code. it is when user clicks and picks section from circular then plots within the widget, right after click and select another section from rectangular. The widget should update and erase previous drawing. But it does't do that. In my previous code the section drawing appears directly in the center of widget, now it sticks to upperleft edge of canvas.
A part of code:
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_())
To make the pan you must follow the following steps:
Get the initial position in mousePressEvent
In mouseMoveEvent move the rectangle with the difference between the current position and the initial position, and update the initial position with the current position.
The implementation is as follows:
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)
But instead of doing it Qt already offers classes that allow you to do the tasks that you point out in a simple way, in this case it is to use QGraphicsView with the 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)
UPDATE:
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)
Update:
One way to implement the task is to have an element or set connected element, in this case it is a QPainterPath plus a QRectF, the first will draw the arrows, lines, etc. and the second the rectangle or central circle. After that, it is enough to move those elements according to the case.
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)