三味线
三味线
Published on 2025-09-22 / 8 Visits
0
0

QTableView代理控件多输入框无法提交数据的问题

环境:Windows10,Qt 6.8.3,VS 2022

自定义了一个输入上下限的控件,作为表格代理。

问题

当代理控件包含多个输入框时,输入数据后点击表格空白区域或界面其他位置,控件不会隐藏,数据也不会提交到表格;只有手动点回车或者切换到另一个单元格时数据才会提交。

它是检测主控件失去焦点来提交数据的,单控件无此问题,包含子控件时,焦点在子控件上,主控件没有焦点也就不会触发focusOut。

尝试解决

询问AI,重写了eventFilter,在子控件丢失焦点时手动发送commitData和closeEditor两个信号,但实际执行无反应,在createEditor函数中绑信号也不行,暂不清除原因。

connect(dTolEdit, &JeToleranceEntry::editingFinished, this, [this, wid]() {
    qDebug() << "editingFinished";
    emit const_cast<JeToleranceItemDelegate*>(this)->commitData(wid);
    emit const_cast<JeToleranceItemDelegate*>(this)->closeEditor(wid);
});

改为手动设置数据到表格,然后删除控件:

QTimer::singleShot(50, [index,dTolEdit]() {
    if (dTolEdit) {
        auto model = const_cast<QAbstractItemModel*>(index.model());
        model->setData(index, dTolEdit->valueString());
        dTolEdit->hide();
        dTolEdit->deleteLater();
    }
});

发现点击空白时控件确实能提交数据并隐藏,但是直接点击下一行进行编辑程序会崩溃,暂不清除原因。

到这里卡住了,没想出解决办法;两个方向:要么一条路走到黑,继续研究怎么能正常提交数据和隐藏;要么改成单个输入框,通过控制输入区域来模拟现在的控件,尝试了第二种,效果很差,放弃。

最终下载了源码,发现了代理发送commitData和closeEditor这两个信号的地方:

在QAbstractItemDelegate的代码中,存在一个函数:

bool QAbstractItemDelegatePrivate::editorEventFilter(QObject object, QEvent event)

其中一段代码如下:

if (event->type() == QEvent::FocusOut || (event->type() == QEvent::Hide && editor->isWindow())) {
        //the Hide event will take care of he editors that are in fact complete dialogs
        if (!editor->isActiveWindow() || (QApplication::focusWidget() != editor)) {
            QWidget *w = QApplication::focusWidget();
            while (w) { // don't worry about focus changes internally in the editor
                if (w == editor)
                    return false;
                w = w->parentWidget();
            }
#if QT_CONFIG(draganddrop)
            // The window may lose focus during an drag operation.
            // i.e when dragging involves the taskbar on Windows.
            QPlatformDrag *platformDrag = QGuiApplicationPrivate::instance()->platformIntegration()->drag();
            if (platformDrag && platformDrag->currentDrag()) {
                return false;
            }
#endif
            if (tryFixup(editor))
                emit q->commitData(editor);

            // If the application loses focus while editing, then the focus needs to go back
            // to the itemview when the editor closes. This ensures that when the application
            // is active again it will have the focus on the itemview as expected.
            QWidget *editorParent = editor->parentWidget();
            const bool manuallyFixFocus = (event->type() == QEvent::FocusOut) && !editor->hasFocus() &&
                    editorParent &&
                    (static_cast<QFocusEvent *>(event)->reason() == Qt::ActiveWindowFocusReason);
            emit q->closeEditor(editor, QAbstractItemDelegate::NoHint);
            if (manuallyFixFocus)
                editorParent->setFocus();
        }
#ifndef QT_NO_SHORTCUT
    }

可以看到,在控件失去焦点或隐藏时能触发提交,先尝试了下第二种:

connect(dTolEdit, &JeToleranceEntry::editorFocusedOut, [=]() {
    if (dTolEdit->hasFocus() || dTolEdit->downEntry()->hasFocus() || dTolEdit->upEntry()->hasFocus()) {
        return;
    }
    dTolEdit->setWindowFlags(Qt::Window);
    dTolEdit->hide();
});

发现能隐藏但是数据未提交,且再次点击这个单元格控件出不来了,排除;

再次查询如何手动触发focusOut,得到了如下方式:

QFocusEvent *event = new QFocusEvent(QEvent::FocusOut, Qt::MouseFocusReason);
QApplication::postEvent(dTolEdit, event);

经过测试,这样是可行的。

最终代码

自定义控件JeToleranceEntry,包含两个输入框,输入框重写了focusOutEvent,发出信号focusedOut:

void JeLineEdit::focusOutEvent(QFocusEvent *e)
{
    emit focusedOut();
    QLineEdit::focusOutEvent(e);
}

两个子输入框关联主控件的信号editorFocusedOut:

connect(d->downEntry, SIGNAL(focusedOut()), this, SIGNAL(editorFocusedOut()));
connect(d->upEntry, SIGNAL(focusedOut()), this, SIGNAL(editorFocusedOut()));

代理的代码如下:

#include <QStyledItemDelegate>

class JeToleranceItemDelegate : public QStyledItemDelegate
{
    Q_OBJECT
public:
    explicit JeToleranceItemDelegate(QWidget *iParent = nullptr);
    ~JeToleranceItemDelegate();

    QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const override;
    void setEditorData(QWidget *editor, const QModelIndex &index) const override;
    void updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const override;
    void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const override;
};
#include <QApplication>
#include <QFocusEvent>
#include "../jetoleranceentry.h"

JeToleranceItemDelegate::JeToleranceItemDelegate(QWidget *iParent)
    : QStyledItemDelegate(iParent)
{}

JeToleranceItemDelegate::~JeToleranceItemDelegate() {}

QWidget *JeToleranceItemDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
    Q_UNUSED(option);
    Q_UNUSED(index);
    JeToleranceEntry *dTolEdit = new JeToleranceEntry(parent);
    dTolEdit->setFocusProxy(dTolEdit->downEntry());
    dTolEdit->setAutoFillBackground(true);
    // 代理是通过检测主控件的QEvent::FocusOut来提交数据的,包含子控件时会无法触发主控件的焦点事件,需手动触发
    connect(dTolEdit, &JeToleranceEntry::editorFocusedOut, [=]() {
        if (dTolEdit->hasFocus() || dTolEdit->downEntry()->hasFocus() || dTolEdit->upEntry()->hasFocus()) {
            return;
        }
        QFocusEvent *event = new QFocusEvent(QEvent::FocusOut, Qt::MouseFocusReason);
        QApplication::postEvent(dTolEdit, event);
    });
    return dTolEdit;
}

void JeToleranceItemDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const
{
    JeToleranceEntry *dTolEdit = qobject_cast<JeToleranceEntry *>(editor);
    if (dTolEdit) {
        dTolEdit->setValueString(index.data().toString());
    }
}

void JeToleranceItemDelegate::updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
    Q_UNUSED(index)
    editor->setGeometry(option.rect);
}

void JeToleranceItemDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const
{
    JeToleranceEntry *dTolEdit = qobject_cast<JeToleranceEntry *>(editor);
    if (dTolEdit) {
        model->setData(index, dTolEdit->valueString());
    }
}


Comment