Updating QTreeView with new data not correct - c++

Starting from the popular Qt SimpleTreeModel, I want to be able to update the entire tree view with new data. The example only populates the tree view once on start up, it does not update the tree view afterwards.
I have made some edits to the example (treeview is now in a dialog so I can press "PushButton" to update treeview) and when I update the tree view, the top most TreeItems become the child TreeItems for each topmost TreeItem. When I update the treeview in this case, before and after should be the same as it is the same data, I don't see why before and after would be different. The screenshots below better illustrate the problem:
Before Updating
After Updating, you can see below that if I click on a item that is used multiple times, they all highlight, which makes sense as they are all (probably) pointing to the same item (I'm not sure how).
My edits to the SimpleTreeModel are below:
main.cpp
#include "dialog.h"
#include <QApplication>
int main(int argc, char *argv[])
{
Q_INIT_RESOURCE(simpletreemodel);
QApplication app(argc, argv);
Dialog dialog;
dialog.show();
return app.exec();
}
dialog.cpp
#include "dialog.h"
#include "ui_dialog.h"
#include <QFile>
Dialog::Dialog(QWidget *parent) :
QDialog(parent),
ui(new Ui::Dialog)
{
ui->setupUi(this);
QFile file(":/default.txt");
file.open(QIODevice::ReadOnly);
model = new TreeModel(file.readAll());
file.close();
ui->treeView->setModel(model);
}
Dialog::~Dialog()
{
delete ui;
}
void Dialog::on_pushButton_clicked()
{
model->redrawAll();
}
treeitem.cpp (exactly the same as example except with new function below)
void TreeItem::removeChildren() {
m_childItems.clear();
}
treemodel.cpp (exactly the same as example except with new functions below). I am removing all children from rootItem so I can put completely new data on a each update.
TreeModel::TreeModel(const QString &data, QObject *parent)
: QAbstractItemModel(parent)
{
QList<QVariant> rootData;
this->data1 = data;
rootData << "Title" << "Summary";
rootItem = new TreeItem(rootData);
setupModelData(data.split(QString("\n")), rootItem);
}
void TreeModel::redrawAll() {
rootItem->removeChildren();
setupModelData(data1.split(QString("\n")), rootItem);
emit dataChanged(QModelIndex(), QModelIndex());
}
EDIT: I have modified the redrawAll function to below. The result is the same screenshot after updating as the previous TreeModel::redrawAll() function with emit dataChanged(QModelIndex(), QModelIndex()).
void TreeModel::redrawAll() {
// beginResetModel();
rootItem->removeChildren();
QFile file(":/default.txt");
file.open(QIODevice::ReadOnly);
QString data = file.readAll();
setupModelData(data.split(QString("\n")), rootItem);
file.close();
// endResetModel();
// emit dataChanged(QModelIndex(), QModelIndex());
qDebug() << "TreeModel::redrawAll() " << rowCount() << columnCount();
// the output is TreeModel::redrawAll() 6 2
QModelIndex topLeft = this->index(0, 0);
QModelIndex bottomRight = this->index(rowCount(), columnCount());
emit dataChanged(topLeft, bottomRight);
}

The dataChanged() signal that you emit is nonsense. Not only is the invalid index range not allowed, but dataChanged() only indicates a change in contents of the data items, not in the structure of the tree. You seem to be changing the structure as well.
Since you seem to be changing the contents the entire model, you should indicate that you've reset the model:
void TreeModel::redrawAll() {
beginResetModel();
...
endResetModel();
}

Related

QModelIndexList returned by selectedIndexes() is always empty

I have a QTableView inside a QTabWidgetwhere i enter new row with 6 columns everytime i press a button. Out of 6 columns, 3 columns have QPushButton set on them. I need the row number of the clicked button from the QTableView. I have connected the QPushButton clicked signal to my slot clickedIndex()
This is how i am trying to get the row index of the clicked button but the list is empty.
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QPushButton>
#include <QDebug>
MainWindow::MainWindow(QWidget* parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
m_view = new QTableView(this);
m_model = new QStandardItemModel(m_view);
m_model->setColumnCount(6);
m_view->setModel(m_model);
m_model->insertRow(0);
QPushButton* button = new QPushButton(this);
button->setText("Click");
m_view->setIndexWidget(m_model->index(0, 2), button);
m_model->insertRow(1);
QPushButton* button1 = new QPushButton(this);
button1->setText("Click");
m_view->setIndexWidget(m_model->index(1, 3), button1);
connect(button, &QPushButton::clicked, this, &MainWindow::tableViewClicked);
connect(button1, &QPushButton::clicked, this, &MainWindow::tableViewClicked);
setCentralWidget(m_view);
}
MainWindow::~MainWindow()
{
delete ui;
}
QModelIndex MainWindow::tableViewClicked()
{
//get the list of currently selected indexes from the model
QModelIndexList indexList = m_view->selectionModel()->selectedIndexes();
if (!indexList.isEmpty())
{
qDebug() << indexList.front().row(); //prints 0 all the time
//usually the list should contain only one index at a time
return indexList.front();
}
}
Here is the header:
#include <QMainWindow>
#include <QModelIndex>
#include <QTableView>
#include <QStandardItemModel>
namespace Ui
{
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget* parent = 0);
~MainWindow();
public slots:
QModelIndex tableViewClicked();
private:
Ui::MainWindow* ui;
QTableView* m_view;
QStandardItemModel* m_model;
};
I also tried connecting pressed signal from QTableView but the slot is never called.
connect(m_ui->tableView, &QTableView::pressed, this, &TabView::clickedIndex);
I believe if there is a QWidget on one of the cells in a QTableView model the slot is never called. When i click on cells which do not carry a button the connection works.
I need:
I just need the QModelIndex to the clicked button in the model.
Note: i am using Qt 5.7.1 and i have found some bug reports related to selectedIndexes() like this
Is there a workaround for this?
EDIT: In my code the QModelIndexList itself is empty.
This is a way to do what you are wanting to do.
connect(button, &QPushButton::clicked, this, [this, button, view] {
qDebug() << view->indexAt(button->pos()).row();
});
You can get a "clicked"-Signal from your TableView.
To get the index, you can use the following Code:
connect(m_ui->tableView, SIGNAL(clicked(QModelIndex)), this,
SLOT(slotClicked(QModelIndex)));
The Slot in the header-File:
void slotCLicked(const QModelIndex &index);
void XY::slotClicked(const QModelIndex &index)
{
qDebug() << index;
}

