QSpinBox внутри QScrollArea: как предотвратить кражу фокуса Spin Box при прокрутке?

У меня есть элемент управления с несколькими объектами QSpinBox внутри QScrollArea. Все работает нормально при прокрутке в области прокрутки, если только мышь не находится над одним из QSpinBox. Затем QSpinBox перехватывает фокус, и события колеса манипулируют значением счетчика, а не прокручивают область прокрутки.

Я не хочу полностью отключать использование колесика мыши для управления QSpinBox, но я хочу, чтобы это происходило только в том случае, если пользователь явно щелкает QSpinBox или вводит вкладку. Есть ли способ предотвратить кражу QSpinBox фокуса из QScrollArea?

Как сказано в комментарии к ответу ниже, установка Qt::StrongFocus предотвращает появление прямоугольника фокуса на элементе управления, однако он по-прежнему крадет колесо мыши и регулирует значение в поле счетчика и останавливает прокрутку QScrollArea. То же самое с Qt::ClickFocus.


person Grant Limberg    schedule 28.04.2011    source источник


Ответы (7)


Попробуйте удалить Qt::WheelFocus из счетчика QWidget::focusPolicy:

spin->setFocusPolicy( Qt::StrongFocus );

Кроме того, вам необходимо предотвратить попадание события колеса в спинбоксы. Вы можете сделать это с помощью фильтра событий:

explicit Widget( QWidget * parent=0 )
    : QWidget( parent )
{
    // setup ...
    Q_FOREACH( QSpinBox * sp, findChildren<QSpinBox*>() ) {
        sp->installEventFilter( this );
        sp->setFocusPolicy( Qt::StrongFocus );
    }

}

/* reimp */ bool eventFilter( QObject * o, QEvent * e ) {
    if ( e->type() == QEvent::Wheel &&
         qobject_cast<QAbstractSpinBox*>( o ) )
    {
        e->ignore();
        return true;
    }
    return QWidget::eventFilter( o, e );
}

отредактируйте от Гранта Лимберга для полноты, так как это дало мне 90% пути:

В дополнение к тому, что mmutz сказал выше, мне нужно было сделать еще несколько вещей. Мне пришлось создать подкласс QSpinBox и реализовать focusInEvent(QFocusEvent*) и focusOutEvent(QFocusEvent*). По сути, на focusInEvent я меняю политику фокусировки на Qt::WheelFocus, а на focusOutEvent снова на Qt::StrongFocus.

void MySpinBox::focusInEvent(QFocusEvent*)
{
     setFocusPolicy(Qt::WheelFocus);
}

void MySpinBox::focusOutEvent(QFocusEvent*)
{
     setFocusPolicy(Qt::StrongFocus);
}

Кроме того, реализация метода eventFilter в классе фильтра событий изменяет свое поведение в зависимости от текущей политики фокуса подкласса счетчика:

bool eventFilter(QObject *o, QEvent *e)
{
    if(e->type() == QEvent::Wheel &&
       qobject_cast<QAbstractSpinBox*>(o))
    {
        if(qobject_cast<QAbstractSpinBox*>(o)->focusPolicy() == Qt::WheelFocus)
        {
            e->accept();
            return false;
        }
        else
        {
            e->ignore();
            return true;
        }
    }
    return QWidget::eventFilter(o, e);
}
person Marc Mutz - mmutz    schedule 28.04.2011
comment
Установка Qt::StrongFocus предотвращает появление прямоугольника фокуса на элементе управления, однако он по-прежнему крадет колесо мыши и регулирует значение в поле счетчика и останавливает прокрутку QScrollArea. - person Grant Limberg; 28.04.2011
comment
Затем вам также нужно отфильтровать события колеса от достижения QSpinBox. Я расширил свой ответ. - person Marc Mutz - mmutz; 28.04.2011
comment
К сожалению, фильтр событий также отфильтровывает события колеса, которые я хочу видеть в поле прокрутки, т. е. когда поле явно выбрано щелчком в области редактирования или с помощью табуляции, фокусирующейся на области редактирования. - person Grant Limberg; 28.04.2011
comment
@Grant: вы пытались просто запросить у счетчика hasFocus() в фильтре событий? - person Marc Mutz - mmutz; 28.04.2011
comment
@mmutz: да. Когда установлен Qt::StrongFocus, событие колеса убирает фокус с виджета, поэтому функция hasFocus() при установленном Qt::StrongFocus всегда будет возвращать false для события колеса. - person Grant Limberg; 28.04.2011
comment
@Грант: хорошо. Кстати: вам не нужно создавать подклассы из QSpinBox, вы также можете использовать тот же фильтр событий для проверки событий входа/выхода фокуса. - person Marc Mutz - mmutz; 28.04.2011
comment
Если вы создаете подкласс QSpinBox, не забудьте добавить вызовы методов суперкласса QSpinBox::focusOutEvent(event) и QSpinBox::focusInEvent(event), если вы не хотите странного поведения. - person gfrigon; 06.12.2013
comment
С этим ответом я обнаружил, что мне также нужно установить содержащую QScrollArea, чтобы иметь сильную политику фокуса, чтобы предотвратить кражу текстового фокуса из счетчика при прокрутке окна. - person Tim MB; 17.01.2014
comment
-1 Код не проверен. Пожалуйста, смотрите последний образец. QObject* определен как obj, но упоминается как o. То же самое относится и к переменной события. - person Valentin Heinitz; 10.02.2015
comment
@ValentinHeinitz: Может быть, вы могли бы отредактировать код вместо того, чтобы голосовать против? Неважно... - person Marc Mutz - mmutz; 10.02.2015

