Reputation: 715
I am using a QTableWidget
to represent some user editable data. Upon editing, the internal representation of the data is updated by connecting to the table's itemChanged(QTableWidgetItem*)
signal.
I have a menu action "save" which saves the data and also if the user tries to close the window, I check if there current document is modified in the closeEvent
. If the user tries to close while having unsaved modifications, they get asked if they want to save.
Now, the problem is, that the itemChanged
signal seems to only be sent when the table loses input focus. Consider this scenario: the user double clicks a cell in the table, changes the text, then immediately clicks save. The save action gets triggered before the itemChanged
signal gets sent and thus before the user input actually gets synchronized. Likewise if the user closes the window.
I tried actively reading the data from the table in the save function instead of waiting for the signal to trigger, but that also doesn't work as the corresponding item still contains the old data while its editor is open. This is obviously a big problem because either the wrong data gets saved or no save happens at all, i.e. data loss.
How can I correctly deal with this?
Attached is a minimal working example demonstrating the problem. For demonstration purposes, the closeEvent just unconditionally executes the "save" (which just prints here). In a real application, it would check if data is modified first, which is not executed correctly.
mainwindow.h:
#include <QMainWindow>
#include <QMenu>
#include <QMenuBar>
#include <QTableWidget>
#include <iostream>
#include <vector>
class MainWindow : public QMainWindow
{
Q_OBJECT
private:
std::vector<double> data;
QTableWidget* table;
public:
MainWindow(QWidget* parent = nullptr) : QMainWindow(parent)
{
data = {1.0, 2.0, 3.0};
table = new QTableWidget(this);
table->setRowCount(1);
table->setColumnCount(data.size());
for (size_t i = 0; i < data.size(); i++)
table->setItem(0, i, new QTableWidgetItem(QString::number(data[i])));
connect(table, SIGNAL(itemChanged(QTableWidgetItem*)),
SLOT(on_table_itemChanged(QTableWidgetItem*)));
setCentralWidget(table);
QMenuBar* menubar = new QMenuBar(this);
QMenu* file = new QMenu("File", menubar);
QAction* save = new QAction("Save", this);
file->addAction(save);
menubar->addMenu(file);
setMenuBar(menubar);
connect(save, SIGNAL(triggered()), SLOT(on_actionSave_triggered()));
setGeometry(0, 0, 400, 100);
}
private slots:
void on_actionSave_triggered()
{
std::cout << "data: " << data[0] << ", " << data[1] << ", " << data[2] << std::endl;
std::cout << "item 0: " << table->item(0, 0)->text().toDouble()
<< ", item 1: " << table->item(0, 1)->text().toDouble()
<< ", item 2: " << table->item(0, 2)->text().toDouble() << std::endl;
}
void on_table_itemChanged(QTableWidgetItem* item)
{
data[item->column()] = item->text().toDouble();
}
};
main.cpp:
#include "mainwindow.h"
#include <QApplication>
int main(int argc, char* argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
return a.exec();
}
Upvotes: 1
Views: 822
Reputation: 1725
In your case, when the save action is triggered, you can first set focus to the QTableWidget
object. The editor will lose focus and commit the data. I consider this a better way than the one I suggested in the comment (since it assumes the editor to be a QLineEdit
object and uses dynamic_cast
).
#include "mainwindow.h"
#include <QDebug>
#include <QDialog>
#include <QMenuBar>
#include <QPushButton>
#include <QTableWidget>
#include <QVBoxLayout>
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
QTableWidget *table_widget = new QTableWidget{this};
table_widget->setRowCount(1);
table_widget->setColumnCount(1);
table_widget->setItem(0, 0, new QTableWidgetItem(QString{"Some text"}));
connect(table_widget, &QTableWidget::itemChanged, [](){qDebug() << "Item changed";});
QDialog *dialog = new QDialog{this};
connect(dialog, &QDialog::accepted, [](){qDebug() << "Dialog accepted";});
connect(dialog, &QDialog::rejected, [](){qDebug() << "Dialog rejected";});
QMenuBar *menu_bar = new QMenuBar(this);
QMenu *file_menu = new QMenu("File", menu_bar);
QAction *save_action = new QAction("Save", this);
file_menu->addAction(save_action);
menu_bar->addMenu(file_menu);
connect(save_action, &QAction::triggered, table_widget, QOverload<>::of(&QWidget::setFocus));
connect(save_action, &QAction::triggered, [=](){qDebug() << "Save triggered";});
QPushButton *save_button = new QPushButton{"Save", this};
connect(save_button, &QPushButton::clicked, [](){qDebug() << "Save clicked";});
QVBoxLayout *layout = new QVBoxLayout{dialog};
layout->addWidget(menu_bar);
layout->addWidget(table_widget);
layout->addWidget(save_button);
QPushButton *open_button = new QPushButton{"Open dialog", this};
connect(open_button, &QPushButton::clicked, dialog, &QDialog::show);
this->setCentralWidget(open_button);
}
MainWindow::~MainWindow()
{
}
I also notice that if you use a push button to save the data, the focus changes automatically to the button, resulting in the same behavior. I guess using QToolBar
and QToolButton
in place of QMenuBar
could work too.
Upvotes: 1