QComboBox Array Access - c++

I'd like to be able to access the values in my QComboBox without having to loop over the contents using itemText.
for( auto i = 0u; i < myQComboBox->count(); i++ )
{
result[i] = myQComboBox->itemText( i );
}
Is there a way that I can get to QComboBox's underlying QList so I can just use the operator[] or even better, iterators and range based loops?

It appears that you're hung on the syntax: you want to replace myQComboBox->itemText(i) with myQComboBox[i]. That can be rather easily done:
// implementation
class ModelAdapter {
QPointer<QAbstractItemModel> m_model;
public:
explicit ModelAdapter(QComboBox & box) : m_model(box.model()) {}
explicit ModelAdapter(QAbstractItemModel * model) : m_model(model) {}
QVariant operator[](int i) { return m_model->index(i, 0); }
};
// point of use
ModelAdapter model(myQComboBox);
for( auto i = 0; i < myQComboBox->count(); i++ )
{
result[i] = model[i];
}
With a good compiler, you can do the below and have it produce the same code as if you used combobox.model->index(i, 0) directly. I don't see the point of it, but hey, it's possible :)
// implementation
class Adapter {
QAbstractItemModel* m_model;
public:
explicit Adapter(QComboBox & box) : m_model(box.model()) {}
explicit Adapter(QAbstractItemModel * model) : m_model(model) {}
QVariant operator[](int i) { return m_model->index(i, 0); }
};
// point of use
for( auto i = 0; i < myQComboBox->count(); i++ )
{
result[i] = Adapter(myQComboBox)[i];
}
A similar adapter could provide you with iterators.

You can retrieve items data using the combo box's model. Here is an example, how I would do that:
QComboBox combo;
combo.addItem("Item 1");
QAbstractItemModel *model = combo.model();
QModelIndex idx = model->index(0, 0); // Refers to the first item
QString item = model->data(idx).toString(); // Returns 'Item 1'
To access the second and further items of the combo box, just change the row number in index() function call:
QModelIndex idx = model->index(0, 0);
the row number ^
I am not aware of any iterators based API for combo boxes so far, but you can use all the strength of QAbstractItemModel.

Related

How to use Qt beginInsertRows correctly

