How to get the Index from an Item in the QTreeView - c++

My Qt-Application was built based on the EditableTreeModel example from Qt. In the TreeModel class there is a function called getItem() which takes the QModelIndex as a parameter and returns a TreeItem.
For my Application I need the reverse function: getting the QModelIndex from an TreeItem. This should be independent from the View. This means that I can't use the function QTreeView::currentIndex().
Is there any good solution for my problem?

You need to call QAbstractItemModel::createIndex passing in the correct row, column and the item iteself.
The following should work:
QModelIndex TreeModel::indexForTreeItem(TreeItem* item)
{
return createIndex(item->childNumber(), 0, item);
}
An explanation of how I came to this:
createIndex also takes a void* data pointer, which in the EditableTreeModel TreeModel example code, is a pointer to the TreeItem. You can see this is the case in the TreeModel::index member function:
QModelIndex TreeModel::index(int row, int column, const QModelIndex &parent) const
{
if (parent.isValid() && parent.column() != 0)
return QModelIndex();
TreeItem *parentItem = getItem(parent);
TreeItem *childItem = parentItem->child(row);
if (childItem)
return createIndex(row, column, childItem); // <-- here childItem is the TreeItem*
else
return QModelIndex();
}
Note that QAbstractItemModel::createIndex is a protected function, so you have to add a new member function to your TreeModel which creates the QModelIndex for you.
In order to calculate the row for a given TreeItem there is a member function childNumber which returns its index in its parent's list of children (ie: its row)
Unfortunately it is not possible to calculate the column for a given TreeItem, as a TreeItem contains all the data for its columns, so encompasses all columns. As such, a reasonable default would be to use 0 (the left-most column)

You can build a map from TreeItem to QModelIndex as following :
void buildMap(const QModelIndex &index, const QAbstractItemModel *model, QMap<TreeItem *, QModelIndex> &itemMap)
{
if( !index.isValid() )
return;
TreeItem *item = static_cast<TreeItem*>(index.internalPointer());
itemMap.insert( item, index );
int rows = model->rowCount(index);
int cols = model->columnCount(index);
for (int i = 0; i < rows; ++i)
for (int j = 0; j < cols; ++j){
QModelIndex idChild = model->index(i, j, index);
buildMap( idChild , model, itemMap );
}
}
The function buildMap should be used as :
QMap<TreeItem *, QModelIndex> itemMap;
TreeModel *model = static_cast<TreeModel *>(view->model());
for (int row = 0; row < model->rowCount(); ++row){
buildMap( model->index(row,0), model, itemMap );
}
qDebug() << "Item map count : " << itemMap.count();
foreach( TreeItem * item, itemMap.keys( )){
qDebug() << "item " << item << " -> " << itemMap[ item ];
}
Output:
Item map count : 35
item 0xbe1fe8 -> QModelIndex(1,1,0xbe1fe8,TreeModel(0x4421390))
item 0xbe2020 -> QModelIndex(2,1,0xbe2020,TreeModel(0x4421390))
item 0xbe22f8 -> QModelIndex(3,1,0xbe22f8,TreeModel(0x4421390))
item 0xbe2330 -> QModelIndex(2,0,0xbe2330,TreeModel(0x4421390))
...

Related

How to create a QTreeView that can handle very large amount of data?

