[英]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.TextEditorInteraction和QGraphicsItem.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 (the
startDragDistance()
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.