简体   繁体   English

如何使覆盖的 QGraphicsTextItem 可编辑和可移动?

[英]how to make an overriden QGraphicsTextItem editable & movable?

I am using PyQt and I'm trying to re-implement a QGraphicsTextItem , but it seems I'm missing something.我正在使用 PyQt 并且我正在尝试重新实现QGraphicsTextItem ,但似乎我错过了一些东西。

I would like to make the NodeTag item's text editable.我想让NodeTag项目的文本可编辑。 I have tried setting flags such as Qt.TextEditorInteraction and QGraphicsItem.ItemIsMovable , but those seem to be ignored...我曾尝试设置诸如Qt.TextEditorInteractionQGraphicsItem.ItemIsMovable 之类的标志,但这些似乎被忽略了......

Here is a Minimal Reproducible Example :这是一个最小的可重现示例:

import sys
from PyQt5.QtWidgets import QGraphicsScene, QGraphicsView, QMainWindow, QApplication, QGraphicsItem, QGraphicsTextItem

from PyQt5.QtCore import *
from PyQt5.QtGui import  QPen
    

class NodeTag(QGraphicsTextItem):
    def __init__(self,text):
        QGraphicsTextItem.__init__(self,text)
        self.text = text
        self.setPos(0,0)
        self.setTextInteractionFlags(Qt.TextEditorInteraction)
        # self.setFlag(QGraphicsItem.ItemIsFocusable, True)   # All these flags are ignored...
        # self.setFlag(QGraphicsItem.ItemIsSelectable, True)
        self.setFlag(QGraphicsItem.ItemIsMovable, True)

    def boundingRect(self):
        return QRectF(0,0,80,25)

    def paint(self,painter,option,widget):
        painter.setPen(QPen(Qt.blue, 2, Qt.SolidLine))
        painter.drawRect(self.boundingRect())
        painter.drawText(self.boundingRect(),self.text)
    
    def mousePressEvent(self, event):
        print("CLICK!")
        # self.setTextInteractionFlags(Qt.TextEditorInteraction) # make text editable on click
        # self.setFocus()


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
        tagItem = NodeTag("myText")   # create a NodeTag item
        self.addItem(tagItem)


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()  # create default constructor for QWidget
        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_())

As you can see I have tried overriding the mousePressEvent and setting flags there too, but no luck so far.正如你所看到的,我已经尝试覆盖 mousePressEvent 并在那里设置标志,但到目前为止没有运气。 Any help appreciated!任何帮助表示赞赏!

Try it:尝试一下:

...

        
class NodeTag(QGraphicsTextItem):
    def __init__(self, text, parent=None):
        super(NodeTag, self).__init__(parent)
        self.text = text
        self.setPlainText(text)

        self.setFlag(QGraphicsItem.ItemIsMovable)
        self.setFlag(QGraphicsItem.ItemIsSelectable)

    def focusOutEvent(self, event):
        self.setTextInteractionFlags(QtCore.Qt.NoTextInteraction)
        super(NodeTag, self).focusOutEvent(event)

    def mouseDoubleClickEvent(self, event):
        if self.textInteractionFlags() == QtCore.Qt.NoTextInteraction:
            self.setTextInteractionFlags(QtCore.Qt.TextEditorInteraction)
        super(NodeTag, self).mouseDoubleClickEvent(event)
       
    def paint(self,painter,option,widget):
        painter.setPen(QPen(Qt.blue, 2, Qt.SolidLine))
        painter.drawRect(self.boundingRect())
#        painter.drawText(self.boundingRect(),self.text)
        super().paint(painter, option, widget)
 ...

在此处输入图片说明

All QGraphicsItem subclasses have a paint method, and all items that paint some contents have that method overridden so that they can actually paint themselves.所有 QGraphicsItem 子类都有一个paint方法,并且所有绘制某些内容的项目都覆盖了该方法,以便它们可以实际绘制自己。

The mechanism is the same as standard QWidgets, for which there is a paintEvent (the difference is that paint of QGraphicsItem receives an already instanciated QPainter), so if you want to do further painting other than what the class already provides, the base implementation must be called.该机制是一样的标准QWidgets,对其中有一个paintEvent (不同的是, paint的QGraphicsItem的接收已经实例化了QPainter),因此,如果您想进一步做画比类已经提供,基本实现必须叫做。

Consider that painting always happen from bottom to top, so everything that needs to be drawn behind the base painting has to be done before calling super().paint() , and everything that is going to be drawn in front of the default painting has to be placed after .考虑到绘画总是从下到上发生,所以所有需要在基础绘画后面绘制的东西都必须调用super().paint()之前完成,并且所有将要绘制默认绘画前面的东西都有之后放置。

