I've taken the time to do a Minimal Reproducible Example of my issue.
Using PyQt5, I have a classic Main window, with a QGraphicsView linked to a QGraphicsScene.
On the scene, I have a NodeHover item (green circle) with a TreeNode child (square).
The NodeHover item is transparent by default, and becomes opaque when hovered.
I want its child item (TreeNode) to remain opaque all the time, no matter the parent's opacity. I've tried setting flags in both items, as you can see below, but they seem not to work.
import sys
from PyQt5.QtWidgets import QGraphicsScene, QGraphicsView, QMainWindow, QApplication, QToolBar, QStatusBar, QAction, QGraphicsItem, QGraphicsLineItem, QLabel, QGraphicsEllipseItem, QGraphicsTextItem
from PyQt5.QtCore import *
from PyQt5.QtGui import QPainter, QIcon, QPen, QBrush, QTransform
class NodeHover(QGraphicsItem):
def __init__(self, *args, **kwargs):
super(NodeHover, self).__init__(*args, **kwargs)
self.setOpacity(0.1)
self.setAcceptHoverEvents(True)
# Setting the flag so Item doesn't propagate its opacity to children
self.GraphicsItemFlag(QGraphicsItem.ItemDoesntPropagateOpacityToChildren)
def hoverEnterEvent(self, event):
self.setOpacity(1) # When mouse enters item, make it opaque
def hoverLeaveEvent(self, event):
self.setOpacity(0.1) # When mouse leaves item, make it transparent
def boundingRect(self):
return QRectF(0,0, 20, 20)
def paint(self, painter, option, widget):
painter.setBrush(QBrush(Qt.green))
painter.drawEllipse(QPointF(10, 10), 10, 10)
class TreeNode(QGraphicsItem):
def __init__(self, *args, **kwargs):
super(TreeNode, self).__init__(*args, **kwargs)
# Setting the flag to ignore parent's opacity
self.GraphicsItemFlag(QGraphicsItem.ItemIgnoresParentOpacity)
def boundingRect(self):
return QRectF(100, 100, 100, 100)
def paint(self, painter, option, widget):
painter.drawRect(110,110,80,80)
class GView(QGraphicsView):
def __init__(self, parent, *args, **kwargs):
super().__init__(*args, **kwargs)
self.parent = parent
self.setGeometry(100, 100, 700, 450)
self.show()
class Scene(QGraphicsScene):
def __init__(self, parent):
super().__init__(parent)
self.parent = parent
hoverItem = NodeHover() # create a NodeHover item
self.addItem(hoverItem)
nodeItem = TreeNode() # create a TreeNode item and make it hoverItem's child
nodeItem.setParentItem(hoverItem)
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setGeometry(900, 70, 1000, 800)
self.createGraphicView()
self.show()
def createGraphicView(self):
self.scene = Scene(self)
gView = GView(self)
scene = Scene(gView)
gView.setScene(scene)
# Set the main window's central widget
self.setCentralWidget(gView)
# Run program
if __name__ == '__main__':
app = QApplication(sys.argv)
window = MainWindow()
sys.exit(app.exec_())
To set a flag to the QGraphicsItem you must use the setFlag() method:
self.setFlag(QGraphicsItem.ItemDoesntPropagateOpacityToChildren)
self.setFlag(QGraphicsItem.ItemIgnoresParentOpacity)
Note: One of those flags is enough.
Related
Apparently, a widget inside a QGraphicsView does not scale correctly on macOS. Does anyone have a workaround?
Problem:
I have a QGraphicsView where I am zooming by calling the scale method.
I also have some QWidgets in my QGraphicScene, but their UI graphics do not scale accordingly when I zoom.
I use a QGraphicsProxyWidget from within a QGraphicsItem to display the widgets, but the scale issue persists even by using the scene.addWidget.
I tested the code on macOS 10.15.7 and 10.14.6, both Intel CPUs.
Edit: As pointed out in the comments by musicamante, the QGraphicsItem.ItemIgnoresTransformations does the exact opposite, so I am taking the reference out of the question to avoid confusion since it was only a failed attempt.
Edit2: On Windows 10 seems to work fine.
import sys
from PySide2.QtCore import Qt
from PySide2.QtGui import QPainterPath, QColor, QBrush
from PySide2.QtWidgets import (
QGraphicsItem,
QPushButton,
QGraphicsScene,
QWidget,
QVBoxLayout,
QMainWindow,
QApplication,
QGraphicsView,
QGraphicsProxyWidget
)
class NodeGraphicsContent(QGraphicsProxyWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.setWidget(QPushButton('QGraphicsItem Button'))
class GraphicsItem(QGraphicsItem):
def __init__(self):
super().__init__()
self.item_body = self.draw_item()
self.draw_content()
def draw_content(self):
NodeGraphicsContent(self)
def draw_item(self):
path = QPainterPath()
path.addRect(0, 0, 200, 200)
return path
def paint(self, painter, option, widget):
painter.setPen(Qt.NoPen)
painter.setBrush(QBrush(QColor("#FF313131")))
painter.drawPath(self.item_body)
def boundingRect(self):
return self.item_body.boundingRect()
class GraphicScene(QGraphicsScene):
def __init__(self, parent=None):
super().__init__(parent)
self.scene_width = 500
self.scene_height = 500
self._grid_pattern = QBrush(QColor("#282828"), Qt.Dense7Pattern)
self.setBackgroundBrush(QColor("#393939"))
self.setSceneRect(-self.scene_width // 2, -self.scene_height // 2,
self.scene_width, self.scene_height)
def drawBackground(self, painter, rect):
super().drawBackground(painter, rect)
painter.fillRect(rect, self._grid_pattern)
class GraphicsView(QGraphicsView):
def __init__(self, parent=None):
super().__init__(parent)
self.setTransformationAnchor(QGraphicsView.AnchorUnderMouse)
def wheelEvent(self, event):
"""Zoom function."""
zoom_factor = 1.25
zoom_out = 1 / zoom_factor
zoom_factor = zoom_factor if event.delta() > 0 else zoom_out
self.scale(zoom_factor, zoom_factor)
class MainWidgets(QWidget):
def __init__(self):
super().__init__()
self.scene = GraphicScene()
self.view = GraphicsView(self.scene)
_layout = QVBoxLayout()
_layout.addWidget(self.view)
self.setLayout(_layout)
self.add_widgets()
def add_widgets(self):
"""Add first a normal widget, then a QGraphicsItem,
which both dont seem to scale correctly.
"""
button = self.scene.addWidget(QPushButton('Widget Button'))
button.setPos(-50, -100)
self.scene.addItem(GraphicsItem())
class MainWindow(QMainWindow):
def __init__(self):
QMainWindow.__init__(self)
self.setCentralWidget(MainWidgets())
self.statusBar().showMessage('Zoom with mouse wheel')
app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec_()
Currently, my code can paint an ellipse but every time I click the mouse for a new ellipse the previous ellipse will not retain .
I am used the **QGraphicsView** Framework because I am using its pantool and zoom function but I have a hard time adding the paint function.
import sys
from PyQt5.QtCore import Qt, QRectF, QPointF
from PyQt5.QtGui import QPixmap, QTransform, QBrush, QColor, QPen, QPainterPath
from PyQt5.QtWidgets import QApplication, QMainWindow, QWidget, QVBoxLayout, QGraphicsView,
QGraphicsScene, QGraphicsPixmapItem, QSizePolicy, QSpacerItem, QGraphicsObject
class MouseBrushObject(QGraphicsObject):
def __init__(self):
QGraphicsObject.__init__(self)
self._size = 10
self._x = 0
self._y = 0
self._pen = None
self._brush = None
self._color = None
self.setColor(QColor(255, 0, 0, 255))
def paint(self, painter, option, widget):
rect = self.boundingRect()
painter.setPen(self._pen)
painter.setBrush(self._brush)
painter.drawEllipse(QPointF(self._x,self._y), 5,5)
def boundingRect(self):
return QRectF(self._x, self._y, self._size, self._size)
def setColor(self, color):
self._color = color
self._pen = QPen(self._color, 1)
self._brush = QBrush(QColor(self._color.red(), self._color.green(), self._color.blue(), 40))
def setSize(self, size):
self._size = size
def setPosition(self, pos):
self._x = pos.x() - pos.x()/2
self._y = pos.y() - pos.y()/2
self.setPos(QPointF(self._x, self._y))
class View(QGraphicsView):
def __init__(self, parent=None):
QGraphicsView.__init__(self, parent=parent)
self.setMouseTracking(True)
self.scene = QGraphicsScene(self)
self.setScene(self.scene)
pixmap = QPixmap(300, 300)
self.scene.addItem(QGraphicsPixmapItem(pixmap))
#self.setTransform(QTransform().scale(1, 1).rotate(0))
self.scene.setBackgroundBrush(QBrush(Qt.lightGray))
self._brushItem = MouseBrushObject()
self.path = QPainterPath()
def mousePressEvent(self, event):
pos = event.pos()
self._brushItem.setPosition(pos)
def enterEvent(self, event):
self.scene.addItem(self._brushItem)
return super(View, self).enterEvent(event)
def leaveEvent(self, event):
self.scene.removeItem(self._brushItem)
return super(View, self).leaveEvent(event)
class Viewer(QWidget):
def __init__(self, parent=None):
QWidget.__init__(self, parent=parent)
layout = QVBoxLayout()
self.view = View(self)
self.setLayout(layout)
layout.addWidget(self.view)
class MainWindow(QMainWindow):
def __init__(self):
QMainWindow.__init__(self)
self.viewer = Viewer(self)
layout = QVBoxLayout()
layout.addWidget(self.viewer)
centralwidget = QWidget(self)
centralwidget.setLayout(layout)
self.setCentralWidget(centralwidget)
if __name__ == '__main__':
app = QApplication(sys.argv)
main = MainWindow()
main.show()
sys.exit(app.exec_())
Every time the mousePressEvent is trigger, it will paint an ellipse but the problem is that it will not retain the previous ellipse will adding the new ellipse.
My goal is for this code is two paint an ellipse when the mouse is clicked as it will serve as a point. Then The two points will be automatically connect using path. I have tried it will the qpainter function and it works but since I can't used qpaintEvent in QgraphicsView, I have a hard time executing the code.
I'm trying to code a widget that slightly increases in size on mouse-over and decreases when the mouse leaves again.
This is what I have come up with so far:
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from random import randrange
class MainWindow(QMainWindow):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.setFixedSize(500, 500)
cent_widget = QWidget()
self.setCentralWidget(cent_widget)
layout = QVBoxLayout()
cent_widget.setLayout(layout)
layout.addWidget(MyItem(), Qt.AlignCenter,
alignment=Qt.AlignCenter)
class MyItem(QLabel):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.setBaseSize(200, 250)
self.setMinimumSize(self.baseSize())
self.resize(self.baseSize())
self.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
self.setStyleSheet('background: #{:02x}{:02x}{:02x}'.format(
randrange(255), randrange(255), randrange(255)
))
# Animation
self._enlarged = False
self.zoom_factor = 1.2
self.anim = QPropertyAnimation(self, b'size')
self.anim.setEasingCurve(QEasingCurve.InOutSine)
self.anim.setDuration(250)
def enterEvent(self, event: QEvent) -> None:
self.resize_anim()
self._enlarged = True
def leaveEvent(self, event: QEvent) -> None:
self.resize_anim()
self._enlarged = False
def resize_anim(self):
if self._enlarged:
new_size = self.baseSize()
else:
new_size = QSize(
int(self.baseSize().width() * self.zoom_factor),
int(self.baseSize().height() * self.zoom_factor)
)
self.anim.setEndValue(new_size)
self.anim.start()
if __name__ == '__main__':
app = QApplication([])
window = MainWindow()
window.show()
app.exec()
It's almost working the way I want, my only problem is that the widget gets resized from the top-left corner instead of from the center.
How can I change that?
Instead of animating using the size property you should use the geometry property as it is relative to the parent widget, so you can animate its geometry making the center remain invariant.
from PyQt5.QtCore import (
QAbstractAnimation,
QEasingCurve,
QEvent,
QPropertyAnimation,
QRect,
Qt,
)
from PyQt5.QtGui import QColor
from PyQt5.QtWidgets import (
QApplication,
QLabel,
QMainWindow,
QSizePolicy,
QVBoxLayout,
QWidget,
)
import random
class MainWindow(QMainWindow):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.setFixedSize(500, 500)
cent_widget = QWidget()
self.setCentralWidget(cent_widget)
layout = QVBoxLayout(cent_widget)
layout.addWidget(MyItem(), alignment=Qt.AlignCenter)
class MyItem(QLabel):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.setBaseSize(200, 250)
self.setMinimumSize(self.baseSize())
self.resize(self.baseSize())
self.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
self.setStyleSheet(
"background: {}".format(QColor(*random.sample(range(255), 3)).name())
)
# Animation
self.zoom_factor = 1.2
self.anim = QPropertyAnimation(self, b"geometry")
self.anim.setEasingCurve(QEasingCurve.InOutSine)
self.anim.setDuration(250)
def enterEvent(self, event: QEvent) -> None:
initial_rect = self.geometry()
final_rect = QRect(
0,
0,
int(initial_rect.width() * self.zoom_factor),
int(initial_rect.height() * self.zoom_factor),
)
final_rect.moveCenter(initial_rect.center())
self.anim.setStartValue(initial_rect)
self.anim.setEndValue(final_rect)
self.anim.setDirection(QAbstractAnimation.Forward)
self.anim.start()
def leaveEvent(self, event: QEvent) -> None:
self.anim.setDirection(QAbstractAnimation.Backward)
self.anim.start()
if __name__ == "__main__":
app = QApplication([])
window = MainWindow()
window.show()
app.exec()
A picture is displayed with scrollbars on a window. We can draw on it. I want to add a menubar to the same window. I tried the following, it didn't work. Nothing is shown on the window when I run this.
#!/usr/bin/env python
from PyQt5.QtCore import (QLineF, QPointF, QRectF, Qt)
from PyQt5.QtGui import (QIcon, QBrush, QColor, QPainter, QPixmap)
from PyQt5.QtWidgets import (QAction, QMainWindow, QApplication, QGraphicsView, QGraphicsScene, QGraphicsItem,
QGridLayout, QVBoxLayout, QHBoxLayout,
QLabel, QLineEdit, QPushButton)
class TicTacToe(QGraphicsItem):
def __init__(self):
super(TicTacToe, self).__init__()
def paint(self, painter, option, widget):
painter.setPen(Qt.black)
painter.drawLine(0,100,300,100)
def boundingRect(self):
return QRectF(0,0,300,300)
def mousePressEvent(self, event):
pos = event.pos()
self.select(int(pos.x()/100), int(pos.y()/100))
self.update()
super(TicTacToe, self).mousePressEvent(event)
class MyGraphicsView(QGraphicsView):
def __init__(self):
super(MyGraphicsView, self).__init__()
scene = QGraphicsScene(self)
self.tic_tac_toe = TicTacToe()
scene.addItem(self.tic_tac_toe)
scene.addPixmap(QPixmap("exit.png"))
self.setScene(scene)
self.setCacheMode(QGraphicsView.CacheBackground)
self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOn)
self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn)
def keyPressEvent(self, event):
key = event.key()
if key == Qt.Key_R:
self.tic_tac_toe.reset()
super(MyGraphicsView, self).keyPressEvent(event)
class Example(QMainWindow):
def __init__(self):
super(Example, self).__init__()
self.y = MyGraphicsView()
self.initUI()
def initUI(self):
menubar = self.menuBar()
menu = menubar.addMenu('File')
db_action = menu.addAction("Open file")
self.setGeometry(30, 30, 30, 20)
self.setWindowTitle('Menubar')
self.show()
if __name__ == '__main__':
import sys
app = QApplication(sys.argv)
mainWindow = Example()
mainWindow.showFullScreen()
sys.exit(app.exec_())
A widget is shown in a window if it is a child of some component of the window, in your case self.y is not the child of Example, but only an attribute, a possible solution is to set it as centralWidget:
class Example(QMainWindow):
def __init__(self):
super(Example, self).__init__()
self.y = MyGraphicsView()
self.setCentralWidget(self.y)
self.initUI()
def initUI(self):
menubar = self.menuBar()
menu = menubar.addMenu('File')
db_action = menu.addAction("Open file")
self.setGeometry(30, 30, 30, 20)
self.setWindowTitle('Menubar')
self.show()
I am unable to get a QFrame to completely surround a QPushButton Like a Border. It only frames the top and left side of the button. I was wondering what I'm doing wrong with the frame.
import sys
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
class main(QWidget):
def __init__(self):
super().__init__()
layout1 = QVBoxLayout()
btn1 = QPushButton("Test")
frame = QFrame(btn1)
frame.setGeometry(btn1.geometry())
frame.setFrameShape(QFrame.Box)
frame.setFrameShadow(QFrame.Plain)
frame.setLineWidth(4)
layout1.addWidget(btn1)
self.setLayout(layout1)
if __name__ == '__main__':
app = QApplication(sys.argv)
window = main()
window.show()
sys.exit(app.exec_())
The problem is caused because the QFrame does not change the size, instead QPushButton does. In my solution I have resized every time the size is changed in the QPushButton
class FrameButton(QPushButton):
def __init__(self, *args, **kwargs):
QPushButton.__init__(self, *args, **kwargs)
self.frame = QFrame(self)
self.frame.setFrameShape(QFrame.Box)
self.frame.setFrameShadow(QFrame.Plain)
self.frame.setLineWidth(4)
def resizeEvent(self, event):
self.frame.resize(self.size())
QWidget.resizeEvent(self, event)
class main(QWidget):
def __init__(self):
super().__init__()
layout = QVBoxLayout(self)
layout.addWidget(FrameButton("Test"))