I'm adding data loaded form a file on disk to a QTreeView , the file contains more than 60.000 rows and new 'rows'/data will be constantly added to it while its being executed, so each time the GUI is reloaded/reopened the file will contain more data.
I tried to load this data using another thread just to figure out that QAbstractItemModel is not thread safe.
Then I tried to created a 'lazy load' subclass of a QAbstractItemModel , that 'process' the items as they get visible in the
QTreeview.
At the beginning it worked great, as it doesn't freeze the entire GUI for like 10~ seconds at runtime anymore.
However when i scroll the QTreeView it get frozen for many seconds, i think its 'loading' the data.
I was reading the documentation of QAbstractItemModel and see that it have these two functions: fetchMore() and canFetchMore(), to control how the data is loaded, but i didn't understand how to use it and if they could help in this case.
How I could dynamically load/unload the data as it get visible in the QTreeView? and free the memory of the rows that are not visible/not visible anymore, instead of having the entire file loaded into the memory.
Here is what I already did, using Qt 6.4.
Reproducible example:
#include "treeview.h"
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindowClass())
{
ui->setupUi(this);
QGridLayout* layout = new QGridLayout(this);
ui->centralWidget->setLayout(layout);
auto treeView = new TreeView(this);
layout->addWidget(treeView);
QShortcut *shortcut = new QShortcut(QKeySequence(Qt::Key_F2), this);
connect(shortcut, &QShortcut::activated, [=] {
treeView->setInfo();
qDebug() << "rowCount: " << treeView->model->rowCount();
});
}
treeview.h
class LazyLoadingModel : public QAbstractItemModel
{
Q_OBJECT
public:
LazyLoadingModel(QObject *parent = nullptr) : QAbstractItemModel(parent) {}
int rowCount(const QModelIndex &parent = QModelIndex()) const override
{
return m_rows.count();
}
int columnCount(const QModelIndex &parent = QModelIndex()) const override
{
return m_columns.count();
}
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override
{
if (!index.isValid())
return QVariant();
int row = index.row();
int column = index.column();
if (row >= m_rows.count() || column >= m_columns.count())
return QVariant();
if (role == Qt::DisplayRole)
{
return m_rows[row][column];
}
return QVariant();
}
bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override
{
if (!index.isValid())
return false;
int row = index.row();
int column = index.column();
if (row >= m_rows.count() || column >= m_columns.count())
return false;
if (role == Qt::EditRole)
{
m_rows[row][column] = value.toString();
emit dataChanged(index, index);
return true;
}
return false;
}
QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override
{
if (!parent.isValid())
return createIndex(row, column);
else
return QModelIndex();
}
QModelIndex parent(const QModelIndex &index) const override
{
return QModelIndex();
}
void setColumns(const QStringList &columns)
{
m_columns = columns;
}
void addData(const QVector<QStringList> &row_info)
{
beginInsertRows(QModelIndex(), m_rows.count(), m_rows.count() + row_info.count() - 1);
for (const auto &row : row_info)
m_rows.append(row);
endInsertRows();
}
private:
QStringList m_columns;
QList<QStringList> m_rows;
};
class TreeView : public QTreeView {
Q_OBJECT
public:
LazyLoadingModel* model = new LazyLoadingModel();
TreeView(QWidget* parent = 0) : QTreeView(parent) {
setModel(model);
}
void setInfo() {
QElapsedTimer timer;
timer.start();
model->setColumns({ "Column 0", "Column 1", "Column 2", "Column 3" });
//
// Load the data from disk and parse it into a qvector
// or:
// Create some random data, just to debug the model:
QVector<QStringList> info{};
QStringList list;
for (size_t i = 0; i < 60000; i++) {
list = {};
QString str = QString::number(i);
for (size_t i = 0; i < 4; i++)
list << str;
info.emplace_back(list);
}
model->addData(info);
qint64 elapsed = timer.elapsed();
qDebug() << "[T] Elapsed time (ms):" << elapsed;
}
};

Printing QTableView with vertical and horizontal headers

