我正在try 创建带有边框的多边形,并在鼠标光标位于其上方时更改其 colored颜色 .当我创建多个多边形并将边界大小设置为大于4时出现问题.第一个多边形运行正常,但对于其他多边形边界的内半部分似乎被视为不属于该多边形,因为在到达该位时会触发hoverLeaveEvent().

我可以不画边框,而是在已有的边框上使用额外的多边形,或者绘制线条,但这会变得有点混乱.我想知道是否有一种方法可以在不创建额外项目的情况下解决这个问题.

以下是显示该问题的一小段示例代码.如果设置边框宽度>5,则效果更好

from PyQt6.QtWidgets import QMainWindow, QGraphicsScene, QGraphicsView, QGraphicsPolygonItem, QApplication
from PyQt6.QtGui import QColor, QPolygonF, QBrush, QPen
from PyQt6.QtCore import QPointF

class Polygon(QGraphicsPolygonItem):
    def __init__(self, parent):
        super().__init__(parent)
        self.setBrush(QBrush(QColor(255, 0, 0, 120)))
        self.setPen(QPen(QColor(255, 0, 0), 10))
        self.setAcceptHoverEvents(True)

    def hoverEnterEvent(self, event):
        self.setBrush(QBrush(QColor(255, 0, 0, 250)))

    def hoverLeaveEvent(self, event):
        self.setBrush(QBrush(QColor(255, 0, 0, 120)))

class MyWindow(QMainWindow):
    def __init__(self):
        super(MyWindow, self).__init__()
        self.setGeometry(200, 0, 500, 600)

        self.canvas = QGraphicsView()
        self.canvas.setScene(QGraphicsScene(self))

        polygon = QPolygonF([
            QPointF(0, 0),
            QPointF(100, 0),
            QPointF(100, 100),
            QPointF(0, 100)
            ])
        self.polygon_item = Polygon(polygon)
        self.canvas.scene().addItem(self.polygon_item)

        polygon = QPolygonF([
            QPointF(110, 110), 
            QPointF(150, 160), 
            QPointF(200, 250), 
            QPointF(200, 100)
            ])

        self.polygon_item = Polygon(polygon)
        self.canvas.scene().addItem(self.polygon_item)
    

        polygon = QPolygonF([
            QPointF(0, 200), 
            QPointF(0, 300), 
            QPointF(100, 300), 
            QPointF(100, 200)
            ])

        self.polygon_item = Polygon(polygon)
        self.canvas.scene().addItem(self.polygon_item)
        self.setCentralWidget(self.canvas)

if __name__ == '__main__':
    app = QApplication([])
    win = MyWindow()

    win.show()
    app.exec()

推荐答案

这实际上看起来像是一个错误,但它还需要进一步判断才能认识到问题的难度.

首先,问题不取决于创建顺序:如果将第一个多边形移动到其他任何一个多边形之后,问题仍然存在.

这个问题涉及以下几个方面:

  1. 最重要的问题是:这些多边形数不是closed
  2. 第一个和最后一个多边形虽然相似且为orthogonal(这使得碰撞检测更容易),但却有different个顶点顺序:虽然两个矩形都是从左上角顶点开始创建的,但第一个顶点顺序为clockwise(左上、右上、右下、左下),最后一个顶点顺序为counterclockwise(左上、左下、右下、右上);
  3. QGraphicsItem使用shape()函数进行冲突检测(包括鼠标悬停),在QAbstractGraphicsShapeItem子类(如QGraphicsPolygonItem)的情况下,该函数返回以项的"路径"开始并使用其pen()使用QGraphicsPathStroker调整的复合体QPainterPath,该复杂QPainterPath使用所提供的QPen创建另一个具有给定路径的"轮廓"的QPainterPath;

不幸的是,尽管QPainterPath功能强大且智能,但它并不完美:Qt开发人员必须在功能和性能之间取得平衡,因为Qt中使用了a lot个QPainterPath,因此要求非常轻便和快速.

结果是,你的最后shape()条道路相当复杂.try 将以下覆盖添加到您的Polygon类:

    def paint(self, qp, opt, widget=None):
        super().paint(qp, opt, widget)
        qp.save()
        qp.setPen(Qt.black)
        qp.drawPath(self.shape())
        qp.restore()

您将看到得到的shape()个路径比预期的要复杂得多:

Screenshot showing the paths of the above override

现在,问题出现了,因为QGraphicsScene使用QPainterPath contains()函数来检测鼠标光标是否位于图形项边界的within.对于如此复杂的路径,默认("快速")行为对于像您设置的那些非常大的轮廓可能会失败.