Storing Directory hierarchy in Datastructure Qt

How should i store data which is going into a TreeView? A Dictionary (QHash)? Plain Text? JSON?
My Hierarchy would be something like:
{
'Cloth': {
'Tissue':None,
'Leather': {
'Bandage': None
}
},
'Smoke': {
'White':{
'Smallscale': None,
'Largescale':None
}
}
}
Actions:
When I click a leaf-Element it will retrieve the Fullpath, like "Smoke/White/Smallscale" and this will be used as a key to place a SQL-Query.
I would use QStandardItem for every entry and when clicked, I would recursively call their parents, till I hit root.
Any thoughts?
Do you know QJsonTreeWidget?
Of course you don't need to use that library, but I do think you should use JSON in any case. It's almost a standard nowadays and very useful when we're working with trees.
Boost also has a wonderful library to work with JSON.
You can use one of the json library (like cajun) to parse json file.
This is the Qt part:
#include <QtGui>
#include <QTreeView>
class SimpleTreeView :public QTreeView
{
Q_OBJECT
public:
SimpleTreeView(QWidget *parent = 0);
public slots:
void slot_item_clicked(const QModelIndex &idx);
private:
QStandardItemModel *model;
};
#include <simpletreeview.h>
#include <qmessagebox.h>
#include <qobject.h>
SimpleTreeView::SimpleTreeView(QWidget *parent) : QTreeView(parent)
{
model = new QStandardItemModel(2,1);
QStandardItem *item1 = new QStandardItem("Cloth");
QStandardItem *item2 = new QStandardItem("Smoke");
model->setItem(0, 0, item1);
model->setItem(1, 0, item2);
QStandardItem *item3 = new QStandardItem("White");
item2->appendRow(item3);
QStandardItem *leaf = new QStandardItem("Smallscale");
leaf->setData("Smoke/White/Smallscale");
item3->appendRow(leaf);
setModel(model);
connect(this, SIGNAL(clicked(const QModelIndex &)), this, SLOT(slot_item_clicked(const QModelIndex &)));
}
void SimpleTreeView::slot_item_clicked(const QModelIndex & idx)
{
QString strData = model->itemFromIndex(idx)->data().toString();
QMessageBox::information(NULL, "Title", strData, QMessageBox::Yes, QMessageBox::Yes);
}
// main.cpp
#include <QApplication>
#include <simpletreeview.h>
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
SimpleTreeView view;
view.show();
return app.exec();
}

