Выбранные строки в QTableView, скопируйте в QClipboard - PullRequest
15 голосов
/ 05 августа 2009

У меня есть база данных SQLite, и я сделал это в QSqlTableModel. Чтобы показать базу данных, я поместил эту модель в QTableView.

Теперь я хочу создать метод, в котором выбранные строки (или вся строка) будут скопированы в QClipboard. После этого я хочу вставить его в мой OpenOffice.Calc-Document.

Но я понятия не имею, что делать с СИГНАЛОМ Selected и QModelIndex и как поместить это в буфер обмена.

Ответы [ 11 ]

26 голосов
/ 05 августа 2009

Чтобы фактически захватить выбор, вы используете модель выбора вида элемента , чтобы получить список индексов . Учитывая, что у вас есть QTableView * с именем view, вы получаете выбор следующим образом:

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 работает.

13 голосов
/ 02 февраля 2010

У меня была похожая проблема, и в итоге я адаптировал QTableWidget (который является расширением QTableView) для добавления функции копирования / вставки. Вот код, который основан на том, что было предоставлено кварк выше:

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

}
5 голосов
/ 08 июня 2015

Ответ Кварка (выбранный) хорош для указания людей в правильном направлении, но его алгоритм совершенно неверен. В дополнение к отключению одной ошибкой и неправильному назначению, это даже не синтаксически правильно. Ниже приведена рабочая версия, которую я только что написал и протестировал.

Давайте предположим, что наш пример таблицы выглядит так:

A | Б | С
D | E | F

Проблема с алгоритмом Кварка заключается в следующем:

Если мы заменим его \ t разделитель на '| ', он выдаст следующее:
B | C | Д
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);

Причина, по которой я решил использовать счетчик вместо итератора, заключается просто в том, что проще проверить, существует ли другой индекс, сверяясь со счетчиком. С итератором, я полагаю, вы могли бы просто увеличить его и сохранить в слабом указателе, чтобы проверить, является ли он действительным, а просто использовать счетчик, как я делал выше.

Нам нужно проверить, будет ли строка next в новой строке. Если мы находимся в новой строке и проверяем предыдущую строку, как это делает алгоритм Кварка, его уже слишком поздно добавлять. Мы могли бы добавить, но тогда мы должны отслеживать последний размер строки. Приведенный выше код выдаст следующий вывод из таблицы примеров:

A | Б | C
D | E | F

4 голосов
/ 28 мая 2010

По какой-то причине у меня не было доступа к функции std :: sort, однако я обнаружил, что в качестве удобной альтернативы решению Корвина Джоя функцию сортировки можно реализовать, заменив

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

с

  qSort(indexes);

Это то же самое, что и запись:

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

Спасибо за ваш полезный код, ребята!

1 голос
/ 10 июня 2014

Я написал код, основанный на ответах других. Я вложил в подкласс 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 голос
/ 13 сентября 2010

Пример py2.t 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 голос
/ 05 августа 2009

Что вам нужно сделать, это получить доступ к текстовым данным в модели, а затем передать этот текст в QClipboard.

Чтобы получить доступ к текстовым данным в модели, используйте QModelIndex::data(). Аргумент по умолчанию - Qt::DisplayRole, т.е. отображаемый текст.

После того, как вы получили текст, передайте этот текст в буфер обмена, используя QClipboard::setText().

0 голосов
/ 01 сентября 2014

Вот вариант того, что опубликовал Корвин Джой, который работает с 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);
}
0 голосов
/ 17 июня 2010

Осторожнее с последним элементом. Обратите внимание, что индексы могут стать пустыми после «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());
0 голосов
/ 05 августа 2009

Не могу не заметить, что вы можете упростить свой код с помощью конструкции foreach() и класса QStringList, который имеет удобный join() функция.

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

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