Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 2 years ago.
Improve this question
I am trying to implement a function that will join two circles(with a line from center to center), by creating a list of lines for every pair of the circles that have to be joined. There was some error but when I passed two objects in circles list and corresponding center points to the lines list it worked.
Could someone help to solve this issue to create a function that will join the circles when join button is clicked and two circles are selected by clicking
Below is the code, which when runs have two circles with a line and a functionlity to add more circles by clicking "Add" button. Or am I taking a wrong technique, Please suggest if anything is much simpler. Struggling for a long time... I am attaching a screenshot.
import random
import sys
from PyQt5.QtWidgets import QMenu
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtCore import QRect, QSize, QPoint, QLineF
class Window(QtWidgets.QMainWindow):
def __init__(self):
super(Window, self).__init__()
self.rect = QtCore.QRect()
self.drag_position = QtCore.QPoint()
self.circles = [QRect(100, 200, 100, 100), QRect(100, 300, 100, 100)]
self.lines = []
self.a = [0, 0, 500, 400]
self.current_circle = None
button = QtWidgets.QPushButton("Add", self)
button.clicked.connect(self.on_clicked)
join = QtWidgets.QPushButton("Join", self)
join.clicked.connect(self.joinAction)
join.setGeometry(100, 0, 100, 30)
Delete = QtWidgets.QPushButton("Delete", self)
Delete.clicked.connect(self.DeleteItem)
Delete.setGeometry(200, 0, 100, 30)
self.resize(640, 480)
def on_clicked(self):
coor = QPoint(random.randrange(self.width() - 100), random.randrange(self.height()) - 100)
self.circles.append(QRect(coor, QSize(100, 100)))
self.update()
def joinAction(self, event):
pass
contextMenu = QMenu(self)
delAct = contextMenu.addAction("Delete Circle")
action = contextMenu.exec_(self.mapToGlobal(event.pos()))
if action == delAct:
pass
def DeleteItem(self):
pass
def paintEvent(self, event):
super().paintEvent(event)
painter = QtGui.QPainter(self)
painter.setRenderHint(QtGui.QPainter.Antialiasing)
painter.setPen(QtGui.QPen(QtCore.Qt.black, 5, QtCore.Qt.SolidLine))
for circle in self.circles:
painter.drawEllipse(circle)
# for line in self.lines:
painter.drawLine(self.circles[0].center(), self.circles[1].center())
def mousePressEvent(self, event):
for circle in self.circles:
line = QLineF(circle.center(), event.pos())
if line.length() < circle.width() / 2:
self.current_circle = circle
self.drag_position = event.pos()
break
def mouseMoveEvent(self, event):
if self.current_circle is not None:
self.current_circle.translate(event.pos() - self.drag_position)
self.drag_position = event.pos()
self.update()
def mouseReleaseEvent(self, event):
self.current_circle = None
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
Rect = Window()
Rect.show()
sys.exit(app.exec_())
Here is one way to do it. I defined a Circle class with the attributes line_to and line_from to keep track of which circles are connected. A list is kept in the Window class, last_two_clicked, so when the join button is clicked, the two most recent circles that were pressed will be joined.
class Circle(QRect):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.line_to = self.line_from = None
def join(self, other):
self.line_to = other
other.line_from = self
if self.line_from and self.line_from.line_to == self:
self.line_from.line_to = None
self.line_from = other.line_to = None
class Window(QtWidgets.QMainWindow):
def __init__(self):
super(Window, self).__init__()
self.rect = QtCore.QRect()
self.drag_position = QtCore.QPoint()
self.circles = [Circle(100, 200, 100, 100), Circle(100, 300, 100, 100)]
self.current_circle = None
self.last_two_clicked = self.circles[:]
button = QtWidgets.QPushButton("Add", self)
button.clicked.connect(self.on_clicked)
join = QtWidgets.QPushButton("Join", self)
join.clicked.connect(self.joinAction)
join.setGeometry(100, 0, 100, 30)
Delete = QtWidgets.QPushButton("Delete", self)
Delete.clicked.connect(self.DeleteItem)
Delete.setGeometry(200, 0, 100, 30)
self.resize(640, 480)
def on_clicked(self):
coor = (random.randrange(self.width() - 100), random.randrange(self.height() - 100))
c = Circle(*coor, 100, 100)
self.circles.append(c)
self.last_two_clicked.insert(0, c)
self.last_two_clicked = self.last_two_clicked[:2]
self.update()
def joinAction(self, event):
c1, c2 = self.last_two_clicked
c1.join(c2)
self.update()
def DeleteItem(self):
pass
def paintEvent(self, event):
super().paintEvent(event)
painter = QtGui.QPainter(self)
painter.setRenderHint(QtGui.QPainter.Antialiasing)
painter.setPen(QtGui.QPen(QtCore.Qt.black, 5, QtCore.Qt.SolidLine))
for circle in self.circles:
painter.drawEllipse(circle)
if circle.line_to:
painter.drawLine(circle.center(), circle.line_to.center())
def mousePressEvent(self, event):
for circle in self.circles:
line = QLineF(circle.center(), event.pos())
if line.length() < circle.width() / 2:
self.current_circle = circle
self.drag_position = event.pos()
self.last_two_clicked.insert(0, circle)
self.last_two_clicked = self.last_two_clicked[:2]
break
def mouseMoveEvent(self, event):
if self.current_circle is not None:
self.current_circle.translate(event.pos() - self.drag_position)
self.drag_position = event.pos()
self.update()
def mouseReleaseEvent(self, event):
self.current_circle = None
Related
I am attempting to design a label class that inherits from the PyQt5 base QLabel class that is able to track another widget. Here is the current code for my class:
class AttachedLabel(QLabel):
def __init__(self, attachedTo, *args, side="left", ** kwargs):
super().__init__(*args, **kwargs) # Run parent initialization
# Define instance variables
self.attached = attachedTo
self.side = side
# Update label position
self.updatePos()
def updatePos(self):
# Get "attached widget" position and dimensions
x = self.attached.geometry().x()
y = self.attached.geometry().y()
aWidth = self.attached.geometry().width()
aHeight = self.attached.geometry().height()
# Get own dimensions
width = self.geometry().width()
height = self.geometry().height()
if self.side == "top": # Above of attached widget
self.setGeometry(x, y-height, width, height)
elif self.side == "bottom": # Below attached widget
self.setGeometry(x, y+height+aHeight, width, height)
elif self.side == "right": # Right of attached widget
self.setGeometry(x + width + aWidth, y, width, height)
else: # Left of attached widget
self.setGeometry(x - width, y, width, height)
I want to be able to instantiate the label like so:
AttachedLabel(self.pushButton, self.centralwidget)
where self.pushButton is the widget it is supposed to be following. The issue is that I don't know how to detect when the widget moves in order to run my updatePos() function. I would ideally only update the label position when the other widget moves, but I want to refrain from havign to add extra code to the class of the widget that is being tracked. I have tried overriding the paintEvent, but that only triggers when the object itself needs to be redrawn, so it doesn't even function as a sub-optimal solution.
Is there some built-in method I can use/override to detect when the widget moves or when the screen itself is updated?
You have to use an eventFilter intersecting the QEvent::Move event and you should also track the resize through the QEvent::Resize event.
from dataclasses import dataclass, field
import random
from PyQt5 import QtCore, QtWidgets
class GeometryTracker(QtCore.QObject):
geometryChanged = QtCore.pyqtSignal()
def __init__(self, widget):
super().__init__(widget)
self._widget = widget
self.widget.installEventFilter(self)
#property
def widget(self):
return self._widget
def eventFilter(self, source, event):
if self.widget is source and event.type() in (
QtCore.QEvent.Move,
QtCore.QEvent.Resize,
):
self.geometryChanged.emit()
return super().eventFilter(source, event)
#dataclass
class TrackerManager:
widget1: field(default_factory=QtWidgets.QWidget)
widget2: field(default_factory=QtWidgets.QWidget)
alignment: QtCore.Qt.Alignment = QtCore.Qt.AlignLeft
enabled: bool = True
valid_alignments = (
QtCore.Qt.AlignLeft,
QtCore.Qt.AlignRight,
QtCore.Qt.AlignHCenter,
QtCore.Qt.AlignTop,
QtCore.Qt.AlignBottom,
QtCore.Qt.AlignVCenter,
)
def __post_init__(self):
self._traker = GeometryTracker(self.widget1)
self._traker.geometryChanged.connect(self.update)
if not any(self.alignment & flag for flag in self.valid_alignments):
raise ValueError("alignment is not valid")
def update(self):
if not self.enabled:
return
r = self.widget1.rect()
p1 = r.center()
c1 = r.center()
if self.alignment & QtCore.Qt.AlignLeft:
p1.setX(r.left())
if self.alignment & QtCore.Qt.AlignRight:
p1.setX(r.right())
if self.alignment & QtCore.Qt.AlignTop:
p1.setY(r.top())
if self.alignment & QtCore.Qt.AlignBottom:
p1.setY(r.bottom())
p2 = self.convert_position(p1)
c2 = self.convert_position(c1)
g = self.widget2.geometry()
g.moveCenter(c2)
if self.alignment & QtCore.Qt.AlignLeft:
g.moveRight(p2.x())
if self.alignment & QtCore.Qt.AlignRight:
g.moveLeft(p2.x())
if self.alignment & QtCore.Qt.AlignTop:
g.moveBottom(p2.y())
if self.alignment & QtCore.Qt.AlignBottom:
g.moveTop(p2.y())
self.widget2.setGeometry(g)
def convert_position(self, point):
gp = self.widget1.mapToGlobal(point)
if self.widget2.isWindow():
return gp
return self.widget2.parent().mapFromGlobal(gp)
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super().__init__(parent)
self.button = QtWidgets.QPushButton("Press me", self)
self.label = QtWidgets.QLabel(
"Tracker\nLabel", self, alignment=QtCore.Qt.AlignCenter
)
self.label.setAttribute(QtCore.Qt.WA_TransparentForMouseEvents, True)
self.label.setFixedSize(200, 200)
self.label.setStyleSheet(
"background-color: salmon; border: 1px solid black; font-size: 40pt;"
)
self.resize(640, 480)
self.manager = TrackerManager(
widget1=self.button,
widget2=self.label,
alignment=QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter,
)
self.move_button()
def move_button(self):
pos = QtCore.QPoint(*random.sample(range(400), 2))
animation = QtCore.QPropertyAnimation(
targetObject=self.button,
parent=self,
propertyName=b"pos",
duration=1000,
startValue=self.button.pos(),
endValue=pos,
)
animation.finished.connect(self.move_button)
animation.start(QtCore.QAbstractAnimation.DeleteWhenStopped)
def main():
import sys
app = QtWidgets.QApplication(sys.argv)
w = MainWindow()
w.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()
I want to display the x and y coordinates from an accelerometer on an image. I used QPainter to set up the image and the drawPoint function to draw the coordinate at the given point. When the new coordinate is drawn, I need to erase the old coordinate so that there is only one at a time. In the code below, I called drawCoordinate twice, what happens is the target image ends up with 2 coordinate points at 0,0 and 0.5,0.5 but ideally I would be left with the last drawn coordinate.
This is my first question on here so I hope my format is correct! Let me know if I need to fix anything to help with clarity.
class Target(QWidget):
def __init__(self):
super().__init__()
self.drawing = False
self.image = QPixmap(r"Pictures\target_png_300.png")
self.setGeometry(0, 0, 300, 300)
self.resize(self.image.width(), self.image.height())
self.show()
def paintEvent(self, event):
painter = QPainter(self)
painter.drawPixmap(self.rect(), self.image)
def paintCoordinate(self, x, y):
painter = QPainter(self.image)
r = QRect(-1, -1, 2, 2)
painter.setWindow(r)
pen = QPen(Qt.black, 0.06, Qt.DotLine, Qt.RoundCap)
painter.setPen(pen)
painter.drawPoint(QPointF(x, y))
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = Target()
ex.paintCoordinate(0, 0)
ex.paintCoordinate(0.5, 0.5)
sys.exit(app.exec_())
If you want only one point then don't modify the QPixmap but just do the painting in the paintEvent method:
class Target(QWidget):
def __init__(self):
super().__init__()
self._pixmap = QPixmap()
self._coordinate = QPointF()
#property
def pixmap(self):
return self._pixmap
#pixmap.setter
def pixmap(self, pixmap):
self._pixmap = pixmap.copy()
self.update()
size = self.pixmap.size()
if size.isValid():
self.resize(size)
else:
self.resize(300, 300)
#property
def coordinate(self):
return self._coordinate
#coordinate.setter
def coordinate(self, point):
self._coordinate = point
self.update()
def paintEvent(self, event):
painter = QPainter(self)
painter.drawPixmap(self.rect(), self.pixmap)
r = QRect(-1, -1, 2, 2)
painter.setWindow(r)
pen = QPen(Qt.black, 0.06, Qt.DotLine, Qt.RoundCap)
painter.setPen(pen)
painter.drawPoint(self.coordinate)
if __name__ == "__main__":
app = QApplication(sys.argv)
ex = Target()
ex.pixmap = QPixmap(r"Pictures\target_png_300.png")
ex.coordinate = QPointF(0, 0)
ex.coordinate = QPointF(0.5, 0.5)
ex.show()
sys.exit(app.exec_())
Drawing on a raster image means overriding its contents, there's no "erase" nor "undo": it's like using a brush on a painting: if you try to "erase" it's like using bleach on what you're trying to "unpaint".
Keep track of what you're manually painting (the coordinates) and then implement the paintEvent accordingly.
class Target(QWidget):
def __init__(self):
super().__init__()
self.drawing = False
self.image = QPixmap(r"Pictures\target_png_300.png")
self.setGeometry(0, 0, 300, 300)
self.resize(self.image.width(), self.image.height())
self.points = []
self.show()
def paintEvent(self, event):
painter = QPainter(self)
painter.drawPixmap(self.rect(), self.image)
r = QRect(-1, -1, 2, 2)
painter.setWindow(r)
painter.setPen(QPen(Qt.black, 0.06, Qt.DotLine, Qt.RoundCap))
for point in self.points:
painter.drawPoint(point)
def paintCoordinate(self, x, y):
self.points.append(QPointF(x, y))
self.update()
def deleteLastPoint(self):
self.points.pop()
self.update()
if __name__ == '__main__':
import sys
app = QApplication(sys.argv)
ex = Target()
ex.paintCoordinate(0, 0)
ex.paintCoordinate(0.5, 0.5)
QTimer.singleShot(1000, lambda: ex.deleteLastPoint())
sys.exit(app.exec_())
I have created a custom QGraphicsWidget with the ability to resize the widget in the scene. I can also add predefined widgets such as buttons, labels, etc. to my custom widget. I now have two problems.
The first being that the widget doesn't change the size (to re-adjust) upon inserting a new label or LineEdit widget as a result newly inserted widget stays out of the custom widget border.
The second problem is encountered when I try to change the setContentMargins of the QGraphicsLayout to something other than 0. For example QGraphicsLayout.setContentMargins(1, 1, 1, 20) will delay the cursor in the LineEdit widget.
Here is the image.
(Drag the grey triangle to change size)
import sys
from PyQt5 import QtWidgets, QtCore, QtGui, Qt
from PyQt5.QtCore import Qt, QRectF, QPointF
from PyQt5.QtGui import QBrush, QPainterPath, QPainter, QColor, QPen, QPixmap
from PyQt5.QtWidgets import QGraphicsRectItem, QApplication, QGraphicsView, QGraphicsScene, QGraphicsItem
class Container(QtWidgets.QWidget):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.layout = QtWidgets.QVBoxLayout(self)
self.layout.setContentsMargins(0, 0, 0, 0)
self.layout.setSpacing(0)
self.setStyleSheet('Container{background:transparent;}')
class GraphicsFrame(QtWidgets.QGraphicsWidget):
def __init__(self, *args, **kwargs):
super(GraphicsFrame, self).__init__()
x, y, h, w = args
rect = QRectF(x, y, h, w)
self.setGeometry(rect)
self.setMinimumSize(150, 150)
self.setMaximumSize(400, 800)
self.setAcceptHoverEvents(True)
self.setFlag(QGraphicsItem.ItemIsMovable, True)
self.setFlag(QGraphicsItem.ItemIsSelectable, True)
self.setFlag(QGraphicsItem.ItemSendsGeometryChanges, True)
self.setFlag(QGraphicsItem.ItemIsFocusable, True)
self.mousePressPos = None
self.mousePressRect = None
self.handleSelected = None
self.polygon = QtGui.QPolygon([
QtCore.QPoint(int(self.rect().width()-10), int(self.rect().height()-20)),
QtCore.QPoint(int(self.rect().width()-10), int(self.rect().height()-10)),
QtCore.QPoint(int(self.rect().width()-20), int(self.rect().height()-10))
])
graphic_layout = QtWidgets.QGraphicsLinearLayout(Qt.Vertical, self)
graphic_layout.setContentsMargins(0, 0, 0, 20) # changing this will cause the second problem
self.container = Container()
proxyWidget = QtWidgets.QGraphicsProxyWidget(self)
proxyWidget.setWidget(self.container)
graphic_layout.addItem(proxyWidget)
self.contentLayout = QtWidgets.QFormLayout()
self.contentLayout.setContentsMargins(10, 10, 20, 20)
self.contentLayout.setSpacing(5)
self.container.layout.addLayout(self.contentLayout)
self.options = []
def addOption(self, color=Qt.white, lbl=None, widget=None):
self.insertOption(-1, lbl, widget, color)
def insertOption(self, index, lbl, widget, color=Qt.white):
if index < 0:
index = self.contentLayout.count()
self.contentLayout.addRow(lbl, widget)
self.options.insert(index, (widget, color))
def update_polygon(self):
self.polygon = QtGui.QPolygon([
QtCore.QPoint(int(self.rect().width() - 10), int(self.rect().height() - 20)),
QtCore.QPoint(int(self.rect().width() - 10), int(self.rect().height() - 10)),
QtCore.QPoint(int(self.rect().width() - 20), int(self.rect().height() - 10))
])
def hoverMoveEvent(self, event):
if self.polygon.containsPoint(event.pos().toPoint(), Qt.OddEvenFill):
self.setCursor(Qt.SizeFDiagCursor)
else:
self.unsetCursor()
super(GraphicsFrame, self).hoverMoveEvent(event)
def mousePressEvent(self, event):
self.handleSelected = self.polygon.containsPoint(event.pos().toPoint(), Qt.OddEvenFill)
if self.handleSelected:
self.mousePressPos = event.pos()
self.mousePressRect = self.boundingRect()
super(GraphicsFrame, self).mousePressEvent(event)
def mouseMoveEvent(self, event):
if self.handleSelected:
self.Resize(event.pos())
else:
super(GraphicsFrame, self).mouseMoveEvent(event)
def mouseReleaseEvent(self, event):
super(GraphicsFrame, self).mouseReleaseEvent(event)
self.handleSelected = False
self.mousePressPos = None
self.mousePressRect = None
self.update()
def paint(self, painter, option, widget):
painter.save()
painter.setBrush(QBrush(QColor(37, 181, 247)))
pen = QPen(Qt.white)
pen.setWidth(2)
if self.isSelected():
pen.setColor(Qt.yellow)
painter.setPen(pen)
painter.drawRoundedRect(self.rect(), 4, 4)
painter.setPen(QtCore.Qt.white)
painter.setBrush(QtCore.Qt.gray)
painter.drawPolygon(self.polygon)
super().paint(painter, option, widget)
painter.restore()
def Resize(self, mousePos):
"""
Perform shape interactive resize.
"""
if self.handleSelected:
self.prepareGeometryChange()
width, height = self.geometry().width()+(mousePos.x()-self.mousePressPos.x()),\
self.geometry().height()+(mousePos.y()-self.mousePressPos.y())
self.setGeometry(QRectF(self.geometry().x(), self.geometry().y(), width, height))
self.contentLayout.setGeometry(QtCore.QRect(0, 30, width-10, height-20))
self.mousePressPos = mousePos
self.update_polygon()
self.updateGeometry()
def main():
app = QApplication(sys.argv)
grview = QGraphicsView()
scene = QGraphicsScene()
grview.setViewportUpdateMode(grview.FullViewportUpdate)
scene.addPixmap(QPixmap('01.png'))
grview.setScene(scene)
item = GraphicsFrame(0, 0, 300, 150)
scene.addItem(item)
item.addOption(Qt.green, lbl=QtWidgets.QLabel('I am a label'), widget=QtWidgets.QLineEdit())
item.addOption(lbl=QtWidgets.QLabel('why'), widget=QtWidgets.QLineEdit())
item.addOption(lbl=QtWidgets.QLabel('How'), widget=QtWidgets.QLineEdit())
item.addOption(lbl=QtWidgets.QLabel('Nooo.'), widget=QtWidgets.QLineEdit())
item.addOption(lbl=QtWidgets.QLabel('Nooo.'), widget=QtWidgets.QLineEdit())
item.addOption(lbl=QtWidgets.QLabel('Nooo.'), widget=QtWidgets.QLineEdit())
item2 = GraphicsFrame(50, 50, 300, 150)
scene.addItem(item2)
grview.fitInView(scene.sceneRect(), Qt.KeepAspectRatio)
grview.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
As already suggested to you more than once, using a QGraphicsWidget with a QGraphicsLayout is not a good idea if you are only using it to embed a QGraphicsProxyWidget, as you will certainly have to face unexpected behavior when changing geometries unless you really know what you're doing.
Then, prepareGeometryChange and updateGeometry are completely unnecessary for QGraphicsWidget, and resizing the widget using the item geometries is absolutely wrong for two reasons: first of all, it's up the graphics layout to manage the content size, then you're using scene coordinates, and since you're using scaling, those coordinates will not be correct as they should be transformed in widget's coordinate.
Since using a QSizeGrip is not doable due to the continuously changing scene rect (which, I have to say, is not always a good idea if done along with interactive resizing of contents), you can use a simple QGraphicsPathItem for it, and use that as a reference for the resizing, which is far more simple than continuously move the polygon and draw it.
class SizeGrip(QtWidgets.QGraphicsPathItem):
def __init__(self, parent):
super().__init__(parent)
path = QtGui.QPainterPath()
path.moveTo(0, 10)
path.lineTo(10, 10)
path.lineTo(10, 0)
path.closeSubpath()
self.setPath(path)
self.setPen(QtGui.QPen(Qt.white))
self.setBrush(QtGui.QBrush(Qt.white))
self.setCursor(Qt.SizeFDiagCursor)
class GraphicsFrame(QtWidgets.QGraphicsItem):
def __init__(self, *args, **kwargs):
super(GraphicsFrame, self).__init__()
x, y, w, h = args
self.setPos(x, y)
self.setAcceptHoverEvents(True)
self.setFlag(QGraphicsItem.ItemIsMovable, True)
self.setFlag(QGraphicsItem.ItemIsSelectable, True)
self.setFlag(QGraphicsItem.ItemSendsGeometryChanges, True)
self.setFlag(QGraphicsItem.ItemIsFocusable, True)
self.container = Container()
self.proxy = QtWidgets.QGraphicsProxyWidget(self)
self.proxy.setWidget(self.container)
self.proxy.setMinimumSize(150, 150)
self.proxy.setMaximumSize(400, 800)
self.proxy.resize(w, h)
self.contentLayout = QtWidgets.QFormLayout()
self.contentLayout.setContentsMargins(10, 10, 20, 20)
self.contentLayout.setSpacing(5)
self.container.layout.addLayout(self.contentLayout)
self.options = []
self.sizeGrip = SizeGrip(self)
self.mousePressPos = None
self.proxy.geometryChanged.connect(self.resized)
self.resized()
def addOption(self, color=Qt.white, lbl=None, widget=None):
self.insertOption(-1, lbl, widget, color)
def insertOption(self, index, lbl, widget, color=Qt.white):
if index < 0:
index = self.contentLayout.count()
self.contentLayout.addRow(lbl, widget)
self.options.insert(index, (widget, color))
def mousePressEvent(self, event):
gripShape = self.sizeGrip.shape().translated(self.sizeGrip.pos())
if event.button() == Qt.LeftButton and gripShape.contains(event.pos()):
self.mousePressPos = event.pos()
else:
super().mousePressEvent(event)
def mouseMoveEvent(self, event):
if self.mousePressPos:
delta = event.pos() - self.mousePressPos
geo = self.proxy.geometry()
bottomRight = geo.bottomRight()
geo.setBottomRight(bottomRight + delta)
self.proxy.setGeometry(geo)
diff = self.proxy.geometry().bottomRight() - bottomRight
if diff.x():
self.mousePressPos.setX(event.pos().x())
if diff.y():
self.mousePressPos.setY(event.pos().y())
else:
super().mouseMoveEvent(event)
def mouseReleaseEvent(self, event):
self.mousePressPos = None
super().mouseReleaseEvent(event)
def resized(self):
rect = self.boundingRect()
self.sizeGrip.setPos(rect.bottomRight() + QtCore.QPointF(-20, -20))
def boundingRect(self):
return self.proxy.boundingRect().adjusted(-11, -11, 11, 11)
def paint(self, painter, option, widget):
painter.save()
painter.setBrush(QBrush(QColor(37, 181, 247)))
painter.drawRoundedRect(self.boundingRect().adjusted(0, 0, -.5, -.5), 4, 4)
painter.restore()
Do note that using fitInView() before showing the view is not a good idea, especially if using proxy widgets and layouts.
I want to make cropping image tool by using python and pyqt5
Below code, I can't use the mouseMoveEvent and mouseReleaseEvent on
the graphic view.
But, I only can use the mousePressEvent on the graphicview. I checked the interactive about QGraphicView properties.
import sys
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from PIL import Image
from PyQt5 import uic
QApplication.setAttribute(Qt.AA_EnableHighDpiScaling, True)
IS_RESULT = False
CROP_UI = uic.loadUiType("Image_crop_screen.ui")[0]
BRING_IN_IMG_ROUTE = "C:/Users/yoon/Desktop/test/test.jpg"
class MainScreen(QMainWindow, CROP_UI):
def __init__(self):
super().__init__()
self.setupUi(self)
# Graphic Screen set
self.img = QGraphicsPixmapItem(QPixmap(BRING_IN_IMG_ROUTE))
self.scene = QGraphicsScene()
self.scene.addItem(self.img)
self.graphicsView.setScene(self.scene)
self.graphicsView.pencolor = QColor(240, 240, 240)
self.graphicsView.brushcolor = QColor(255, 255, 255, 0)
QGV = QGraphicsView()
self.items = []
# Full Screen set size
_WIDTH_ADD = 25
_HEIGHT_ADD = 25
self.setGeometry(0, 0, 640 + _WIDTH_ADD, 500 + _HEIGHT_ADD)
def moveEvent(self, e):
rect = QRectF(self.rect())
rect.adjust(0, 0, 0, 0) # 창 스크롤 바 없애기 위해서 일부 크기 작게 설정
self.scene.setSceneRect(rect)
# Draw rectangular while moving mouse
def mouseMoveEvent(self, e):
# e.buttons()는 정수형 값을 리턴, e.button()은 move시 Qt.Nobutton 리턴
print(Qt.LeftButton)
print(e.buttons())
print(e)
if e.buttons() & Qt.LeftButton:
self.end = e.pos()
pen = QPen(self.parent().pencolor)
brush = QBrush(self.parent().brushcolor)
pen.setWidth(3)
# 장면에 그려진 이전 선을 제거
if len(self.items) > 0:
self.scene.removeItem(self.items[-1])
del (self.items[-1])
rect = QRectF(self.start, self.end)
self.items.append(self.scene.addRect(rect, pen, brush))
def mousePressEvent(self, e):
if e.button() == Qt.LeftButton:
# 시작점 저장
self.start = e.pos()
self.end = e.pos()
print(Qt.LeftButton)
print(e.buttons())
print(e)
def mouseReleaseEvent(self, e):
print("aaa2")
if e.button() == Qt.LeftButton:
global IS_RESULT
print("ttt2")
pen = QPen(self.parent().pencolor)
brush = QBrush(self.parent().brushcolor)
self.items.clear()
rect = QRectF(self.start, self.end)
self.scene.addRect(rect, pen, brush)
print("(" + str(self.start.x()) + ", " + str(self.start.y()) + "), (" + str(self.end.x()) + ", " + str(
self.end.y()) + ")")
area = (self.start.x(), self.start.y(), self.end.x(), self.end.y())
if __name__ == '__main__':
app = QApplication(sys.argv)
w = MainScreen()
w.show()
sys.exit(app.exec_())
If you want to listen to the events of the QGraphicsView you should not override the events of another widget since some events will not be transmitted. In this case it is better to use an event filter that tracks the mouse, in this case the events of the viewport() of the QGraphicsView should be monitored.
In the following example I show how to create rectangles with the mouse.
class MainScreen(QMainWindow, CROP_UI):
def __init__(self):
super().__init__()
self.setupUi(self)
# Graphic Screen set
self.img = QGraphicsPixmapItem(QPixmap(BRING_IN_IMG_ROUTE))
self.scene = QGraphicsScene()
self.scene.addItem(self.img)
self.graphicsView.setScene(self.scene)
# Full Screen set size
_WIDTH_ADD = 25
_HEIGHT_ADD = 25
self.setGeometry(0, 0, 640 + _WIDTH_ADD, 500 + _HEIGHT_ADD)
self.graphicsView.viewport().installEventFilter(self)
self.current_item = None
self.start_pos = QPointF()
self.end_pos = QPointF()
def eventFilter(self, o, e):
if self.graphicsView.viewport() is o:
if e.type() == QEvent.MouseButtonPress:
if e.buttons() & Qt.LeftButton:
print("press")
self.start_pos = self.end_pos = self.graphicsView.mapToScene(
e.pos()
)
pen = QPen(QColor(240, 240, 240))
pen.setWidth(3)
brush = QBrush(QColor(100, 255, 100, 100))
self.current_item = self.scene.addRect(QRectF(), pen, brush)
self._update_item()
elif e.type() == QEvent.MouseMove:
if e.buttons() & Qt.LeftButton and self.current_item is not None:
print("move")
self.end_pos = self.graphicsView.mapToScene(e.pos())
self._update_item()
elif e.type() == QEvent.MouseButtonRelease:
print("release")
self.end_pos = self.graphicsView.mapToScene(e.pos())
self._update_item()
self.current_item = None
return super().eventFilter(o, e)
def _update_item(self):
if self.current_item is not None:
self.current_item.setRect(QRectF(self.start_pos, self.end_pos).normalized())
You have to create a class with type QGraphicView in which you will use the mouse events
class MyGraphicView(QGraphicsView):
def __init__():
super.__init__(self)
self.myScene = QGraphicsScene()
self.setScene(self.myScene)
# the rest code
def mousePressEvent(self, event):
...
def mouseMoveEvent(self, event):
...
def mouseReleaseEvent(self, event):
...
I found this example by #serge_gubenko in SO.
Moving a QGraphicsItem around a central point in PyQt4
I did then some modifications to end up with:
Why is my QGraphicsItem not selectable?
If I run the example (Moving a QGraphicsItem around a central point in PyQt4) and click on the graphics item, it automatically shows up with a dashed frame which indicates that it is selected. I prepared images to show the effect, but due to my low reputation I am not yet allowed to upload those ;)
To me it looks that this "is selected indication" by the dashed frame comes automatically somehow.
In my modified example (Why is my QGraphicsItem not selectable?), this does not happen and I can't figure out why?
You use QtGui.QGraphicsItem, so you define the boundingRect and paint methods, where you used the painter drawEllipse method. In the first example you found, the class uses directly QtGui.QGraphicsEllipseItem and it does all the difference, because those methods are already defined. By the way I didn't find why the boundingRect is not drawn in your case.
Here is a working example which has its own drawFocusRect method.
Focus is indicated in two ways:
1) By clicking on the Qgraphicsitem, then the bounding rect is drawn.
2) Hovering over the item. When fireing the hoverEnterEvent the pen style is changed to DotLine converting back to SolidLine when the hoverLeaveEvent is fired.
#!d:/python27/python -u
import sys
from PyQt4 import QtGui, QtCore
class GraphicsItem(QtGui.QGraphicsItem):
"""
From the QT docs:
To write your own graphics item, you first create a subclass
of QGraphicsItem, and then start by implementing its two pure
virtual public functions: boundingRect(), which returns an estimate
of the area painted by the item, and paint(),
which implements the actual painting.
"""
# call constructor of GraphicsItem
def __init__(self, rect, pen, brush, tooltip='No tip here', parent=None):
# call constructor of QGraphicsItem
super(GraphicsItem, self).__init__()
self.setFlag(QtGui.QGraphicsItem.ItemIsMovable, True)
self.setFlag(QtGui.QGraphicsItem.ItemIsSelectable, True)
self.setFlag(QtGui.QGraphicsItem.ItemIsFocusable, True)
self.setAcceptsHoverEvents(True)
self.pen = pen
pw = self.pen.widthF()
self.brush = QtGui.QBrush(QtCore.Qt.blue)
self.brush = brush
self.setToolTip(tooltip)
self.parent = parent
self.rect = QtCore.QRectF(rect[0], rect[1], rect[2], rect[3])
self.focusrect = QtCore.QRectF(rect[0]-pw/2, rect[1]-pw/2,
rect[2]+pw, rect[3]+pw)
def mouseMoveEvent(self, event):
# move object
QtGui.QGraphicsItem.mouseMoveEvent(self, event)
def mousePressEvent(self, event):
# select object
# set item as topmost in stack
self.setZValue(self.parent.scene.items()[0].zValue() + 1)
self.setSelected(True)
QtGui.QGraphicsItem.mousePressEvent(self, event)
def boundingRect(self):
return self.rect
def paint(self, painter, option, widget):
painter.setBrush(self.brush)
painter.setPen(self.pen)
painter.drawEllipse(self.rect)
if self.isSelected():
self.drawFocusRect(painter)
def drawFocusRect(self, painter):
self.focusbrush = QtGui.QBrush()
self.focuspen = QtGui.QPen(QtCore.Qt.DotLine)
self.focuspen.setColor(QtCore.Qt.black)
self.focuspen.setWidthF(1.5)
painter.setBrush(self.focusbrush)
painter.setPen(self.focuspen)
painter.drawRect(self.focusrect)
def hoverEnterEvent(self, event):
self.pen.setStyle(QtCore.Qt.DotLine)
QtGui.QGraphicsItem.hoverEnterEvent(self, event)
def hoverLeaveEvent(self, event):
self.pen.setStyle(QtCore.Qt.SolidLine)
QtGui.QGraphicsItem.hoverLeaveEvent(self, event)
class MyMainWindow(QtGui.QMainWindow):
# call constructor of MyMainWindow
def __init__(self, parent=None):
# call constructor of QMainWindow
super(MyMainWindow, self).__init__(parent)
w = 1000
h = 800
self.scene = QtGui.QGraphicsScene(-w/2, -h/2, w, h)
self.view = QtGui.QGraphicsView()
# set QGraphicsView attributes
self.view.setRenderHints(QtGui.QPainter.Antialiasing |
QtGui.QPainter.HighQualityAntialiasing)
self.view.setViewportUpdateMode(QtGui.QGraphicsView.FullViewportUpdate)
self.view.setScene(self.scene)
# set central widget for the application
self.setCentralWidget(self.view)
# add items to the scene
self.addGraphicsItem((0, 0, 250, 250), 8.0, (255, 0, 0), (0, 0, 255), 'My first item')
self.addGraphicsItem((-250, -250, 300, 200), 4.0, (0, 0, 0), (255, 0, 100), 'My 2nd item')
self.addGraphicsItem((200, -200, 200, 200), 10.0, (0, 0, 255), (0, 255, 100), 'My 3rd item')
def addGraphicsItem(self, rect, pw, pc, bc, tooltip):
pen = QtGui.QPen(QtCore.Qt.SolidLine)
pen.setColor(QtGui.QColor(pc[0], pc[1], pc[2], 255))
pen.setWidth(pw)
brush = QtGui.QBrush(QtGui.QColor(bc[0], bc[1], bc[2], 255))
item = GraphicsItem(rect, pen, brush, tooltip, self)
self.scene.addItem(item)
def mousePressEvent(self, event):
#print 'from MainWindow'
pass
def keyPressEvent(self, event):
key = event.key()
if key == QtCore.Qt.Key_Escape:
sys.exit(QtGui.qApp.quit())
else:
super(GraphicsView, self).keyPressEvent(event)
def main():
app = QtGui.QApplication(sys.argv)
form = MyMainWindow()
form.setGeometry(700, 100, 1050, 850)
form.show()
app.exec_()
if __name__ == '__main__':
main()
Updated for PyQt5
import sys
from PyQt5 import QtGui, QtCore, QtWidgets
class GraphicsItem(QtWidgets.QGraphicsItem):
"""
From the QT docs:
To write your own graphics item, you first create a subclass
of QGraphicsItem, and then start by implementing its two pure
virtual public functions: boundingRect(), which returns an estimate
of the area painted by the item, and paint(),
which implements the actual painting.
"""
# call constructor of GraphicsItem
def __init__(self, rect, pen, brush, tooltip='No tip here', parent=None):
# call constructor of QGraphicsItem
super(GraphicsItem, self).__init__()
self.setFlag(QtWidgets.QGraphicsItem.ItemIsMovable, True)
self.setFlag(QtWidgets.QGraphicsItem.ItemIsSelectable, True)
self.setFlag(QtWidgets.QGraphicsItem.ItemIsFocusable, True)
self.setAcceptHoverEvents(True)
self.pen = pen
pw = self.pen.widthF()
self.brush = QtGui.QBrush(QtCore.Qt.blue)
self.brush = brush
self.setToolTip(tooltip)
self.parent = parent
self.rect = QtCore.QRectF(rect[0], rect[1], rect[2], rect[3])
self.focusrect = QtCore.QRectF(rect[0]-pw/2, rect[1]-pw/2,
rect[2]+pw, rect[3]+pw)
def mouseMoveEvent(self, event):
# move object
QtWidgets.QGraphicsItem.mouseMoveEvent(self, event)
def mousePressEvent(self, event):
# select object
# set item as topmost in stack
self.setZValue(self.parent.scene.items()[0].zValue() + 1)
self.setSelected(True)
QtWidgets.QGraphicsItem.mousePressEvent(self, event)
def boundingRect(self):
return self.rect
def paint(self, painter, option, widget=None):
painter.setBrush(self.brush)
painter.setPen(self.pen)
painter.drawEllipse(self.rect)
if self.isSelected():
self.drawFocusRect(painter)
def drawFocusRect(self, painter):
self.focusbrush = QtGui.QBrush()
self.focuspen = QtGui.QPen(QtCore.Qt.DotLine)
self.focuspen.setColor(QtCore.Qt.black)
self.focuspen.setWidthF(1.5)
painter.setBrush(self.focusbrush)
painter.setPen(self.focuspen)
painter.drawRect(self.focusrect)
def hoverEnterEvent(self, event):
self.pen.setStyle(QtCore.Qt.DotLine)
QtWidgets.QGraphicsItem.hoverEnterEvent(self, event)
def hoverLeaveEvent(self, event):
self.pen.setStyle(QtCore.Qt.SolidLine)
QtWidgets.QGraphicsItem.hoverLeaveEvent(self, event)
class MyMainWindow(QtWidgets.QMainWindow):
# call constructor of MyMainWindow
def __init__(self, parent=None):
# call constructor of QMainWindow
super(MyMainWindow, self).__init__(parent)
w = 1000
h = 800
self.scene = QtWidgets.QGraphicsScene(-w/2, -h/2, w, h)
self.view = QtWidgets.QGraphicsView()
# set QGraphicsView attributes
self.view.setRenderHints(QtGui.QPainter.Antialiasing |
QtGui.QPainter.HighQualityAntialiasing)
self.view.setViewportUpdateMode(QtWidgets.QGraphicsView.FullViewportUpdate)
self.view.setScene(self.scene)
# set central widget for the application
self.setCentralWidget(self.view)
# add items to the scene
self.addGraphicsItem((0, 0, 250, 250), 8.0, (255, 0, 0), (0, 0, 255), 'My first item')
self.addGraphicsItem((-250, -250, 300, 200), 4.0, (0, 0, 0), (255, 0, 100), 'My 2nd item')
self.addGraphicsItem((200, -200, 200, 200), 10.0, (0, 0, 255), (0, 255, 100), 'My 3rd item')
def addGraphicsItem(self, rect, pw, pc, bc, tooltip):
pen = QtGui.QPen(QtCore.Qt.SolidLine)
pen.setColor(QtGui.QColor(pc[0], pc[1], pc[2], 255))
pen.setWidth(pw)
brush = QtGui.QBrush(QtGui.QColor(bc[0], bc[1], bc[2], 255))
item = GraphicsItem(rect, pen, brush, tooltip, self)
self.scene.addItem(item)
def mousePressEvent(self, event):
#print 'from MainWindow'
pass
def main():
app = QtWidgets.QApplication(sys.argv)
form = MyMainWindow()
form.setGeometry(700, 100, 1050, 850)
form.show()
app.exec_()
if __name__ == '__main__':
main()