Чтобы решить эту проблему, нам нужно позаботиться о следующих двух вещах:

  1. Счетчик не должен получать фокус с помощью колесика мыши. Это можно сделать, установив для политики фокуса значение Qt::StrongFocus.
  2. Счетчик должен принимать события колеса только в том случае, если он уже имеет фокус. Это можно сделать, повторно реализуя QWidget::wheelEvent в подклассе QSpinBox.

Полный код для класса MySpinBox, реализующего это:

class MySpinBox : public QSpinBox {

    Q_OBJECT

public:

    MySpinBox(QWidget *parent = 0) : QSpinBox(parent) {
        setFocusPolicy(Qt::StrongFocus);
    }

protected:

    virtual void wheelEvent(QWheelEvent *event) {
        if (!hasFocus()) {
            event->ignore();
        } else {
            QSpinBox::wheelEvent(event);
        }
    }
};

Вот и все. Обратите внимание: если вы не хотите создавать новый подкласс QSpinBox, вы также можете использовать фильтры событий, чтобы решить эту проблему.

person emkey08    schedule 15.10.2013
comment
Этот способ работает для меня вместо принятого ответа. Однако для того, чтобы вращение работало, когда MySpinBox действительно имеет фокус, вам также необходимо переопределить focusInEvent и focusOutEvent, чтобы установить политику фокуса на Qt::WheelFocus, когда фокус включен, и снова установить на Qt::StrongFocus, когда фокус отсутствует. - person kkpattern; 03.04.2015
comment
Нет, менять политику фокуса не нужно, приведенный выше код и так работает нормально. Обратите внимание, однако, что вы можете изменить значение счетчика с помощью колесика мыши только в том случае, если 1) счетчик имеет фокус и 2) курсор мыши находится над счетчиком при прокручивании. - person emkey08; 18.10.2017
comment
Что ж. Делая это с PySide, SpinBox никогда не фокусируется на wheelEvent. переопределение focusIn/OutEvents сделало это! - person ewerybody; 29.08.2018

Моя попытка решения. Прост в использовании, не требует подклассов.

Сначала я создал новый вспомогательный класс:

#include <QObject>

class MouseWheelWidgetAdjustmentGuard : public QObject
{
public:
    explicit MouseWheelWidgetAdjustmentGuard(QObject *parent);

protected:
    bool eventFilter(QObject* o, QEvent* e) override;
};

#include <QEvent>
#include <QWidget>

MouseWheelWidgetAdjustmentGuard::MouseWheelWidgetAdjustmentGuard(QObject *parent) : QObject(parent)
{
}

bool MouseWheelWidgetAdjustmentGuard::eventFilter(QObject *o, QEvent *e)
{
    const QWidget* widget = static_cast<QWidget*>(o);
    if (e->type() == QEvent::Wheel && widget && !widget->hasFocus())
    {
        e->ignore();
        return true;
    }

    return QObject::eventFilter(o, e);
}