I have a program that gets data from SQL (using QSQLQuery) and puts the result into a QTableView.
For user ease of reading I needed to transpose the SQL output (swap rows and columns) but couldn't achieve this easily in SQL (even using PIVOT). Instead I used a proxy model in Qt which works great.
Problem is I need to be able to print the entire QTableView. I can print the contents of the table with the horizontal headers but for the life of me, cant work out how to print the vertical headers (technically the first column since each row has a label due to the transposition). Qt doesn't recognize the first 'column' as being a column of row-names, nor do I know how to get it to treat it as vertical headers for printing.
Proxy model code:
class Horizontal_proxy_model : public QAbstractProxyModel {
public:
Horizontal_proxy_model(QObject * parent = 0);
QModelIndex mapToSource(const QModelIndex &proxyIndex) const;
QModelIndex mapFromSource(const QModelIndex &sourceIndex) const;
QModelIndex index(int row, int column, const QModelIndex &parent =
QModelIndex()) const;
QModelIndex parent(const QModelIndex &child) const;
int rowCount(const QModelIndex &parent) const;
int columnCount(const QModelIndex &parent) const;
QVariant headerData(int section, Qt::Orientation orientation, int role)
const;
};
Horizontal_proxy_model::Horizontal_proxy_model(QObject *parent) :
QAbstractProxyModel(parent) {
}
QModelIndex Horizontal_proxy_model::mapToSource(const QModelIndex
&proxyIndex) const {
if (sourceModel()) {
return sourceModel()->index(proxyIndex.column(), proxyIndex.row());
} else {
return QModelIndex();
}
}
QModelIndex Horizontal_proxy_model::mapFromSource(const QModelIndex
&sourceIndex) const {
return index(sourceIndex.column(), sourceIndex.row());
}
QModelIndex Horizontal_proxy_model::index(int row, int column, const
QModelIndex &) const {
return createIndex(row, column, (void*) 0);
}
QModelIndex Horizontal_proxy_model::parent(const QModelIndex &) const {
return QModelIndex();
}
int Horizontal_proxy_model::rowCount(const QModelIndex &) const {
return sourceModel() ? sourceModel()->columnCount() : 0;
}
int Horizontal_proxy_model::columnCount(const QModelIndex &) const {
return sourceModel() ? sourceModel()->rowCount() : 0;
}
QVariant Horizontal_proxy_model::headerData(
int section, Qt::Orientation orientation, int role) const {
if (!sourceModel()) { return QVariant(); }
Qt::Orientation new_orientation = orientation == Qt::Horizontal ?
Qt::Vertical : Qt::Horizontal;
return sourceModel()->headerData(section, new_orientation, role);
}
Here are the basics of how I'm trying to print;
void Snapshot_finance::on_pushButton_print_clicked()
{
QString html;
html = "<html><body><table border=\"0\">";
for(int row = 0; row < ui->tableView->model()->rowCount(); row++) {
html += "<tr>";
for(int column = 0; column < ui->tableView->model()->columnCount();
column++) {
QString data = ui->tableView->model()->data(ui->tableView->model()->
index(row, column), Qt::DisplayRole).toString();
html += "<td>" + data + "</td>";
}
html += "</tr>";
}
html += "</table></body></html>";
QPrinter printer;
QPrintDialog *dialog = new QPrintDialog(&printer);
if(dialog->exec() == QDialog::Accepted) {
QTextDocument document;
document.setHtml(html);
document.print(&printer);
}
}
Really appreciate any help or advice!
If you want to print the headers you must add them as shown in the following code:
void Snapshot_finance::on_pushButton_print_clicked()
const QString format("<td>%1</td>");
QString html;
QAbstractItemModel *md = ui->tableView->model();
html = "<html><body><table border=\"0\">";
html += "<td></td>";
for(int column = 0; column < md->columnCount();
column++) {
QString data = md->headerData(column, Qt::Horizontal, Qt::DisplayRole).toString();
html += format.arg(data);
}
for(int row = 0; row < md->rowCount() ; row++) {
html += "<tr>";
QString data = md->headerData(row, Qt::Vertical, Qt::DisplayRole).toString();
html += format.arg(data);
for(int column = 0; column < md->columnCount();
column++) {
QString data = md->index(row, column).data(Qt::DisplayRole).toString();
html += format.arg(data);
}
html += "</tr>";
}
html += "</table></body></html>";
QPrinter printer;
QPrintDialog *dialog = new QPrintDialog(&printer);
if(dialog->exec() == QDialog::Accepted) {
QTextDocument document;
document.setHtml(html);
document.print(&printer);
}
}
TableView:
Part of PDF
The code that is implemented for the test can be found in the following link.

QTableView + QAbstractTableModel: Move rows via drag'n'drop

