简体   繁体   中英

How to copy paste an Qgraphicsitem in pyqt5?

I have a problem with copy-pasting a QGraphicsitem in a scene. I have tried the following code but it is not working properly. If I tried to Paste the item, the first instance it is pasting correctly. For the second instance, it is removing the first instance item and pasting the second instance.

As of now, I have tried to get the path of the item in copy action and adding it to the scene in paste action.

@pos2 is my grid position

I just want to paste the copied item for n number of time until the new item is copied. Correct me if I am doing copy-paste in the wrong way.

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_())

Copying a QGraphicsItem is not a trivial task, in this case what can be done is to save the most important features and recreate another object using that information. In this case, a QDataStream can be used to serialize said information that can be easily transported to another application that knows how to decode it.

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_())

Update:

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_())

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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