Затем я устанавливаю политику фокуса проблемного виджета на StrongFocus либо во время выполнения, либо в Qt Designer. Затем я устанавливаю свой фильтр событий:

ui.comboBox->installEventFilter(new MouseWheelWidgetAdjustmentGuard(ui.comboBox));

Сделанный. MouseWheelWidgetAdjustmentGuard будет удалено автоматически, когда родительский объект — поле со списком — будет уничтожен.

person Violet Giraffe    schedule 26.05.2016
comment
Это кажется значительно проще, чем приведенные выше решения, до сих пор отлично работает. - person Thomas; 19.05.2017
comment
Работает как шарм. Спасибо. - person Norbert Szenasi; 22.09.2017
comment
Я перенес это на Python под PyQt5, и он отлично работает (без focusInEvent или focusOutEvent), см. мой ответ - person Richard Whitehead; 17.04.2020

Просто для расширения вы можете сделать это с помощью eventFilter вместо того, чтобы избавиться от необходимости создавать новый класс типа QMySpinBox:

bool eventFilter(QObject *obj, QEvent *event)
{
    QAbstractSpinBox* spinBox = qobject_cast<QAbstractSpinBox*>(obj);
    if(spinBox)
    {
        if(event->type() == QEvent::Wheel)
        {
            if(spinBox->focusPolicy() == Qt::WheelFocus)
            {
                event->accept();
                return false;
            }
            else
            {
                event->ignore();
                return true;
            }
        }
        else if(event->type() == QEvent::FocusIn)
        {
            spinBox->setFocusPolicy(Qt::WheelFocus);
        }
        else if(event->type() == QEvent::FocusOut)
        {
            spinBox->setFocusPolicy(Qt::StrongFocus);
        }
    }
    return QObject::eventFilter(obj, event);
}
person William Woodbury    schedule 15.07.2013

С помощью этого поста мы подготовили решение для Python/PySide. Если кто-то наткнется на это. Как и мы:]

class HumbleSpinBox(QtWidgets.QDoubleSpinBox):
    def __init__(self, *args):
        super(HumbleSpinBox, self).__init__(*args)
        self.setFocusPolicy(QtCore.Qt.StrongFocus)

    def focusInEvent(self, event):
        self.setFocusPolicy(QtCore.Qt.WheelFocus)
        super(HumbleSpinBox, self).focusInEvent(event)

    def focusOutEvent(self, event):
        self.setFocusPolicy(QtCore.Qt.StrongFocus)
        super(HumbleSpinBox, self).focusOutEvent(event)

    def wheelEvent(self, event):
        if self.hasFocus():
            return super(HumbleSpinBox, self).wheelEvent(event)
        else:
            event.ignore()
person ewerybody    schedule 29.08.2018

Это мой порт Python PyQt5 ответа Violet Giraffe:


def preventAnnoyingSpinboxScrollBehaviour(self, control: QAbstractSpinBox) -> None:
    control.setFocusPolicy(Qt.StrongFocus)
    control.installEventFilter(self.MouseWheelWidgetAdjustmentGuard(control))

class MouseWheelWidgetAdjustmentGuard(QObject):
    def __init__(self, parent: QObject):
        super().__init__(parent)

    def eventFilter(self, o: QObject, e: QEvent) -> bool:
        widget: QWidget = o
        if e.type() == QEvent.Wheel and not widget.hasFocus():
            e.ignore()
            return True
        return super().eventFilter(o, e)

person Richard Whitehead    schedule 17.04.2020

Просто чтобы помочь нуждающимся, в нем не хватает маленькой детали:

call focusInEvent and focusOutEvent from QSpinBox :

    void MySpinBox::focusInEvent(QFocusEvent* pEvent)
    {
        setFocusPolicy(Qt::WheelFocus);
        QSpinBox::focusInEvent(pEvent);
    }

    void MySpinBox::focusOutEvent(QFocusEvent*)
    {
        setFocusPolicy(Qt::StrongFocus);
        QSpinBox::focusOutEvent(pEvent);
    }
person jerome    schedule 05.09.2013
comment
Привет, Джером, добро пожаловать в Stack Overflow. Не могли бы вы немного обосновать свой ответ, чтобы указать причины, по которым он не сработал? Это помогает объяснить другим (особенно людям, которые не так хорошо владеют определенным языком), почему это решает проблему? Спасибо! - person Qantas 94 Heavy; 05.09.2013