Depending on the situation, overriding might require that the default base implementation is called anyway, and that's important in your case for boundingRect too.根据情况,覆盖可能需要调用默认的基本实现,这对于boundingRect也很重要。 QGraphicsTextItem automatically resizes itself when its contents change, so you should not always return a fixed QRect. QGraphicsTextItem 在内容改变时会自动调整大小,所以你不应该总是返回一个固定的 QRect。 If you need to have a minimum size, the solution is to merge a minimum rectangle with those provided by the default boundingRect() function.如果需要最小尺寸,解决方案是将最小矩形默认boundingRect()函数提供的矩形合并。

Then, editing on a QGraphicsTextItem happens when the item gets focused , but since you also want to be able to move the item, things get trickier as both actions are based on mouse clicks.然后,在 QGraphicsTextItem 上的编辑发生在项目获得焦点时,但由于您还希望能够移动项目,事情变得更加棘手,因为这两个操作都基于鼠标点击。 If you want to be able to edit the text with a single click, the solution is to make the item editable only when the mouse button has been released and has not been moved by some amount of pixels (thestartDragDistance() property), otherwise the item is moved with the mouse.如果您希望能够通过单击来编辑文本,解决方案是在释放鼠标按钮并且没有移动一定数量的像素(startDragDistance()属性)时才使项目可编辑,否则项目随鼠标移动。 This obviously makes the ItemIsMovable flag useless, as we're going to take care of the movement internally.这显然使ItemIsMovable标志无用,因为我们将在内部处理移动。

Finally, since a minimum size is provided, we also need to override the shape() method in order to ensure that collision and clicks are correctly mapped, and return a QPainterPath that includes the whole bounding rect (for normal QGraphicsItem that should be the default behavior, but that doesn't happen with QGraphicsRectItem).最后,由于提供了最小尺寸,我们还需要覆盖shape()方法以确保正确映射碰撞和点击,并返回包含整个边界矩形的 QPainterPath(对于应该是默认值的普通 QGraphicsItem行为,但 QGraphicsRectItem 不会发生这种情况)。

Here's a full implementation of what described above:这是上述内容的完整实现:

class NodeTag(QGraphicsTextItem):
    def __init__(self, text):
        QGraphicsTextItem.__init__(self, text)
        self.startPos = None
        self.isMoving = False
        # the following is useless, not only because we are leaving the text
        # painting to the base implementation, but also because the text is 
        # already accessible using toPlainText() or toHtml()
        #self.text = text
        # this is unnecessary too as all new items always have a (0, 0) position
        #self.setPos(0, 0)

    def boundingRect(self):
        return super().boundingRect() | QRectF(0, 0, 80, 25)

    def paint(self, painter, option, widget):
        # draw the border *before* (as in "behind") the text
        painter.setPen(QPen(Qt.blue, 2, Qt.SolidLine))
        painter.drawRect(self.boundingRect())
        super().paint(painter, option, widget)

    def shape(self):
        shape = QPainterPath()
        shape.addRect(self.boundingRect())
        return shape

    def focusOutEvent(self, event):
        # this is required in order to allow movement using the mouse
        self.setTextInteractionFlags(Qt.NoTextInteraction)

    def mousePressEvent(self, event):
        if (event.button() == Qt.LeftButton and 
            self.textInteractionFlags() != Qt.TextEditorInteraction):
                self.startPos = event.pos()
        else:
            super().mousePressEvent(event)

    def mouseMoveEvent(self, event):
        if self.startPos:
            delta = event.pos() - self.startPos
            if (self.isMoving or 
                delta.manhattanLength() >= QApplication.startDragDistance()):
                    self.setPos(self.pos() + delta)
                    self.isMoving = True
                    return
        super().mouseMoveEvent(event)

    def mouseReleaseEvent(self, event):
        if (not self.isMoving and 
            self.textInteractionFlags() != Qt.TextEditorInteraction):
                self.setTextInteractionFlags(Qt.TextEditorInteraction)
                self.setFocus()
                # the following lines are used to correctly place the text 
                # cursor at the mouse cursor position
                cursorPos = self.document().documentLayout().hitTest(
                    event.pos(), Qt.FuzzyHit)
                textCursor = self.textCursor()
                textCursor.setPosition(cursorPos)
                self.setTextCursor(textCursor)

        super().mouseReleaseEvent(event)
        self.startPos = None
        self.isMoving = False

As a side note, remember that QGraphicsTextItem supports rich text formatting, so even if you want more control on the text painting process you should not use QPainter.drawText() , because you'd only draw the plain text .作为旁注,请记住 QGraphicsTextItem 支持富文本格式,因此即使您想对文本绘制过程进行更多控制,也不应使用QPainter.drawText() ,因为您只会绘制纯文本 In fact, QGraphicsTextItem draws its contents using the drawContents() function of the underlying text document .实际上, QGraphicsTextItem 使用底层文本文档drawContents()函数绘制其内容。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM