Copy/Paste multiple items from QTableView in pyqt4?

Question:

We can select multiple items(partial rows and partial columns) from QTableView using self.tableView.setSelectionMode(QAbstractItemView.ExtendedSelection), but after selecting some rows and columns(partial and partial) if I do CTRL+C and paste it in notepad it only pastes one item(one value from the tableView)?

My Code:

tab_table_view = QtGui.QWidget()
self.Tab.insertTab(0, tab_table_view, self.File_Name)
self.tableView = QtGui.QTableView(tab_table_view)
self.tableView.setGeometry(QtCore.QRect(0, 0, 721, 571))
self.model = QtGui.QStandardItemModel(self)
self.model.setSortRole(QtCore.Qt.UserRole)
self.tableView.setModel(self.model)

    self.tableView.setSelectionMode(QAbstractItemView.ExtendedSelection) '''this helps for selecting multiple items but not able to copy and paste multiple values to a text/ excel (it only copies single value)'''

How can we do copy and paste multiple items?

Asked By: learncode

||

Answers:

QAbstractItemView.ExtendedSelection
When the user selects an item in the usual way, the selection is cleared and the new item selected. However, if the user presses the Ctrl key when clicking on an item, the clicked item gets toggled and all other items are left untouched. If the user presses the Shift key while clicking on an item, all items between the current item and the clicked item are selected or unselected, depending on the state of the clicked item. Multiple items can be selected by dragging the mouse over them.
you can also use

QAbstractItemView.MultiSelection
Answered By: Ari Gold
self.tableView.installEventFilters(self)

Now, adding event filter:

def eventFilter(self, source, event):
        if (event.type() == QtCore.QEvent.KeyPress and
            event.matches(QtGui.QKeySequence.Copy)):
            self.copySelection()
            return True
        return super(Window, self).eventFilter(source, event)

Copy Function:

def copySelection(self):
        selection = self.tableView.selectedIndexes()
        if selection:
            rows = sorted(index.row() for index in selection)
            columns = sorted(index.column() for index in selection)
            rowcount = rows[-1] - rows[0] + 1
            colcount = columns[-1] - columns[0] + 1
            table = [[''] * colcount for _ in range(rowcount)]
            for index in selection:
                row = index.row() - rows[0]
                column = index.column() - columns[0]
                table[row][column] = index.data()
            stream = io.StringIO()
            csv.writer(stream).writerows(table)
            QtGui.qApp.clipboard().setText(stream.getvalue())       
Answered By: learncode

Many thanks to @learncode comment above, I manage to get the copy part. Moverover, I modify a bit to support copy to Excel with corresponding order of cells:

def copySelection(self):
    selection = self.selectedIndexes()
    if selection:
        rows = sorted(index.row() for index in selection)
        columns = sorted(index.column() for index in selection)
        rowcount = rows[-1] - rows[0] + 1
        colcount = columns[-1] - columns[0] + 1
        table = [[''] * colcount for _ in range(rowcount)]
        for index in selection:
            row = index.row() - rows[0]
            column = index.column() - columns[0]
            table[row][column] = index.data()
        stream = io.StringIO()
        csv.writer(stream, delimiter='t').writerows(table)
        QtWidgets.qApp.clipboard().setText(stream.getvalue())
    return

In addition to copy, I also create the paste part. Depending the range of cell user selected (one cell or range of cell), this snippet supports pasting of different shape of data.

def pasteSelection(self):
    selection = self.selectedIndexes()
    if selection:
        model = self.model()

        buffer = QtWidgets.qApp.clipboard().text() 
        rows = sorted(index.row() for index in selection)
        columns = sorted(index.column() for index in selection)
        reader = csv.reader(io.StringIO(buffer), delimiter='t')
        if len(rows) == 1 and len(columns) == 1:
            for i, line in enumerate(reader):
                for j, cell in enumerate(line):
                    model.setData(model.index(rows[0]+i,columns[0]+j), cell)
        else:
            arr = [ [ cell for cell in row ] for row in reader]
            for index in selection:
                row = index.row() - rows[0]
                column = index.column() - columns[0]
                model.setData(model.index(index.row(), index.column()), arr[row][column])
    return
Answered By: Frederick Li

This answer extends @learncode and @Frederick Li’s answers to cater for the cases where

  1. The rows and columns are not in ascending order
  2. Some of the rows and/or columns are hidden in the view.

It also includes code to extend the event filter to handle pasting.

