{
...
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(",");
}
Related
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;
}
};
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))
...
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.
{
...
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(",");
}
I am developing a Qt application using QTreeView and QFileSystemModel.
I am able to get the parent's child till one level but I am not able to get the parent's child's child.
For eg:
C is child of B,
B is child of A
I am able to get B as A's child but I also want C as A's Child.
I want like this C->B->A.
Can someone give some input regarding this and help me out.
Thanks in advance.
//QItemSelectionModel *sel = ui->dir_tree->selectionModel();
QStringList strings = extractStringsFromModel(ui->dir_tree->model(), ui->dir_tree->rootIndex());
QFileSystemModel* model = (QFileSystemModel*)ui->dir_tree->model();
QModelIndexList indexlist = ui->dir_tree->selectionModel()->selectedIndexes();
QVariant data;
//QList<QModelIndex> modelindex(indexlist);
int row = -1;
for(int i=0; i<indexlist.size();i=i+4)
{
QModelIndex mi=indexlist.at(i);
info1 = model->fileInfo(mi);
QString childstr = info1.filePath();
QString childname = info1.fileName();
QModelIndex mi2= indexlist.at(i).parent();
info = model->fileInfo(mi2);
QString parentstr = info.filePath();
QString parentname = info.fileName();
QStringList childlist;
for(int j=0;j<model->rowCount(indexlist.at(i));j++)
{
QModelIndex mi3 = indexlist.at(i).child(j, 0);
info2 = model->fileInfo(mi3);
QString childrenstr = info2.filePath();
childlist << childrenstr;
qDebug()<<"parents' children"<<childrenstr<<j;
}
}
I like to use recursion for this kind of thing:
void MyTreeView::GetAllChildren(QModelIndex &index, QModelIndexList &indicies)
{
indicies.push_back(index);
for (int i = 0; i != model()->rowCount(index); ++i)
GetAllChildren(index.child(i,0), indicies);
}
Usage (from somewhere inside the tree view):
QModelIndexList list;
GetAllChildren(model()->index(0,0), list);
Getting all children breadth-first:
QModelIndexList children;
// Get top-level first.
for ( int i = 0; i < model->rowCount(); ++i ) {
children << model->index( i, 0 ); // Use whatever column you are interested in.
}
// Now descend through the generations.
for ( int i = 0; i < children.size(); ++i ) {
for ( int j = 0; j < model->rowCount( children[i] ); ++j ) {
children << children[i].child( j, 0 );
}
}
Getting all parents from a child:
QModelIndexList parents;
parents << child.parent();
while ( parents.last().isValid() ) {
parents << parents.last().parent();
}
Please note, I haven't tried these samples!
//Delete
QList<QTreeWidgetItem *> selected = ui->twg->selectedItems();
QTreeWidgetItem *item = selected.first();
QTreeWidgetItem *parent = item->parent();
if(parent) {
int index = parent->indexOfChild(item);
delete parent->takeChild(index);
}
//Add
QList<QTreeWidgetItem *> selected = ui->twg->selectedItems();
QTreeWidgetItem *item = selected.first();
QTreeWidgetItem *parent = item->parent();
if(parent) {
QTreeWidgetItem *tmp = new QTreeWidgetItem(parent);
tmp->setText(0, "Dude");
parent->addChild(tmp);
}