当 QGraphicLayout 中的项目悬停在其他项目上时,使边框变粗

问题描述 投票:0回答:1

我在容器小部件中有多个由 QGraphicLayout 管理的 QGraphicItem。

QGraphicItems 位于 QGraphicsItemGroup 内

我想实现当一个项目悬停在其他项目上时移动该项目时,使它们的边框更厚。有点强调它。

这是我当前状态的视频:https://streamable.com/104bdy

例如在随附的视频中:

  • 首先拖动

    setNetworkOffline
    时,
    awaitMapPage
    的上边框可能是粗体

  • 当该方法被向下拖动时,一旦到达放下的位置,它就会最终位于:

    awaitMapPage
    /
    clickOnLevelPin
    之间,它们之间的边框会变成粗体

  • setNetworkOffline
     进一步拖动到下方时,
    clickOnLevelPin
     / 
    sleepMillis
     之间的边框可以变为粗体等。

我的容器类

创建的所有新项目都会自动生成唯一的node_id

class Container(QGraphicsWidget):
    def __init__(self, node_id, parent=None):
        super(Container, self).__init__(parent)
        self._id = node_id
        self.layout = QGraphicsGridLayout()
        self.setLayout(self.layout)
        self.layout.setContentsMargins(0, 0, 0, 0)
        self.layout.setSpacing(0)
        self.prev = None
        self.setHandlesChildEvents(False)
        self.setFlag(QGraphicsItem.ItemIsMovable, False)
        self.setFlag(QGraphicsWidget.ItemIsMovable, True)
        self.setFlag(QGraphicsWidget.ItemIsSelectable, True)
        self.acceptTouchEvents()
        self.setZValue(0)

我的物品类别

class CustomItem(GraphNodeItem):
    def __init__(self, nodeId, parent, width):
        super(CustomItem, self).__init__(nodeId, parent)

        self.width = width
        self.prev = None
        self.parent = parent

    def mousePressEvent(self, mouse_event):
        if mouse_event.button() == Qt.LeftButton:
            self.prev = self.pos()
            color = QGraphicsColorizeEffect()
            color.setColor(Qt.darkGreen)
            if type(self) is CustomItem:
                self.setGraphicsEffect(color)
            super().mousePressEvent(mouse_event)

    def mouseReleaseEvent(self, event):
        self.graphicsEffect().setEnabled(False)
        super().mouseReleaseEvent(event)
        if self.prev is not None and event.button() == Qt.LeftButton:
            colliding = self.collidingItems()
            hit = False
            for item in colliding:
                if type(item) is CustomItem:
                    print("Switch methods")
                    container = self.parent.get_container()
                    container.switch(self, item)
                    hit = True
                    break
            if not hit:
                self.setPos(self.prev)

    def itemChange(self, change, value):
        if change == QGraphicsItem.ItemPositionChange and self.scene():
            br = self.boundingRect().translated(value)
            sceneRect = self.boundingRect()


            if not sceneRect.contains(br):
                if br.right() > sceneRect.right():
                    br.moveRight(sceneRect.right())
                if br.x() < sceneRect.x():
                    br.moveLeft(sceneRect.x())
                if br.y() < sceneRect.y():
                    br.moveTop(sceneRect.top())
                return br.topLeft()

        return super().itemChange(change, value)

请问如何实现这个效果?有任何想法吗?预先感谢。

qt pyqt pyqt5 qgraphicsview qgraphicsitem
1个回答
0
投票

您需要做的事情最可接受的选择至少需要两个方面:

  1. 使用占位符项目来正确显示布局内的更改;
  2. 考虑子项之间的关系,包括它们在布局中的变化;

有很多可能的方法来实现你想要的,这深深地取决于对象结构;不幸的是,您提供的代码不足以理解这些要求,因此我将提供一个通用实现,它显示了一种“可能”的方法。 对象关系和交互是通过自定义信号来实现的,这些信号“告诉”父级

如果

他们将被移动,何时他们被移动,以及之后他们的移动可能已经结束。然后他们的容器将决定如何处理上述信号:

如果要移动托管项目,它将从布局中删除该项目并为其创建相关占位符;
  • 如果移动了项目,则根据当前项目和移动项目之间的关系,占位符也会在布局内随之移动;
  • 当项目“完成”其移动时,容器将删除占位符并将项目恢复到其布局中的最新可能位置;
  • 最后,根据项目移动的变化,父级最终将“通知”其他托管项目,以便它们根据与占位符相关的位置绘制更粗的线。

