C++ QTableView中的选定行,复制到QClipboard

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1230222/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-27 19:14:27  来源:igfitidea点击:

Selected Rows in QTableView, copy to QClipboard

c++qtqt4clipboardqtableview

提问by Berschi

I have a SQLite-Database and I did it into a QSqlTableModel. To show the Database, I put that Model into a QTableView.

我有一个 SQLite 数据库,我把它做成了一个QSqlTableModel. 为了显示数据库,我将该模型放入QTableView.

Now I want to create a Method where the selected Rows (or the whole Line) will be copied into the QClipboard. After that I want to insert it into my OpenOffice.Calc-Document.

现在我想创建一个方法,其中选定的行(或整行)将被复制到QClipboard. 之后我想将它插入到我的 OpenOffice.Calc-Document 中。

But I have no Idea what to do with the SelectedSIGNAL and the QModelIndexand how to put this into the Clipboard.

但我不知道如何处理Selected信号QModelIndex以及如何将其放入剪贴板。

回答by quark

To actually capture the selection you use the item view's selection modelto get a list of indices. Given that you have a QTableView *called viewyou get the selection this way:

要实际捕获选择,您可以使用项目视图的选择模型来获取索引列表。鉴于您有一个QTableView *电话,view您可以通过以下方式获得选择:

QAbstractItemModel * model = view->model();
QItemSelectionModel * selection = view->selectionModel();
QModelIndexList indexes = selection->selectedIndexes();

Then loop through the index list calling model->data(index)on each index. Convert the data to a string if it isn't already and concatenate each string together. Then you can use QClipboard.setTextto paste the result to the clipboard. Note that, for Excel and Calc, each column is separated from the next by a newline ("\n") and each row is separated by a tab ("\t"). You have to check the indices to determine when you move to the next row.

然后循环遍历索引列表,调用model->data(index)每个索引。如果尚未将数据转换为字符串并将每个字符串连接在一起。然后您可以使用QClipboard.setText将结果粘贴到剪贴板。请注意,对于 Excel 和 Calc,每一列与下一列由换行符 ("\n") 分隔,每行由制表符 ("\t") 分隔。您必须检查索引以确定何时移至下一行。

QString selected_text;
// You need a pair of indexes to find the row changes
QModelIndex previous = indexes.first();
indexes.removeFirst();
foreach(const QModelIndex &current, indexes)
{
    QVariant data = model->data(current);
    QString text = data.toString();
    // At this point `text` contains the text in one cell
    selected_text.append(text);
    // If you are at the start of the row the row number of the previous index
    // isn't the same.  Text is followed by a row separator, which is a newline.
    if (current.row() != previous.row())
    {
        selected_text.append('\n');
    }
    // Otherwise it's the same row, so append a column separator, which is a tab.
    else
    {
        selected_text.append('\t');
    }
    previous = current;
}
QApplication.clipboard().setText(selected_text);

Warning: I have not had a chance to try this code, but a PyQt equivalent works.

警告:我还没有机会尝试这个代码,但是 PyQt 等效的工作。

回答by Corwin Joy

I had a similar problem and ended up adapting QTableWidget (which is an extension of QTableView) to add copy/paste functionality. Here is the code which builds on what was provided by quark above:

我遇到了类似的问题,最终调整了 QTableWidget(它是 QTableView 的扩展)来添加复制/粘贴功能。这是建立在上面夸克提供的内容的代码:

qtablewidgetwithcopypaste.h

qtablewidgetwithcopypaste.h

// QTableWidget with support for copy and paste added
// Here copy and paste can copy/paste the entire grid of cells
#ifndef QTABLEWIDGETWITHCOPYPASTE_H
#define QTABLEWIDGETWITHCOPYPASTE_H

#include <QTableWidget>
#include <QKeyEvent>
#include <QWidget>

class QTableWidgetWithCopyPaste : public QTableWidget
{
    Q_OBJECT
public:
  QTableWidgetWithCopyPaste(int rows, int columns, QWidget *parent = 0) :
      QTableWidget(rows, columns, parent)
  {}

  QTableWidgetWithCopyPaste(QWidget *parent = 0) :
  QTableWidget(parent)
  {}

private:
  void copy();
  void paste();

public slots:
  void keyPressEvent(QKeyEvent * event);
};

#endif // QTABLEWIDGETWITHCOPYPASTE_H

qtablewidgetwithcopypaste.cpp

qtablewidgetwithcopypaste.cpp

#include "qtablewidgetwithcopypaste.h"
#include <QApplication>
#include <QMessageBox>
#include <QClipboard>
#include <QMimeData>

void QTableWidgetWithCopyPaste::copy()
{
    QItemSelectionModel * selection = selectionModel();
    QModelIndexList indexes = selection->selectedIndexes();

    if(indexes.size() < 1)
        return;

    // QModelIndex::operator < sorts first by row, then by column.
    // this is what we need
//    std::sort(indexes.begin(), indexes.end());
    qSort(indexes);

    // You need a pair of indexes to find the row changes
    QModelIndex previous = indexes.first();
    indexes.removeFirst();
    QString selected_text_as_html;
    QString selected_text;
    selected_text_as_html.prepend("<html><style>br{mso-data-placement:same-cell;}</style><table><tr><td>");
    QModelIndex current;
    Q_FOREACH(current, indexes)
    {
        QVariant data = model()->data(previous);
        QString text = data.toString();
        selected_text.append(text);
        text.replace("\n","<br>");
        // At this point `text` contains the text in one cell
        selected_text_as_html.append(text);

        // If you are at the start of the row the row number of the previous index
        // isn't the same.  Text is followed by a row separator, which is a newline.
        if (current.row() != previous.row())
        {
            selected_text_as_html.append("</td></tr><tr><td>");
            selected_text.append(QLatin1Char('\n'));
        }
        // Otherwise it's the same row, so append a column separator, which is a tab.
        else
        {
            selected_text_as_html.append("</td><td>");
            selected_text.append(QLatin1Char('\t'));
        }
        previous = current;
    }

    // add last element
    selected_text_as_html.append(model()->data(current).toString());
    selected_text.append(model()->data(current).toString());
    selected_text_as_html.append("</td></tr>");
    QMimeData * md = new QMimeData;
    md->setHtml(selected_text_as_html);
//    qApp->clipboard()->setText(selected_text);
    md->setText(selected_text);
    qApp->clipboard()->setMimeData(md);

//    selected_text.append(QLatin1Char('\n'));
//    qApp->clipboard()->setText(selected_text);
}

void QTableWidgetWithCopyPaste::paste()
{
    if(qApp->clipboard()->mimeData()->hasHtml())
    {
        // TODO, parse the html data
    }
    else
    {
        QString selected_text = qApp->clipboard()->text();
        QStringList cells = selected_text.split(QRegExp(QLatin1String("\n|\t")));
        while(!cells.empty() && cells.back().size() == 0)
        {
            cells.pop_back(); // strip empty trailing tokens
        }
        int rows = selected_text.count(QLatin1Char('\n'));
        int cols = cells.size() / rows;
        if(cells.size() % rows != 0)
        {
            // error, uneven number of columns, probably bad data
            QMessageBox::critical(this, tr("Error"),
                                  tr("Invalid clipboard data, unable to perform paste operation."));
            return;
        }

        if(cols != columnCount())
        {
            // error, clipboard does not match current number of columns
            QMessageBox::critical(this, tr("Error"),
                                  tr("Invalid clipboard data, incorrect number of columns."));
            return;
        }

        // don't clear the grid, we want to keep any existing headers
        setRowCount(rows);
        // setColumnCount(cols);
        int cell = 0;
        for(int row=0; row < rows; ++row)
        {
            for(int col=0; col < cols; ++col, ++cell)
            {
                QTableWidgetItem *newItem = new QTableWidgetItem(cells[cell]);
                setItem(row, col, newItem);
            }
        }
    }
}

void QTableWidgetWithCopyPaste::keyPressEvent(QKeyEvent * event)
{
    if(event->matches(QKeySequence::Copy) )
    {
        copy();
    }
    else if(event->matches(QKeySequence::Paste) )
    {
        paste();
    }
    else
    {
        QTableWidget::keyPressEvent(event);
    }

}

回答by Josh Sanders

Quark's answer (the selected one) is good for pointing people in the right direction, but his algorithm is entirely incorrect. In addition to an off by one error and incorrect assignment, its not even syntactically correct. Below is a working version that I just wrote and tested.

Quark 的答案(选定的答案)有利于为人们指明正确的方向,但他的算法完全不正确。除了一个错误和不正确的分配之外,它甚至在语法上都不正确。以下是我刚刚编写和测试的工作版本。

Let's assume our example table looks like so:

让我们假设我们的示例表如下所示:

A | B | C
D | E | F

一个 | 乙 | C
D | E | F

The problem with Quark's algorithm is the following:

Quark 算法的问题如下:

If we replace his \tseparator with a ' | ', it will produce this output:
B | C | D
E | F |

如果我们用一个' |替换他的\t分隔符 ',它将产生以下输出:
B | C | 德
| F |

The off by one error is that Dappears in the first row. The incorrect assignment is evidenced by the omission of A

off by one的错误是D出现在第一行。不正确的分配由A的遗漏证明

The following algorithm corrects these two problems with correct syntax.

下面的算法用正确的语法纠正了这两个问题。

    QString clipboardString;
    QModelIndexList selectedIndexes = view->selectionModel()->selectedIndexes();

    for (int i = 0; i < selectedIndexes.count(); ++i)
    {
        QModelIndex current = selectedIndexes[i];
        QString displayText = current.data(Qt::DisplayRole).toString();

        // If there exists another column beyond this one.
        if (i + 1 < selectedIndexes.count())
        {
            QModelIndex next = selectedIndexes[i+1];

            // If the column is on different row, the clipboard should take note.
            if (next.row() != current.row())
            {
                displayText.append("\n");
            }
            else
            {
                // Otherwise append a column separator.
                displayText.append(" | ");
            }
        }
        clipboardString.append(displayText);
    }

    QApplication::clipboard()->setText(clipboardString);

The reason I chose to use a counter instead of an iterator is just because it is easier to test if there exists another index by checking against the count. With an iterator, I suppose maybe you could just increment it and store it in a weak pointer to test if it is valid but just use a counter like I did above.

我选择使用计数器而不是迭代器的原因只是因为通过检查计数更容易测试是否存在另一个索引。使用迭代器,我想也许您可以增加它并将其存储在弱指针中以测试它是否有效,但只需像我上面那样使用计数器。

We need to check if the nextline will be on on a new row. If we are on a new row and we check the previous row as Quark's algorithm does, its already too late to append. We could prepend, but then we have to keep track of the last string size. The above code will produce the following output from the example table:

我们需要检查下一行是否会出现在新行上。如果我们在一个新行上,并且像 Quark 的算法那样检查前一行,那么追加就已经太晚了。我们可以预先添加,但是我们必须跟踪最后一个字符串的大小。上面的代码将从示例表中产生以下输出:

A | B | C
D | E | F

一个 | 乙 | C
D | E | F

回答by Cotlone

For whatever reason I didn't have access to the std::sort function, however I did find that as a neat alternative to Corwin Joy's solution, the sort function can be implemented by replacing

无论出于何种原因,我无法访问 std::sort 函数,但是我确实发现,作为 Corwin Joy 解决方案的巧妙替代方案,可以通过替换来实现 sort 函数

 std::sort(indexes.begin(), indexes.end());

with

  qSort(indexes);

This is the same as writing:

这与编写相同:

 qSort(indexes.begin(), indexes.end());

Thanks for your helpful code guys!

感谢您提供帮助的代码家伙!

回答by hugo24

a pyqt py2.x example:

一个 pyqt py2.x 示例:

selection = self.table.selectionModel() #self.table = QAbstractItemView
indexes = selection.selectedIndexes()

columns = indexes[-1].column() - indexes[0].column() + 1
rows = len(indexes) / columns
textTable = [[""] * columns for i in xrange(rows)]

for i, index in enumerate(indexes):
 textTable[i % rows][i / rows] = unicode(self.model.data(index).toString()) #self.model = QAbstractItemModel 

return "\n".join(("\t".join(i) for i in textTable))

回答by Peter Tseng

I wrote some code based on some of the others' answers. I subclassed QTableWidgetand overrode keyPressEvent()to allow the user to copy the selected rows to the clipboard by typing Control-C.

我根据其他人的一些答案编写了一些代码。我子类化QTableWidget并覆盖keyPressEvent()以允许用户通过键入 Control-C 将选定的行复制到剪贴板。

void MyTableWidget::keyPressEvent(QKeyEvent* event) {
    // If Ctrl-C typed
    if (event->key() == Qt::Key_C && (event->modifiers() & Qt::ControlModifier))
    {
        QModelIndexList cells = selectedIndexes();
        qSort(cells); // Necessary, otherwise they are in column order

        QString text;
        int currentRow = 0; // To determine when to insert newlines
        foreach (const QModelIndex& cell, cells) {
            if (text.length() == 0) {
                // First item
            } else if (cell.row() != currentRow) {
                // New row
                text += '\n';
            } else {
                // Next cell
                text += '\t';
            }
            currentRow = cell.row();
            text += cell.data().toString();
        }

        QApplication::clipboard()->setText(text);
    }
}

Output example (tab-separated):

输出示例(制表符分隔):

foo bar baz qux
bar baz qux foo
baz qux foo bar
qux foo bar baz

回答by swongu

What you'll need to do is access the text data in the model, then pass that text to the QClipboard.

您需要做的是访问模型中的文本数据,然后将该文本传递给QClipboard.

To access the text data in the model, use QModelIndex::data(). The default argument is Qt::DisplayRole, i.e. the displayed text.

要访问模型中的文本数据,请使用QModelIndex::data()。默认参数是Qt::DisplayRole,即显示的文本。

Once you've retrieved the text, pass that text to the clipboard using QClipboard::setText().

检索文本后,使用 将该文本传递到剪贴板QClipboard::setText()

回答by Berschi

I finally got it, thanks.

我终于明白了,谢谢。

void Widget::copy() {

QItemSelectionModel *selectionM = tableView->selectionModel();
QModelIndexList selectionL = selectionM->selectedIndexes();

selectionL.takeFirst(); // ID, not necessary
QString *selectionS = new QString(model->data(selectionL.takeFirst()).toString());
selectionS->append(", ");
selectionS->append(model->data(selectionL.takeFirst()).toString());
selectionS->append(", ");
selectionS->append(model->data(selectionL.takeFirst()).toString());
selectionS->append(", ");
selectionS->append(model->data(selectionL.takeFirst()).toString());

clipboard->setText(*selectionS);
}

and

connect (tableView, SIGNAL(clicked(QModelIndex)), this, SLOT(copy()));

回答by swongu

I can't help but notice that you can simplify your code using a foreach()construct and the QStringListclass, which has a convenient join()function.

我不禁注意到您可以使用foreach()结构和QStringList类来简化代码,它具有一个方便的join()功能。

void Widget::copy()
{
   QStringList list ;
   foreach ( const QModelIndex& index, tableView->selectedIndexes() )
   {
      list << index.data() ;
   }

   clipboard->setText( list.join( ", " ) ) ;
}

回答by Josh

Here is a variation on what Corwin Joy posted that works with QTableView and handles sparse selections differently. With this code if you have different columns selected in different rows (e.g. selected cells are (1,1), (1, 2), (2, 1), (3,2)) then when you paste it you will get empty cells corresponding to the "holes" in your selection (e.g. cells (2,2) and (3,1)). It also pulls in the column header text for columns that intersect the selection.

这是 Corwin Joy 发布的内容的一个变体,它适用于 QTableView 并以不同的方式处理稀疏选择。使用此代码,如果您在不同的行中选择了不同的列(例如,选定的单元格为 (1,1), (1, 2), (2, 1), (3,2)),那么当您粘贴它时,您将变为空对应于您选择中的“洞”的单元格(例如单元格 (2,2) 和 (3,1))。它还为与选择相交的列拉入列标题文本。

void CopyableTableView::copy()
{
    QItemSelectionModel *selection = selectionModel();
    QModelIndexList indices = selection->selectedIndexes();

    if(indices.isEmpty())
        return;

    QMap<int, bool> selectedColumnsMap;
    foreach (QModelIndex current, indices) {
        selectedColumnsMap[current.column()] = true;
    }
    QList<int> selectedColumns = selectedColumnsMap.uniqueKeys();
    int minCol = selectedColumns.first();

    // prepend headers for selected columns
    QString selectedText;

    foreach (int column, selectedColumns) {
        selectedText += model()->headerData(column, Qt::Horizontal, Qt::DisplayRole).toString();
        if (column != selectedColumns.last())
            selectedText += QLatin1Char('\t');
    }
    selectedText += QLatin1Char('\n');

    // QModelIndex::operator < sorts first by row, then by column.
    // this is what we need
    qSort(indices);

    int lastRow = indices.first().row();
    int lastColumn = minCol;

    foreach (QModelIndex current, indices) {

        if (current.row() != lastRow) {
            selectedText += QLatin1Char('\n');
            lastColumn = minCol;
            lastRow = current.row();
        }

        if (current.column() != lastColumn) {
            for (int i = 0; i < current.column() - lastColumn; ++i)
                selectedText += QLatin1Char('\t');
            lastColumn = current.column();
        }

        selectedText += model()->data(current).toString();
    }

    selectedText += QLatin1Char('\n');

    QApplication::clipboard()->setText(selectedText);
}