环境: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());
}
}