How to use the threads to create the images thumbnail

I'm use QTreeView to get the images path, then I'm use QListView to display the images that in specific path as thumbnail.
The problem in the period, create and display the thumbnail images.
The previous process, take a long time to done, depend on the number of images.
And for that reason I decided to use the threads, maybe helps to prevent the hung up which occur in application and increase the speed of create and display the thumbnail images.
void mainWidget::on_treeView_clicked(const QModelIndex &index){
filesModel->clear();
QFileSystemModel *sysModel = qobject_cast<QFileSystemModel*>(ui->treeView->model());
QDir dir(sysModel->filePath(ui->treeView->currentIndex()));
QFileInfoList filesList = dir.entryInfoList(QStringList() << "*.jpg" << "*.jpeg" << "*.tif" << "*.png" << "*.gif" << "*.bmp" ,QDir::Files);
int filesCount = filesList.size();
for(int i=0;i<filesCount;i++){
QPixmap originalImage(filesList[i].filePath());
if(!originalImage.isNull()){
QPixmap scaledImage = originalImage.scaled(150, 120);
filesModel->setItem(i, new QStandardItem(QIcon(scaledImage), filesList[i].baseName()));
}
}
}
How can I use threads with the previous code ?
I believe that a simple and correct approach is using QtConcurrent as the following:
Note: If you are using Qt 5 you will need to add QT += concurrent to the .pro file.
Header:
#include <QtCore>
#include <QtGui>
#include <QtWidgets>
#include <QtConcurrent>
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
signals:
void UpdateItem(int, QImage);
private slots:
void on_treeView_clicked(const QModelIndex &);
void List(QFileInfoList filesList, QSize size);
void setThumbs(int index, QImage img);
private:
Ui::MainWindow *ui;
QFileSystemModel *model;
QStandardItemModel *filesmodel;
QFuture<void> thread;
bool running;
};
CPP file:
#include "mainwindow.h"
#include "ui_mainwindow.h"
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
QThreadPool::globalInstance()->setMaxThreadCount(1);
model = new QFileSystemModel(this);
model->setRootPath("\\");
filesmodel = new QStandardItemModel(this);
ui->treeView->setModel(model);
ui->listView->setModel(filesmodel);
connect(this, SIGNAL(UpdateItem(int,QImage)), SLOT(setThumbs(int,QImage)));
ui->treeView->header()->setSectionResizeMode(QHeaderView::ResizeToContents);
running = false;
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::on_treeView_clicked(const QModelIndex&)
{
filesmodel->clear();
running = false;
thread.waitForFinished();
QDir dir(model->filePath(ui->treeView->currentIndex()));
QFileInfoList filesList = dir.entryInfoList(QStringList() << "*.jpg" << "*.jpeg" << "*.tif" << "*.png" << "*.gif" << "*.bmp", QDir::Files);
int filesCount = filesList.size();
QPixmap placeholder = QPixmap(ui->listView->iconSize());
placeholder.fill(Qt::gray);
for (int i = 0; i < filesCount; i++)
filesmodel->setItem(i, new QStandardItem(QIcon(placeholder), filesList[i].baseName()));
running = true;
thread = QtConcurrent::run(this, &MainWindow::List, filesList, ui->listView->iconSize());
}
void MainWindow::List(QFileInfoList filesList, QSize size)
{
int filesCount = filesList.size();
for (int i = 0; running && i < filesCount; i++)
{
QImage originalImage(filesList[i].filePath());
if (!originalImage.isNull())
{
QImage scaledImage = originalImage.scaled(size);
if (!running) return;
emit UpdateItem(i, scaledImage);
}
}
}
void MainWindow::setThumbs(int index, QImage img)
{
QIcon icon = QIcon(QPixmap::fromImage(img));
QStandardItem *item = filesmodel->item(index);
filesmodel->setItem(index, new QStandardItem(icon, item->text()));
}
You don't have to use threads to keep your application responsive in this case. Use QCoreApplication::processEvents() in the loop to keep the application responsive. QCoreApplication::processEvents() will process all the events in the event queue of the thread which calls it.
This is an older thread, but still comes up in Google. Check out the answer to this related question. I found the use of QIdentityProxyModel to be a bit more elegant, as it allowed QFileSystemModel to be used as the list view's model.

