I'm trying to create a small app, which will find, read and export some *.xml documents. App must read the hierarchical folder structure and visualise it in QTreeView on the form. For better managing I want it to expand all objects of treeview on the app's startup. I've tried many different solutions like this:
void expandChildren(const QModelIndex &index, QTreeView *view)
{
if (!index.isValid())
{
return;
}
int childCount = index.model()->rowCount(index);
for (int i = 0; i < childCount; i++)
{
const QModelIndex &child = index.child(i, 0);
expandChildren(child, view);
}
if (!view->isExpanded(index))
{
view->expand(index);
}
}
from some forums and embeded solutions like QTreeView::expandRecursively and QTreeView::expandAll, but I haven't achived what I wanted.
mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QFileSystemModel>
#include <QDebug>
#include <QDirModel>
#include <QTreeView>
QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE
class MainWindow : public QMainWindow
{
Q_OBJECT
QModelIndex fs_index;
QModelIndex child_1_index;
QModelIndex child_2_index;
QModelIndex child_3_index;
QFileSystemModel *fs_model = new QFileSystemModel;
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
private slots:
void on_treeView_clicked(const QModelIndex &index);
void on_pushButton_2_clicked();
void on_pushButton_clicked();
private:
Ui::MainWindow *ui;
void expandChildren(const QModelIndex &index, QTreeView *view)
{
if (!index.isValid())
{
return;
}
int childCount = index.model()->rowCount(index);
for (int i = 0; i < childCount; i++)
{
const QModelIndex &child = index.child(i, 0);
expandChildren(child, view);
}
if (!view->isExpanded(index))
{
view->expand(index);
}
}
};
#endif // MAINWINDOW_H
mainwindow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
QString str_root_path;
QDir dir;
qDebug() << "Current user's home folder is: " <<QDir::home().path();
str_root_path = QDir::home().path() + "/AppData/Local/app/settings";
qDebug() << "Settings storing folder id: " << str_root_path;
dir.setPath(str_root_path);
if (!dir.exists())
{
qDebug() << "Settings folder doesn't exist.";
return;
}
if (!dir.isReadable())
{
qDebug() << "Folder found. Read access denied. Check access rights.";
return;
}
qDebug() << "Folder found. Read access granted. Reading...";
ui->treeView->setModel(fs_model);
fs_index = fs_model->index(str_root_path);
qDebug() << fs_model->fileName(fs_index);
fs_model->setRootPath(str_root_path);
QStringList filter;
filter << "*.xml";
fs_model->setNameFilters(filter);
fs_model->setFilter( QDir::AllDirs | QDir::Files | QDir::NoDotAndDotDot);
ui->treeView->setRootIndex(fs_index);
ui->treeView->setCurrentIndex(fs_index);
for (int i = 1; i < fs_model->columnCount(); i++)
{
ui->treeView->setColumnHidden(i, true);
}
ui->treeView->show();
qDebug().nospace() << "View loaded. Expanding....";
ui->treeView->setExpanded(fs_index, true);
int fs_index_rows = fs_model->rowCount(fs_index);
qDebug().nospace() << "Number of found profiles:" << fs_index_rows;
for (int i = 0; i < fs_index_rows; ++i)
{
child_1_index = fs_model->index(i,0,fs_index);
ui->treeView->setExpanded(child_1_index, true);
int child_1_index_rows = fs_model->rowCount(child_1_index);
qDebug().nospace() << "Step #" << i+1 << " Object name: " << fs_model->fileName(child_1_index) << ". Num of children: " << child_1_index_rows;
for (int j = 0; j < child_1_index_rows; ++j)
{
child_2_index = ui->treeView->model()->index(j,0,child_1_index);
//qDebug() << child_2_index;
ui->treeView->setExpanded(child_2_index, true);
int child_2_index_rows = ui->treeView->model()->rowCount(child_2_index);
qDebug().nospace() << "Step #" << i+1 << "/" << j+1 << " Object name: " << fs_model->fileName(child_1_index) << "/" << fs_model->fileName(child_2_index) << ". Num of children: " << child_2_index_rows;
for (int k = 0; k < child_2_index_rows; ++k)
{
child_3_index = ui->treeView->model()->index(k,0,child_2_index);
ui->treeView->setExpanded(child_3_index, true);
int child_3_index_rows = ui->treeView->model()->rowCount(child_3_index);
qDebug().nospace() << "Step #" << i+1 << "/" << j+1 << "/" << k+1 << " Object name: " << fs_model->fileName(child_1_index) << "/" << fs_model->fileName(child_2_index) << "/" << fs_model->fileName(child_3_index) << ". Num of children: " << child_3_index_rows;
}
}
}
}
If I paste that code to the slot which connected to the signal of "pushbutton_Clicked" for example, each click expand the treeview for a one more depth level (the same action appearing if I connect QTreeView::expandRecursively or QTreeView::expandAll to "pushbutton_Clicked"). I've tried to debug every step of app and I understuud that each new index object can't achive parent's index of filesystemmodel.
Please help me understand, where is the error and how to fix it.
I'm new in programming on Qt and my knoledgement is not full but I still searching, reading and trying to understand.
Thank you in advance and sorry for bad English.
All the code of app:
main.cpp
#include "mainwindow.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
return a.exec();
}
mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QFileSystemModel>
#include <QDebug>
#include <QDirModel>
#include <QTreeView>
QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE
class MainWindow : public QMainWindow
{
Q_OBJECT
QModelIndex fs_index;
QFileSystemModel fs_model;
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
private slots:
void on_treeView_clicked(const QModelIndex &index);
void on_pushButton_2_clicked();
void on_pushButton_clicked();
private:
Ui::MainWindow *ui;
void expandChildren(const QModelIndex &index, QTreeView *view)
{
if (!index.isValid()) {
return;
}
int childCount = index.model()->rowCount(index);
for (int i = 0; i < childCount; i++) {
const QModelIndex &child = index.child(i, 0);
// Recursively call the function for each child node.
expandChildren(child, view);
}
if (!view->isExpanded(index))
{
view->expand(index);
}
}
void expand_the_tree(const QModelIndex &root_index, QTreeView *view)
{
qDebug() << fs_model.canFetchMore(root_index);
while (fs_model.canFetchMore(root_index) == true)
{
fs_model.fetchMore(root_index);
}
qDebug().nospace() << "Model fetched on root layer.";
QModelIndex child_1_index;
QModelIndex child_2_index;
QModelIndex child_3_index;
view->expand(root_index);
int root_index_rows = fs_model.rowCount(root_index);
qDebug().nospace() << "Number of found profiles:" << root_index_rows;
for (int i = 0; i < root_index_rows; ++i)
{
child_1_index = fs_model.index(i,0,root_index);
view->expand(child_1_index);
expandChildren(child_1_index, view);
int child_1_index_rows = fs_model.rowCount(child_1_index);
qDebug().nospace() << "Step #" << i+1 << " Object name: " << fs_model.fileName(child_1_index) << ". Num of children: " << child_1_index_rows;
for (int j = 0; j < child_1_index_rows; ++j)
{
child_2_index = fs_model.index(j,0,child_1_index);
//qDebug() << child_2_index;
view->expand(child_2_index);
int child_2_index_rows = fs_model.rowCount(child_2_index);
qDebug().nospace() << "Step #" << i+1 << "/" << j+1 << " Object name: " << fs_model.fileName(child_1_index) << "/" << fs_model.fileName(child_2_index) << ". Num of children: " << child_2_index_rows;
for (int k = 0; k < child_2_index_rows; ++k)
{
child_3_index = fs_model.index(k,0,child_2_index);
view->expand(child_3_index);
int child_3_index_rows = fs_model.rowCount(child_3_index);
qDebug().nospace() << "Step #" << i+1 << "/" << j+1 << "/" << k+1 << " Object name: " << fs_model.fileName(child_1_index) << "/" << fs_model.fileName(child_2_index) << "/" << fs_model.fileName(child_3_index) << ". Num of children: " << child_3_index_rows;
}
}
}
}
};
#endif // MAINWINDOW_H
mainwindow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QApplication>
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
QString str_root_path;
QDir dir;
qDebug() << "Current user's home folder is: " <<QDir::home().path();
str_root_path = QDir::home().path() + "/AppData/Local/app/settings";
qDebug() << "Preset's storing folder id: " << str_root_path;
dir.setPath(str_root_path);
if (!dir.exists())
{
qDebug() << "Settings folder doesn't exist.";
return;
}
if (!dir.isReadable())
{
qDebug() << "Folder found. Read access denied. Check access rights.";
return;
}
qDebug() << "Folder found. Read access granted. Reading...";
fs_index = fs_model.index(str_root_path);
qDebug() << fs_model.fileName(fs_index);
fs_model.setRootPath(str_root_path);
QStringList filter;
filter << "*.xml";
fs_model.setNameFilters(filter);
fs_model.setFilter( QDir::AllDirs | QDir::Files | QDir::NoDotAndDotDot);
ui->treeView->setModel(&fs_model);
ui->treeView->setRootIndex(fs_index);
ui->treeView->setCurrentIndex(fs_index);
qDebug() << fs_model.canFetchMore(fs_index);
for (int c = 1; c < fs_model.columnCount(); c++)
{
ui->treeView->setColumnHidden(c, true);
}
qDebug().nospace() << "View loaded. Expanding....";
expand_the_tree(fs_index, ui->treeView);
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::on_treeView_clicked(const QModelIndex &index)
{
qDebug() << index;
}
void MainWindow::on_pushButton_2_clicked()
{
expandChildren(fs_index, ui->treeView);
}
void MainWindow::on_pushButton_clicked()
{
expand_the_tree(fs_index, ui->treeView);
}
mainwindow.ui
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>712</width>
<height>635</height>
</rect>
</property>
<property name="windowTitle">
<string>MainWindow</string>
</property>
<widget class="QWidget" name="centralwidget">
<widget class="QTreeView" name="treeView">
<property name="geometry">
<rect>
<x>20</x>
<y>60</y>
<width>311</width>
<height>531</height>
</rect>
</property>
</widget>
<widget class="QTableView" name="tableView">
<property name="geometry">
<rect>
<x>370</x>
<y>60</y>
<width>321</width>
<height>531</height>
</rect>
</property>
</widget>
<widget class="QPushButton" name="pushButton">
<property name="geometry">
<rect>
<x>20</x>
<y>600</y>
<width>75</width>
<height>23</height>
</rect>
</property>
<property name="text">
<string>Refresh</string>
</property>
</widget>
<widget class="QPushButton" name="pushButton_2">
<property name="geometry">
<rect>
<x>110</x>
<y>600</y>
<width>75</width>
<height>23</height>
</rect>
</property>
<property name="text">
<string>Retrieve all</string>
</property>
</widget>
</widget>
</widget>
<resources/>
<connections/>
</ui>
Is this the whole code? Can you provide also the main?
Anyway I am not able to compile your code, line 29:
ui->treeView->setModel(fs_model);
calls the function treeView, that does not exist.
Anyway if you wanna do a UI I suggest you use QML and you could have a look at the already done example in qt.
Tree model https://doc-snapshots.qt.io/qt5-5.11/qtquickcontrols-filesystembrowser-example.html
You can look and run them when you open Qt creator. you click on welcome, then example and in the end, you type tree
Once you have the basic code you can modify it as you want.
Related
I'm creating a QSqlRecord object and then I set the values to that QSqlRecord object. But even if I insert the QSqlRecord object to the QSqlTableModel object, the function of inserting records, returns false.
I have this C++ code and it create a QSqlRecord object and set the values. It setting the values in the correct indexed order as how the table was created.
/* Insert data */
int column_index = 0; /* Index 0 is the ID column */
QSqlRecord record;
qDebug() << CALIBRATION_COLUMNS.at(column_index).first;
record.setValue(CALIBRATION_COLUMNS.at(column_index++).first, 1); /* ID */
qDebug() << CALIBRATION_COLUMNS.at(column_index).first;
record.setValue(CALIBRATION_COLUMNS.at(column_index++).first, calibration_id);
qDebug() << CALIBRATION_COLUMNS.at(column_index).first;
record.setValue(CALIBRATION_COLUMNS.at(column_index++).first, calibration_comment);
qDebug() << CALIBRATION_COLUMNS.at(column_index).first;
record.setValue(CALIBRATION_COLUMNS.at(column_index++).first, calibration_date_time);
for(int i = 0; i < 12; i++){
record.setValue(CALIBRATION_COLUMNS.at(column_index++).first, min_adc[i]);
record.setValue(CALIBRATION_COLUMNS.at(column_index++).first, max_adc[i]);
record.setValue(CALIBRATION_COLUMNS.at(column_index++).first, bias_adc[i]);
}
for(int i = 0; i < 5; i++){
record.setValue(CALIBRATION_COLUMNS.at(column_index++).first, min_dadc[i]);
record.setValue(CALIBRATION_COLUMNS.at(column_index++).first, max_dadc[i]);
record.setValue(CALIBRATION_COLUMNS.at(column_index++).first, bias_dadc[i]);
}
for(int i = 0; i < 2; i++)
record.setValue(CALIBRATION_COLUMNS.at(column_index++).first, pulses_per_revolution_encoder[i]);
/* -1 means append record */
qDebug() << calibration_model->insertRecord(-1, record);
qDebug() << calibration_model->lastError().text();
qDebug() << "Submit:";
if(!calibration_model->submitAll()){
qDebug() << calibration_model->lastError().text();
return DATABASE_STATUS_COULD_NOT_INSERT_ROW;
}
return DATABASE_STATUS_OK;
But even if I insert the record, this function calibration_model->insertRecord(-1, record); returns false but the calibration_model->submitAll() returns true.
Output:
"ID"
"calibration_id"
"calibration_comment"
"calibration_date_time"
false
"No Fields to update"
Submit:
So tell me. What I'm I doing wrong here?
I'm getting the error No Fields to update, but what does that mean? I have an empty table and I just want to append with one row.
Not sure why you're getting that error. I have a small example for QSqlTableModel. Let me put it here. Maybe you could compare with your code.
main.cpp
#include <QApplication>
#include "mysqltablemodel.h"
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE");
db.setDatabaseName("mydb");
if(!db.open()) {
qDebug() << db.lastError().text();
return 0;
}
QSqlQuery query(db);
if(!query.exec("DROP TABLE IF EXISTS mytable")) {
qDebug() << "create table error: " << query.lastError().text();
return 0;
}
if(!query.exec("CREATE TABLE IF NOT EXISTS mytable \
(id integer primary key autoincrement, name varchar(15), salary integer)")) {
qDebug() << "create table error: " << query.lastError().text();
return 0;
}
MySqlTableModel *model = new MySqlTableModel(0, db);
model->setTable("mytable");
model->setEditStrategy(QSqlTableModel::OnManualSubmit);
model->select();
QSqlRecord rec = model->record();
rec.setValue(1, "peter");
rec.setValue(2, 100);
qDebug() << model->insertRecord(-1, rec);
rec.setValue(1, "luke");
rec.setValue(2, 200);
qDebug() << model->insertRecord(-1, rec);
if(model->submitAll()) {
model->database().commit();
} else {
model->database().rollback();
qDebug() << "database error: " << model->lastError().text();
}
query.exec("SELECT name, salary FROM mytable");
while (query.next()){
QString name = query.value(0).toString();
int salary = query.value(1).toInt();
qDebug() << name << salary;
}
return app.exec();
}
mysqltablemodel.h
#ifndef MYSQLTABLEMODEL_H
#define MYSQLTABLEMODEL_H
#include <QSqlTableModel>
#include <QSqlRecord>
#include <QSqlError>
#include <QSqlQuery>
#include <QDebug>
class MySqlTableModel : public QSqlTableModel
{
Q_OBJECT
public:
MySqlTableModel(QObject *parent = 0, QSqlDatabase db = QSqlDatabase());
QVariant data(const QModelIndex &index, int role=Qt::DisplayRole ) const;
protected:
QHash<int, QByteArray> roleNames() const;
private:
QHash<int, QByteArray> roles;
};
#endif // MYSQLTABLEMODEL_H
mysqltablemodel.cpp
#include "mysqltablemodel.h"
MySqlTableModel::MySqlTableModel(QObject *parent, QSqlDatabase db): QSqlTableModel(parent, db) {}
QVariant MySqlTableModel::data ( const QModelIndex & index, int role ) const
{
if(index.row() >= rowCount()) {
return QString("");
}
if(role < Qt::UserRole) {
return QSqlQueryModel::data(index, role);
}
else {
return QSqlQueryModel::data(this->index(index.row(), role - Qt::UserRole), Qt::DisplayRole);
}
}
QHash<int, QByteArray> MySqlTableModel::roleNames() const
{
QHash<int, QByteArray> roles;
roles[Qt::UserRole + 1] = "name";
roles[Qt::UserRole + 2] = "salary";
return roles;
}
I'm experimenting with QThreadPool and realized that my program exited with SIGSEGV after QThreadPool reaches expiry timeout.
I started the program creating pointers to QLabel and QLineEdits as placeholders, which are kept in QLists.
Then, the method carregar2() is called, when the menu is clicked, to start the QThreadPool.
I implemented 4 QRunnables. They're all the same: they invoke external program (QProcess) with different parameters. Their result are written to QLineEdits.
If I set setExpiryTimeout(-1), the program does not crash.
This is the MainWindow class, it is the called from the main.cpp :
#include "MainWindow.h"
#include "ui_MainWindow.h"
#include <QtWidgets>
#include "ClickableLabel.h"
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
QMainWindow::showMaximized();
ui->setupUi(this);
QStringList args = QApplication::arguments();
QString path;
if(args.size()<=1)
path = QFileDialog::getExistingDirectory(parent, "", "/studio/FOTOS", QFileDialog::ShowDirsOnly);
else
path = static_cast<QString>(args.at(1));
QDir *dir = new QDir(path);
setWindowTitle("QExif - " + path);
QScrollArea *scroll = new QScrollArea();
QGridLayout *grid = new QGridLayout(scroll);
QFrame *frame = new QFrame();
int row=0;
int col=0;
QStringList filtro;
filtro << "*.jpg";
const QFileInfoList fil = dir->entryInfoList(filtro,QDir::Files );
qDebug() << "FOTOS: " << fil.size();
foreach (QFileInfo fi, fil ) {
QString f = fi.absoluteFilePath();
QLabel *l = new ClickableLabel(); //A custom QLabel that implements a clicked() signal and "emit clicked()" on mousePressEvent
l->setStyleSheet("border: 5px solid white");
l->setMaximumSize(w,h);
l->setMinimumSize(w,h);
l->setProperty("foto", f);
l->setToolTip(f);
connect(l, SIGNAL(clicked()), this, SLOT(abrirVisualizadorExterno()));
grid->addWidget(l,row,col,1,1,Qt::AlignTop);
//tag buttons
QHBoxLayout *box = new QHBoxLayout(parent);
QFrame *btnFrame = new QFrame();
btnFrame->setLayout(box);
btnFrame->setMaximumWidth(w);
QLineEdit *tagArtista = new QLineEdit(parent);
tagArtista->setToolTip("Etiquetas");
grid->addWidget(tagArtista,row+1,col,1,1,Qt::AlignTop);
QLineEdit *tagDescricao = new QLineEdit(parent);
tagDescricao->setToolTip("Descrição da imagem");
grid->addWidget(tagDescricao,row+2,col,1,1,Qt::AlignTop);
QLineEdit *tagDataHora = new QLineEdit(parent);
tagDataHora->setToolTip("Data e Hora");
grid->addWidget(tagDataHora,row+3,col,1,1,Qt::AlignTop);
tagArtista->setProperty("foto", f);
tagDescricao->setProperty("foto", f);
tagDataHora->setProperty("foto", f);
fList->append(f);
labelList->append(l);
tagList->append(tagArtista);
descricaoList->append(tagDescricao);
dataHoraList->append(tagDataHora);
col++;
if(col >3) { col=0; row+=4; }
} //foreach
frame->setLayout(grid);
scroll->setWidget(frame);
setCentralWidget(scroll);
/*
* MENU
* */
QMenu *menuExif = ui->menuBar->addMenu("Exif");
menuExif->addAction("Carregar");
connect(menuExif,SIGNAL(triggered(QAction*)),this,SLOT(menuHandler(QAction*)));
}
void MainWindow::menuHandler(QAction *action){
if(action->text() == "Carregar"){
carregar2();
}
}
void MainWindow::carregar2(){
QThreadPool *pool = QThreadPool::globalInstance();
pool->setExpiryTimeout(-1);
for(int i=0; i<fList->count(); i++){
ImagemRunnable *imageRunnable = new ImagemRunnable(labelList->at(i), fList->at(i), w, h);
QThreadPool::globalInstance()->start(imageRunnable);
TagRunnable *tagRunnable = new TagRunnable(tagList->at(i), fList->at(i));
pool->start(tagRunnable);
TagDescricaoRunnable *tagDescricaoRunnable = new TagDescricaoRunnable(descricaoList->at(i), fList->at(i));;
pool->start(tagDescricaoRunnable);
TagDataHoraRunnable *tagDataHoraRunnable = new TagDataHoraRunnable(dataHoraList->at(i), fList->at(i));
pool->start(tagDataHoraRunnable);
}
}
void MainWindow::abrirVisualizadorExterno(){
ClickableLabel *l = (ClickableLabel *) sender();
qDebug() << "Abrir" << l->property("foto");
if (l->property("foto").isValid()){
QStringList cmd;
cmd << QString("eog %1").arg(l->property("foto").toString());
system(cmd.at(0).toUtf8().data());
}
}
bool MainWindow::eventFilter(QObject *watched, QEvent *event){
qDebug() << watched->property("foto").toString();
return false;
}
MainWindow::~MainWindow()
{
delete ui;
}
And this is one of the implemented QRunnables:
header:
#ifndef TAGRUNNABLE_H
#define TAGRUNNABLE_H
#include <QRunnable>
#include <QLineEdit>
class TagRunnable : public QRunnable, QObject
{
public:
TagRunnable(QLineEdit * f, QString file);
void run() override;
private:
QLineEdit *field;
QString file;
};
#endif // TAGRUNNABLE_H
cpp:
#include "tagrunnable.h"
#include <QProcess>
#include <QDebug>
TagRunnable::TagRunnable(QLineEdit * f, QString file)
{
field = f;
this->file = file;
}
void TagRunnable::run(){
QProcess *proc = new QProcess();
//pessoas na foto
proc->start("/usr/bin/exif", QStringList()
<< "-t" << "0x013b"
<< "-m"
<< file
);
if(!proc->waitForFinished()){
qDebug() << "Timeout ao ler exif tag (pessoas)." << file;
}
field->setText(proc->readAllStandardOutput());
field->setProperty("tagOriginal", field->text());
}
My questions:
Suppose I use 30 seconds as expiry timeout. After that time, is my reference to the QProcess removed from memory and causing the QLineEdit text to be lost as well?
Is it ok to set expiry timeout to -1 in any situation or, perhaps, I'm not using it properly because of other architectural error of my program ?
If I call removeRows on records which were read from the database a ! will be displayed in the first column of the tableView.
The only thing which I would like to achieve is that this row will not be displayed in the view. I tried it with QSortFilterProxyModel but I don't know where I can get the flag which is used to display the ! in the first column. Is there a way to set a filter in QSortFilterProxyModel that it only contains the rows which don't have this flag?
From where does the view take the information, that the removed row is marked with a "!" ? This information might be hidden somewhere in the model, but I cannot find out where.
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent),
m_ui(new Ui::MainWindow),
m_model(new QSqlTableModel(this)),
m_proxyModel(new QSortFilterProxyModel(this))
{
m_ui->setupUi(this);
m_model->setTable("test");
m_model->setEditStrategy(QSqlTableModel::OnManualSubmit);
m_model->select();
m_proxyModel->setSourceModel(m_model);
m_ui->tableView->setModel(m_proxyModel);
qDebug() << "Select : Row count:" << m_model->rowCount();
connect(m_ui->tableView->selectionModel(), &QItemSelectionModel::selectionChanged,
this, &MainWindow::on_selectionChanged);
}
MainWindow::~MainWindow()
{
delete m_ui;
}
void MainWindow::on_pushButtonNewRecord_clicked()
{
qDebug() << "New Record";
m_record = m_model->record();
m_record.setValue("firstname", "john");
m_record.setValue("lastname", "doe");
m_record.setValue("email", "john.doe#email.com");
m_model->insertRecord(-1, m_record);
qDebug() << "New Record : Row count:" << m_model->rowCount();
}
void MainWindow::on_pushButtonRemoveRow_clicked()
{
qDebug() << "Remove Row";
if (m_row >= 0) {
m_proxyModel->removeRow(m_row);
qDebug() << "Remove Record: Row count:" << m_model->rowCount();
}
for (int i = 0; i < m_model->rowCount(); i++) {
qDebug() << "\n";
qDebug() << "Remove Row: index :" << m_model->index(i, 0);
qDebug() << "Remove Row: isValid:" << m_model->index(i, 0).isValid();
qDebug() << "Remove Row: isDirty:" << m_model->isDirty(m_model->index(i, 0));
qDebug() << "Remove Row: flags :" << m_model->index(i, 0).flags();
qDebug() << "Remove Row: data :" << m_model->index(i, 0).data(Qt::DisplayRole);
qDebug() << "Remove Row: heaader:" << m_model->headerData(i, Qt::Vertical);
QVariant verticalHeader = m_model->headerData(i, Qt::Vertical);
if (verticalHeader == "!") {
qDebug() << "Deleted";
}
//qDebug() << m_model->record(i);
}
}
void MainWindow::on_pushButtonSubmit_clicked()
{
qDebug() << "Submit";
m_model->submitAll();
m_model->select();
}
void MainWindow::on_selectionChanged(const QItemSelection &selected, const QItemSelection &deselected)
{
Q_UNUSED(deselected)
if (!selected.isEmpty()) {
m_row = selected.indexes().first().row();
}
}
You have to implement a QSortFilterProxyModel that filters the rows that are Dirty and whose vertical header text is "!", You must also call the invalidate() method to force the application of the filter.
dirtyfilterproxymodel.h
#ifndef DIRTYFILTERPROXYMODEL_H
#define DIRTYFILTERPROXYMODEL_H
#include <QSortFilterProxyModel>
#include <QSqlTableModel>
class DirtyFilterProxyModel : public QSortFilterProxyModel
{
public:
using QSortFilterProxyModel::QSortFilterProxyModel;
protected:
bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const
{
if(QSqlTableModel *source_model = qobject_cast<QSqlTableModel *>(sourceModel())){
QModelIndex ix = source_model->index(source_row, 0, source_parent);
QString row_header_item = source_model->headerData(source_row, Qt::Vertical, Qt::DisplayRole).toString();
return !(source_model->isDirty(ix) && row_header_item == QLatin1String("!"));
}
return QSortFilterProxyModel::filterAcceptsRow(source_row, source_parent);
}
};
#endif // DIRTYFILTERPROXYMODEL_H
mainwindow.h
// ...
class DirtyFilterProxyModel;
// ...
class MainWindow : public QMainWindow
{
// ...
private:
Ui::MainWindow *m_ui;
DirtyFilterProxyModel *m_proxyModel;
// ...
mainwindow.cpp
#include "dirtyfilterproxymodel.h"
// ...
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent),
m_ui(new Ui::MainWindow),
m_model(new QSqlTableModel(this)),
m_proxyModel(new DirtyFilterProxyModel(this))
{
// ...
}
void MainWindow::on_pushButtonNewRecord_clicked()
{
// ...
m_model->insertRecord(-1, m_record);
m_proxyModel->invalidate();
}
// ...
void MainWindow::on_pushButtonRemoveRow_clicked()
{ if (m_row >= 0) {
m_proxyModel->removeRow(m_row);
m_proxyModel->invalidate();
}
}
I need to assign a pointer to a custom class in qml using QQmlContext::setContextProperty(). Another qml object has Q_PROPERTY of the same type to retrieve it again.
A simple test showed me that the conversion does not work like i thought.
#include <QCoreApplication>
#include <QDebug>
#include <QMetaType>
class TestClass
{
public: TestClass() { qDebug() << "TestClass()::TestClass()"; }
};
Q_DECLARE_METATYPE(TestClass*)
int main(int argc, char *argv[])
{
QCoreApplication app(argc, argv);
qDebug() << "metaTypeId =" << qMetaTypeId<TestClass*>();
auto testObject = new TestClass;
QVariant variant(qMetaTypeId<TestClass*>(), testObject);
auto test = variant.value<TestClass*>();
qDebug() << testObject << variant << test;
return 0;
}
This tiny test application gives me an output like this:
metaTypeId = 1024
TestClass::TestClass()
0x1b801e0 QVariant(TestClass*, ) 0x0
I would really like to get the same pointer out again after converting it down to a QVariant. Later I will assign it to a qml context and then the conversation must work correctly.
This works for me using Qt 5.9:
#include <QVariant>
#include <QDebug>
class CustomClass
{
public:
CustomClass()
{
}
};
Q_DECLARE_METATYPE(CustomClass*)
class OtherClass
{
public:
OtherClass()
{
}
};
Q_DECLARE_METATYPE(OtherClass*)
int main()
{
CustomClass *c = new CustomClass;
OtherClass *o = new OtherClass;
QVariant v;
v.setValue(c);
QVariant v2;
v2.setValue(o);
qDebug() << v.userType() << qMetaTypeId<CustomClass*>() << v2.userType() << qMetaTypeId<OtherClass*>();
qDebug() << v.value<CustomClass*>() << c << v2.value<OtherClass*>() << o;
return 0;
}
And the output i got:
1024 1024 1025 1025
0x81fca50 0x81fca50 0x81fca60 0x81fca60
As #thuga mentioned in the comments, you need to use void* and static_cast along with QVariant::fromValue.
#include <QCoreApplication>
#include <QDebug>
#include <QMetaType>
class TestClass
{
public: TestClass() { qDebug() << "TestClass()::TestClass()"; }
};
Q_DECLARE_METATYPE(TestClass*)
int main(int argc, char *argv[])
{
QCoreApplication app(argc, argv);
qDebug() << "metaTypeId =" << qMetaTypeId<TestClass*>();
auto testObject = new TestClass;
QVariant variant(QVariant::fromValue(static_cast<void*>(testObject)));
auto test = static_cast<TestClass*>(variant.value<void*>());
qDebug() << testObject << variant << test;
return 0;
}
If used QTextStream console(stdout) - all work just fine, but if I wrote custom IODevice, after qInstallMsgHandler() no text in console
main.cpp
#include "remoteconsole.h"
#include <QCoreApplication>
#include <QDateTime>
#include <QTimer>
QTextStream *out;
void logOutput(QtMsgType type, const char *msg)
{
QString debugdate = QDateTime::currentDateTime().toString("yyyy.MM.dd hh:mm:ss.zzz");
*out << debugdate << " " << type << msg << endl;
}
int main(int argc, char *argv[])
{
int i;
QCoreApplication a(argc, argv);
RemoteConsole * remote = new RemoteConsole(&a);
QTextStream console((QIODevice *)remote);
out = &console;
qDebug() << "start qInstallMsgHandler";
qInstallMsgHandler(logOutput);
qDebug() << "end qInstallMsgHandler"<<endl;
for(i=0;i<10;i++){
qDebug() << i<<endl;
}
QTimer *timer = new QTimer();
a.connect(timer, SIGNAL(timeout()), &a, SLOT(quit()));
timer->start(5000);
a.exec();
return 0;
}
my IODevice implementation in file remoteconsole.h .cpp
#ifndef REMOTECONSOLE_H
#define REMOTECONSOLE_H
#include <QIODevice>
#include <QDebug>
class RemoteConsole: public QIODevice
{
Q_OBJECT
public:
RemoteConsole(QObject *parent);
~RemoteConsole();
private:
Q_DISABLE_COPY(RemoteConsole)
protected:
qint64 readData(char* data, qint64 maxSize);
qint64 writeData(const char* data, qint64 maxSize);
};
#endif // REMOTECONSOLE_H
#include "remoteconsole.h"
RemoteConsole::RemoteConsole(QObject* parent=0) :
QIODevice(parent)
{
}
RemoteConsole::~RemoteConsole(){}
qint64 RemoteConsole::readData(char *data, qint64 maxlen){
qDebug() << data <<endl;
return maxlen;
}
qint64 RemoteConsole::writeData(const char *data, qint64 len){
printf("writeData");
qDebug() << data <<endl;
return len;
}
In future I want to expand this code with QTCPServer, that send debug outputs to client connected to the device by telnet or nc.
You don't get any text in console after qInstallMsgHandler call because you send all debug data into your RemoteConsole object.
Also you've got some other problems in your code.
You should call QIODevice::open before you can operate on that device.
In the RemoteConsole::writeData function you will have an infinite loop because you use qDebug() there. qDebug() will call logOutput which will call RemoteConsole::writeData again.