Выбранные строки/строка в копии QTableView в QClipboard

Прежде всего, извините за плохой английский.

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

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

Но у меня нет идеи, что делать с "Selected" -SIGNAL и QModelIndex и как поместить это в буфер обмена.

Так что, пожалуйста, помогите мне?

Berschi

Ответы

Ответ 1

Чтобы действительно захватить выделение, вы используете представление элемента модель выбора, чтобы получить список индексов. Учитывая, что у вас есть 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(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 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 работает.

Ответ 2

У меня была аналогичная проблема, и я решил адаптировать 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 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);
    }

}

Ответ 3

Ответ 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, функцию сортировки можно реализовать, заменив

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

с

  qSort(indexes);

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

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

Благодарим вас за полезного парня!

Ответ 5

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

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

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

Ответ 6

Пример 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))

Ответ 7

Я написал код, основанный на некоторых ответах других. Я подклассифицировал QTableWidget и overrode 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

Ответ 8

Я наконец получил его, спасибо.

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

Ответ 9

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

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

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

Ответ 10

Осторожно с последним элементом. Примечание ниже, индексы могут стать пустыми после 'removeFirst()'. Таким образом, "текущий" никогда не действует и не должен использоваться в данных модели() → (текущий).

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

Ответ 11

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