(Qt C++) How to print chosen files/folders into a text file from a QTableView

I have a QTableView *tableView. When an user chooses the files / folders in tableView and right click -> choose "Print these items", I want my program to print those names into a text file or assign to a string. How can I do that? Thank you.
frmainwindow.h:
private slots:
void showContextMenuRequested(QPoint pos);
frmainwindow.cpp:
#include "frmainwindow.h"
#include "ui_frmainwindow.h"
FrMainWindow::FrMainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::FrMainWindow)
{
ui->setupUi(this);
model1->setRootPath("c:\\");
ui->tableView->setModel(model1);
connect(ui->tableView, SIGNAL(customContextMenuRequested(QPoint)), this,
SLOT (showContextMenuRequested(QPoint)));
}
FrMainWindow::~FrMainWindow()
{
delete ui;
}
void FrMainWindow::showContextMenuRequested(QPoint pos)
{
QMenu *contextMenu = new QMenu(this);
contextMenu->addAction(new QAction("Print these items", this));
contextMenu->popup(ui->tableView->viewport()->mapToGlobal(pos));
}
First of all, connect your action to a processing slot:
QAction* action = new QAction("Print these items", this);
connect(action, SIGNAL(triggered(), this, SLOT(printItems())));
Then you can access selected indexes tableView->selectionModel()->selectedIndexes() and using these indexes access data model1->data(index):
void printItems()
{
QFile file(QLatin1String("file.txt"));
file.open(QIODevice::WriteOnly);
QModelIndexList indexes = ui->tableView->selectionModel()->selectedIndexes();
foreach (QModelIndex index, indexes)
{
file.write(model1->data(index).toString().toLatin1());
}
}

Detecting if an item is clicked at at some row in a QlistWidget

I Have been given this simple task ,
I have this list where i instert items whenever ok is clicked,void Form::ok() handle that event is supposed to add new list items to the list.
Now what am not able to do is to detect the if an item is clicked at at some row then do something according to that, this is my code..
#include "form1.h"
#include "form.h"
#include "ui_form.h"
#include "ui_form1.h"
#include<QScrollArea>
#include<QScrollBar>
//#include <QgeoPositioninfo.h>
Form::Form(QWidget *parent) :
QWidget(parent),
ui(new Ui::Form)
{
ui->setupUi(this);
}
Form::~Form()
{
delete ui;
}
void Form::ok()
{
QIcon mypix (":/karim/test.png");
QListWidgetItem* newItem = new QListWidgetItem;
newItem->setText("pixmix");
newItem->setIcon(mypix);
int row = ui->listWidget->row(ui->listWidget->currentItem());
this->ui->listWidget->insertItem(row, newItem);
//if(item at row x is clicked)
{
//do something
}
}
Please be specific in yout answer i will appreciate that
Something as below :
connect(ui->listWidget, SIGNAL(itemClicked(QListWidgetItem *)), this, SLOT(itemClickedSlot(QListWidgetItem *)));
void Form::itemClickedSlot (QListWidgetItem * itemClicked)
{
//Do something with clicked item
}
The QListWidgetItem stores its text as a QString so you may need to cast it to something else if you want to manipulate it. The QListWidgetItem itself holds no information about it's position, but QListWidget does.
If you look at the documentation for QListWidget under signals you can see that there are a couple different states that you can execute a function during. I personally use currentItemChanged.
http://qt-project.org/doc/qt-4.8/QListWidget.html#signals
Update your constructor to include connecting your listWidget to myFunc:
Form::Form(QWidget *parent) : QWidget(parent), ui(new Ui::Form) {
ui->setupUi(this);
connect(ui->listWidget,
SIGNAL(currentItemChanged(QListWidgetItem*, QListWidgetItem*)), this,
SLOT(myFunc(QListWidgetItem *)));
}
And add this function to your class:
void Form::myFunc(QListWidget *item) {
int currentRow = ui->listWidget->currentRow();
std::cout << (item->text()).toStdString() << std::endl;
}
That should get you the current position of the QListWidgetItem in the list and its text. Using item-> you can then change it's text and change some other things:
http://qt-project.org/doc/qt-4.8/qlistwidgetitem.html
Happy coding.
You need to connect the itemClicked(QListWidgetItem * item) signal to some slot to handle clicks on an item.