我不会深入到数学方面,但考虑到collision detection是众所周知的dilemma,处理它意味着在处理泛型API行为时必须做出一些利弊决定.QPolygons可能是凸的,甚至可能有相交的线:例如,如果一个多边形like this及其线太粗,您将如何处理它们?

image of intersected polygon shape

现在,假设您的多边形始终具有一致的顶点(这意味着绝对有no个交集,包括由笔宽引起的交集),并且您不会使用太多不太复杂的项目或形状,则有一个可能的解决方法:自己提供shape()个.

诀窍是得到默认返回的shape(),breakit in toSubpathPolygons(),并遍历它们,以判断它们中的哪个包含其他值.

不幸的是,不能确定哪个子路径多边形实际上属于笔划器的边界:它是第一个子路径多边形,但它可能不是,所以我们需要使用While循环仔细地迭代所有的子路径多边形.

注意,如上所述,多边形mustclosed.为了简单起见,我在__init__中创建了一个新的QPolygonF(可变对象永远不应该在另一个对象的初始化中更改),但如果您想以correct的方式进行操作,您应该添加另一个实现,即eventually返回一个闭合的多边形,如果需要,则返回only.

class Polygon(QGraphicsPolygonItem):
    def __init__(self, polygon):
        if not polygon.isClosed():
            polygon = QPolygonF(polygon)
            polygon.append(polygon[0])
        super().__init__(polygon)
        self.setBrush(QBrush(QColor(255, 0, 0, 120)))
        self.setPen(QPen(QColor(255, 0, 0), 10))
        self.setAcceptHoverEvents(True)

    def shape(self):
        shape = super().shape().simplified()
        polys = iter(shape.toSubpathPolygons(self.transform()))
        outline = next(polys)
        while True:
            try:
                other = next(polys)
            except StopIteration:
                break
            for p in other:
                # check if all points of the other polygon are *contained*
                # within the current (possible) "outline"
                if outline.containsPoint(p, Qt.WindingFill):
                    # the point is *inside*, meaning that the "other"
                    # polygon is probably an internal intersection
                    break
            else:
                # no intersection found, the "other" polygon is probably the
                # *actual* outline of the QPainterPathStroker
                outline = other
        path = QPainterPath()
        path.addPolygon(outline)
        return path

由于上面的计算对于复杂路径可能有些苛刻,您可以考虑仅在需要时构建它,并最终将其作为私有实例成员进行缓存,然后在任何可能影响它的更改时清除它(对于多边形项,将是笔和多边形):

class Polygon(QGraphicsPolygonItem):
    _cachedShape = None
    def setPen(self, pen):
        self._cachedShape = None
        super().setPen(pen)

    def setPolygon(self, polygon):
        self._cachedShape = None
        super().setPolygon(polygon)

    def shape(self):
        if not self._cachedShape:
            self._cachedShape = self._createShape()
        return self._cachedShape

    def _createShape(self):
        shape = super().shape().simplified()
        polys = iter(shape.toSubpathPolygons(self.transform()))
        outline = next(polys)
        while True:
            try:
                other = next(polys)
            except StopIteration:
                break
            for p in other:
                # check if all points of the other polygon are *contained*
                # within the current (possible) "outline"
                if outline.containsPoint(p, Qt.WindingFill):
                    # the point is *inside*, meaning that the "other"
                    # polygon is probably an internal intersection
                    break
            else:
                # no intersection found, the "other" polygon is probably the
                # *actual* outline of the QPainterPathStroker
                outline = other
        path = QPainterPath()
        path.addPolygon(outline)
        return path

Python相关问答推荐

多处理代码在while循环中不工作

将numpy数组存储在原始二进制文件中

Pandas 第二小值有条件

根据在同一数据框中的查找向数据框添加值

ModuleNotFound错误:没有名为flags.State的模块; flags不是包

Vectorize多个头寸的止盈/止盈回溯测试pythonpandas

PMMLPipeline._ fit()需要2到3个位置参数,但给出了4个位置参数

使用NeuralProphet绘制置信区间时出错

Polars asof在下一个可用日期加入

判断solve_ivp中的事件

* 动态地 * 修饰Python中的递归函数

Python Tkinter为特定样式调整所有ttkbootstrap或ttk Button填充的大小,适用于所有主题

交替字符串位置的正则表达式

根据客户端是否正在传输响应来更改基于Flask的API的行为

Python Mercury离线安装

Autocad使用pyautocad/comtypes将对象从一个图形复制到另一个图形

随机森林n_估计器的计算

如何将验证器应用于PYDANC2中的EACHY_ITEM?

有没有一种方法可以根据不同索引集的数组从2D数组的对称子矩阵高效地构造3D数组?

大Pandas 中的群体交叉融合