How to save a table in a file



  • Hello. I'm new here, but i've already read many topic of this forum.
    I'm creating a program that monitors a directory.
    I've searched for a method to save a tablewidget to a file and this is what I've made:

    void MainWindow::on_pushButton_2_clicked()
    {
    QFile csv("log_activity.csv");
    
    if(!csv.open(QFile::WriteOnly))
        return;
    
    QTextStream out(&csv);
    
    for(int i = 0; i < ui->tableWidget->rowCount(); ++i)
        for (int j = 1; j < ui->tableWidget->columnCount(); j++)
            out<<ui->tableWidget->item(i, j)->text();
    
    csv.close();
    }
    

    This is my tablewidget:

    0_1531208468134_7dddbc22-3ed3-431d-99d3-0f05a3a3dc79-image.png

    The problem is that once i click on the save button (pushButton_2) the program doesn't save anything in the file. What am i missing?



  • I'm developing a library that includes this feature: https://github.com/VSRonin/QtModelUtilities/tree/dev
    I still need to get it to stable and fully documented but should be good enough for rock and roll.
    Saving to csv is done via the CsvModelSerialiser class



  • I'd like to not implement other libraries. I want to resolve this only with given libraries by Qt.


  • Moderators

    @Daesos Are you sure it does not save the file? You're using a relative path, that means the file will be located in the current working directory of your app, most probably in the build directory. Also did you check whether MainWindow::on_pushButton_2_clicked() was actually called?



  • The image above is the directory of my project. If I click on save button, the cell corresponding to log_activity.csv and Event changes into modified.
    It shows "Modified" when the date of the previous directory is different from the current one.
    So I guess the save_button works.

    0_1531210402194_729a0dca-9bc9-4745-a532-9d41af2aef78-image.png

    Also, if the file doesn't exit, the program create the file with the given name.



  • @Daesos said in How to save a table in a file:

    I want to resolve this only with given libraries by Qt

    Then copy-paste my code. I only use Qt in those and license is apache 2 so you can basically do what you want with it.

    In your code above you are not:

    • Writing any new line to separate rows.
    • Writing any column separator
    • Escaping column separators inside the data

  • Moderators

    @Daesos Did you try to debug? Especially the nested for loops?



  • @Daesos
    Assuming the file is opened for write, you are saying you end up with nothing in it, and your table does indeed contain desired text (ui->tableWidget->item(i, j)->text()). You stream all your data to QTextStream out but you only close the QFile csv. I don't know if that knows to fetch all remaining data out of the stream. Try out.flush() prior to the csv.close()?



  • @jsulm I'm trying, but it stops with this message: Stopped "end-stepping-range". I've put a breakpoint at "out<<ui->tableWidget->item(i, j)->text();" and as i can see, this returns "main.window.cpp", which is correct. Then, I don't what happen.


  • Moderators

    @Daesos Try calling http://doc.qt.io/qt-5/qtextstream.html#flush before closing the file



  • @jsulm Already did, and nothing changed.


  • Moderators

    @Daesos Last idea I have: is it possible that you open same file somewhere else in your app, or that there is still an instance of your app running?
    Oh, one more idea: try to write the file using an absolute path to some other location.



  • @jsulm I checked and there is no other instance opened. Also, I used another path. The program created the file.csv (empty), then quitted. Application Output: "The program has unexpectedly finished."

    Since I don't know if I have mistaken something I will post all the mainwindow.cpp code:

    #include "mainwindow.h"
    #include "ui_mainwindow.h"
    #include <QFileDialog>
    #include <QFileInfo>
    #include <QTimer>
    #include <QDateTime>
    #include <QFile>
    #include <QTextStream>
    
    MainWindow::MainWindow(QWidget *parent) :
        QMainWindow(parent),
        ui(new Ui::MainWindow)
    {
        ui->setupUi(this);
    
        setWindowTitle("DirectoryMonitor");
        ui->tableWidget->setColumnCount(4);
        ui->tableWidget->setHorizontalHeaderLabels(QStringList() << "" << "Filename" << "Event" << "Time");
    
        f_name = "log_activity.csv";
    }
    
    MainWindow::~MainWindow()
    {
        delete ui;
    }
    
    void MainWindow::on_pushButton_clicked()
    {
        ui->tableWidget->clearContents();
        path = QFileDialog::getExistingDirectory(this, tr("Open Directory"), ".", QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks);
        ui->lineEdit->setText(path);
    
    QDir dir(path);
    
    dir.setFilter(QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot);
    dir.setSorting(QDir::Time);
    list = dir.entryInfoList();
    
    for(int i = 0; i < list.size(); ++i){
            ui->tableWidget->insertRow(ui->tableWidget->rowCount());
            ui->tableWidget->setItem(i, 1, new QTableWidgetItem(list.at(i).fileName()));
            ui->tableWidget->setItem(i, 3, new QTableWidgetItem(list.at(i).lastModified().toString()));
    }
    
    int count = 0;
    
    for(int i = 0; i < ui->tableWidget->rowCount(); ++i){
        QTableWidgetItem *itm = ui->tableWidget->item(i, 1);
        if(itm != 0)
           ++count;
    }
    //rimuovi righe vuote
    ui->tableWidget->setRowCount(count);
    
    QTimer *time = new QTimer(this);
    
    connect(time, SIGNAL(timeout()), this, SLOT(changes()));
    time->start(3000);
    }
    
    void MainWindow::changes()
    {
    QDir dir(path);
    dir.setFilter(QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot);
    dir.setSorting(QDir::Time);
    
    QFileInfoList newlist = dir.entryInfoList();
    
    int index_delete = -1;
    
    //caso modified e delete file
    for(int i = 0; i < list.size(); ++i){
        int count = 0;
        for(int j = 0; j < newlist.size(); ++j){
            if(list.at(i).fileName() == newlist.at(j).fileName()){
                ++count;
                if(list.at(i).lastModified() != newlist.at(j).lastModified()){
                    QTableWidgetItem *itm = new QTableWidgetItem("");
                    itm->setBackground(QColor(109, 158, 235));
                    ui->tableWidget->setItem(i, 0, itm);
                    ui->tableWidget->setItem(i, 2, new QTableWidgetItem("Modified"));
                    ui->tableWidget->setItem(i, 3, new QTableWidgetItem(newlist.at(j).lastModified().toString()));
                }
            }
        }
        if(count == 0){
            index_delete = i;
        }
    }
    
    if(index_delete != -1){
        QTableWidgetItem *itm = new QTableWidgetItem("");
        itm->setBackground(QColor(224, 102, 99));
        ui->tableWidget->setItem(index_delete, 0, itm);
        if(ui->tableWidget->item(index_delete, 2)->text() != "Deleted")
            ui->tableWidget->setItem(index_delete, 3, new QTableWidgetItem(QDateTime::currentDateTime().toString()));
        ui->tableWidget->setItem(index_delete, 2, new QTableWidgetItem("Deleted"));
    }
    
    //caso new file
    int index_new = -1;
    
    for(int i = 0; i < newlist.size(); ++i){
        int count = 0;
        for(int j = 0; (j < list.size() && count == 0); ++j){
            if(newlist.at(i).fileName() == list.at(j).fileName())
                ++count;
        }
        if(count == 0)
            index_new = i;
    }
    
    if(index_new != -1){
        ui->tableWidget->insertRow(ui->tableWidget->rowCount());
        QTableWidgetItem *itm = new QTableWidgetItem("");
        itm->setBackground(QColor(148, 195, 123));
        ui->tableWidget->setItem(ui->tableWidget->rowCount() - 1, 0, itm);
        ui->tableWidget->setItem(ui->tableWidget->rowCount() - 1, 1, new QTableWidgetItem(newlist.at(index_new).fileName()));
        ui->tableWidget->setItem(ui->tableWidget->rowCount() - 1, 2, new QTableWidgetItem("Created"));
        ui->tableWidget->setItem(ui->tableWidget->rowCount() - 1, 3, new QTableWidgetItem(newlist.at(index_new).lastModified().toString()));
        list.append(newlist.at(index_new));
    }
    }
    void MainWindow::on_pushButton_2_clicked()
    {
    QFile csv(f_name);
    
    if(!csv.open(QFile::WriteOnly))
        return;
    
    QTextStream out(&csv);
    
    for(int i = 0; i < ui->tableWidget->rowCount(); ++i)
        for (int j = 1; j < ui->tableWidget->columnCount(); j++){
            QString txt(ui->tableWidget->item(i, j)->text());
            out<<txt;
        }
    out.flush();
    csv.close();
    }
    

    Sorry for the indentation.
    This is the declaration of the global variables in mainwindow.h

    QString path, f_name;
    QFileInfoList list;

  • Moderators

    @Daesos Start your app with debugger (F5) and when it crashes check the stack trace to see where it crashes (use debug build of your app).



  • @Daesos
    Going through your code, I noticed, you're mixing postfix, prefix in your nested loop to access the Tablewidget Item. I would search there for an out of bounds error.



  • @Daesos
    If you're still having problems with the file (once you've solved the "crash"), try replacing your QFile with a QString, and use https://doc.qt.io/Qt-5/qtextstream.html#QTextStream-3. You should be successfully building up the string of the file content. If that doesn't work, nor will write to file.



  • I used debugger and when i click on pushButton_2 (save on interface) it returns a Segmentation Fault at "QTableWidgetItem::text".
    @J.Hilk, I didn't even notice. I edited the increment.

    Segmentation Fault occurs when I try to access to a NULL pointer, right? So, it might be out of bound.



  • @Daesos
    I don't see save_button in the code you've posted?

    I notice you have:

                    ui->tableWidget->setItem(i, 0, itm);
                    ui->tableWidget->setItem(i, 2, new QTableWidgetItem("Modified"));
                    ui->tableWidget->setItem(i, 3, new QTableWidgetItem(newlist.at(j).lastModified().toString()));
    

    Where do you setItem() on i, 1 ?

    for(int i = 0; i < ui->tableWidget->rowCount(); ++i)
        for (int j = 1; j < ui->tableWidget->columnCount(); j++){
            QString txt(ui->tableWidget->item(i, j)->text());
            out<<txt;
        }
    

    Check your accesses on every i, j here. Somewhere you do not have an item at item(i, j)!



  • I've edited the increment:

       for(int i = 0; i < ui->tableWidget->rowCount(); ++i)
           for (int j = 1; j < ui->tableWidget->columnCount(); ++j)
               out<<ui->tableWidget->item(i, j)->text();
    

    @JonB said in How to save a table in a file:

    @Daesos

    I notice you have:

                    ui->tableWidget->setItem(i, 0, itm);
                    ui->tableWidget->setItem(i, 2, new QTableWidgetItem("Modified"));
                    ui->tableWidget->setItem(i, 3, new QTableWidgetItem(newlist.at(j).lastModified().toString()));
    

    Where do you setItem() on i, 1 ?

    I've inizialized item at (i, 1) before. Once inizialized the name of the file must not be modified. So I didn't use setItem on i, 1 again.

    @Daesos
    ui->tableWidget->setItem(ui->tableWidget->rowCount() - 1, 1, new QTableWidgetItem(newlist.at(index_new).fileName()));



  • @Daesos said in How to save a table in a file:

    for(int i = 0; i < ui->tableWidget->rowCount(); ++i)
    for (int j = 1; j < ui->tableWidget->columnCount(); ++j)
    out<<ui->tableWidget->item(i, j)->text();

    Maybe it's time to #include good old <QDebug>

    for(int i = 0; i < ui->tableWidget->rowCount(); ++i)
           for (int j = 1; j < ui->tableWidget->columnCount(); ++j){
               qDebug() << i << "of" << ui->tableWidget->rowCount() << endl
                                 << j << "of" << ui->tableWidget->columnCount();
               out<<ui->tableWidget->item(i, j)->text();
               qDebug() << QString("Found text at %1 | %2").arg(i).arg(j);
           }
    


  • Replace out<<ui->tableWidget->item(i, j)->text(); with out<<ui->tableWidget->model()->index(i, j).data().tostring(); to prevent segfaults

    The problems I highlighted are still present though

    @VRonin said in How to save a table in a file:

    In your code above you are not:

    • Writing any new line to separate rows.
    • Writing any column separator
    • Escaping column separators inside the data


  • @VRonin In a previous version I've added both ";" and "\n", but I forgot to write them again.
    However I forgot one case: what happen if I did not create/delete/modify any file, folder, or other?
    The cells (i, 2) might be empty and here is where the program fails.
    I resolved by editing some code:

    void MainWindow::on_pushButton_2_clicked()
    {
    QFile csv(f_name);
    
    if(!csv.open(QFile::WriteOnly))
        return;
    
    QTextStream out(&csv);
    int tmp_column = 0;
    
    for(int i = 0; i < ui->tableWidget->rowCount(); ++i){
        for (int j = 1; j < ui->tableWidget->columnCount(); ++j){
            if(ui->tableWidget->item(i, j) != 0)
                out<<ui->tableWidget->item(i, j)->text();
                out<<";";
                tmp_column = j;
        }
        if(ui->tableWidget->item(i, tmp_column) != 0)
            out<<"\n";
    }
    
    out.flush();
    csv.close();
    }
    

    It may not be quite good-looking but it works:

    0_1531227135676_6d7d870f-2968-47b7-8941-b6ffbaac0431-image.png

    I've also prevented segmentation faults like this for example:

    ui->tableWidget->setItem(index_delete, 0, itm);
        if(ui->tableWidget->item(index_delete, 2) != 0 && ui->tableWidget->item(index_delete, 2)->text() != "Deleted")
            ui->tableWidget->setItem(index_delete, 3, new QTableWidgetItem(QDateTime::currentDateTime().toString()));


  • Try naming a folder Ugly; Name and save your model



  • @VRonin You got me, this is another case to write.



  • P.S.
    new QTableWidgetItem(QDateTime::currentDateTime().toString()) see https://bugreports.qt.io/browse/QTBUG-65555

    this should be separated in 3 lines:

    QTableWidgetItem* tableItem = new QTableWidgetItem;
    tableItem->setData(Qt::EditRole,QDateTime::currentDateTime());
    ui->tableWidget->setItem(index_delete, 3,tableItem);
    


  • Say I want to order the items of the table by name or date once I push Save, but without sorting the table itself (just the output in the file.csv). What can I do? Should I create a temporary table, order it and then store in the file?
    Tell me if the question is not clear.



  • Not a temporary table, you can use a QSortFilterProxyModel and then serialise the proxy rather than the original model.
    Since your current architecture relies on the QStandardItem interface rether than the QAbstractItemModel one you'll need to do some rewriting.

    To give you an idea, being QString m_csvSeparator the column separator:

    #include <QStringBuilder>
    QString escapedCSV(QString unexc) const
    {
        if (!unexc.contains(m_csvSeparator))
            return unexc;
        unexc.replace(QLatin1Char('\"'), QStringLiteral("\"\""));
        return '\"' % unexc % '\"';
    
    }
    bool writeCsv(QTextStream& writer, const QAbstractItemModel* constModel) const
    {
        if (!constModel)
            return false;
        for (int i = 0, maxRow = constModel->rowCount(); i < maxRow ; ++i) {
            for (int j = 0, maxCol = constModel->columnCount(); j < maxCol ; ++j) {
                const QString roleData = constModel->index(i, j).data().toString();
                if (j>0)
                    writer << m_csvSeparator;
                if (!roleData.isEmpty()) 
                    writer << escapedCSV(roleData);
            }
            writer << '\n';
        }
        return writer.status() == QTextStream::Ok;
    }
    

Log in to reply
 

Looks like your connection to Qt Forum was lost, please wait while we try to reconnect.