在 QGraphicsItem 绘制方法中围绕所有对象/形状绘制边框

发布于 2025-01-19 13:12:43 字数 4346 浏览 4 评论 0原文

我想在 QGraphicsItem Paint 方法中的所有对象/形状周围绘制边框。 (绿色和红色圆圈是单独项目的一部分,因此在这种情况下它们不算数

我当前正在绘制 RoundedRects,但我正在寻找可扩展的解决方案,还可以支持更复杂的对象(星星、香蕉等);物体相互重叠的情况。

我有一个项目,当选择它时,会更改两个 RoundedRect 的边框颜色。 输入图片此处描述

在此处输入图像描述

我想要一个只有轮廓边框改变颜色的解决方案,而不是内部的颜色。

我认为可能有效的方法是使用 QGraphicsDropShadowEffect 或创建 QtCore.Qt.MaskOutColor 并以某种方式控制线条粗细。我之前通过放大重复的蒙版形状来完成此操作,但结果并不理想。所以我真的很想听听其他人的解决方案!

这是我的基本 QGraphicsItem

class Node(QtWidgets.QGraphicsItem):
    def __init__(self, scene, parent=None):
        super(Node, self).__init__(parent)

        scene.addItem(self)

        # Variables
        self.main_background_colour = QtGui.QColor(31, 176, 224)
        self.title_background_colour = QtGui.QColor("#fffeb3")
        self.name_background_colour = QtGui.QColor("#b8b64b")
        self.brush = QtGui.QBrush(self.main_background_colour)
        self.pen = QtGui.QPen(self.title_text_colour, 2)

        self.main_rect = QtCore.QRectF(0, 0, 400, 200)
        self.title_rect = QtCore.QRectF(self.main_rect.x() + (self.main_rect.width() * 0.05), self.main_rect.y() - 10, (self.main_rect.width() * 0.9), (self.main_rect.height() * 0.2))
        self.name_rect = QtCore.QRectF(self.main_rect.x() + (self.main_rect.width() * 0.02), self.title_rect.bottom() - 10, (self.main_rect.width() * 0.96), (self.main_rect.height() * 0.3))
        self.name_font_rect = QtCore.QRectF(self.name_rect.x() + (self.name_rect.width() * 0.05), self.name_rect.y() + 10, self.name_rect.width() * 0.9, self.name_rect.height() * 0.65)

        # Flags
        self.setFlag(self.ItemIsMovable, True)
        self.setFlag(self.ItemSendsGeometryChanges, True)
        self.setFlag(self.ItemIsSelectable, True)
        self.setFlag(self.ItemIsFocusable, True)

        self.setCacheMode(QtWidgets.QGraphicsItem.DeviceCoordinateCache)

    def boundingRect(self):
        return QtCore.QRectF(self.main_rect.x(), self.title_rect.y(), self.main_rect.width(), self.main_rect.height() + abs(self.main_rect.y() + self.title_rect.y()))

    def paint(self, painter, option, widget=None):

        # Border
        if self.isSelected():
            border_colour = QtGui.QColor(241, 175, 0)

        else:
            border_colour = self.main_background_colour.lighter()

        self.pen.setColor(border_colour)
        self.pen.setWidth(2)
        painter.setPen(self.pen)

        # Background
        self.brush.setColor(self.main_background_colour)
        painter.setBrush(self.brush)
        painter.drawRoundedRect(self.main_rect, 4, 4)

        # Name
        self.brush.setColor(self.name_background_colour)
        painter.setBrush(self.brush)
        self.pen.setColor(QtGui.QColor("black"))
        painter.setPen(self.pen)
        painter.drawRoundedRect(self.name_rect, 4, 4)


        # Tile
        self.brush.setColor(self.title_background_colour)
        painter.setBrush(self.brush)
        self.pen.setColor(border_colour)
        painter.setPen(self.pen)

        painter.drawRoundedRect(self.title_rect, 4, 4)

更新 我尝试过使用 musicamante 答案中所示的绘制方法,但它会单独在所有矩形周围绘制边框,而不是在整体外观造型。

我的基本代码的其余部分是相同的,但我更改了绘制以匹配 musicamante 回复。 输入图片这里的描述

def paint(self, painter, option, widget=None):

    path = QtGui.QPainterPath()
    path.setFillRule(QtCore.Qt.WindingFill)

    for rect in (self.main_rect, self.name_rect, self.title_rect):
        path.addRoundedRect(rect, 4, 4)

    if self.isSelected():
        border_colour = QtGui.QColor(241, 175, 0)
    else:
        border_colour = self.main_background_colour.lighter()

    painter.setPen(QtGui.QPen(border_colour, 2))
    painter.drawPath(path)

为了澄清,这是我想要实现的边界: 输入图片此处描述

I want to draw a border around all objects/shapes within a QGraphicsItem Paint method. (The green and red circles are part of a separate item, so they don't count in this situation)

I am currently drawing RoundedRects, but I'm looking for a scalable solution that could also support more complicated objects (stars, bananas, etc); situations where you have objects overlaid on each other.

I have an Item that, when selected, changes the border colour of two RoundedRects.
enter image description here

enter image description here

I want a solution where only the outline border changes colour, not the internal.

Possible methods I think might work are using a QGraphicsDropShadowEffect or creating a QtCore.Qt.MaskOutColor and controlling the line thickness, somehow. I've done this before by scaling up a duplicate masked shape, but the results aren't ideal. So I'd be really interested in hearing other people's solutions!

This is my basic QGraphicsItem.

class Node(QtWidgets.QGraphicsItem):
    def __init__(self, scene, parent=None):
        super(Node, self).__init__(parent)

        scene.addItem(self)

        # Variables
        self.main_background_colour = QtGui.QColor(31, 176, 224)
        self.title_background_colour = QtGui.QColor("#fffeb3")
        self.name_background_colour = QtGui.QColor("#b8b64b")
        self.brush = QtGui.QBrush(self.main_background_colour)
        self.pen = QtGui.QPen(self.title_text_colour, 2)

        self.main_rect = QtCore.QRectF(0, 0, 400, 200)
        self.title_rect = QtCore.QRectF(self.main_rect.x() + (self.main_rect.width() * 0.05), self.main_rect.y() - 10, (self.main_rect.width() * 0.9), (self.main_rect.height() * 0.2))
        self.name_rect = QtCore.QRectF(self.main_rect.x() + (self.main_rect.width() * 0.02), self.title_rect.bottom() - 10, (self.main_rect.width() * 0.96), (self.main_rect.height() * 0.3))
        self.name_font_rect = QtCore.QRectF(self.name_rect.x() + (self.name_rect.width() * 0.05), self.name_rect.y() + 10, self.name_rect.width() * 0.9, self.name_rect.height() * 0.65)

        # Flags
        self.setFlag(self.ItemIsMovable, True)
        self.setFlag(self.ItemSendsGeometryChanges, True)
        self.setFlag(self.ItemIsSelectable, True)
        self.setFlag(self.ItemIsFocusable, True)

        self.setCacheMode(QtWidgets.QGraphicsItem.DeviceCoordinateCache)

    def boundingRect(self):
        return QtCore.QRectF(self.main_rect.x(), self.title_rect.y(), self.main_rect.width(), self.main_rect.height() + abs(self.main_rect.y() + self.title_rect.y()))

    def paint(self, painter, option, widget=None):

        # Border
        if self.isSelected():
            border_colour = QtGui.QColor(241, 175, 0)

        else:
            border_colour = self.main_background_colour.lighter()

        self.pen.setColor(border_colour)
        self.pen.setWidth(2)
        painter.setPen(self.pen)

        # Background
        self.brush.setColor(self.main_background_colour)
        painter.setBrush(self.brush)
        painter.drawRoundedRect(self.main_rect, 4, 4)

        # Name
        self.brush.setColor(self.name_background_colour)
        painter.setBrush(self.brush)
        self.pen.setColor(QtGui.QColor("black"))
        painter.setPen(self.pen)
        painter.drawRoundedRect(self.name_rect, 4, 4)


        # Tile
        self.brush.setColor(self.title_background_colour)
        painter.setBrush(self.brush)
        self.pen.setColor(border_colour)
        painter.setPen(self.pen)

        painter.drawRoundedRect(self.title_rect, 4, 4)

UPDATE
I have tried using the paint method as shown in musicamante answer, but it draws a border around all Rects individually, rather than the overall exterior shape.

The rest of my base code is the same, but I have changed the paint to match musicamante reply.
enter image description here

def paint(self, painter, option, widget=None):

    path = QtGui.QPainterPath()
    path.setFillRule(QtCore.Qt.WindingFill)

    for rect in (self.main_rect, self.name_rect, self.title_rect):
        path.addRoundedRect(rect, 4, 4)

    if self.isSelected():
        border_colour = QtGui.QColor(241, 175, 0)
    else:
        border_colour = self.main_background_colour.lighter()

    painter.setPen(QtGui.QPen(border_colour, 2))
    painter.drawPath(path)

To clarify, this is the border I am trying to achieve:
enter image description here

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

遇见了你 2025-01-26 13:12:43

最简单(但不是最佳)的解决方案是创建一个 QPainterPath所有矩形,并绘制连接项目包含的所有形状的“最终”边框:

    def paint(self, painter, option, widget=None):
        # draw all items here, using default values
        # ...

        path = QtGui.QPainterPath()
        path.setFillRule(Qt.WindingFill)

        for rect in (self.main_rect, self.name_rect, self.title_rect):
            path.addRoundedRect(rect, 4, 4)

        if self.isSelected():
            border_colour = QtGui.QColor(241, 175, 0)
        else:
            border_colour = self.main_background_colour.lighter()

        painter.setPen(QtGui.QPen(border_colour, 2))
        painter.drawPath(path.simplified())

但请注意,绘画函数非常经常被调用。一定程度的缓存和现有的实现始终是首选,特别是考虑到 python 是一个巨大的瓶颈:您应该始终尝试利用 C++ 实现,可能使用现有的基本 QGraphicsItem 形状。

例如,您可以使用 QGraphicsPathItem 并为每个项目设置预定义的 QGraphicsPath,而不是总是从 python 绘制三个圆角矩形。

由于您已经设置了 ItemSendsGeometryChanges 标志,因此您可以重写 itemChange() 以在需要时更新该路径,这样您就不需要创建新的 QPainterPath 对象对于每个 paint() 调用。

如果这些项目用于显示文本,则将 QGraphicsPathItem 与 QGraphicsSimpleTextItem 设置为它的子级。

或者,考虑使用 QPicture 作为将使用的类/实例变量作为“中间缓存”对象:虽然它可能会在实现中增加一定程度的复杂性,但它肯定会提高性能,特别是在显示多个项目时。

The simplest (but yet not optimal) solution is to create a QPainterPath based on all the rectangles, and draw the "final" border joining all the shapes the item contains:

    def paint(self, painter, option, widget=None):
        # draw all items here, using default values
        # ...

        path = QtGui.QPainterPath()
        path.setFillRule(Qt.WindingFill)

        for rect in (self.main_rect, self.name_rect, self.title_rect):
            path.addRoundedRect(rect, 4, 4)

        if self.isSelected():
            border_colour = QtGui.QColor(241, 175, 0)
        else:
            border_colour = self.main_background_colour.lighter()

        painter.setPen(QtGui.QPen(border_colour, 2))
        painter.drawPath(path.simplified())

Be aware, though, that painting functions are called very often. Some level of caching and existing implementation is always preferred, especially considering that python is a huge bottleneck: you should always try to take advantage of the C++ implementation, possibly with existing base QGraphicsItem shapes.

For instance, instead of always painting three rounded rects from python, you could use a QGraphicsPathItem with a predefined QGraphicsPath set for each item.

Since you're already setting the ItemSendsGeometryChanges flag, you could override itemChange() to update that path whenever required, so that you don't need to create a new QPainterPath object for every paint() call.

And if those items are used to show text, then use a QGraphicsPathItem with a QGraphicsSimpleTextItem set as a child of it.

Alternatively, consider using a QPicture as a class/instance variable that would be used as a "middle-cache" object: while it might add some level of complexity in the implementation, it certainly would improve performance, especially when multiple items are being shown.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文