在QTableView中选择行,复制到QClipboard

19

我有一个SQLite数据库并将其加载到了QSqlTableModel中。为了显示数据库,我将该模型放入了QTableView中。

现在我想创建一个方法,可以将选定的行(或整行)复制到QClipboard中。然后我想将其插入到我的OpenOffice.Calc文档中。

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

12个回答

29

要实际捕获选择,您可以使用项视图的selection model获取索引列表。假设您有一个名为viewQTableView *,则可以通过以下方式获取选择:

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

然后遍历索引列表,在每个索引上调用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);

警告:我没有试过这段代码,但是有一个PyQt的等价物可以使用。


抱歉,我不明白在这种情况下如何使用Model和TableView中的QItemSelectionModel和QModelIndexList。 - Berschi
在这里,您还可以使用方便的函数QAbstractItemView::selectedIndexes(),该函数可用于您的QTableView(其中QAbstractItemView是其父级)。返回的是一个包含QModelIndex对象的简单列表容器。 - swongu
我稍微扩展了一下这个例子,以说明swongu所描述的内容。 - quark
非常感谢,我也找到了一个(不太好的)解决方案,但它能用。 - Berschi
这个解决方案是不正确的。它有一个偏移量错误,一个错误的赋值错误和几个语法错误。请查看我的答案以获取正确的算法。 - Josh Sanders
显示剩余2条评论

15

我遇到了类似的问题,最终使用QTableWidget(它是QTableView的扩展)来添加复制/粘贴功能。这里是在quark提供的代码基础上进行修改的代码:

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

#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);
    }

}


9
Quark的答案(被选中的那个)很好地指导了人们走向正确的方向,但是他的算法完全不正确。除了一个错位的错误和错误的赋值外,它甚至在语法上都不正确。下面是我刚写并测试过的可用版本。
假设我们的示例表格如下:
A | B | C D | E | F
Quark算法的问题如下:
如果我们将他的\t分隔符替换为' | ',则会产生以下输出: B | C | D E | F |
这个错位的错误是D出现在第一行的原因。通过省略A,证明了错误的分配。
下面的算法纠正了这两个问题并具有正确的语法。
    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);

我选择使用计数器而不是迭代器的原因仅仅是因为通过检查计数器是否存在另一个索引更易于测试。对于迭代器,我想你可能只需将其递增并将其存储在弱指针中以测试其是否有效,但只需像上面一样使用计数器即可。
我们需要检查下一个行是否在新行上。如果我们在新行上,并且像Quark算法一样检查前一行,则已经太晚进行附加。我们可以进行预处理,但那么我们就必须跟踪最后一个字符串大小。以上代码将从示例表格生成以下输出: A | B | C
D | E | F

4

由于某些原因我无法使用std::sort函数,但是我发现作为Corwin Joy解决方案的一个不错替代品,可以通过替换来实现sort函数

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

使用

  qSort(indexes);

这与编写以下内容相同:

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

感谢你们提供的有用代码!


3

我基于其他人的答案编写了一些代码。我创建了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);
    }
}

输出示例(以制表符分隔):
foo bar baz qux
bar baz qux foo
baz qux foo bar
qux foo bar baz

1

一个关于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))

1
你需要做的是访问模型中的文本数据,然后将该文本传递给QClipboard
要访问模型中的文本数据,请使用QModelIndex::data()。默认参数是Qt::DisplayRole,即显示的文本。
一旦您检索到文本,请使用QClipboard::setText()将该文本传递到剪贴板。

谢谢,这真的很有帮助,目前为止。 我添加了以下代码:connect (tableView, SIGNAL(clicked(QModelIndex)), this, SLOT(copy(QModelIndex)));以及:void Widget::copy(QModelIndex sel) { clipboard->setText((sel.data(Qt::DisplayRole)).toString());}这对于单个行正常工作。 但是如果我选择2个或更多行,则无法正常工作。 如何将更多行或整行复制到剪贴板? - Berschi
哦,抱歉,我在这里是新手... 而且我上面说的“row”其实是指单元格。抱歉:( - Berschi
因为你使用了clicked(QModelIndex),这只会返回用户点击的单元格。如果你想要从所有选定的单元格中复制文本,你可以使用quark提出的解决方案。另外,你应该将你的代码片段作为对原始问题的更新添加。 - swongu

0

我终于明白了,谢谢。

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);
}

并且

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

2
如果您使用QStringList,则可以利用<<运算符和QStringList :: join()函数。 - swongu

0
我不禁注意到,您可以使用foreach()结构和QStringList类来简化代码,该类具有方便的join()函数。
void Widget::copy()
{
   QStringList list ;
   foreach ( const QModelIndex& index, tableView->selectedIndexes() )
   {
      list << index.data() ;
   }

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

0

注意最后一个元素。请注意,索引可能在“removeFirst()”之后变为空。因此,“current”永远无效,不应在model()->data(current)中使用。

  indexes.removeFirst();
  QString selected_text;
  QModelIndex current;
  Q_FOREACH(current, indexes)
  {
  .
  .
  .
  }
  // add last element
  selected_text.append(model()->data(current).toString());

请考虑

  QModelIndex last = indexes.last();
  indexes.removeFirst();
  QString selected_text;
  Q_FOREACH(QModelIndex current, indexes)
  {
  .
  .
  .
  }
  // add last element
  selected_text.append(model()->data(last).toString());

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接