class TableView(QTableView):
    def __init__(self, *args, **kwargs):
        super(TableView, self).__init__(*args, **kwargs)
        self.installEventFilter(self)

    def eventFilter(self, source, event):
        if event.type() == QEvent.KeyPress and event.matches(QKeySequence.Copy):
            self.copy_selection()
            return True
        elif event.type() == QEvent.KeyPress and event.matches(QKeySequence.Paste):
            self.paste_selection()
            return True
        return super(TableView, self).eventFilter(source, event)

    def copy_selection(self):
        selection = self.selectedIndexes()
        if selection:
            all_rows = []
            all_columns = []
            for index in selection:
                if not index.row() in all_rows:
                    all_rows.append(index.row())
                if not index.column() in all_columns:
                    all_columns.append(index.column())
            visible_rows = [row for row in all_rows if not self.isRowHidden(row)]
            visible_columns = [
                col for col in all_columns if not self.isColumnHidden(col)
            ]
            table = [[""] * len(visible_columns) for _ in range(len(visible_rows))]
            for index in selection:
                if index.row() in visible_rows and index.column() in visible_columns:
                    selection_row = visible_rows.index(index.row())
                    selection_column = visible_columns.index(index.column())
                    table[selection_row][selection_column] = index.data()
            stream = io.StringIO()
            csv.writer(stream, delimiter="t").writerows(table)
            QApplication.clipboard().setText(stream.getvalue())

    def paste_selection(self):
        selection = self.selectedIndexes()
        if selection:
            model = self.model()

            buffer = QApplication.clipboard().text()
            all_rows = []
            all_columns = []
            for index in selection:
                if not index.row() in all_rows:
                    all_rows.append(index.row())
                if not index.column() in all_columns:
                    all_columns.append(index.column())
            visible_rows = [row for row in all_rows if not self.isRowHidden(row)]
            visible_columns = [
                col for col in all_columns if not self.isColumnHidden(col)
            ]

            reader = csv.reader(io.StringIO(buffer), delimiter="t")
            arr = [[cell for cell in row] for row in reader]
            if len(arr) > 0:
                nrows = len(arr)
                ncols = len(arr[0])
                if len(visible_rows) == 1 and len(visible_columns) == 1:
                    # Only the top-left cell is highlighted.
                    for i in range(nrows):
                        insert_rows = [visible_rows[0]]
                        row = insert_rows[0] + 1
                        while len(insert_rows) < nrows:
                            row += 1
                            if not self.isRowHidden(row):
                                insert_rows.append(row)                              
                    for j in range(ncols):
                        insert_columns = [visible_columns[0]]
                        col = insert_columns[0] + 1
                        while len(insert_columns) < ncols:
                            col += 1
                            if not self.isColumnHidden(col):
                                insert_columns.append(col)
                    for i, insert_row in enumerate(insert_rows):
                        for j, insert_column in enumerate(insert_columns):
                            cell = arr[i][j]
                            model.setData(model.index(insert_row, insert_column), cell)
                else:
                    # Assume the selection size matches the clipboard data size.
                    for index in selection:
                        selection_row = visible_rows.index(index.row())
                        selection_column = visible_columns.index(index.column())
                        model.setData(
                            model.index(index.row(), index.column()),
                            arr[selection_row][selection_column],
                        )
        return
Answered By: kinverarity

Copy and paste of single selection of multiple items can be done in c++ by overriding Keypressevent of QTableView

void CustomTableView::keyPressEvent(QKeyEvent *event)
{

    this->setSelectionBehavior(QAbstractItemView::SelectRows);
        this->setSelectionMode(QAbstractItemView::MultiSelection);
        QModelIndexList selectedRows = selectionModel()->selectedRows();
        QModelIndexList selectedColumns = selectionModel()->selectedColumns();
    if(event->key() == Qt::Key_Insert)
    {
        tblview_model->customInsertRows();
    }
    else if(event->key() == Qt::Key_Delete)
    {  if(!selectedRows.isEmpty())
            model()->removeRows(selectedRows.at(0).row(),
                                selectedRows.size());
    }
    else if(event->matches(QKeySequence::Paste)) {
        if(copyPasteEnabled == true){
            text = QApplication::clipboard()->text();
            if(!text.isEmpty()){
                tblview_model->pasteData(text);
            }
        }
    }

    if(!selectedIndexes().isEmpty()){
        if(event->matches(QKeySequence::Copy)){
            QString text;
            QStringList hdr_lst;
            const std::vector<std::string>& hdrs = tblview_model->getHeader();

            QItemSelectionRange range = selectionModel()->selection().first();
            if (!selectedColumns.isEmpty())
            {
                for (auto j = 0; j < selectedColumns.size(); ++j)
                {
                    std::string hd = hdrs[selectedColumns.at(j).column()];
                    if (std::find(hdrs.begin(), hdrs.end(), hd) != hdrs.end())
                        hdr_lst << QString::fromStdString(hd);
                }
                text += hdr_lst.join("t");
                text += "n";

                for (auto i = 0; i < tblview_model->rowCount(); ++i)
                {
                    QStringList rowContents;
                    for (auto j = 0; j < selectedColumns.size(); ++j)
                    {
                        if (!isColumnHidden(j)) {
                            rowContents << model()->index(i, selectedColumns.at(j).column()).data().toString();
                        }
                    }
                    text += rowContents.join("t");
                    text += "n";
                }
            }
            else if (!selectedRows.isEmpty())
            {
                for (auto h : hdrs)
                {
                    hdr_lst << QString::fromStdString(h);
                }

                text += hdr_lst.join("t");
                text += "n";
                //for (auto i = range.top(); i <= range.bottom(); ++i)
                for (auto i = 0; i < selectedRows.size(); ++i)
                {
                    QStringList rowContents;
                    for (auto j = 0; j <= tblview_model->columnCount(); ++j)
                    {
                        if (!isColumnHidden(j)) {
                            rowContents << model()->index(selectedRows.at(i).row(), j).data().toString();
                        }
                    }
                    text += rowContents.join("t");
                    text += "n";
                }
            }
            QApplication::clipboard()->setText(text);
        }
        else
            QTableView::keyPressEvent(event);
    }


}