I have my custom ItemModel and ItemDelegate:
class ItemModel : public QAbstractListModel {
Q_OBJECT
public:
// return items_.size();
int rowCount(const QModelIndex &parent = QModelIndex()) const;
// return items_[index.row()];
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
void Insert(const QVector<QString> &data);
private:
QVector<QString> items_;
};
void ItemModel::Insert(const QVector<QString> &data) {
// my question is the 'first' and 'last' args of beginInsertRows
beginInsertRows(QModelIndex(), 0, 0);
items_.insert(items_.begin(), data.begin(), begin.end());
endInsertRows();
}
From Qt Documentation, it say beginInsertRows has three args:
void QAbstractItemModel::beginInsertRows(const QModelIndex &parent, int first, int last)
Begins a row insertion operation.
When reimplementing insertRows() in a subclass, you must call this function before inserting data into the model's underlying data store.
The parent index corresponds to the parent into which the new rows are inserted; first and last are the row numbers that the new rows will have after they have been inserted.
I am not sure how to pass last and first, in my ItemModel::Insert, whatever the size of the inserted data is 0 or 10 or other count, I pass first = 0 and last = 0, the view work correctly. When I insert 10 items, and pass first = 0 and last = 9, the view work correctly too. It confuse me.
void ItemModel::Insert() {
beginInsertRows(QModelIndex(), 0, 0);
for(int i = 0; i < 10; ++i) {
items_.push_back(QString::number(i);
}
endInsertRows();
}
// or
void ItemModel::Insert() {
beginInsertRows(QModelIndex(), 0, 9);
for(int i = 0; i < 10; ++i) {
items_.push_back(QString::number(i));
}
endInsertRows();
}
0, 0 is not correct because "first and last are the row numbers that the new rows will have after they have been inserted." The view may still look correct using these parameters but there might be problems from it you haven't seen yet.
0, 9 is correct, but only the first time Insert() is called, because you are adding the new numbers to the end. You need to add items_.size() to both parameters. I.e.:
beginInsertRows(QModelIndex(), items_.size(), items_.size() + 9);
As the doc you highlighted says, you need to call those two functions when you reimplement the virtual function QAbstractItemModel::insertRows
https://doc.qt.io/qt-5/qabstractitemmodel.html#insertRows
Which is obviously not what you are doing in your ItemModel::Insert function.

How to create a Generic ListModel for QML

My application consists of many Lists that I display in QML-ListViews by using QAbstractListModel derived model-classes. It's always the same, with the difference of the Item-Type. That's why I want to know how to build a class-template for this approach.
I figured out, that it is not possible to use the Q_OBJECT-Macro in a class-template. That's why my GenericListModel consists of two part.
1. GenericListModelData
The first part is the Model itself that derives from QAbstractListModel and implements the basic functions data(), rowCount() and roleNames().
2. GenericListModel
The second part is the class-template that is used as a wrapper to provide functions similar to a QListView.
If you have any suggestions or questions please let me know. It would be really nice to improve this solution.
I uploaded the full sourcecode here:
https://github.com/sebabebibobu/QGenericListModel/
1. GenericListModelData
QVariant GenericListModelData::data(const QModelIndex &index, int role) const
{
QObject *item = m_itemList.at(index.row());
return item->property(item->metaObject()->property(role).name());
}
/*
* Returns the number of items attached to the list.
*/
int GenericListModelData::rowCount(const QModelIndex &parent) const
{
Q_UNUSED(parent)
return m_itemList.size();
}
/*
* Generates a hash out of QMetaObject property-index and property-name.
*/
QHash<int, QByteArray> GenericListModelData::roleNames() const
{
QHash<int, QByteArray> roles;
if (!m_itemList.isEmpty()) {
for(int i = 0; i < m_itemList.at(0)->metaObject()->propertyCount(); i++) {
roles[i] = m_itemList.at(0)->metaObject()->property(i).name();
}
}
return roles;
}
/*
* Append Item to List.
*/
void GenericListModelData::appendItem(QObject *item)
{
/* map the notify()-signal-index with the property-index when the first item get's inserted */
if (m_itemList.isEmpty()) {
for(int i = 0; i < item->metaObject()->propertyCount(); i++) {
m_propertySignalIndexHash.insert(item->metaObject()->property(i).notifySignalIndex(), i);
}
}
/* connect each notify()-signals to the onDataChanged()-slot which call's the dataChanged()-signal */
for(int i = 0; i < item->metaObject()->propertyCount(); i++) {
connect(item, "2" + item->metaObject()->property(i).notifySignal().methodSignature(), this, SLOT(onDataChanged()));
}
/* finally append the item the list */
beginInsertRows(QModelIndex(), rowCount(), rowCount());
m_itemList.append(item);
endInsertRows();
}
/*
* Helper-Slot that emit's the dataChanged()-signal of QAbstractListModel.
*/
void GenericListModelData::onDataChanged()
{
QModelIndex index = createIndex(m_itemList.indexOf(sender()),0);
QVector<int> roles;
roles.append(m_propertySignalIndexHash.value(senderSignalIndex()));
emit dataChanged(index, index, roles);
}
2. GenericListModel
template <typename T>
class GenericListModel : public GenericListModelData
{
public:
explicit GenericListModel(QObject *parent) : GenericListModelData(parent) {
}
void append(T *item) {
appendItem(item);
}
T *at(int i) {
return qobject_cast<T *>(m_itemList.at(i));
}
};
Update 01.05.2016
GrecKo posted in the comments, that a project like mine already exists. That's why I decided to share the link of this project here too:
http://gitlab.unique-conception.org/qt-qml-tricks/qt-qml-models

How to refresh a QSqlTableModel while preserving the selection?

I am using a QSqlTableModel and QTableView to view an SQLite database table.
I would like to have the table auto refresh every second or so (it's not going to be a very large table - a couple of hundred rows). And i can do this - like so:
QTimer *updateInterval = new QTimer(this);
updateInterval->setInterval(1000);
updateInterval->start();
connect(updateInterval, SIGNAL(timeout()),this, SLOT(update_table()));
...
void MainWindow::update_table()
{
model->select(); //QSqlTableModel*
sqlTable->reset(); //QTableView*
}
But this removes any selection I have, so the selections only last for up to a second. This is annoying, as another pane in the GUI depends on what is selected. If nothing is selected, then it resets to an explanation splash page.
I then tried a somewhat hacky approach, which gets the selected row number, resets the table, and then selects that row. But this doesn't work either, as the selected row can move up or down based on additions to the table.
I know other classes have a dataChanged() signal, which would be ideal.
Do any of you know how I could have the table refresh to reflect changes to the database (from either command line usage, or other instances of the program) AND keep the current selection?
I know I could get data from the current selection, and then after the reset search for the same row and then reselect it, but this seems like a counter productive and bad solution to the problem.
EDIT: Current attempt at solution:
void MainWindow::update_table()
{
QList<QModelIndex> selection = sqlTable->selectionModel()->selection().indexes();
QList<int> selectedIDs;
bool somethingSelected = true;
for(QList<QModelIndex>::iterator i = selection.begin(); i != selection.end(); ++i){
int col = i->column();
QVariant data = i->data(Qt::DisplayRole);
if(col == 0) {
selectedIDs.append(data.toInt());
}
}
if(selectedIDs.empty()) somethingSelected = false;
model->select();
sqlTable->reset();
if(somethingSelected){
QList<int> selectedRows;
int rows = model->rowCount(QModelIndex());
for(int i = 0; i < rows; ++i){
sqlTable->selectRow(i);
if(selectedIDs.contains(sqlTable->selectionModel()->selection().indexes().first().data(Qt::DisplayRole).toInt())) selectedRows.append(i);
}
for(QList<int>::iterator i = selectedRows.begin(); i != selectedRows.end(); ++i){
sqlTable->selectRow(*i);
}
}
}
Okay so this more or less works now...
The real deal is the primary key on the result of your query. Qt's APIs offer a rather circuitous route from QSqlTableModel::primaryKey() to a list of columns. The result of primaryKey() is a QSqlRecord, and you can iterate over its field()s to see what they are. You can also look up all of the fields that comprise the query proper from QSqlTableModel::record(). You find the former in the latter to get a list of model columns that comprise the query.
If your query doesn't contain a primary key, you'll have to design one yourself and offer it using some protocol. For example, you can choose that if primaryKey().isEmpty() is true, the last column returned by the model is to be used as the primary key. It's up to you to figure out how to key the result of an arbitrary query.
The selected rows can then be indexed simply by their primary keys (a list of values of the cells that comprise the key -- a QVariantList). For this, you could use a custom selection model (QItemSelectionModel) if its design wasn't broken. The key methods, such as isRowSelected() aren't virtual, and you can't reimplement them :(.
Instead, you can use a proxy model that mimicks selection by providing a custom Qt::BackgroundRole for the data. Your model sits on top of the table model, and keeps a sorted list of selected keys. Each time the proxy model's data() is called, you get the row's key from underlying query model, then search for it in your sorted list. Finally, you return a custom background role if the item is selected. You'll have to write relevant comparison operator for QVariantList. If QItemSelectionModel was usable for this purpose, you could put this functionality into a reimplementation of isRowSelected().
The model is generic since you subscribe to a certain protocol for extracting the key from a query model: namely, using primaryKey().
Instead of using primary keys explicitly, you can also use persistent indices if the model supports them. Alas, until at lest Qt 5.3.2, QSqlTableModel does not preserve the persistent indices when the query is rerun. Thus, as soon as the view changes the sort order, the persistent indices become invalid.
Below is a fully worked out example of how one might implement such a beast:
#include <QApplication>
#include <QTableView>
#include <QSqlRecord>
#include <QSqlField>
#include <QSqlQuery>
#include <QSqlTableModel>
#include <QIdentityProxyModel>
#include <QSqlDatabase>
#include <QMap>
#include <QVBoxLayout>
#include <QPushButton>
// Lexicographic comparison for a variant list
bool operator<(const QVariantList &a, const QVariantList &b) {
int count = std::max(a.count(), b.count());
// For lexicographic comparison, null comes before all else
Q_ASSERT(QVariant() < QVariant::fromValue(-1));
for (int i = 0; i < count; ++i) {
auto aValue = i < a.count() ? a.value(i) : QVariant();
auto bValue = i < b.count() ? b.value(i) : QVariant();
if (aValue < bValue) return true;
}
return false;
}
class RowSelectionEmulatorProxy : public QIdentityProxyModel {
Q_OBJECT
Q_PROPERTY(QBrush selectedBrush READ selectedBrush WRITE setSelectedBrush)
QMap<QVariantList, QModelIndex> mutable m_selection;
QVector<int> m_roles;
QBrush m_selectedBrush;
bool m_ignoreReset;
class SqlTableModel : public QSqlTableModel {
public:
using QSqlTableModel::primaryValues;
};
SqlTableModel * source() const {
return static_cast<SqlTableModel*>(dynamic_cast<QSqlTableModel*>(sourceModel()));
}
QVariantList primaryValues(int row) const {
auto record = source()->primaryValues(row);
QVariantList values;
for (int i = 0; i < record.count(); ++i) values << record.field(i).value();
return values;
}
void notifyOfChanges(int row) {
emit dataChanged(index(row, 0), index(row, columnCount()-1), m_roles);
}
void notifyOfAllChanges(bool remove = false) {
auto it = m_selection.begin();
while (it != m_selection.end()) {
if (it->isValid()) notifyOfChanges(it->row());
if (remove) it = m_selection.erase(it); else ++it;
}
}
public:
RowSelectionEmulatorProxy(QObject* parent = 0) :
QIdentityProxyModel(parent), m_roles(QVector<int>() << Qt::BackgroundRole),
m_ignoreReset(false) {
connect(this, &QAbstractItemModel::modelReset, [this]{
if (! m_ignoreReset) {
m_selection.clear();
} else {
for (auto it = m_selection.begin(); it != m_selection.end(); ++it) {
*it = QModelIndex(); // invalidate the cached mapping
}
}
});
}
QBrush selectedBrush() const { return m_selectedBrush; }
void setSelectedBrush(const QBrush & brush) {
if (brush == m_selectedBrush) return;
m_selectedBrush = brush;
notifyOfAllChanges();
}
QList<int> selectedRows() const {
QList<int> result;
for (auto it = m_selection.begin(); it != m_selection.end(); ++it) {
if (it->isValid()) result << it->row();
}
return result;
}
bool isRowSelected(const QModelIndex &proxyIndex) const {
if (! source() || proxyIndex.row() >= rowCount()) return false;
auto primaryKey = primaryValues(proxyIndex.row());
return m_selection.contains(primaryKey);
}
Q_SLOT void selectRow(const QModelIndex &proxyIndex, bool selected = true) {
if (! source() || proxyIndex.row() >= rowCount()) return;
auto primaryKey = primaryValues(proxyIndex.row());
if (selected) {
m_selection.insert(primaryKey, proxyIndex);
} else {
m_selection.remove(primaryKey);
}
notifyOfChanges(proxyIndex.row());
}
Q_SLOT void toggleRowSelection(const QModelIndex &proxyIndex) {
selectRow(proxyIndex, !isRowSelected(proxyIndex));
}
Q_SLOT virtual void clearSelection() {
notifyOfAllChanges(true);
}
QVariant data(const QModelIndex &proxyIndex, int role) const Q_DECL_OVERRIDE {
QVariant value = QIdentityProxyModel::data(proxyIndex, role);
if (proxyIndex.row() < rowCount() && source()) {
auto primaryKey = primaryValues(proxyIndex.row());
auto it = m_selection.find(primaryKey);
if (it != m_selection.end()) {
// update the cache
if (! it->isValid()) *it = proxyIndex;
// return the background
if (role == Qt::BackgroundRole) return m_selectedBrush;
}
}
return value;
}
bool setData(const QModelIndex &, const QVariant &, int) Q_DECL_OVERRIDE {
return false;
}
void sort(int column, Qt::SortOrder order) Q_DECL_OVERRIDE {
m_ignoreReset = true;
QIdentityProxyModel::sort(column, order);
m_ignoreReset = false;
}
void setSourceModel(QAbstractItemModel * model) Q_DECL_OVERRIDE {
m_selection.clear();
QIdentityProxyModel::setSourceModel(model);
}
};
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QWidget w;
QVBoxLayout layout(&w);
QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE");
db.setDatabaseName(":memory:");
if (! db.open()) return 255;
QSqlQuery query(db);
query.exec("create table chaps (name, age, constraint pk primary key (name, age));");
query.exec("insert into chaps (name, age) values "
"('Bob', 20), ('Rob', 30), ('Sue', 25), ('Hob', 40);");
QSqlTableModel model(nullptr, db);
model.setTable("chaps");
RowSelectionEmulatorProxy proxy;
proxy.setSourceModel(&model);
proxy.setSelectedBrush(QBrush(Qt::yellow));
QTableView view;
view.setModel(&proxy);
view.setEditTriggers(QAbstractItemView::NoEditTriggers);
view.setSelectionMode(QAbstractItemView::NoSelection);
view.setSortingEnabled(true);
QObject::connect(&view, &QAbstractItemView::clicked, [&proxy](const QModelIndex & index){
proxy.toggleRowSelection(index);
});
QPushButton clearSelection("Clear Selection");
QObject::connect(&clearSelection, &QPushButton::clicked, [&proxy]{ proxy.clearSelection(); });
layout.addWidget(&view);
layout.addWidget(&clearSelection);
w.show();
app.exec();
}
#include "main.moc"

Maintaining checkedstatus inheritance in a QTreeView

I'm trying to do something basic : you have a QTreeView. 1st depth are folders only, 2nd depth are files only. I want to have a check box with the checked status next to each item. Files are either checked or unchecked, folders can also be partiallyChecked depending on their files; all in all quite natural I believe.
The way I though I should go was using a QStandardItemModel and populate it with a custom subclass of QStandardItem : DescriptionFileItem. Maybe that was a bad idea, if there's an easier way please enlight me.
I tried using signals and slots so that my signal CheckStateChanged on a file would be connected to a slot UpdateCheckedStateOnChildStateChanged on its containing folder. This required my DescriptionFileItem to inherit from QObject as well (BTW, I was surprised that QStandardItem did not inherit from QObject). I initially hoped this would work seamlessly with the provided base classes but it did not : emitDataChanged() didn't seem to trigger my model's dataChanged() signal...
Using the model's dataChanged signals directly didn't work either: it's call is protected so you can't use it without subclassing (I think that's my next move unless somebody can help me get it right).
At the moment I have a signal -> slot connection that won't work and I have no idea why; compile and link work ok. Here's the code; perhapps you'll spot my mistakes easily. I'm leaving some commented lines so you can maybe see what I did wrong in a previous attempt. Thanks for your input!
#ifndef DESCRIPTIONFILEITEM_H
#define DESCRIPTIONFILEITEM_H
#include <QStandardItem>
#include <Qt>
class DescriptionFileItem : public QObject, public QStandardItem
{
Q_OBJECT
public:
explicit DescriptionFileItem(const QString & text, bool isFileName=false, QObject* parent = 0);
void setData ( const QVariant & value, int role = Qt::UserRole + 1 );
QVariant data( int role = Qt::UserRole + 1 ) const;
QString text;
Qt::CheckState checkedState;
bool isFileName;
signals:
void CheckStateChanged();
public slots:
void UpdateCheckedStateOnChildStateChanged();
};
#endif // DESCRIPTIONFILEITEM_H
Corresponding .cpp :
#include "DescriptionFileItem.h"
DescriptionFileItem::DescriptionFileItem(const QString & text, bool isFileName, QObject* parent):
QObject(parent),QStandardItem(text)
{
this->isFileName = isFileName;
checkedState = Qt::Checked;
}
void DescriptionFileItem::setData ( const QVariant & value, int role){
if(role == Qt::CheckStateRole){
Qt::CheckState newCheckState = (Qt::CheckState)value.toInt();
checkedState = newCheckState;
if(isFileName){
if(newCheckState == Qt::Unchecked || newCheckState == Qt::Checked){
for(int i = 0; i<rowCount(); i++){
DescriptionFileItem* child = (DescriptionFileItem*)QStandardItem::child(i);
QModelIndex childIndex = child->index();
child->model()->setData(childIndex,newCheckState, Qt::CheckStateRole);
//child->setCheckState(newCheckState);
//child->setData(newCheckState,Qt::CheckStateRole);
}
/*if(rowCount()>1){
emit this->model()->dataChanged(this->child(0)->index(),this->child(rowCount()-1)->index());
}else{
emit this->model()->dataChanged(this->child(0)->index(),this->child(0)->index());
}*/
}
}else{
emit CheckStateChanged();
}
//emit this->model()->dataChanged(this->index(),this->index());
}else{
QStandardItem::setData(value,role);
}
}
QVariant DescriptionFileItem::data( int role ) const{
if (role == Qt::CheckStateRole){
return checkedState;
}
return QStandardItem::data(role);
}
void DescriptionFileItem::UpdateCheckedStateOnChildStateChanged()
{
Qt::CheckState min = Qt::Checked;
Qt::CheckState max = Qt::Unchecked;
Qt::CheckState childState;
for(int i = 0; i<rowCount(); i++){
DescriptionFileItem* child = (DescriptionFileItem*)QStandardItem::child(i);
childState = (Qt::CheckState) child->data(Qt::CheckStateRole).toInt();
min = min>childState ? childState: min;
max = max<childState ? childState: max;
}
if(min >= max)
setData(min, Qt::CheckStateRole);
else
setData(Qt::PartiallyChecked, Qt::CheckStateRole);
}
And the construction of the connection / tree:
DescriptionFileItem* descFileStdItem = new DescriptionFileItem(descriptionFileName, true);
descFileStdItem->setFlags(Qt::ItemIsSelectable|Qt::ItemIsUserCheckable|Qt::ItemIsEnabled|Qt::ItemIsTristate);
descriptionFileSIModel.appendRow(descFileStdItem);
typedef pair<string,int> indexType;
foreach(indexType index,dataFile->indexes){
DescriptionFileItem* key_xItem = new DescriptionFileItem(index.first.c_str());
descFileStdItem->appendRow(key_xItem);
key_xItem->setFlags(Qt::ItemIsSelectable|Qt::ItemIsUserCheckable|Qt::ItemIsEnabled);
QObject::connect(key_xItem,SIGNAL(CheckStateChanged()),descFileStdItem,SLOT(UpdateCheckedStateOnModelDataChanged()));
}
EDIT: final answer, thanks to stu (see below)
void DataLoadWidget::ModelItemChanged(QStandardItem *item)
{
QStandardItem* parent = item->parent();
if(parent == 0){
//folder state changed--> update children if not partially selected
Qt::CheckState newState = item->checkState();
if(newState != Qt::PartiallyChecked){
for (int i = 0; i < item->rowCount(); i++)
{
item->child(i)->setCheckState(newState);
}
}
}
else{//child item changed--> count parent's children that are checked
int checkCount = 0;
for (int i = 0; i < parent->rowCount(); i++)
{
if (parent->child(i)->checkState() == Qt::Checked)
checkCount++;
}
if(checkCount == 0)
parent->setCheckState(Qt::Unchecked);
else if (checkCount == parent->rowCount())
parent->setCheckState(Qt::Checked);
else
parent->setCheckState(Qt::PartiallyChecked);
}
}
Unless I've misunderstood your question it seems that your solution is massively over-complicated. You should be able to do this trivially with the default QStandardItemModel implementation.
How about something like this (error handling omitted)?
QObject::connect(model, SIGNAL(itemChanged(QStandardItem*)), someObject, SLOT(modelItemChanged(QStandardItem*)));
And then in the signal handler:
void modelItemChanged(QStandardItem* item)
{
QStandardItem* parent = item->parent();
int checkCount = 0;
int rowCount = parent->rowCount();
for (int i = 0; i < rowCount; i++)
{
if (parent->child(i)->checkState() == Qt::Checked)
checkCount++;
}
switch (checkCount)
{
case 0:
parent->setCheckState(Qt::Unchecked);
break;
case rowCount:
parent->setCheckState(Qt::Checked);
break;
default:
parent->setCheckState(Qt::PartiallyChecked);
}
}
This is by no means optimal but it may be good enough for your purposes.

Selected Rows in QTableView, copy to QClipboard

I have a SQLite-Database and I did it into a QSqlTableModel.
To show the Database, I put that Model into a QTableView.
Now I want to create a Method where the selected Rows (or the whole Line) will be copied into the QClipboard. After that I want to insert it into my OpenOffice.Calc-Document.
But I have no Idea what to do with the Selected SIGNAL and the QModelIndex and how to put this into the Clipboard.
To actually capture the selection you use the item view's selection model to get a list of indices. Given that you have a QTableView * called view you get the selection this way:
QAbstractItemModel * model = view->model();
QItemSelectionModel * selection = view->selectionModel();
QModelIndexList indexes = selection->selectedIndexes();
Then loop through the index list calling model->data(index) on each index. Convert the data to a string if it isn't already and concatenate each string together. Then you can use QClipboard.setText to paste the result to the clipboard. Note that, for Excel and Calc, each column is separated from the next by a newline ("\n") and each row is separated by a tab ("\t"). You have to check the indices to determine when you move to the next row.
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);
Warning: I have not had a chance to try this code, but a PyQt equivalent works.
I had a similar problem and ended up adapting QTableWidget (which is an extension of QTableView) to add copy/paste functionality. Here is the code which builds on what was provided by quark above:
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);
}
}
Quark's answer (the selected one) is good for pointing people in the right direction, but his algorithm is entirely incorrect. In addition to an off by one error and incorrect assignment, its not even syntactically correct. Below is a working version that I just wrote and tested.
Let's assume our example table looks like so:
A | B | C
D | E | F
The problem with Quark's algorithm is the following:
If we replace his \t separator with a ' | ', it will produce this output:
B | C | D
E | F |
The off by one error is that D appears in the first row. The incorrect assignment is evidenced by the omission of A
The following algorithm corrects these two problems with correct syntax.
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);
The reason I chose to use a counter instead of an iterator is just because it is easier to test if there exists another index by checking against the count. With an iterator, I suppose maybe you could just increment it and store it in a weak pointer to test if it is valid but just use a counter like I did above.
We need to check if the next line will be on on a new row. If we are on a new row and we check the previous row as Quark's algorithm does, its already too late to append. We could prepend, but then we have to keep track of the last string size. The above code will produce the following output from the example table:
A | B | C
D | E | F
For whatever reason I didn't have access to the std::sort function, however I did find that as a neat alternative to Corwin Joy's solution, the sort function can be implemented by replacing
std::sort(indexes.begin(), indexes.end());
with
qSort(indexes);
This is the same as writing:
qSort(indexes.begin(), indexes.end());
Thanks for your helpful code guys!
I wrote some code based on some of the others' answers. I subclassed QTableWidget and overrode keyPressEvent() to allow the user to copy the selected rows to the clipboard by typing 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);
}
}
Output example (tab-separated):
foo bar baz qux
bar baz qux foo
baz qux foo bar
qux foo bar baz
What you'll need to do is access the text data in the model, then pass that text to the QClipboard.
To access the text data in the model, use QModelIndex::data(). The default argument is Qt::DisplayRole, i.e. the displayed text.
Once you've retrieved the text, pass that text to the clipboard using QClipboard::setText().
a pyqt py2.x example:
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))
I finally got it, thanks.
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);
}
and
connect (tableView, SIGNAL(clicked(QModelIndex)), this, SLOT(copy()));
I can't help but notice that you can simplify your code using a foreach() construct and the QStringList class, which has a convenient join() function.
void Widget::copy()
{
QStringList list ;
foreach ( const QModelIndex& index, tableView->selectedIndexes() )
{
list << index.data() ;
}
clipboard->setText( list.join( ", " ) ) ;
}
Careful with the last element. Note below, indexes may become empty after 'removeFirst()'. Thus, 'current' is never valid and should not be used in model()->data(current).
indexes.removeFirst();
QString selected_text;
QModelIndex current;
Q_FOREACH(current, indexes)
{
.
.
.
}
// add last element
selected_text.append(model()->data(current).toString());
Consider
QModelIndex last = indexes.last();
indexes.removeFirst();
QString selected_text;
Q_FOREACH(QModelIndex current, indexes)
{
.
.
.
}
// add last element
selected_text.append(model()->data(last).toString());
Here is a variation on what Corwin Joy posted that works with QTableView and handles sparse selections differently. With this code if you have different columns selected in different rows (e.g. selected cells are (1,1), (1, 2), (2, 1), (3,2)) then when you paste it you will get empty cells corresponding to the "holes" in your selection (e.g. cells (2,2) and (3,1)). It also pulls in the column header text for columns that intersect the selection.
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);
}
If anybody is interested, this web page provide a working code project on this topic, it's working pretty well.
Copy / paste functionality implementation for QAbstractTableModel / QTableView