from PyQt5.QtCore import * from PyQt5.QtGui import * from PyQt5.QtWidgets import * class Container(QGraphicsWidget): _placeholder = _movingItem = None def __init__(self): super().__init__() layout = QGraphicsLinearLayout(Qt.Vertical, self) layout.setSpacing(1) for i in range(5): item = CustomItem('item {}'.format(i + 1)) layout.addItem(item) item.moveStarted.connect(self.moveStarted) item.moving.connect(self.childIsMoving) item.moveFinished.connect(self.moveFinished) def _indexOf(self, item): layout = self.layout() for i in range(layout.count()): if layout.itemAt(i) == item: return i return -1 def moveStarted(self, item): index = self._indexOf(item) if index < 0: return self._movingItem = item self.layout().removeItem(item) item.setParentItem(self) item.setProperty('_oldZValue', item.zValue()) item.setZValue(1) self._placeholder = Placeholder(item.size()) self.layout().insertItem(index, self._placeholder) def childIsMoving(self, item, pos): if not self._placeholder: return targetPos = item.boundingRect().translated(pos).center() targetIndex = 0 layout = self.layout() for i in range(layout.count()): geo = layout.itemAt(i).geometry() if targetPos.y() > geo.y(): targetIndex = i elif targetPos.y() < geo.y(): break layout.insertItem(targetIndex, self._placeholder) self.update() def moveFinished(self, item): if self._movingItem != item: return if self._placeholder: index = self._indexOf(self._placeholder) self.layout().removeItem(self._placeholder) self.scene().removeItem(self._placeholder) self.layout().insertItem(index, item) z = item.property('_oldZValue') if isinstance(z, float): item.setZValue(z) item.setProperty('_oldZValue', None) self._placeholder = None self._movingItem = None self.update() def _dragItemLines(self, child): top = bottom = False if self._placeholder: index = self._indexOf(child) placeholderIndex = self._indexOf(self._placeholder) if index >= 0 and placeholderIndex >= 0: if index == placeholderIndex - 1: bottom = True if index == placeholderIndex + 1: top = True return top, bottom def paint(self, qp, opt, widget=None): qp.drawRect(self.boundingRect()) class Placeholder(QGraphicsWidget): def __init__(self, size): self._size = size super().__init__() def sizeHint(self, *args): return self._size def paint(self, qp, opt, widget=None): qp.save() qp.setPen(Qt.red) qp.drawRect(self.boundingRect()) qp.restore() class TextItem(QGraphicsLayoutItem): def __init__(self, text): super().__init__() self.textItem = QGraphicsSimpleTextItem(text) self.setGraphicsItem(self.textItem) def setGeometry(self, *args): super().setGeometry(*args) br = self.textItem.boundingRect() br.moveCenter(self.geometry().center()) self.textItem.setPos(br.topLeft()) def sizeHint(self, which, constraint): sz = self.textItem.boundingRect().size() if which == Qt.MaximumSize: sz.setWidth(2000) return sz class CustomItem(QGraphicsWidget): moving = pyqtSignal(object, QPointF) moveStarted = pyqtSignal(object) moveFinished = pyqtSignal(object) basePen = QPen(Qt.blue) dragPen = QPen(Qt.green, 4) _background = QColor(127, 127, 127, 127) _isMoving = False def __init__(self, text): super().__init__() self.setFlag(self.ItemIsMovable) self.setFlag(self.ItemSendsGeometryChanges) layout = QGraphicsLinearLayout(Qt.Vertical, self) self.textItem = TextItem(text) layout.addItem(self.textItem) def itemChange(self, change, value): if change == self.ItemPositionChange and self._isMoving: value.setX(self.x()) self.moving.emit(self, value) return super().itemChange(change, value) def mousePressEvent(self, event): super().mousePressEvent(event) if event.button() == Qt.LeftButton: self._isMoving = True self.moveStarted.emit(self) def mouseReleaseEvent(self, event): super().mouseReleaseEvent(event) if event.button() == Qt.LeftButton and self._isMoving: self._isMoving = False self.moveFinished.emit(self) def paint(self, qp, opt, widget=None): qp.setPen(self.basePen) qp.fillRect(self.boundingRect(), self._background) qp.drawRect(self.boundingRect()) parent = self.parentItem() if isinstance(parent, Container): drawTop, drawBottom = parent._dragItemLines(self) if drawTop: br = self.boundingRect() qp.setPen(self.dragPen) qp.drawLine(br.topLeft(), br.topRight()) if drawBottom: br = self.boundingRect() qp.setPen(self.dragPen) qp.drawLine(br.bottomLeft(), br.bottomRight()) if __name__=='__main__': import sys app = QApplication(sys.argv) scene = QGraphicsScene() scene.addItem(Container()) view = QGraphicsView(scene) view.resize(app.primaryScreen().size() / 2) view.setRenderHint(QPainter.Antialiasing) view.show() sys.exit(app.exec_())

如前所述,这只是“一个”可能的实现。例如,您可以在不同级别实现子项目(或其“拖动线”)的绘制。没有绝对好的方法来实现这一切,这一切都取决于很多方面(程序要求、对象结构、项目标准等),这些方面甚至可能从根本上改变这一切的实现方式。

此外,我强烈建议您耐心地花时间研究上面的代码

原样

,然后再尝试将其应用到您的程序中。

© www.soinside.com 2019 - 2024. All rights reserved.