Наконец-то я провел небольшое исследование по этому поводу.
Объяснение
Когда представление вызывает createEditor
функцию делегата, оно также устанавливает фильтр событий делегата в редактор.
QWidget *QAbstractItemViewPrivate::editor(const QModelIndex &index,
const QStyleOptionViewItem &options)
{
Q_Q(QAbstractItemView);
QWidget *w = editorForIndex(index).widget.data();
if (!w) {
QAbstractItemDelegate *delegate = delegateForIndex(index);
if (!delegate)
return 0;
w = delegate->createEditor(viewport, options, index);
if (w) {
w->installEventFilter(delegate);
......
}
Однако делегат может перехватывать только события виджета редактора, но не события его дочерних элементов. Когда нажата клавиша Tab
, вызывается функция QWidget::event
, она использует ее для переключения фокуса на другой виджет:
bool QWidget::event(QEvent *event)
{
......
switch (event->type()) {
......
case QEvent::KeyPress: {
QKeyEvent *k = (QKeyEvent *)event;
bool res = false;
if (!(k->modifiers() & (Qt::ControlModifier | Qt::AltModifier))) { //### Add MetaModifier?
if (k->key() == Qt::Key_Backtab
|| (k->key() == Qt::Key_Tab && (k->modifiers() & Qt::ShiftModifier)))
res = focusNextPrevChild(false);
else if (k->key() == Qt::Key_Tab)
res = focusNextPrevChild(true);
if (res)
break;
}
......
}
......
}
Соответственно, в моем случае фокус установлен на следующий QPushButton
после QLineEdit
, и событие не распространяется на родителя (LineEditor
).
Решение
Правильный способ решения проблемы — это как QSpinBox
. Потому что он также имеет QLineEdit
. В конструкторе виджета он устанавливает фокус прокси для редактирования строки:
edit->setFocusProxy(this);
Таким образом, все события фокуса будут достигать основного виджета. Также должно быть установлено свойство focusPolicy
, потому что по умолчанию оно равно NoFocus
:
setFocusPolicy(Qt::WheelFocus);
Все, что нам нужно сделать в этот момент, это распространить необходимые события на QLineEdit
из главного виджета следующим образом:
bool LineEditor::event(QEvent *e)
{
switch(e->type())
{
case QEvent::ShortcutOverride:
if(m_lineEdit->event(e))
return true;
break;
case QEvent::InputMethod:
return m_lineEdit->event(e);
default:
break;
}
return QWidget::event(e);
}
void LineEditor::keyPressEvent(QKeyEvent *e)
{
m_lineEdit->event(e);
}
void LineEditor::mousePressEvent(QMouseEvent *e)
{
if(e->button() != Qt::LeftButton)
return;
e->ignore();
}
void LineEditor::mouseReleaseEvent(QMouseEvent *e)
{
e->accept();
}
void LineEditor::focusInEvent(QFocusEvent *e)
{
m_lineEdit->event(e);
QWidget::focusInEvent(e);
}
void LineEditor::focusOutEvent(QFocusEvent *e)
{
m_lineEdit->event(e);
QWidget::focusOutEvent(e);
}
Этого должно быть достаточно.
Сложный
Как было сказано выше, делегат не может перехватывать события потомков редактора. Поэтому, чтобы сделать поведение редактора «родным», я должен дублировать события от дочерних элементов к редактору.
LineEditor
устанавливает фильтр событий в QLineEdit
в конструкторе:
edit->installEventFilter(this);
Реализация фильтра выглядит так:
bool LineEditor::eventFilter(QObject *object, QEvent *event)
{
if(event->type() == QEvent::KeyPress)
{
QKeyEvent* keyEvent = static_cast<QKeyEvent *>(event);
if(keyEvent->key() == Qt::Key_Tab || keyEvent->key() == Qt::Key_Backtab)
{
QApplication::postEvent(this, new QKeyEvent(keyEvent->type(), keyEvent->key(), keyEvent->modifiers()));
// Filter this event because the editor will be closed anyway
return true;
}
}
else if(event->type() == QEvent::FocusOut)
{
QFocusEvent* focusEvent = static_cast<QFocusEvent *>(event);
QApplication::postEvent(this, new QFocusEvent(focusEvent->type(), focusEvent->reason()));
// Don't filter because focus can be changed internally in editor
return false;
}
return QWidget::eventFilter(object, event);
}
Также можно использовать qApp->notify(this, event)
для QKeyEvent
вместо QApplication::postEvent
, потому что мы все равно фильтруем эти события. Но это невозможно для QFocusEvent
, потому что notify
перенаправит событие, и оно не достигнет дочернего элемента.
Обратите внимание, что стандартный (QItemDelegate
или QStyledItemDelegate
) делегат сам по себе будет заботиться о ситуации, когда фокус изменяется внутри:
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();
}
......
person
fasked
schedule
15.06.2013