I am trying to model a chess board and i want the pawn GraphicsItem object to be dropped in the center of the case GraphicsItem object .
i only implemented a mouseRelease event for the pawn object, it checks the list of items in the position, if a case is present i drop the pawn at the case position .
now when trying it in the window it works fine for the first move, but after that when i try to move the pawn it goes back to its original position i can still move it but its not under the mouse cursor
here is the code:
import sys
from PyQt5.QtWidgets import (QApplication, QWidget, QGridLayout,
QGraphicsView, QGraphicsScene, QGraphicsItem)
from PyQt5.QtGui import QPen, QBrush, QTransform
from PyQt5.QtCore import Qt, QRectF, QPointF
class Pawn(QGraphicsItem):
def __init__(self, parent = None):
super().__init__(parent)
self.setFlag(QGraphicsItem.ItemIsMovable)
self.setPos(0, 0)
self.setZValue(1)
self.originalPos = self.scenePos()
def paint(self, painter, options, widget):
painter.setBrush(Qt.white)
painter.drawEllipse(0, 0, 30, 30)
def boundingRect(self):
return QRectF(0, 0, 30, 30)
def mouseReleaseEvent(self, event):
dropPos = self.mapToScene(event.pos())
dropCase = None
for item in self.scene().items(dropPos.x(), dropPos.y(), 0.0001, 0.0001,
Qt.IntersectsItemShape,
Qt.AscendingOrder):
if isinstance(item, Case):
dropCase = item
if dropCase:
newP = dropCase.scenePos()
self.setPos(newP)
self.originalPos = newP
else:
self.setPos(self.originalPos)
class Case(QGraphicsItem):
def __init__(self, parent = None):
super().__init__(parent)
self.setPos(100, 0)
self.setZValue(0)
def paint(self, painter, options, widget):
painter.setPen(Qt.black)
painter.setBrush(Qt.black)
painter.drawRect(0, 0, 40, 40)
def boundingRect(self):
return QRectF(0, 0, 40, 40)
def getCenter(self):
x, y = self.scenePos().x() + 10, self.scenePos().y() + 10
return QPointF(x, y)
class MainWin(QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
scene = QGraphicsScene()
view = QGraphicsView(scene, self)
view.setGeometry(0, 0, 290, 290)
case = Case()
pawn = Pawn()
scene.addItem(case)
scene.addItem(pawn)
self.setWindowTitle('doodling')
self.setGeometry(200, 200, 300, 300)
self.show()
if __name__ == '__main__':
app = QApplication(sys.argv)
window = MainWin()
sys.exit(app.exec_())
I did not implement the mouseMoveEvent as i confused it with hover behaviour.
this code works as intended
from PyQt5.QtWidgets import (QApplication, QWidget, QGridLayout,
QGraphicsView, QGraphicsScene, QGraphicsItem)
from PyQt5.QtGui import QPen, QBrush, QTransform
from PyQt5.QtCore import Qt, QRectF, QPointF
class Pawn(QGraphicsItem):
def __init__(self, parent = None):
super().__init__(parent)
self.setFlag(QGraphicsItem.ItemIsMovable)
self.setPos(0, 0)
self.setZValue(1)
self.originalPos = self.scenePos()
def paint(self, painter, options, widget):
painter.setBrush(Qt.white)
painter.drawEllipse(0, 0, 30, 30)
def boundingRect(self):
return QRectF(0, 0, 30, 30)
def mouseMoveEvent(self, event):
movePos = self.mapToScene(event.pos())
self.setPos(movePos.x(), movePos.y())
def mouseReleaseEvent(self, event):
dropPos = self.mapToScene(event.pos())
dropCase = None
for item in self.scene().items(dropPos.x(), dropPos.y(), 0.0001, 0.0001,
Qt.IntersectsItemShape,
Qt.AscendingOrder):
if isinstance(item, Case):
dropCase = item
if dropCase:
newP = dropCase.scenePos()
self.setPos(newP)
self.originalPos = newP
else:
self.setPos(self.originalPos)
class Case(QGraphicsItem):
def __init__(self, x_coord, y_coord, parent = None):
super().__init__(parent)
self.setPos(x_coord, y_coord)
self.setZValue(0)
def paint(self, painter, options, widget):
painter.setPen(Qt.black)
painter.setBrush(Qt.black)
painter.drawRect(0, 0, 40, 40)
def boundingRect(self):
return QRectF(0, 0, 40, 40)
def getCenter(self):
x, y = self.scenePos().x() + 10, self.scenePos().y() + 10
return QPointF(x, y)
class MainWin(QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
scene = QGraphicsScene()
view = QGraphicsView(scene, self)
view.setGeometry(0, 0, 290, 290)
case1 = Case(-100, 0)
case2 = Case(100, 0)
pawn = Pawn()
scene.addItem(case1)
scene.addItem(case2)
scene.addItem(pawn)
self.setWindowTitle('doodling')
self.setGeometry(200, 200, 300, 300)
self.show()
if __name__ == '__main__':
app = QApplication(sys.argv)
window = MainWin()
sys.exit(app.exec_())
Related
I am trying to use PyQt (PySide6) to draw a Pipeline network like in this example gif
[1]: https://i.stack.imgur.com/uQsRg.gif
I know i have to use the QGraphicsView class with QGraphicsScene to draw elements in the screen.
What i dont know how to do is how to handle all the mouse click and move events as well as having Ports on each side of the pipe to be able to attach other pipes/elements to pipes.
i also have to be able to double click on elements to configure them.
Is there any good documentation where i can learn how to achieve this ? or any tutorials ?
Thank you.
import sys
from PySide6.QtWidgets import (
QApplication,
QMainWindow,
QGraphicsView,
QGraphicsScene,
)
from PySide6.QtGui import QAction
from PySide6.QtCore import Qt
class GraphicsScene(QGraphicsScene):
def __init__(self):
super().__init__()
def mousePressEvent(self, event) -> None:
if event.button() == Qt.LeftButton:
print("Left button pressed")
pos_x = event.scenePos().x()
pos_y = event.scenePos().y()
print(f"Position: {pos_x}, {pos_y}")
class GraphicsView(QGraphicsView):
def __init__(self):
super().__init__()
self.scene = self.setScene(GraphicsScene())
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
"""Set up the application's GUI."""
self.setMinimumSize(450, 350)
self.setWindowTitle("Main Window")
self.setup_main_window()
self.create_actions()
self.create_menu()
self.show()
def setup_main_window(self):
"""Create and arrange widgets in the main window."""
self.setCentralWidget(GraphicsView())
def create_actions(self):
"""Create the application's menu actions."""
# Create actions for File menu
self.quit_act = QAction("&Quit")
self.quit_act.setShortcut("Ctrl+Q")
self.quit_act.triggered.connect(self.close)
def create_menu(self):
"""Create the application's menu bar."""
self.menuBar().setNativeMenuBar(False)
# Create file menu and add actions
file_menu = self.menuBar().addMenu("File")
file_menu.addAction(self.quit_act)
if __name__ == "__main__":
app = QApplication(sys.argv)
window = MainWindow()
sys.exit(app.exec())
EDIT/UPDATE :
Here's a solution. Thanks for the help
import sys
from PySide6.QtWidgets import (
QApplication,
QMainWindow,
QGraphicsView,
QGraphicsScene,
QGraphicsEllipseItem,
)
from PySide6.QtGui import QPainterPath, QTransform, QPen, QBrush, QColor, QPainter
from PySide6.QtCore import Qt
PORT_PEN_COLOR = "#000000"
PORT_BRUSH_COLOR = "#ebebeb"
EDGE_PEN_COLOR = "#474747"
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setGeometry(0, 0, 800, 600)
self.setCentralWidget(GraphicsView())
self.show()
class GraphicsView(QGraphicsView):
def __init__(self, parent=None):
super().__init__(parent)
self.setMouseTracking(True)
self.setScene(GraphicsScene())
self.setRenderHint(QPainter.RenderHint.Antialiasing)
class GraphicsScene(QGraphicsScene):
def __init__(self, parent=None):
super().__init__(parent)
self.setSceneRect(-10000, -10000, 20000, 20000)
self._port_pen = QPen(QColor(PORT_PEN_COLOR))
self._port_brush = QBrush(QColor(PORT_BRUSH_COLOR))
self._edge_pen = QPen(QColor(EDGE_PEN_COLOR))
self._edge_pen.setWidth(4)
def mousePressEvent(self, event):
clicked_item = self.itemAt(event.scenePos(), QTransform())
if event.buttons() == Qt.MouseButton.LeftButton:
if clicked_item is not None:
# edge item
pos = clicked_item.scenePos()
pos.setX(pos.x() + 6)
pos.setY(pos.y() + 6)
self.edge = self.addPath(QPainterPath())
self.edge.setPen(self._edge_pen)
self.start_pos = pos
self.end_pos = self.start_pos
self.update_path()
else:
x = event.scenePos().x()
y = event.scenePos().y()
# port item
start_port = Ellipse()
start_port.setPos(x - 6, y - 6)
start_port.setPen(self._port_pen)
start_port.setBrush(self._port_brush)
start_port.setZValue(10000.0)
self.addItem(start_port)
# edge item
self.edge = self.addPath(QPainterPath())
self.edge.setPen(self._edge_pen)
self.start_pos = event.scenePos()
self.end_pos = self.start_pos
self.update_path()
def mouseMoveEvent(self, event):
if event.buttons() == Qt.MouseButton.LeftButton:
print(f"moving, x : {event.scenePos().x()}, y : {event.scenePos().y()}")
self.end_pos = event.scenePos()
try:
self.update_path()
except AttributeError:
pass
def mouseReleaseEvent(self, event) -> None:
released_item = self.itemAt(event.scenePos(), QTransform())
if event.button() == Qt.MouseButton.LeftButton:
if released_item is not None and released_item.type() != 2:
self.end_pos = released_item.scenePos()
self.end_pos.setX(self.end_pos.x() + 6)
self.end_pos.setY(self.end_pos.y() + 6)
if not self.start_pos.isNull() and not self.end_pos.isNull():
path = QPainterPath()
path.moveTo(self.start_pos.x() - 1, self.start_pos.y() - 1)
path.lineTo(self.end_pos)
self.edge.setPath(path)
else:
x = event.scenePos().x() + 1
y = event.scenePos().y() + 1
end_port = QGraphicsEllipseItem(0, 0, 10, 10)
end_port.setPos(x - 6, y - 6)
end_port.setPen(self._port_pen)
end_port.setBrush(self._port_brush)
end_port.setZValue(10000.0)
self.addItem(end_port)
def update_path(self):
if not self.start_pos.isNull() and not self.end_pos.isNull():
path = QPainterPath()
path.moveTo(self.start_pos.x() - 1, self.start_pos.y() - 1)
path.lineTo(self.end_pos)
self.edge.setPath(path)
class Ellipse(QGraphicsEllipseItem):
def __init__(self):
super().__init__()
self.setRect(0, 0, 10, 10)
if __name__ == "__main__":
app = QApplication(sys.argv)
win = MainWindow()
sys.exit(app.exec())
It may need to write all code on your own - check button, check if there is object in small distance, remeber this object, draw this object as selected (with some extra color), update object position when move mouse, redraw all objects, etc. So it may need some list to keep all objects, search if mouse is near of one of object on list, update objects on list, use list to redraw objects on screen (in new positions)
Minimal code which use left click to add item (rectangle), and right click to delete it.
EDIT:
I found out that scene has function .items() to access all items and I don't have to use own list objects for this.
import sys
from PySide6.QtWidgets import (
QApplication,
QMainWindow,
QGraphicsView,
QGraphicsScene,
QGraphicsRectItem,
)
from PySide6.QtGui import QAction
from PySide6.QtCore import Qt
#objects = []
class GraphicsScene(QGraphicsScene):
def __init__(self, parent=None):
super().__init__(parent)
self.setSceneRect(-100, -100, 200, 200)
def mousePressEvent(self, event) -> None:
if event.button() == Qt.LeftButton:
#print("Left button pressed")
x = event.scenePos().x()
y = event.scenePos().y()
#print(f"Position: {x}, {y}")
rectitem = QGraphicsRectItem(0, 0, 10, 10)
# set center of rectangle in mouse position
rectitem.setPos(x-5, y-5)
self.addItem(rectitem)
#objects.append(rectitem)
elif event.button() == Qt.RightButton:
#print("Right button pressed")
x = event.scenePos().x()
y = event.scenePos().y()
#print(f"Position: {x}, {y}")
#for item in objects:
for item in self.items():
pos = item.pos()
if abs(x-pos.x()) < 10 and abs(y-pos.y()) < 10:
print('selected:', item)
self.removeItem(item)
#objects.remove(item)
break
class GraphicsView(QGraphicsView):
def __init__(self):
super().__init__()
self.scene = self.setScene(GraphicsScene())
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
"""Set up the application's GUI."""
self.setMinimumSize(450, 350)
self.setWindowTitle("Main Window")
self.setup_main_window()
self.create_actions()
self.create_menu()
self.show()
def setup_main_window(self):
"""Create and arrange widgets in the main window."""
self.setCentralWidget(GraphicsView())
def create_actions(self):
"""Create the application's menu actions."""
# Create actions for File menu
self.quit_act = QAction("&Quit")
self.quit_act.setShortcut("Ctrl+Q")
self.quit_act.triggered.connect(self.close)
def create_menu(self):
"""Create the application's menu bar."""
self.menuBar().setNativeMenuBar(False)
# Create file menu and add actions
file_menu = self.menuBar().addMenu("File")
file_menu.addAction(self.quit_act)
if __name__ == "__main__":
app = QApplication(sys.argv)
window = MainWindow()
sys.exit(app.exec_())
EDIT:
Example which use
left click to add rectangle (white background, black border)
first right click to select rectangle (red background)
second right click to put selected rectangle in new place (white background, black border)
import sys
from PySide6.QtWidgets import (
QApplication,
QMainWindow,
QGraphicsView,
QGraphicsScene,
QGraphicsRectItem,
)
from PySide6.QtGui import QAction
from PySide6.QtCore import Qt
from PySide6.QtGui import QColor, QPen, QBrush
class GraphicsScene(QGraphicsScene):
def __init__(self, parent=None):
super().__init__(parent)
self.setSceneRect(-100, -100, 200, 200)
self.selected = []
def mousePressEvent(self, event) -> None:
if event.button() == Qt.LeftButton:
#print("Left button pressed")
x = event.scenePos().x()
y = event.scenePos().y()
#print(f"Position: {x}, {y}")
rectitem = QGraphicsRectItem(0, 0, 10, 10)
# set center of rectangle in mouse position
rectitem.setPos(x-5, y-5)
rectitem.setPen(QPen(QColor(0, 0, 0), 1.0, Qt.SolidLine))
rectitem.setBrush(QBrush(QColor(255, 255, 255, 255)))
self.addItem(rectitem)
elif event.button() == Qt.RightButton:
#print("Right button pressed")
x = event.scenePos().x()
y = event.scenePos().y()
#print(f"Position: {x}, {y}")
if self.selected:
print('moved')
for item in self.selected:
item.setPos(x-5, y-5)
item.setPen(QPen(QColor(0, 0, 0), 1.0, Qt.SolidLine))
item.setBrush(QBrush(QColor(255, 255, 255, 255)))
self.selected.clear()
else:
for item in self.items():
pos = item.pos()
if abs(x-pos.x()) < 10 and abs(y-pos.y()) < 10:
print('selected:', item)
self.selected.append(item)
item.setPen(QPen(QColor(255, 0, 0), 1.0, Qt.SolidLine))
item.setBrush(QBrush(QColor(255, 0, 0, 255)))
class GraphicsView(QGraphicsView):
def __init__(self):
super().__init__()
self.scene = self.setScene(GraphicsScene())
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
"""Set up the application's GUI."""
self.setMinimumSize(450, 350)
self.setWindowTitle("Main Window")
self.setup_main_window()
self.create_actions()
self.create_menu()
self.show()
def setup_main_window(self):
"""Create and arrange widgets in the main window."""
self.setCentralWidget(GraphicsView())
def create_actions(self):
"""Create the application's menu actions."""
# Create actions for File menu
self.quit_act = QAction("&Quit")
self.quit_act.setShortcut("Ctrl+Q")
self.quit_act.triggered.connect(self.close)
def create_menu(self):
"""Create the application's menu bar."""
self.menuBar().setNativeMenuBar(False)
# Create file menu and add actions
file_menu = self.menuBar().addMenu("File")
file_menu.addAction(self.quit_act)
if __name__ == "__main__":
app = QApplication(sys.argv)
window = MainWindow()
sys.exit(app.exec_())
EDIT:
Version which uses mouseMoveEvent and mouseReleaseEvent to drag rect (keeping right click)
Based on code in answer to pyqt add rectangle in Qgraphicsscene
import sys
from PySide6.QtWidgets import (
QApplication,
QMainWindow,
QGraphicsView,
QGraphicsScene,
QGraphicsRectItem,
QGraphicsItem,
)
from PySide6.QtGui import QAction
from PySide6.QtCore import Qt
from PySide6.QtGui import QColor, QPen, QBrush, QTransform
class GraphicsScene(QGraphicsScene):
def __init__(self, parent=None):
super().__init__(parent)
self.setSceneRect(-100, -100, 200, 200)
self.selected = None
self.selected_offset_x = 0
self.selected_offset_y = 0
def mousePressEvent(self, event) -> None:
if event.button() == Qt.LeftButton:
x = event.scenePos().x()
y = event.scenePos().y()
rectitem = QGraphicsRectItem(0, 0, 10, 10)
rectitem.setPos(x-5, y-5)
rectitem.setPen(QPen(QColor(0, 0, 0), 1.0, Qt.SolidLine))
rectitem.setBrush(QBrush(QColor(255, 255, 255, 255)))
#rectitem.setFlag(QGraphicsItem.ItemIsMovable, True)
self.addItem(rectitem)
elif event.button() == Qt.RightButton:
x = event.scenePos().x()
y = event.scenePos().y()
if not self.selected:
item = self.itemAt(event.scenePos(), QTransform())
#print(item)
if item:
print('selected:', item)
self.selected = item
self.selected.setBrush(QBrush(QColor(255, 0, 0, 255)))
self.selected_offset_x = x - item.pos().x()
self.selected_offset_y = y - item.pos().y()
#self.selected_offset_x = 5 # rect_width/2 # to keep center of rect
#self.selected_offset_y = 5 # rect_height/2 # to keep center of rect
#super().mousePressEvent(event)
def mouseMoveEvent(self, event):
#print('move:', event.button())
#print('move:', event.buttons())
if event.buttons() == Qt.RightButton: # `buttons()` instead of `button()`
if self.selected:
print('moved')
x = event.scenePos().x()
y = event.scenePos().y()
self.selected.setPos(x-self.selected_offset_x, y-self.selected_offset_y)
#super().mouseMoveEvent(event)
def mouseReleaseEvent(self, event):
#print('release:', event.button())
#print('release:', event.buttons())
if event.button() == Qt.RightButton:
if self.selected:
print('released')
self.selected.setBrush(QBrush(QColor(255, 255, 255, 255)))
self.selected = None
#super().mouseReleaseEvent(event)
class GraphicsView(QGraphicsView):
def __init__(self):
super().__init__()
self.scene = self.setScene(GraphicsScene())
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
"""Set up the application's GUI."""
self.setMinimumSize(450, 350)
self.setWindowTitle("Main Window")
self.setup_main_window()
self.create_actions()
self.create_menu()
self.show()
def setup_main_window(self):
"""Create and arrange widgets in the main window."""
self.setCentralWidget(GraphicsView())
def create_actions(self):
"""Create the application's menu actions."""
# Create actions for File menu
self.quit_act = QAction("&Quit")
self.quit_act.setShortcut("Ctrl+Q")
self.quit_act.triggered.connect(self.close)
def create_menu(self):
"""Create the application's menu bar."""
self.menuBar().setNativeMenuBar(False)
# Create file menu and add actions
file_menu = self.menuBar().addMenu("File")
file_menu.addAction(self.quit_act)
if __name__ == "__main__":
app = QApplication(sys.argv)
window = MainWindow()
sys.exit(app.exec_())
I was thinking about changing color when mouse hover item (using mouseMoveEvent) but at this moment I don't have it.
I found this code on here:
https://python.tutorialink.com/drawing-straight-line-between-two-points-using-qpainterpath/
which lets you create a line with mousePress and move events
import sys
from PySide6.QtWidgets import (
QApplication,
QMainWindow,
QGraphicsView,
QGraphicsScene,
)
from PySide6.QtGui import QAction, QPainterPath
from PySide6.QtCore import Qt, QPointF
class GraphicsScene(QGraphicsScene):
def __init__(self, *args, **kwargs):
super(GraphicsScene, self).__init__(*args, **kwargs)
self.path_item = self.addPath(QPainterPath())
self.start_point = QPointF()
self.end_point = QPointF()
def mousePressEvent(self, event):
self.start_point = event.scenePos()
self.end_point = self.start_point
self.update_path()
super().mousePressEvent(event)
def mouseMoveEvent(self, event):
if event.buttons() & Qt.LeftButton:
self.end_point = event.scenePos()
self.update_path()
super(GraphicsScene, self).mouseMoveEvent(event)
def mouseReleaseEvent(self, event):
self.end_point = event.scenePos()
self.update_path()
super(GraphicsScene, self).mouseReleaseEvent(event)
def update_path(self):
if not self.start_point.isNull() and not self.end_point.isNull():
path = QPainterPath()
path.moveTo(self.start_point)
path.lineTo(self.end_point)
self.path_item.setPath(path)
class GraphicsView(QGraphicsView):
def __init__(self):
super().__init__()
self.scene = self.setScene(GraphicsScene())
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
"""Set up the application's GUI."""
self.setMinimumSize(450, 350)
self.setWindowTitle("Main Window")
self.setup_main_window()
self.create_actions()
self.create_menu()
self.show()
def setup_main_window(self):
"""Create and arrange widgets in the main window."""
self.setCentralWidget(GraphicsView())
def create_actions(self):
"""Create the application's menu actions."""
# Create actions for File menu
self.quit_act = QAction("&Quit")
self.quit_act.setShortcut("Ctrl+Q")
self.quit_act.triggered.connect(self.close)
def create_menu(self):
"""Create the application's menu bar."""
self.menuBar().setNativeMenuBar(False)
# Create file menu and add actions
file_menu = self.menuBar().addMenu("File")
file_menu.addAction(self.quit_act)
if __name__ == "__main__":
app = QApplication(sys.argv)
window = MainWindow()
sys.exit(app.exec_())
EDIT :
I combined it with Furas's code and got this
import sys
from PySide6.QtWidgets import (
QApplication,
QMainWindow,
QGraphicsView,
QGraphicsScene,
QGraphicsRectItem,
)
from PySide6.QtGui import QAction, QPainterPath
from PySide6.QtCore import Qt
from PySide6.QtGui import QColor, QPen, QBrush, QTransform
class GraphicsScene(QGraphicsScene):
def __init__(self, parent=None):
super().__init__(parent)
self.setSceneRect(-100, -100, 200, 200)
self.selected = None
self.selected_offset_x = 0
self.selected_offset_y = 0
def mousePressEvent(self, event) -> None:
if event.button() == Qt.LeftButton:
x = event.scenePos().x()
y = event.scenePos().y()
# rectangle
rectitem = QGraphicsRectItem(0, 0, 10, 10)
rectitem.setPos(x - 5, y - 5)
rectitem.setPen(QPen(QColor(0, 0, 0), 1.0, Qt.SolidLine))
rectitem.setBrush(QBrush(QColor(255, 255, 255, 255)))
# rectitem.setFlag(QGraphicsItem.ItemIsMovable, True)
# Line
self.path_item = self.addPath(QPainterPath())
self.start_point = event.scenePos()
self.end_point = self.start_point
self.update_path()
self.addItem(rectitem)
elif event.button() == Qt.RightButton:
x = event.scenePos().x()
y = event.scenePos().y()
if not self.selected:
item = self.itemAt(event.scenePos(), QTransform())
# print(item)
if item:
print("selected:", item)
self.selected = item
self.selected.setBrush(QBrush(QColor(255, 0, 0, 255)))
self.selected_offset_x = x - item.pos().x()
self.selected_offset_y = y - item.pos().y()
# self.selected_offset_x = 5 # rect_width/2 # to keep center of rect
# self.selected_offset_y = 5 # rect_height/2 # to keep center of rect
# super().mousePressEvent(event)
def mouseMoveEvent(self, event):
# print('move:', event.button())
# print('move:', event.buttons())
if event.buttons() == Qt.RightButton: # `buttons()` instead of `button()`
if self.selected:
print("moved")
x = event.scenePos().x()
y = event.scenePos().y()
self.selected.setPos(
x - self.selected_offset_x, y - self.selected_offset_y
)
elif event.buttons() == Qt.LeftButton:
self.end_point = event.scenePos()
self.update_path()
# super().mouseMoveEvent(event)
def mouseReleaseEvent(self, event):
# print('release:', event.button())
# print('release:', event.buttons())
if event.button() == Qt.RightButton:
if self.selected:
print("released")
self.selected.setBrush(QBrush(QColor(255, 255, 255, 255)))
self.selected = None
elif event.button() == Qt.LeftButton:
self.end_point = event.scenePos()
x = event.scenePos().x()
y = event.scenePos().y()
rectitem = QGraphicsRectItem(0, 0, 10, 10)
rectitem.setPos(x - 5, y - 5)
rectitem.setPen(QPen(QColor(0, 0, 0), 1.0, Qt.SolidLine))
rectitem.setBrush(QBrush(QColor(255, 255, 255, 255)))
self.addItem(rectitem)
self.update_path()
# super().mouseReleaseEvent(event)
def update_path(self):
if not self.start_point.isNull() and not self.end_point.isNull():
path = QPainterPath()
path.moveTo(self.start_point)
path.lineTo(self.end_point)
self.path_item.setPath(path)
class GraphicsView(QGraphicsView):
def __init__(self):
super().__init__()
self.scene = self.setScene(GraphicsScene())
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
"""Set up the application's GUI."""
self.setMinimumSize(450, 350)
self.setWindowTitle("Main Window")
self.setup_main_window()
self.create_actions()
self.create_menu()
self.show()
def setup_main_window(self):
"""Create and arrange widgets in the main window."""
self.setCentralWidget(GraphicsView())
def create_actions(self):
"""Create the application's menu actions."""
# Create actions for File menu
self.quit_act = QAction("&Quit")
self.quit_act.setShortcut("Ctrl+Q")
self.quit_act.triggered.connect(self.close)
def create_menu(self):
"""Create the application's menu bar."""
self.menuBar().setNativeMenuBar(False)
# Create file menu and add actions
file_menu = self.menuBar().addMenu("File")
file_menu.addAction(self.quit_act)
if __name__ == "__main__":
app = QApplication(sys.argv)
window = MainWindow()
sys.exit(app.exec_())
In below figure I have QGraphicsPathItem on scene as red portion and override it's shape as blue portion. I want when the red space is dragged and moved then the item is lengthened or shortened linearly, and when the blue space is dragged then the entire item must be moved.
Here is what I tried...
import sys
from PyQt5.QtCore import QRectF, Qt, QPointF
from PyQt5.QtGui import QPainterPath, QPen, QPainterPathStroker, QPainter
from PyQt5.QtWidgets import QApplication, QMainWindow, QGraphicsScene, QGraphicsView, QGraphicsPathItem, QGraphicsItem
class Item(QGraphicsPathItem):
circle = QPainterPath()
circle.addEllipse(QRectF(-5, -5, 10, 10))
def __init__(self):
super(Item, self).__init__()
self.setPath(Item.circle)
self.setFlag(QGraphicsItem.ItemIsSelectable, True)
self.setFlag(QGraphicsItem.ItemIsMovable, True)
def paint(self, painter, option, widget):
color = Qt.red if self.isSelected() else Qt.black
painter.setPen(QPen(color, 2, Qt.SolidLine))
painter.drawPath(self.path())
# To paint path of shape
painter.setPen(QPen(Qt.blue, 1, Qt.SolidLine))
painter.drawPath(self.shape())
def shape(self):
startPoint = self.mapFromScene(self.pos())
endPoint = self.mapFromScene(QPointF(10, 10))
path = QPainterPath(startPoint)
path.lineTo(endPoint)
stroke = QPainterPathStroker()
stroke.setWidth(10)
return stroke.createStroke(path)
if __name__ == "__main__":
app = QApplication(sys.argv)
window = QMainWindow()
window.show()
scene = QGraphicsScene()
scene.setSceneRect(0, 0, 200, 200)
view = QGraphicsView()
view.setScene(scene)
window.setCentralWidget(view)
scene.addItem(Item())
sys.exit(app.exec_())
I am getting output as disturbed path
Handling the task of resizing and stretching in the same item is complicated, so to avoid it I have used 2 items: A handle and a Pipe. Thus each one manages his own task and updates the position of the other elements:
import sys
from PyQt5 import QtCore, QtGui, QtWidgets
class HandleItem(QtWidgets.QGraphicsPathItem):
def __init__(self, parent=None):
super().__init__(parent)
path = QtGui.QPainterPath()
path.addEllipse(QtCore.QRectF(-5, -5, 10, 10))
self.setPath(path)
self.setFlag(QtWidgets.QGraphicsItem.ItemIsSelectable, True)
self.setFlag(QtWidgets.QGraphicsItem.ItemIsMovable, True)
self.setFlag(QtWidgets.QGraphicsItem.ItemSendsGeometryChanges, True)
self._pipe_item = None
#property
def pipe_item(self):
return self._pipe_item
#pipe_item.setter
def pipe_item(self, item):
self._pipe_item = item
def itemChange(self, change, value):
if change == QtWidgets.QGraphicsItem.ItemPositionChange and self.isEnabled():
ip = self.pipe_item.mapFromScene(value)
self.pipe_item.end_pos = ip
elif change == QtWidgets.QGraphicsItem.ItemSelectedChange:
color = QtCore.Qt.red if value else QtCore.Qt.black
self.setPen(QtGui.QPen(color, 2, QtCore.Qt.SolidLine))
return super().itemChange(change, value)
class PipeItem(QtWidgets.QGraphicsPathItem):
def __init__(self, parent=None):
super().__init__(parent)
self.setFlag(QtWidgets.QGraphicsItem.ItemIsSelectable, True)
self.setFlag(QtWidgets.QGraphicsItem.ItemIsMovable, True)
self.setFlag(QtWidgets.QGraphicsItem.ItemSendsGeometryChanges, True)
self._end_pos = QtCore.QPointF()
self._handle = HandleItem()
self.handle.pipe_item = self
self.end_pos = QtCore.QPointF(10, 10)
self.handle.setPos(self.end_pos)
self.setPen(QtGui.QPen(QtCore.Qt.blue, 1, QtCore.Qt.SolidLine))
#property
def handle(self):
return self._handle
#property
def end_pos(self):
return self._end_pos
#end_pos.setter
def end_pos(self, p):
path = QtGui.QPainterPath()
path.lineTo(p)
stroke = QtGui.QPainterPathStroker()
stroke.setWidth(10)
self.setPath(stroke.createStroke(path))
self._end_pos = p
def paint(self, painter, option, widget):
option.state &= ~QtWidgets.QStyle.State_Selected
super().paint(painter, option, widget)
def itemChange(self, change, value):
if change == QtWidgets.QGraphicsItem.ItemSceneHasChanged:
if self.scene():
self.scene().addItem(self.handle)
elif change == QtWidgets.QGraphicsItem.ItemPositionChange and self.isEnabled():
p = self.mapToScene(self.end_pos)
self.handle.setPos(p)
return super().itemChange(change, value)
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
scene = QtWidgets.QGraphicsScene(sceneRect=QtCore.QRectF(0, 0, 200, 200))
item = PipeItem()
scene.addItem(item)
view = QtWidgets.QGraphicsView(scene)
window = QtWidgets.QMainWindow()
window.setCentralWidget(view)
window.resize(640, 480)
window.show()
sys.exit(app.exec_())
UPDATE:
If you want the logic you want to be implemented then it is more complicated. The cause of the error is that the paint() method uses the boundingRect() to set the paint area, but in your case it does not take into account that it varies, a possible solution is the following:
class Item(QGraphicsPathItem):
circle = QPainterPath()
circle.addEllipse(QRectF(-5, -5, 10, 10))
# ...
def boundingRect(self):
return self.shape().boundingRect()
There is a roundrect on graphicsscene and i am trying to move but when it goes 'end of the' any side of screen it goes out of the screen but it should not go outside of the screen it should move like a game callled "SNAKE XENZIA"
from PyQt5.QtWidgets import QApplication, QWidget, QDesktopWidget, QGraphicsView, QGraphicsScene, QGraphicsItem
from PyQt5.QtCore import Qt, QRectF
from PyQt5.QtGui import QBrush, QColor
import sys
class GraphicsItem(QGraphicsItem):
def __init__(self, parent):
super().__init__()
self.setFlag(QGraphicsItem.ItemIsFocusable)
self.setFocus()
self.screenSize = QDesktopWidget().screenGeometry(0)
self.screenHeight = self.screenSize.height()
self.screenWidth = self.screenSize.width()
def boundingRect(self):
return QRectF(0, 0, self.screenWidth, self.screenHeight)
def paint(self, painter, option, widget):
painter.setBrush(QBrush(Qt.magenta))
painter.drawRoundedRect(10, 10, 70, 70, 5, 5)
# painter.drawEllipse(10, 100, 70, 70)
def keyPressEvent(self, event):
if event.key() == Qt.Key_Right:
self.moveBy(10, 0)
elif event.key() == Qt.Key_Left:
self.moveBy(-10, 0)
elif event.key() == Qt.Key_Down:
self.moveBy(0, 10)
elif event.key() == Qt.Key_Up:
self.moveBy(0, -10)
self.update()
class GraphicsScene(QGraphicsScene):
def __init__(self, parent):
super().__init__(parent=parent)
graphicsitem = GraphicsItem(self)
self.addItem(graphicsitem)
self.setBackgroundBrush(QColor(10, 155, 79))
self.screenSize = QDesktopWidget().screenGeometry(0)
self.screenHeight = self.screenSize.height()
self.screenWidth = self.screenSize.width()
class GraphicsView(QGraphicsView):
def __init__(self, parent):
super().__init__(parent=parent)
graphicsscene = GraphicsScene(self)
self.screenSize = QDesktopWidget().screenGeometry(0)
self.screenHeight = self.screenSize.height()
self.screenWidth = self.screenSize.width()
self.setGeometry(0, 0, self.screenWidth, self.screenHeight)
self.setScene(graphicsscene)
self.show()
class Widget(QWidget):
def __init__(self):
super().__init__()
graphicsview = GraphicsView(self)
self.screenSize = QDesktopWidget().screenGeometry(0)
self.screenHeight = self.screenSize.height()
self.screenWidth = self.screenSize.width()
self.initUI()
def initUI(self):
self.setWindowTitle("Graphics View")
self.setGeometry(0, 0, self.screenWidth, self.screenHeight)
self.show()
if __name__ == "__main__":
app = QApplication(sys.argv)
widget = Widget()
sys.exit(app.exec_())
Try it:
import sys
from PyQt5.QtWidgets import QApplication, QWidget, QDesktopWidget, QGraphicsView, QGraphicsScene, QGraphicsItem
from PyQt5.QtCore import Qt, QRectF
from PyQt5.QtGui import QBrush, QColor
class GraphicsItem(QGraphicsItem):
def __init__(self, parent):
super().__init__()
self.setFlag(QGraphicsItem.ItemIsFocusable)
self.setFocus()
self.screenSize = QDesktopWidget().screenGeometry(0)
self.screenHeight = self.screenSize.height()
self.screenWidth = self.screenSize.width()
def boundingRect(self):
self.posX = self.pos().x() # +++
self.posY = self.pos().y() # +++
return QRectF(0, 0, self.screenWidth, self.screenHeight)
def paint(self, painter, option, widget):
painter.setBrush(QBrush(Qt.magenta))
painter.drawRoundedRect(10, 10, 70, 70, 5, 5)
# painter.drawEllipse(10, 100, 70, 70)
def keyPressEvent(self, event):
if event.key() == Qt.Key_Right:
# self.moveBy(10, 0)
if self.posX >= 1270: self.moveBy(-1270, 0) # +++
else: self.moveBy(10, 0) # +++
elif event.key() == Qt.Key_Left:
# self.moveBy(-10, 0)
if self.posX > 0: self.moveBy(-10, 0) # +++
else: self.moveBy(1270, 0) # +++
elif event.key() == Qt.Key_Down:
# self.moveBy(0, 10)
if self.posY >= 670: self.moveBy(0, -670) # +++
else: self.moveBy(0, 10) # +++
elif event.key() == Qt.Key_Up:
# self.moveBy(0, -10)
if self.posY > 0: self.moveBy(0, -10) # +++
else: self.moveBy(0, 670) # +++
# self.update()
class GraphicsScene(QGraphicsScene):
def __init__(self, parent):
super().__init__(parent=parent)
graphicsitem = GraphicsItem(self)
self.addItem(graphicsitem)
self.setBackgroundBrush(QColor(10, 155, 79))
self.screenSize = QDesktopWidget().screenGeometry(0)
self.screenHeight = self.screenSize.height()
self.screenWidth = self.screenSize.width()
class GraphicsView(QGraphicsView):
def __init__(self, parent):
super().__init__(parent=parent)
graphicsscene = GraphicsScene(self)
self.screenSize = QDesktopWidget().screenGeometry(0)
self.screenHeight = self.screenSize.height()
self.screenWidth = self.screenSize.width()
self.setGeometry(0, 0, self.screenWidth, self.screenHeight)
self.setScene(graphicsscene)
# self.show()
class Widget(QWidget):
def __init__(self):
super().__init__()
graphicsview = GraphicsView(self)
self.screenSize = QDesktopWidget().screenGeometry(0)
self.screenHeight = self.screenSize.height()
self.screenWidth = self.screenSize.width()
self.initUI()
self.setFixedSize(self.screenWidth, self.screenHeight) # +++
def initUI(self):
self.setWindowTitle("Graphics View")
self.setGeometry(0, 0, self.screenWidth, self.screenHeight)
# self.show()
if __name__ == "__main__":
app = QApplication(sys.argv)
widget = Widget()
widget.show()
sys.exit(app.exec_())
I want to update the opacity of some QGraphicsItem after the mouse clicking. As suggested from other solution, the QGraphicScene manually update the GraphicsItem after the mouser press event. I have tried different setOpacity() and update() in QGraphicsScene and QGraphicsItem. But none works and do not know what is wrong.
import sys
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
CUBE_POS = {
"a":( 8.281, 18.890),
"b":( 8.668, 23.692),
"c":( 21.493, 23.423),
"d":( 21.24, 15.955),
}
class CubeItem(QGraphicsItem):
def __init__(self, x, y, parent=None):
super(CubeItem,self).__init__(parent)
self.x = x
self.y = y
self.polygon = QPolygonF([
QPointF(self.x-10, self.y-10), QPointF(self.x-10, self.y+10),
QPointF(self.x+10, self.y+10), QPointF(self.x+10, self.y-10),
])
self._painter = QPainter()
##Estimate the drawing area
def boundingRect(self):
return QRectF(self.x-10, self.y-10, 20, 20)
##Real Shape of drawing area
def shape(self):
path = QPainterPath()
path.addRect(self.x-10, self.y-10, 20, 20)
return path
##paint function called by graphicview
def paint(self, painter, option, widget):
painter.setBrush(Qt.red)
painter.setOpacity(0.2)
painter.drawRect(self.x-10, self.y-10, 20, 20)
self._painter = painter
def activate(self):
try:
#self._painter.setOpacity(1.0)
self.setOpacity(1.0)
self.update()
except ValueError as e:
print(e)
class TagScene(QGraphicsScene):
def __init__(self, parent=None):
super(TagScene, self).__init__(parent)
self.cubes_items_ref = {}
self.addCubes()
def addCubes(self):
for cube in CUBE_POS:
newCube = CubeItem(CUBE_POS[cube][0]*15,
CUBE_POS[cube][1]*15)
self.addItem(newCube)
self.cubes_items_ref[cube] = newCube
def mousePressEvent(self, event):
print("mouse pressed")
#for cube in self.cubes_items_ref:
# self.cubes_items_ref[cube].setOpacity(1.0)
# #self.cubes_items_ref[cube].activate()
#self.update(QRectF(0,0,500,500))
for cube in self.items():
cube.setOpacity(1.0)
self.update(QRectF(0,0,500,500))
class MainWindow(QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
layout = QHBoxLayout()
self.scene = TagScene()
self.view = QGraphicsView(self.scene)
self.scene.setSceneRect(QRectF(0,0,500,500))
layout.addWidget(self.view)
self.widget = QWidget()
self.widget.setLayout(layout)
self.setCentralWidget(self.widget)
if __name__ == "__main__":
app = QApplication(sys.argv)
test = MainWindow()
test.show()
sys.exit(app.exec_())
The problem is that when you overwrite the paint method of the QGraphicsItem you are setting a constant opacity
def paint(self, painter, option, widget):
painter.setBrush(Qt.red)
painter.setOpacity(0.2) # <-- this line is the problem
painter.drawRect(self.x-10, self.y-10, 20, 20)
self._painter = painter
And you will not use the opacity that the QPainter already passes paint() method.
If you want to set an initial opacity you must do it in the constructor.On the other hand the setOpacity() method already calls update() so it is not necessary to make an explicit call.
import sys
from PyQt5 import QtCore, QtGui, QtWidgets
CUBE_POS = {
"a": (8.281, 18.890),
"b": (8.668, 23.692),
"c": (21.493, 23.423),
"d": (21.24, 15.955),
}
class CubeItem(QtWidgets.QGraphicsItem):
def __init__(self, x, y, parent=None):
super(CubeItem, self).__init__(parent)
self.x = x
self.y = y
self.polygon = QtGui.QPolygonF(
[
QtCore.QPointF(self.x - 10, self.y - 10),
QtCore.QPointF(self.x - 10, self.y + 10),
QtCore.QPointF(self.x + 10, self.y + 10),
QtCore.QPointF(self.x + 10, self.y - 10),
]
)
self.setOpacity(0.2) # initial opacity
##Estimate the drawing area
def boundingRect(self):
return QtCore.QRectF(self.x - 10, self.y - 10, 20, 20)
##Real Shape of drawing area
def shape(self):
path = QtGui.QPainterPath()
path.addRect(self.boundingRect())
return path
##paint function called by graphicview
def paint(self, painter, option, widget):
painter.setBrush(QtCore.Qt.red)
painter.drawRect(self.x - 10, self.y - 10, 20, 20)
class TagScene(QtWidgets.QGraphicsScene):
def __init__(self, parent=None):
super(TagScene, self).__init__(parent)
self.cubes_items_ref = {}
self.addCubes()
def addCubes(self):
for cube in CUBE_POS:
newCube = CubeItem(CUBE_POS[cube][0] * 15, CUBE_POS[cube][1] * 15)
self.addItem(newCube)
self.cubes_items_ref[cube] = newCube
def mousePressEvent(self, event):
for cube in self.items():
cube.setOpacity(1.0) # update opacity
class MainWindow(QtWidgets.QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
layout = QtWidgets.QHBoxLayout()
self.scene = TagScene()
self.view = QtWidgets.QGraphicsView(self.scene)
self.scene.setSceneRect(QtCore.QRectF(0, 0, 500, 500))
layout.addWidget(self.view)
self.widget = QtWidgets.QWidget()
self.widget.setLayout(layout)
self.setCentralWidget(self.widget)
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
test = MainWindow()
test.show()
sys.exit(app.exec_())
Here is my code:
import sys
from PyQt5.QtWidgets import (QApplication, QLabel, QWidget)
from PyQt5.QtGui import QPainter, QColor, QPen
from PyQt5.QtCore import Qt
class MouseTracker(QWidget):
distance_from_center = 0
def __init__(self):
super().__init__()
self.initUI()
self.setMouseTracking(True)
def initUI(self):
self.setGeometry(200, 200, 1000, 500)
self.setWindowTitle('Mouse Tracker')
self.label = QLabel(self)
self.label.resize(500, 40)
self.show()
def mouseMoveEvent(self, event):
distance_from_center = round(((event.y() - 250)**2 + (event.x() - 500)**2)**0.5)
self.label.setText('Coordinates: ( %d : %d )' % (event.x(), event.y()) + "Distance from center: " + str(distance_from_center))
q = QPainter() #Painting the line
q.begin(self)
q.drawLine(event.x(), event.y(), 250, 500)
q.end()
def drawPoints(self, qp, x, y):
qp.setPen(Qt.red)
qp.drawPoint(x, y)
app = QApplication(sys.argv)
ex = MouseTracker()
sys.exit(app.exec_())
What I'm trying to do is paint a simple line from the position of the mouse to the middle of the widget using this:
q = QPainter() #Painting the line
q.begin(self)
q.drawLine(event.x(), event.y(), 250, 500)
q.end()
But when I run it, no line is visible. What do I need to do?
You must implement the function QPaintEvent, in this function you must draw the line, in addition you must call the function update() to update the drawing.
import sys
from PyQt5.QtWidgets import (QApplication, QLabel, QWidget)
from PyQt5.QtGui import QPainter, QColor, QPen
from PyQt5.QtCore import Qt
class MouseTracker(QWidget):
distance_from_center = 0
def __init__(self):
super().__init__()
self.initUI()
self.setMouseTracking(True)
def initUI(self):
self.setGeometry(200, 200, 1000, 500)
self.setWindowTitle('Mouse Tracker')
self.label = QLabel(self)
self.label.resize(500, 40)
self.show()
self.pos = None
def mouseMoveEvent(self, event):
distance_from_center = round(((event.y() - 250)**2 + (event.x() - 500)**2)**0.5)
self.label.setText('Coordinates: ( %d : %d )' % (event.x(), event.y()) + "Distance from center: " + str(distance_from_center))
self.pos = event.pos()
self.update()
def paintEvent(self, event):
if self.pos:
q = QPainter(self)
q.drawLine(self.pos.x(), self.pos.y(), 500, 250)
app = QApplication(sys.argv)
ex = MouseTracker()
sys.exit(app.exec_())
Output:
You can only use a QPainter inside the paintEvent method. So one way to fix is to record the x and y coordinates inside the class and update the root widget. This then calls paintEvent and you will see the line.
example
import sys
from PyQt5.QtWidgets import QApplication, QLabel, QWidget
from PyQt5.QtGui import QPainter
from PyQt5.QtCore import Qt
class MouseTracker(QWidget):
distance_from_center = 0
def __init__(self):
super().__init__()
self.initUI()
self.setMouseTracking(True)
self.x = -1
self.y = -1
def initUI(self):
self.setGeometry(200, 200, 1000, 500)
self.setWindowTitle('Mouse Tracker')
self.label = QLabel(self)
self.label.resize(500, 40)
self.show()
def paintEvent(self, e):
if not (self.x == -1 or self.y == -1):
q = QPainter() #Painting the line
q.begin(self)
q.drawLine(self.x, self.y, 250, 500)
q.end()
def mouseMoveEvent(self, event):
distance_from_center = round(((event.y() - 250)**2 + (event.x() - 500)**2)**0.5)
self.label.setText('Coordinates: ( %d : %d )' % (event.x(), event.y()) + "Distance from center: " + str(distance_from_center))
self.x = event.x()
self.y = event.y()
self.update()
def drawPoints(self, qp, x, y):
qp.setPen(Qt.red)
qp.drawPoint(x, y)
app = QApplication(sys.argv)
ex = MouseTracker()
sys.exit(app.exec_())
I wasn't sure about how self.x and self.y would be set initially. The -1 and the check in the paintEvent feels a bit hacky, but at least it paints.
For the previous answer, I tried it under Python3.7 and PyQt5. The result was program crash
'Process finished with exit code -1073740791 (0xC0000409)'.
And in the comments, there were someone else also mentioned the crash.
I find the solution to this crash:
self.x and self.y must be initialized before the calling of self.show()
So I simply modified the code to as follows:
def initUI(self):
self.setGeometry(200, 200, 1000, 500)
self.setWindowTitle('Mouse Tracker')
self.label = QLabel(self)
self.label.resize(500, 40)
self.x = 100
self.y = 100
self.show()