I have a simple QAbstractTableModel-based model and a QTableView for it.
My aim is simple as well: allow to move/reorder rows via drag'n'drop. Notes:
D'n'd changes inside QTableView should be reflected in my model;
D'n'd supposed to be internal - movement should be performed only inside my view, no external MIME exports;
I want to drag and drop whole row. Separate items should not be dragged or dropped;
Dragging horizontal header is not a suitable solution for me because I want headers to be hided and because I want to let user to grab row at any place to drag it;
I'm reeeally close to my aim. But still it doesn't work as I expect. Now I can drag rows, but seems that any cell can accept a drop, although I've specified Qt::ItemIsDropEnabled only for a global table's parent and do not specify this flag for actual table items because I do not want to drop to them, I want somehow to drop "between the rows", just to perform row movement. Because table items for some reason can accept drops I get curious behaviour: if a drop to first cell of any row, I achieve exactly what I want: my row moves correctly. But if I drop to nonfirst cell of any row, it goes totally wrong. But it's better to show a pic of what happens here:
My code (minimal sample that has exactly my problem):
main.cpp
void setupView(QTableView &t)
{
t.verticalHeader()->hide();
t.horizontalHeader()->hide();
t.horizontalHeader()->setStretchLastSection(true);
t.setSelectionBehavior(QAbstractItemView::SelectRows);
t.setSelectionMode(QAbstractItemView::SingleSelection);
t.setDragEnabled(true);
t.setDropIndicatorShown(true);
t.setAcceptDrops(true);
t.viewport()->setAcceptDrops(true);
t.setDefaultDropAction(Qt::MoveAction);
t.setDragDropMode(QTableView::InternalMove);
t.setDragDropOverwriteMode(false);
}
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QMainWindow w;
QTableView *table = new QTableView(&w);
setupView(*table);
table->setModel(new TableModel);
w.setCentralWidget(table);
w.show();
return a.exec();
}
tablemodel.cpp
#include "tablemodel.h"
TableModel::TableModel()
{
// m_data is a QList<QStringList>
m_data = {
{"Name", "Kelly"},
{"Age", "19"},
{"Gender", "Female"},
};
}
int TableModel::rowCount(const QModelIndex &parent) const {
return m_data.size();
}
int TableModel::columnCount(const QModelIndex &parent) const {
return 2;
}
QVariant TableModel::data(const QModelIndex &i, int r) const
{
return (r == Qt::DisplayRole) ? m_data[i.row()][i.column()] : QVariant();
}
QVariant TableModel::headerData(int section, Qt::Orientation orientation, int r) const
{
return QVariant();
}
Qt::ItemFlags TableModel::flags(const QModelIndex &index) const
{
Qt::ItemFlags f = Qt::ItemIsEnabled | Qt::ItemIsSelectable
| Qt::ItemIsEditable | Qt::ItemIsDragEnabled;
if(!index.isValid()) {
f |= Qt::ItemIsDropEnabled;
}
return f;
}
Qt::DropActions TableModel::supportedDropActions() const
{
return Qt::MoveAction | Qt::CopyAction;
}
bool TableModel::setData(const QModelIndex &i, const QVariant &v, int r)
{
if(r == Qt::EditRole || r == Qt::DisplayRole) {
m_data[i.row()][i.column()] = v.toString();
return true;
}
return false;
}
bool TableModel::setItemData(const QModelIndex &i, const QMap<int, QVariant> &roles)
{
if(!roles.contains(Qt::EditRole) && !roles.contains(Qt::DisplayRole)) {
return false;
}
m_data[i.row()][i.column()] = roles[Qt::DisplayRole].toString();
return true;
}
bool TableModel::insertRows(int row, int count, const QModelIndex &parent)
{
beginInsertRows(QModelIndex(), row, row + count - 1);
for(int i = 0; i<count; ++i) {
m_data.insert(row, QStringList({"", ""}));
}
endInsertRows();
return true;
}
bool TableModel::removeRows(int row, int count, const QModelIndex &parent)
{
beginRemoveRows(QModelIndex(), row, row + count - 1);
for(int i = 0; i<count; ++i) {
m_data.removeAt(row);
}
endRemoveRows();
return true;
}
bool TableModel::moveRows(const QModelIndex &srcParent, int srcRow, int count,
const QModelIndex &dstParent, int dstChild)
{
beginMoveRows(QModelIndex(), srcRow, srcRow + count - 1, QModelIndex(), dstChild);
for(int i = 0; i<count; ++i) {
m_data.insert(dstChild + i, m_data[srcRow]);
int removeIndex = dstChild > srcRow ? srcRow : srcRow+1;
m_data.removeAt(removeIndex);
}
endMoveRows();
return true;
}
Please, give me some hint, what is wrong with model or view setup now.
UPD
For those who is interested in the solution:
bool TableModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent)
{
Q_UNUSED(parent);
Q_UNUSED(column);
if(row == -1) {
row = rowCount();
}
return QAbstractTableModel::dropMimeData(data, action, row, 0, parent);
}
You should add dropMimeData method to your model and implement it properly. If the drop on the first column works fine for you, you could probably simply call QAbstractItemModel::dropMimeData from inside your model's dropMimeData with column parameter equal to 0 regardless of which column the drop was actually made on.
Nitpick: These two lines are not necessary:
t.setAcceptDrops(true);
t.viewport()->setAcceptDrops(true);

