Lignes sélectionnées dans QTableView, copiez sur QClipboard

j'ai un SQLite-Base de données et je l'ai fait dans un QSqlTableModel. Pour montrer la base de données, j'ai mis Ce modèle dans un QTableView.

maintenant je veux créer une méthode où les lignes sélectionnées (ou la ligne entière) seront copiées dans le QClipboard. Je veux l'insérer dans mon OpenOffice.Calc-Document.

Mais je n'ai aucune Idée de quoi faire avec le Selected le SIGNAL et le QModelIndex et comment le mettre dans le presse-papiers.

15
demandé sur Sid S 2009-08-05 02:26:38

11 réponses

pour réellement capturer la sélection vous utilisez la vue d'élément modèle de sélection pour obtenir un liste des indices. Étant donné que vous avez un QTableView *view vous obtenez la sélection de cette façon:

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

puis boucle la liste d'index appelant model->data(index) sur chaque indice. Convertissez les données en une chaîne si elle ne l'est pas déjà et concaténez chaque chaîne ensemble. Ensuite, vous pouvez utiliser QClipboard.setText pour coller le résultat au presse-papiers. Notez que, pour Excel et Calc, chaque colonne est séparée de la suivante par un saut de ligne ("\n") et chaque ligne est séparé par une tabulation ("\t"). Vous devez vérifier les indices pour déterminer quand vous passez à la ligne suivante.

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

Avertissement: je n'ai pas eu la chance d'essayer ce code, mais un PyQt équivalent œuvres.

25
répondu quark 2016-08-09 15:31:40

j'ai eu un problème similaire et j'ai fini par adapter Qtablwidget (qui est une extension de QTableView) pour ajouter des fonctionnalités copier/coller. Voici le code qui s'appuie sur ce qui a été fourni par quark ci-dessus:

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.rpc

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

}
13
répondu Corwin Joy 2014-12-13 15:27:23

Quark réponse (un) est bon pour diriger les gens dans la bonne direction, mais son algorithme est entièrement erronée. En plus d'un off par une erreur et une affectation incorrecte, il n'est même pas syntaxiquement correct. Voici une version de travail que je viens d'écrire et de tester.

supposons que notre table d'exemple ressemble ainsi:

A / B / C

D | E / F

le problème avec L'algorithme de Quark est le suivantes:

si nous remplaçons son \t séparateur avec un '/', il produira cette sortie:

B / C / D

E / F/

arrêt par une erreur, c'est que D apparaît dans la première ligne. La mauvaise affectation est attestée par l'omission de

l'algorithme suivant corrige ces deux problèmes avec une syntaxe correcte.

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

La raison pour laquelle je choisi d'utiliser un compteur au lieu d'un itérateur est juste parce qu'il est plus facile de tester si il existe un autre indice en cochant contre le comte. Avec un itérateur, je suppose que vous pourriez peut-être juste l'incrémenter et le stocker dans un pointeur faible pour tester si elle est valide, mais juste utiliser un compteur comme je l'ai fait ci-dessus.

nous devons vérifier si le ligne sera sur une nouvelle ligne. Si nous sommes sur une nouvelle ligne et nous vérifions la rangée précédente comme Quark de l'algorithme, il est déjà trop tard pour annexer. Nous pourrions préparer, mais ensuite nous devons garder la trace de la dernière taille de chaîne. Le code ci-dessus produira la sortie suivante à partir de la table exemple:

A / B / C

D | E / F

5
répondu Josh Sanders 2015-06-07 23:58:52

pour n'importe quelle raison je n'ai pas eu accès à la fonction std:: sort, cependant j'ai trouvé que comme alternative à la solution de Corwin Joy, la fonction sort peut être implémentée en remplaçant

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

  qSort(indexes);

C'est la même chose que d'écrire:

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

Merci pour votre code utile gars!

4
répondu Cotlone 2010-05-28 01:01:54

ce que vous aurez besoin de faire est d'accéder aux données textuelles dans le modèle, puis passer ce texte à QClipboard.

pour accéder aux données textuelles dans le modèle, utilisez QModelIndex::data(). L'argument par défaut est Qt::DisplayRole, c'est à dire le texte affiché à l'écran.

une fois que vous avez récupéré le texte, passez-le au bloc-notes en utilisant QClipboard::setText().

1
répondu swongu 2009-08-04 23:09:07

un pyqt py2.x exemple:

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
répondu hugo24 2010-09-13 08:04:40

j'ai écrit un code basé sur les réponses des autres. Je sous-classé QTableWidget et emportait keyPressEvent() pour permettre à l'utilisateur de copier les lignes sélectionnées dans le presse-papiers en tapant 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);
    }
}

exemple de sortie (tab-separated):

foo bar baz qux
bar baz qux foo
baz qux foo bar
qux foo bar baz
1
répondu Peter Tseng 2014-06-10 05:10:28

j'ai enfin compris, merci.

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

et

connect (tableView, SIGNAL(clicked(QModelIndex)), this, SLOT(copy()));
0
répondu Berschi 2009-08-05 00:55:09

Je ne peux pas m'empêcher de remarquer que vous pouvez simplifier votre code en utilisant un foreach() Construction et le QStringList de la classe, qui a une pratique join() fonction.

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

   clipboard->setText( list.join( ", " ) ) ;
}
0
répondu swongu 2009-08-05 17:19:40

attention avec le dernier élément. Note ci-dessous, les index peuvent devenir vides après 'removeFirst()'. Par conséquent, "current" n'est jamais valide et ne devrait pas être utilisé dans model()->data(current).

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

Considérer

  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
répondu Russell Collison 2010-06-17 16:52:22

voici une variation sur ce que Corwin Joy a posté qui fonctionne avec QTableView et traite les sélections clairsemées différemment. Avec ce code si vous avez différentes colonnes sélectionnées dans des lignes différentes (par exemple, les cellules sélectionnées sont (1,1), (1, 2), (2, 1), (3,2)) ensuite, lorsque vous le collez, vous obtiendrez des cellules vides correspondant aux "trous" de votre sélection (par exemple les cellules (2,2) et (3,1)). Il tire également dans le texte d'en-tête de colonne pour les colonnes qui croisent la sélection.

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
répondu Josh 2014-09-01 16:52:37