QTableWidget:信号 itemChanged 触发太迟(在发出其他操作、关闭等信号之后)
QTableWidget: signal itemChanged fires too late (after signals for other actions, close, etc.)
我正在使用 QTableWidget
来表示一些用户 editable 数据。编辑后,通过连接到 table 的 itemChanged(QTableWidgetItem*)
信号更新数据的内部表示。
我有一个菜单操作“保存”来保存数据,如果用户试图关闭 window,我会检查 closeEvent
中是否修改了当前文档。如果用户在有未保存的修改时尝试关闭,系统会询问他们是否要保存。
现在的问题是,itemChanged
信号似乎只在 table 失去输入焦点时才发送。考虑这种情况:用户双击 table 中的一个单元格,更改文本,然后立即单击保存。保存操作在 itemChanged
信号发送之前触发,因此在用户输入实际同步之前触发。同样,如果用户关闭 window.
我尝试在保存函数中主动读取 table 中的数据,而不是等待信号触发,但这也不起作用,因为相应的项目在其编辑器中仍然包含旧数据开了。
这显然是一个大问题,因为要么保存了错误的数据,要么根本没有保存,即数据丢失。
如何正确处理?
附件是一个演示问题的最小工作示例。出于演示目的,closeEvent 仅无条件地执行“保存”(仅在此处打印)。在实际应用中,它会先检查数据是否被修改,这并没有正确执行。
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();
}
在您的情况下,当触发保存操作时,您可以先将焦点设置到 QTableWidget
对象。编辑器将失去焦点并提交数据。我认为这是一种比我在评论中建议的更好的方法(因为它假设编辑器是一个 QLineEdit
对象并使用 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()
{
}
我还注意到,如果您使用按钮保存数据,焦点会自动更改到该按钮,从而导致相同的行为。我想用 QToolBar
和 QToolButton
代替 QMenuBar
也可以。
我正在使用 QTableWidget
来表示一些用户 editable 数据。编辑后,通过连接到 table 的 itemChanged(QTableWidgetItem*)
信号更新数据的内部表示。
我有一个菜单操作“保存”来保存数据,如果用户试图关闭 window,我会检查 closeEvent
中是否修改了当前文档。如果用户在有未保存的修改时尝试关闭,系统会询问他们是否要保存。
现在的问题是,itemChanged
信号似乎只在 table 失去输入焦点时才发送。考虑这种情况:用户双击 table 中的一个单元格,更改文本,然后立即单击保存。保存操作在 itemChanged
信号发送之前触发,因此在用户输入实际同步之前触发。同样,如果用户关闭 window.
我尝试在保存函数中主动读取 table 中的数据,而不是等待信号触发,但这也不起作用,因为相应的项目在其编辑器中仍然包含旧数据开了。 这显然是一个大问题,因为要么保存了错误的数据,要么根本没有保存,即数据丢失。
如何正确处理?
附件是一个演示问题的最小工作示例。出于演示目的,closeEvent 仅无条件地执行“保存”(仅在此处打印)。在实际应用中,它会先检查数据是否被修改,这并没有正确执行。
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();
}
在您的情况下,当触发保存操作时,您可以先将焦点设置到 QTableWidget
对象。编辑器将失去焦点并提交数据。我认为这是一种比我在评论中建议的更好的方法(因为它假设编辑器是一个 QLineEdit
对象并使用 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()
{
}
我还注意到,如果您使用按钮保存数据,焦点会自动更改到该按钮,从而导致相同的行为。我想用 QToolBar
和 QToolButton
代替 QMenuBar
也可以。