How to get current selected row from ListView in Qt with C++? [duplicate]

{
...
nrow = 10;
ncol = 1;
/*create QListView */
m_listView = new QListView(this);
m_listView->setGeometry(QRect(QPoint(0,100), QSize(100, 150)));
QStandardItemModel *model = new QStandardItemModel( nrow, 1, this );
//fill model value
for( int r=0; r<nrow; r++ )
{
QString sstr = "[ " + QString::number(r) + " ]";
QStandardItem *item = new QStandardItem(QString("Idx ") + sstr);
model->setItem(r, 0, item);
}
//set model
m_listView->setModel(model);
m_listView->setSelectionMode( QAbstractItemView::ExtendedSelection );
connect(m_listView, SIGNAL(pressed(QModelIndex)), this, SLOT(hItem(QModelIndex)));
}
void MainWindow::hItem(QModelIndex m)
{
QItemSelectionModel *selectionModel = m_listView->selectionModel();
m_txt2->setText(QString::number(selectionModel->selectedIndexes().at(0),'d',0));//???
//not sure how to get the items selected: index and string per selection
}
I just tested this for my own needs and it works in Qt 5.1.
I'm pretty new to C++ so in this line:
foreach(const QModelIndex &index, list){
I don't know if the const and the dereferencing (&) is needed - it works with or without. I cobbled this together from various examples I've seen.
Perhaps someone who understands C++ better can comment.
void MainWindow::on_keywordsList_clicked(const QModelIndex &index)
{
QModelIndexList list =keywordListView->selectionModel()->selectedIndexes();
QStringList slist;
foreach(const QModelIndex &index, list){
slist.append( index.data(Qt::DisplayRole ).toString());
}
qDebug() << slist.join(",");
}

How do I get the items selected from a QListView?

{
...
nrow = 10;
ncol = 1;
/*create QListView */
m_listView = new QListView(this);
m_listView->setGeometry(QRect(QPoint(0,100), QSize(100, 150)));
QStandardItemModel *model = new QStandardItemModel( nrow, 1, this );
//fill model value
for( int r=0; r<nrow; r++ )
{
QString sstr = "[ " + QString::number(r) + " ]";
QStandardItem *item = new QStandardItem(QString("Idx ") + sstr);
model->setItem(r, 0, item);
}
//set model
m_listView->setModel(model);
m_listView->setSelectionMode( QAbstractItemView::ExtendedSelection );
connect(m_listView, SIGNAL(pressed(QModelIndex)), this, SLOT(hItem(QModelIndex)));
}
void MainWindow::hItem(QModelIndex m)
{
QItemSelectionModel *selectionModel = m_listView->selectionModel();
m_txt2->setText(QString::number(selectionModel->selectedIndexes().at(0),'d',0));//???
//not sure how to get the items selected: index and string per selection
}
I just tested this for my own needs and it works in Qt 5.1.
I'm pretty new to C++ so in this line:
foreach(const QModelIndex &index, list){
I don't know if the const and the dereferencing (&) is needed - it works with or without. I cobbled this together from various examples I've seen.
Perhaps someone who understands C++ better can comment.
void MainWindow::on_keywordsList_clicked(const QModelIndex &index)
{
QModelIndexList list =keywordListView->selectionModel()->selectedIndexes();
QStringList slist;
foreach(const QModelIndex &index, list){
slist.append( index.data(Qt::DisplayRole ).toString());
}
qDebug() << slist.join(",");
}