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_())
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_())
Is there a way to determine the width of the arrow buttons in the qspinbox?
I'm trying to overwrite the context menu event, and i only want my custom event to take place if the user right-clicks over the arrow button, otherwise i want the normal context menu to appear.
Right now I'm just hardcoding a value of 20 which is not ideal.
import sys
import os
from PySide import QtGui, QtCore
class MySpinner(QtGui.QSpinBox):
def __init__(self, parent=None):
super(MySpinner, self).__init__(parent)
self.setAccelerated(False)
self.setRange(-1000,1000)
self.setSingleStep(1)
self.setValue(300)
def contextMenuEvent(self, event):
if event.pos().x() > self.rect().right()-20:
self.setValue(50)
self.selectAll()
else:
super(self.__class__, self).contextMenuEvent(event)
class MainWindow(QtGui.QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.resize(300, 200)
grid = QtGui.QVBoxLayout()
grid.addWidget(MySpinner())
content = QtGui.QWidget()
content.setLayout(grid)
self.setCentralWidget(content)
def main():
app = QtGui.QApplication(sys.argv)
ex = MainWindow()
ex.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
Instead of obtaining the width it is only necessary to obtain the SubControl to know if it was pressed in one of the arrows buttons:
def contextMenuEvent(self, event):
opt = QtGui.QStyleOptionSpinBox()
self.initStyleOption(opt)
opt.subControls = QtGui.QStyle.SC_All
hoverControl = self.style().hitTestComplexControl(QtGui.QStyle.CC_SpinBox, opt, event.pos(), self)
if hoverControl == QtGui.QStyle.SC_SpinBoxUp:
print("up")
elif hoverControl == QtGui.QStyle.SC_SpinBoxDown:
print("down")
else:
super(self.__class__, self).contextMenuEvent(event)
If you want to get the QRect of each subcontrol you should use
# up
rect_up = self.style().subControlRect(QtGui.QStyle.CC_SpinBox, opt, QtGui.QStyle.SC_SpinBoxUp, self)
# down
rect_down = self.style().subControlRect(QtGui.QStyle.CC_SpinBox, opt, QtGui.QStyle.SC_SpinBoxDown, self)
Another option:
def contextMenuEvent(self, event):
opt = QtGui.QStyleOptionSpinBox()
self.initStyleOption(opt)
r = QtCore.QRect()
for sc in (QtGui.QStyle.SC_SpinBoxUp, QtGui.QStyle.SC_SpinBoxDown):
r= r.united(self.style().subControlRect(QtGui.QStyle.CC_SpinBox, opt, sc, self))
if r.contains(event.pos()):
print("arrow")
else:
super(self.__class__, self).contextMenuEvent(event)
I have a scene like this
class Scene(QtWidgets.QGraphicsScene):
def __init__(self, parent=None):
super(Scene, self).__init__(parent)
def mousePressEvent(self, event):
print('scene pressed')
self.wid = MyRect(event.pos(), event.pos())
self.addItem(self.wid)
self.wid.show()
I would like class MyRect(QtWidgets.QGraphicsRectItem) with painter, mouse event and so on to be a draggable rectangle.
all stuff in MyRect
So then I could have many Rectangle to the scene and even after draw line between them and so on (kind of diagram app), but keeping objects related editable options in MyRect, MyLine , ....
I thought :
class MyRect(QtWidgets.QGraphicsRectItem):
def __init__(self, begin, end, parent=None):
super().__init__(parent)
self.begin = begin
self.end = end
def paintEvent(self, event):
print('painting')
qp = QtGui.QPainter(self)
qp.drawRect(QtCore.QRect(self.begin, self.end))
def mousePressEvent(self, event):
self.begin = event.pos()
self.end = event.pos()
self.update()
def mouseMoveEvent(self, event):
self.end = event.pos()
self.update()
def mouseReleaseEvent(self, event):
self.begin = event.pos()
self.end = event.pos()
self.update()
But I does not work (paint event not initiated whereas mousepressed event in scene is intiated)
I did not find what I wanted through the web so started totry do it by myself. I'm pretty sure it is a must known starting point but I cannot find it
First of all a QGraphicsItem is not a QWidget, so it has those events and does not handle them directly, that's what QGraphicsView and QGraphicsScene do. For example you say that you want to have a moveable rectangle because that task is simple is QGraphicsView, it is not necessary to overwrite:
from PyQt5 import QtCore, QtWidgets
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
scene = QtWidgets.QGraphicsScene(self)
view = QtWidgets.QGraphicsView(scene)
self.setCentralWidget(view)
rect_item = QtWidgets.QGraphicsRectItem(QtCore.QRectF(0, 0, 100, 100))
rect_item.setFlag(QtWidgets.QGraphicsItem.ItemIsMovable, True)
scene.addItem(rect_item)
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
w = MainWindow()
w.resize(640, 480)
w.show()
sys.exit(app.exec_())
If you want to change the way you paint the rectangle you must overwrite the paint() method as shown below:
from PyQt5 import QtCore, QtGui, QtWidgets
class RectItem(QtWidgets.QGraphicsRectItem):
def paint(self, painter, option, widget=None):
super(RectItem, self).paint(painter, option, widget)
painter.save()
painter.setRenderHint(QtGui.QPainter.Antialiasing)
painter.setBrush(QtCore.Qt.red)
painter.drawEllipse(option.rect)
painter.restore()
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
scene = QtWidgets.QGraphicsScene(self)
view = QtWidgets.QGraphicsView(scene)
self.setCentralWidget(view)
rect_item = RectItem(QtCore.QRectF(0, 0, 100, 100))
rect_item.setFlag(QtWidgets.QGraphicsItem.ItemIsMovable, True)
scene.addItem(rect_item)
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
w = MainWindow()
w.resize(640, 480)
w.show()
sys.exit(app.exec_())
Update:
from PyQt5 import QtCore, QtGui, QtWidgets
class GraphicsScene(QtWidgets.QGraphicsScene):
def __init__(self, parent=None):
super(GraphicsScene, self).__init__(QtCore.QRectF(-500, -500, 1000, 1000), parent)
self._start = QtCore.QPointF()
self._current_rect_item = None
def mousePressEvent(self, event):
if self.itemAt(event.scenePos(), QtGui.QTransform()) is None:
self._current_rect_item = QtWidgets.QGraphicsRectItem()
self._current_rect_item.setBrush(QtCore.Qt.red)
self._current_rect_item.setFlag(QtWidgets.QGraphicsItem.ItemIsMovable, True)
self.addItem(self._current_rect_item)
self._start = event.scenePos()
r = QtCore.QRectF(self._start, self._start)
self._current_rect_item.setRect(r)
super(GraphicsScene, self).mousePressEvent(event)
def mouseMoveEvent(self, event):
if self._current_rect_item is not None:
r = QtCore.QRectF(self._start, event.scenePos()).normalized()
self._current_rect_item.setRect(r)
super(GraphicsScene, self).mouseMoveEvent(event)
def mouseReleaseEvent(self, event):
self._current_rect_item = None
super(GraphicsScene, self).mouseReleaseEvent(event)
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
scene =GraphicsScene(self)
view = QtWidgets.QGraphicsView(scene)
self.setCentralWidget(view)
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
w = MainWindow()
w.resize(640, 480)
w.show()
sys.exit(app.exec_())
I am using PyQt5 to build a GUI and inside it I am using a Class Drawer to enable for the user to draw using the mouse but when I am saving the image it is always empty can any one tell me why?
class Drawer(QWidget):
newPoint = pyqtSignal(QPoint)
def __init__(self, parent=None):
QWidget.__init__(self, parent)
self.setAttribute(QtCore.Qt.WA_StaticContents)
self.modified = False
self.scribbling = False
imageSize = QtCore.QSize(9500, 9500)
h=400
w=400
self.myPenWidth = 13
self.myPenColor = QtCore.Qt.black
self.image = QtGui.QImage()
self.image=QtGui.QImage(w,h,QtGui.QImage.Format_RGB32)
self.path = QPainterPath()
def setPenColor(self, newColor):
self.myPenColor = newColor
def setPenWidth(self, newWidth):
self.myPenWidth = newWidth
def clearImage(self):
self.path = QPainterPath()
self.image.fill(QtGui.qRgb(255, 255, 255)) ## switch it to else
self.modified = True
self.update()
def saveImage(self, fileName, fileFormat):
self.image.save(fileName,fileFormat)
def paintEvent(self, event):
painter = QPainter(self)
#painter.setPen(QColor(0, 0, 0))
painter.setPen(QtGui.QPen(self.myPenColor,
self.myPenWidth,QtCore.Qt.SolidLine, QtCore.Qt.RoundCap,
QtCore.Qt.RoundJoin))
#painter.setFont(QFont('Decorative', 10))
painter.drawImage(event.rect(), self.image)
painter.drawPath(self.path)
def mousePressEvent(self, event):
self.path.moveTo(event.pos())
self.update()
def mouseMoveEvent(self, event):
self.path.lineTo(event.pos())
self.newPoint.emit(event.pos())
self.update()
def sizeHint(self):
return QSize(300, 300)
when I am calling it i use this function that calls the function inside the Drawer class
def saveFile(self):#, fileFormat):
fileFormat="PNG"
fileName="ar.png"
self.draw2.saveImage(fileName,fileFormat)
It is always empty because you have never painted the image, what you have painted has been the background of the QWidget, in the following code I have created a QPainter that takes as a base the image and draws on it and in the paintEvent() only the image is painted :
import sys
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
class Drawer(QWidget):
def __init__(self, parent=None):
QWidget.__init__(self, parent)
self.setAttribute(Qt.WA_StaticContents)
h = 400
w = 400
self.myPenWidth = 13
self.myPenColor = Qt.black
self.image = QImage(w, h, QImage.Format_RGB32)
self.path = QPainterPath()
self.clearImage()
def setPenColor(self, newColor):
self.myPenColor = newColor
def setPenWidth(self, newWidth):
self.myPenWidth = newWidth
def clearImage(self):
self.path = QPainterPath()
self.image.fill(Qt.white) ## switch it to else
self.update()
def saveImage(self, fileName, fileFormat):
self.image.save(fileName, fileFormat)
def paintEvent(self, event):
painter = QPainter(self)
painter.drawImage(event.rect(), self.image, self.rect())
def mousePressEvent(self, event):
self.path.moveTo(event.pos())
def mouseMoveEvent(self, event):
self.path.lineTo(event.pos())
p = QPainter(self.image)
p.setPen(QPen(self.myPenColor,
self.myPenWidth, Qt.SolidLine, Qt.RoundCap,
Qt.RoundJoin))
p.drawPath(self.path)
p.end()
self.update()
def sizeHint(self):
return QSize(300, 300)
if __name__ == '__main__':
app = QApplication(sys.argv)
w = QWidget()
btnSave = QPushButton("Save image")
btnClear = QPushButton("Clear")
drawer = Drawer()
w.setLayout(QVBoxLayout())
w.layout().addWidget(btnSave)
w.layout().addWidget(btnClear)
w.layout().addWidget(drawer)
btnSave.clicked.connect(lambda: drawer.saveImage("image.png", "PNG"))
btnClear.clicked.connect(drawer.clearImage)
w.show()
sys.exit(app.exec_())