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())
CodePudding user response:
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.
CodePudding user response:
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_())