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()
{
}

我还注意到,如果您使用按钮保存数据,焦点会自动更改到该按钮,从而导致相同的行为。我想用 QToolBarQToolButton 代替 QMenuBar 也可以。