2009-08-04 148 views
15

Antes que nada, disculpe el mal inglés.Filas/Línea seleccionadas en la copia de QTableView en QClipboard

Se trata de C++ y Qt. Tengo una base de datos SQLite y lo hice en un QSqlTableModel. Para mostrar la base de datos, puse ese modelo en un QTableView.

Ahora quiero crear un método donde las Filas seleccionadas (o toda la Línea) se copiarán en el QClipboard. Después de eso, quiero insertarlo en mi OpenOffice.Calc-Document.

Pero no tengo idea de qué hacer con el "Selected" -SIGNAL y el QModelIndex y cómo poner esto en el Portapapeles.

¿Puedes ayudarme?

Berschi

Respuesta

24

Para capturar realmente la selección, utiliza la vista del artículo selection model para obtener un list of indices. Teniendo en cuenta que usted tiene una llamada QTableView *view se obtiene la selección de esta manera:

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

a continuación, recorrer la lista de índices llamando model->data(index) en cada índice. Convierta los datos en una cadena si aún no lo está y concatene cada cadena. Luego puede usar QClipboard.setText para pegar el resultado en el portapapeles. Tenga en cuenta que, para Excel y Calc, cada columna está separada de la siguiente por una nueva línea ("\ n") y cada fila está separada por una pestaña ("\ t"). Debe verificar los índices para determinar cuándo pasará a la siguiente fila.

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

Advertencia: No he tenido la oportunidad de probar este código, pero funciona un equivalente PyQt.

+0

lo siento, pero yo no lo entienden cómo utilizar QItemSelectionModel y QModelIndexList en este caso con el modelo y TableView – Berschi

+0

Aquí, también se puede utilizar la función conveniente QAbstractItemView :: selectedIndexes() que está disponible en su QTableView (del cual QAbstractItemView es padre). Lo que se devuelve es un simple contenedor de lista de objetos QModelIndex. – swongu

+0

He ampliado un poco el ejemplo para ilustrar lo que Swongu describe. – quark

1

Lo que usted tiene que hacer es acceder a los datos de texto en el modelo, a continuación, pasar ese texto a la QClipboard.

Para acceder a los datos de texto en el modelo, use QModelIndex::data(). El argumento predeterminado es Qt::DisplayRole, es decir, el texto que se muestra.

Una vez que hayas recuperado el texto, pasa ese texto al portapapeles usando QClipboard::setText().

+0

gracias, realmente ayudó, por ahora. He añadido el siguiente código: connect (tableView, SIGNAL (presionado (QModelIndex)), esto, SLOT (copia (QModelIndex))); y esto: void Widget :: copy (QModelIndex sel) { portapapeles-> setText ((sel.data (Qt :: DisplayRole)). ToString()); } esto funciona bien para una sola fila. Pero si selecciono 2 o más filas, esto no funciona. ¿Cómo puedo copiar más filas o una línea completa al Portapapeles? – Berschi

+0

oh, lo siento, soy realmente nuevo aquí ... y con la fila (comentario anterior) Quise decir celda. lo siento :( – Berschi

+0

Como está haciendo clic en (QModelIndex), esto solo devolverá la celda en la que el usuario hizo clic. Si desea copiar texto de todas las celdas seleccionadas, usaría el quark de solución propuesto. Por cierto, debería agregar sus fragmentos de código como cambios a su pregunta original. – swongu

0

Finalmente lo tengo, gracias.

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

y

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

Si usa QStringList, puede aprovechar el operador << y la función QStringList :: join(). – swongu

0

no puedo dejar de notar que se puede simplificar el código utilizando una construcción foreach() y la clase QStringList, que tiene una función conveniente join().

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

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

gracias por su ayuda, el método foreach era nuevo para mí – Berschi

13

que tenía un problema similar y terminó adaptándose QTableWidget (que es una extensión de QTableView) para añadir funcionalidad de copiar/pegar. Aquí está el código que se basa en lo que fue provisto por quark arriba:

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

} 
+0

Dos enlaces más que pueden ser útiles son http://books.google.com/books?id=tSCR_4LH2KsC&pg=PA101&lpg=PA101&dq=copy+paste&source=web&ots=E511hS4aCk&sig = EYaYF4Er89UKAnKFRZLHYgtTdO4 # PPP1, M1 y http://stackoverflow.com/a/12979530/999943 y – phyatt

4

Por alguna razón no tenía acceso a la función std :: sort, sin embargo, me encontré que como una alternativa limpia a la solución de Corwin alegría, la función de clasificación se puede implementar mediante la sustitución de

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

con

qSort(indexes); 

Esto es lo mismo que escribir:

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

Gracias por sus votos tipos de código!

0

Cuidado con el último elemento. Nota a continuación, los índices pueden quedar vacíos después de 'removeFirst()'. Por lo tanto, 'actual' nunca es válido y no debe usarse en el modelo() -> datos (actual).

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

Considere

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

un ejemplo 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

escribí algo de código basado en algunas de las respuestas de los demás. Subclasé QTableWidget y anulé keyPressEvent() para permitir que el usuario copie las filas seleccionadas al portapapeles escribiendo 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); 
    } 
} 

Ejemplo de salida (separado por tabuladores):

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

Aquí es una variación de lo que Corwin Alegría publicó que trabaja con QTableView y maneja selecciones dispersas de manera diferente. Con este código si tiene diferentes columnas seleccionadas en diferentes filas (p. Ej., Las celdas seleccionadas son (1,1), (1, 2), (2, 1), (3,2)), al pegarlas se obtendrá vacío celdas correspondientes a los "agujeros" en su selección (por ejemplo, celdas (2,2) y (3,1)). También extrae el texto del encabezado de columna para las columnas que se cruzan con la selección.

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

La respuesta de Quark (la seleccionada) es buena para señalar a las personas en la dirección correcta, pero su algoritmo es completamente incorrecto. Además de un error de uno por uno y una asignación incorrecta, ni siquiera es sintácticamente correcto. A continuación hay una versión de trabajo que acabo de escribir y probar.

Asumamos nuestra tabla de ejemplo se ve así:

A | B | C
D | E | F

El problema con el algoritmo de Quark es la siguiente:

Si reemplazamos su \ t separador con un '| ', producirá esta salida:
B | C | D
E | F |

El error de uno por uno es que D aparece en la primera fila. La asignación incorrecta se evidencia por la omisión de A

El siguiente algoritmo corrige estos dos problemas con la sintaxis correcta.

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 razón que elegimos utilizar un contador en lugar de un iterador es simplemente porque es más fácil de probar si existe otro índice marcando contra el conde. Con un iterador, supongo que tal vez podrías incrementarlo y almacenarlo en un puntero débil para probar si es válido, pero solo usa un contador como lo hice anteriormente.

tenemos que comprobar si el próxima línea será encendido en una nueva fila. Si estamos en una nueva fila y revisamos la fila anterior como lo hace el algoritmo de Quark, ya es demasiado tarde para agregar. Podría preceder, pero luego tenemos que hacer un seguimiento del último tamaño de cadena. El código anterior producirá la siguiente salida de la tabla de ejemplo:

A | B | C
D | E | F

Cuestiones relacionadas