如何在 pyqt5 中复制粘贴 Qgraphicsitem?

How to copy paste an Qgraphicsitem in pyqt5?

我在场景中复制粘贴 QGraphicsitem 时遇到问题。 我尝试了以下代码,但它无法正常工作。 如果我尝试粘贴该项目,首先它会正确粘贴。对于第二个实例,它正在删除第一个实例项目并粘贴第二个实例。

截至目前,我已经尝试在复制动作中获取项目的路径并在粘贴动作中将其添加到场景中。

@pos2 是我的网格位置

我只想将复制的项目粘贴 n 次,直到复制新项目。如果我以错误的方式进行复制粘贴,请纠正我。

from PyQt5.QtCore import (QByteArray, QDataStream, QIODevice, QMimeData, QPointF, QPoint, Qt, QRect,QTimer,QLineF, QEvent,QRectF)
from PyQt5.QtGui import QColor, QDrag, QPainter, QPixmap,QFont,QFontMetrics,QBrush, QLinearGradient, QIcon, QPen, QPainterPath, QTransform,QCursor,QMouseEvent,QClipboard
from PyQt5.QtWidgets import QApplication,QGraphicsTextItem,QGraphicsItemGroup, QSizePolicy, QScrollArea, QPushButton,QLineEdit, QMainWindow,QInputDialog, QGraphicsPathItem,QDialog, QVBoxLayout,QGraphicsItem,QStatusBar,QTextEdit, QAction,QMenu, qApp,QSplitter, QButtonGroup, QToolButton, QFrame, QHBoxLayout, QGraphicsView, QGraphicsItem, QGraphicsPixmapItem, QLabel, QGraphicsScene, QWidget
class GraphicsSceneClass(QGraphicsScene):
    global selectedObjType
    def __init__(self, parent=None):
        super(GraphicsSceneClass, self).__init__(parent)

        self.setSceneRect(0, 0, 1920, 1080)
        self.setItemIndexMethod(QGraphicsScene.NoIndex)
        self.setBackgroundBrush(QBrush(Qt.black))

    def mousePressEvent(self, event):
            sampleTransform = QTransform()
            objectAtMouse = self.itemAt(event.scenePos(), sampleTransform)

            if objectAtMouse and event.button()== Qt.LeftButton:
                objectAtMouse.setSelected(True)

            elif objectAtMouse==None and event.button()==Qt.RightButton:
                self.grid = self.TargPosForLine(event.scenePos(), "ForLine")
    def TargPosForLine(self, position, mode):

        clicked_column = int((position.y() // 16)) * 16
        clicked_row = int((position.x() // 16)) * 16
        if clicked_column < 0:
            clicked_column = 0
        if clicked_row < 0:
            clicked_row = 0
        if(mode == "ForRect"):
            return QRect(clicked_row, clicked_column,16,16)
        elif(mode == "ForLine"):
            return QPointF(clicked_row,clicked_column)
class MainWindow(QMainWindow):
    global selectedObjType
    # global item
    def __init__(self,):
        super(MainWindow, self).__init__()
        self.scene = GraphicsSceneClass()
        MainWindow.obj = self.scene
        self.view = QGraphicsView(self.scene)
        self.view.setMouseTracking(True)
        self.view.setRenderHint(QPainter.HighQualityAntialiasing)
        self.widg = QWidget()
        self.horizontalLayout = QHBoxLayout()
        self.horizontalLayout.addWidget(self.view)
        self.widg.setMouseTracking(True)
        self.widget = QWidget()
        self.widget.setLayout(self.horizontalLayout)
        self.setCentralWidget(self.widget)
        self.obj=None

    def contextMenuEvent(self, event):
        contextMenu = QMenu(self)

        Cutaction = contextMenu.addAction("Cut")
        Coaction = contextMenu.addAction("Copy")
        Paaction = contextMenu.addAction("Paste")
        Propaction = contextMenu.addAction("draw")
        quitAct = contextMenu.addAction("quit")
        action = contextMenu.exec_(self.mapToGlobal(event.pos()))
        if action == quitAct:
            self.close()
        elif action == Propaction:
            painterPath = QPainterPath()

            painterPath.moveTo(10, 50.0)
            painterPath.lineTo(50,50)
            painterPath.lineTo(50,55)
            painterPath.lineTo(10,55)
            gradient = QLinearGradient(1, 1, 1, 5)
            gradient.setColorAt(0, QColor(Qt.gray))
            gradient.setColorAt(0.5, QColor(192, 192, 192, 255))
            gradient.setColorAt(1, QColor(Qt.darkGray))
            painterPath.closeSubpath()

            objectDrop = QGraphicsPathItem()
            objectDrop.setPath(painterPath)
            objectDrop.setBrush(QBrush(gradient))
            self.scene.addItem(objectDrop)
            objectDrop.setFlag(QGraphicsItem.ItemIsSelectable)
            objectDrop._type1="line"
            # self.scene.addPath(painterPath)


        elif action == Coaction:
            self.copy()
        elif action == Paaction:
            self.paste()

    def copy(self):
        item = self.selectedItem()
        self.dragObject = item
        x = str(self.dragObject._type1)
        if item is None:
            return
        if 'text' in x:
            self.object = QGraphicsPixmapItem(item.pixmap())
        else:

            self.object = QGraphicsPathItem(item.path())
            gradient = QLinearGradient(1, 1, 1, 5)
            gradient.setColorAt(0, QColor(Qt.gray))
            gradient.setColorAt(0.5, QColor(192, 192, 192, 255))
            gradient.setColorAt(1, QColor(Qt.darkGray))
            self.object.setBrush(QBrush(gradient))
            self.object.setPen(QPen(Qt.NoPen))
            self.object._type1 = item._type1


    def paste(self):
        self.scene.addItem(self.object)
        self.object.setPos(self.scene.grid.x(), self.scene.grid.y())


    def selectedItem(self):
        items = self.scene.selectedItems()

        if len(items) == 1:
            return items[0]
        return None
if __name__=="__main__":
    import sys
    app=QApplication(sys.argv)
    mainWindow = MainWindow()

    mainWindow.show()

    sys.exit(app.exec_())

复制 QGraphicsItem 不是一项简单的任务,在这种情况下,可以做的是保存最重要的特征并使用该信息重新创建另一个对象。在这种情况下,QDataStream 可用于序列化所述信息,这些信息可以轻松传输到另一个知道如何对其进行解码的应用程序。

import importlib
import random
from PyQt5 import QtCore, QtGui, QtWidgets

custom_mimeType = "application/x-qgraphicsitems"


def item_to_ds(it, ds):
    if not isinstance(it, QtWidgets.QGraphicsItem):
        return
    ds.writeQString(it.__class__.__module__)
    ds.writeQString(it.__class__.__name__)
    ds.writeInt(it.flags())
    ds << it.pos()
    ds.writeFloat(it.opacity())
    ds.writeFloat(it.rotation())
    ds.writeFloat(it.scale())
    if isinstance(it, QtWidgets.QAbstractGraphicsShapeItem):
        ds << it.brush() << it.pen()
    if isinstance(it, QtWidgets.QGraphicsPathItem):
        ds << it.path()


def ds_to_item(ds):
    module_name = ds.readQString()
    class_name = ds.readQString()
    mod = importlib.import_module(module_name)
    it = getattr(mod, class_name)()
    flags = QtWidgets.QGraphicsItem.GraphicsItemFlag(ds.readInt())
    pos = QtCore.QPointF()
    ds >> pos
    it.setFlags(flags)
    it.setPos(pos)
    it.setOpacity(ds.readFloat())
    it.setRotation(ds.readFloat())
    it.setScale(ds.readFloat())

    if isinstance(it, QtWidgets.QAbstractGraphicsShapeItem):
        pen, brush = QtGui.QPen(), QtGui.QBrush()
        ds >> brush
        ds >> pen
        it.setPen(pen)
        it.setBrush(brush)
    if isinstance(it, QtWidgets.QGraphicsPathItem):
        path = QtGui.QPainterPath()
        ds >> path
        it.setPath(path)
    return it


class GraphicsView(QtWidgets.QGraphicsView):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.setDragMode(QtWidgets.QGraphicsView.RubberBandDrag)
        self.setScene(
            QtWidgets.QGraphicsScene(QtCore.QRectF(-200, -200, 400, 400), self)
        )

        for _ in range(4):
            path = QtGui.QPainterPath()
            poly = QtGui.QPolygonF(
                [
                    QtCore.QPointF(0, -40),
                    QtCore.QPointF(-38, -12),
                    QtCore.QPointF(-24, 32),
                    QtCore.QPointF(24, 32),
                    QtCore.QPointF(38, -12),
                    QtCore.QPointF(0, -40),
                ]
            )
            path.addPolygon(poly)
            it = QtWidgets.QGraphicsPathItem(path)
            it.setBrush(QtGui.QColor(*random.sample(range(255), 3)))
            it.setPen(QtGui.QColor(*random.sample(range(255), 3)))
            self.scene().addItem(it)
            it.setPos(QtCore.QPointF(*random.sample(range(-100, 100), 2)))
            it.setFlags(
                it.flags()
                | QtWidgets.QGraphicsItem.ItemIsSelectable
                | QtWidgets.QGraphicsItem.ItemIsMovable
            )

        QtWidgets.QShortcut(
            QtGui.QKeySequence(QtGui.QKeySequence.Copy), self, activated=self.copy_items
        )
        QtWidgets.QShortcut(
            QtGui.QKeySequence(QtGui.QKeySequence.Paste),
            self,
            activated=self.paste_items,
        )

    @QtCore.pyqtSlot()
    def copy_items(self):
        mimedata = QtCore.QMimeData()
        ba = QtCore.QByteArray()
        ds = QtCore.QDataStream(ba, QtCore.QIODevice.WriteOnly)
        for it in self.scene().selectedItems():
            item_to_ds(it, ds)
        mimedata.setData(custom_mimeType, ba)
        clipboard = QtGui.QGuiApplication.clipboard()
        clipboard.setMimeData(mimedata)

    @QtCore.pyqtSlot()
    def paste_items(self):
        pos2 = QtCore.QPointF(40, 40)

        clipboard = QtGui.QGuiApplication.clipboard()
        mimedata = clipboard.mimeData()
        if mimedata.hasFormat(custom_mimeType):
            ba = mimedata.data(custom_mimeType)
            ds = QtCore.QDataStream(ba)
            while not ds.atEnd():
                it = ds_to_item(ds)
                self.scene().addItem(it)
                it.setPos(pos2)


if __name__ == "__main__":
    import sys

    app = QtWidgets.QApplication(sys.argv)
    w = GraphicsView()
    w.resize(640, 480)
    w.show()
    sys.exit(app.exec_())

更新:

import importlib
from PyQt5 import QtCore, QtGui, QtWidgets


custom_mimeType = "application/x-qgraphicsitems"


def item_to_ds(it, ds):
    if not isinstance(it, QtWidgets.QGraphicsItem):
        return
    ds.writeQString(it.__class__.__module__)
    ds.writeQString(it.__class__.__name__)
    ds.writeInt(it.flags())
    ds << it.pos()
    ds.writeFloat(it.opacity())
    ds.writeFloat(it.rotation())
    ds.writeFloat(it.scale())
    if isinstance(it, QtWidgets.QAbstractGraphicsShapeItem):
        ds << it.brush() << it.pen()
    if isinstance(it, QtWidgets.QGraphicsPathItem):
        ds << it.path()


def ds_to_item(ds):
    module_name = ds.readQString()
    class_name = ds.readQString()
    mod = importlib.import_module(module_name)
    it = getattr(mod, class_name)()
    flags = QtWidgets.QGraphicsItem.GraphicsItemFlag(ds.readInt())
    pos = QtCore.QPointF()
    ds >> pos
    it.setFlags(flags)
    it.setPos(pos)
    it.setOpacity(ds.readFloat())
    it.setRotation(ds.readFloat())
    it.setScale(ds.readFloat())

    if isinstance(it, QtWidgets.QAbstractGraphicsShapeItem):
        pen, brush = QtGui.QPen(), QtGui.QBrush()
        ds >> brush
        ds >> pen
        it.setPen(pen)
        it.setBrush(brush)
    if isinstance(it, QtWidgets.QGraphicsPathItem):
        path = QtGui.QPainterPath()
        ds >> path
        it.setPath(path)
    return it


class GraphicsScene(QtWidgets.QGraphicsScene):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.setSceneRect(0, 0, 1920, 1080)
        self.setItemIndexMethod(QtWidgets.QGraphicsScene.NoIndex)
        self.setBackgroundBrush(QtGui.QBrush(QtCore.Qt.black))

    def mousePressEvent(self, event):
        if event.button == QtCore.Qt.LeftButton:
            it = self.itemAt(event.scenePos())
            if it:
                it.setSelected(True)


class GraphicsView(QtWidgets.QGraphicsView):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.setScene(GraphicsScene(self))
        self.setRenderHint(QtGui.QPainter.HighQualityAntialiasing)

    def contextMenuEvent(self, event):
        menu = QtWidgets.QMenu(self)

        cut_action = menu.addAction("Cut")
        copy_action = menu.addAction("Copy")
        paste_action = menu.addAction("Paste")
        draw_action = menu.addAction("draw")
        quit_action = menu.addAction("quit")
        action = menu.exec_(self.mapToGlobal(event.pos()))
        if action == quit_action:
            self.window().close()
        elif action == draw_action:
            path = QtGui.QPainterPath()
            path.moveTo(-20, -2.5)
            path.lineTo(20, -2.5)
            path.lineTo(20, 2.5)
            path.lineTo(-20, 2.5)
            path.closeSubpath()

            gradient = QtGui.QLinearGradient(1, 1, 1, 5)
            gradient.setColorAt(0, QtGui.QColor(QtCore.Qt.gray))
            gradient.setColorAt(0.5, QtGui.QColor(192, 192, 192, 255))
            gradient.setColorAt(1, QtGui.QColor(QtCore.Qt.darkGray))

            item = QtWidgets.QGraphicsPathItem(path)
            item.setFlag(QtWidgets.QGraphicsItem.ItemIsSelectable)
            item.setBrush(QtGui.QBrush(gradient))
            self.scene().addItem(item)
            item.setPos(self.mapToScene(event.pos()))

        elif action == copy_action:
            self.copy_items()

        elif action == paste_action:
            self.paste_items(self.mapToScene(event.pos()))

    def copy_items(self):
        mimedata = QtCore.QMimeData()
        ba = QtCore.QByteArray()
        ds = QtCore.QDataStream(ba, QtCore.QIODevice.WriteOnly)
        for it in self.scene().selectedItems():
            item_to_ds(it, ds)
        mimedata.setData(custom_mimeType, ba)
        clipboard = QtGui.QGuiApplication.clipboard()
        clipboard.setMimeData(mimedata)

    def paste_items(self, pos):
        clipboard = QtGui.QGuiApplication.clipboard()
        mimedata = clipboard.mimeData()
        if mimedata.hasFormat(custom_mimeType):
            ba = mimedata.data(custom_mimeType)
            ds = QtCore.QDataStream(ba)
            while not ds.atEnd():
                it = ds_to_item(ds)
                self.scene().addItem(it)
                it.setPos(pos)


class MainWindow(QtWidgets.QMainWindow):
    def __init__(self, parent=None):
        super().__init__(parent)
        self._view = GraphicsView()
        self.setCentralWidget(self._view)


if __name__ == "__main__":
    import sys

    app = QtWidgets.QApplication(sys.argv)

    w = MainWindow()
    w.show()
    sys.exit(app.exec_())