【问题标题】:QSpinBox inside a QScrollArea: How to prevent Spin Box from stealing focus when scrolling?QScrollArea 内的 QSpinBox:如何防止 Spin Box 在滚动时窃取焦点?
【发布时间】:2011-08-14 20:59:58
【问题描述】:

我在 QScrollArea 中有一个带有多个 QSpinBox 对象的控件。在滚动区域中滚动时一切正常,除非鼠标恰好位于其中一个 QSpinBoxes 上。然后 QSpinBox 窃取焦点,滚轮事件操纵旋转框值而不是滚动滚动区域。

我不想完全禁用使用鼠标滚轮来操作 QSpinBox,但我只希望在用户明确单击或切换到 QSpinBox 时发生这种情况。有没有办法防止 QSpinBox 从 QScrollArea 窃取焦点?

正如在下面对答案的评论中所说,设置 Qt::StrongFocus 确实会阻止焦点矩形出现在控件上,但是它仍然会窃取鼠标滚轮并调整旋转框中的值并阻止 QScrollArea 滚动.与 Qt::ClickFocus 相同。

【问题讨论】:

    标签: c++ qt qscrollarea qspinbox


    【解决方案1】:

    这是我的紫罗兰色长颈鹿答案的 Python PyQt5 端口:

    
    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)
    
    

    【讨论】:

      【解决方案2】:

      在这篇文章的帮助下,我们为 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()
      

      【讨论】:

        【解决方案3】:

        我的解决方案尝试。易于使用,无需子类化。

        首先,我创建了一个新的辅助类:

        #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-&gt;installEventFilter(new MouseWheelWidgetAdjustmentGuard(ui.comboBox));

        完成。当父对象 - 组合框 - 被销毁时,MouseWheelWidgetAdjustmentGuard 将被自动删除。

        【讨论】:

        • 这似乎比上述解决方案容易得多,到目前为止效果很好。
        • 像魅力一样工作。谢谢。
        • 我将它移植到 PyQt5 下的 Python 并且效果很好(没有 focusInEvent 或 focusOutEvent),请参阅我的答案
        【解决方案4】:

        尝试从旋转框中删除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 );
        }
        

        为了完整起见,来自 Grant Limberg 的编辑,因为这让我完成了 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 方法实现会根据 spinbox 子类的当前焦点策略改变其行为:

        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);
        }
        

        【讨论】:

        • 设置 Qt::StrongFocus 确实会阻止焦点矩形出现在控件上,但它仍然会窃取鼠标滚轮并调整旋转框中的值并阻止 QScrollArea 滚动。
        • 那么你还需要过滤掉轮子事件到达QSpinBox。我已经扩展了我的答案。
        • 不幸的是,事件过滤器还过滤掉了我在旋转框上想要的滚轮事件,即,当通过单击编辑区域或标签聚焦到编辑区域显式选择框时。
        • @Grant:你有没有试过在事件过滤器中向 spinbox 询问 hasFocus() ?
        • 如果您将 QSpinBox 子类化,如果您不想出现奇怪的行为,请不要忘记添加对超类方法 QSpinBox::focusOutEvent(event)QSpinBox::focusInEvent(event) 的调用。
        【解决方案5】:

        为了解决这个问题,我们需要关心两个以下的事情:

        1. 旋转框不得通过使用鼠标滚轮获得焦点。这可以通过将焦点策略设置为 Qt::StrongFocus 来完成。
        2. 如果旋转框已经获得焦点,它必须只接受滚轮事件。这可以通过在 QSpinBox 子类中重新实现 QWidget::wheelEvent 来完成。

        实现此功能的 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 子类,那么您也可以使用事件过滤器来解决此问题。

        【讨论】:

        • 这种方式对我有用,而不是接受的答案。然而,当MySpinBox 确实有焦点时,为了使旋转工作,您还需要覆盖focusInEventfocusOutEvent 以在焦点进入时将焦点策略设置为Qt::WheelFocus,并在焦点离开时设置回Qt::StrongFocus
        • 不,不需要更改焦点策略,上面的代码可以正常工作。但是请注意,只有在以下情况下,您才能使用鼠标滚轮更改旋转框的值:1) 旋转框具有焦点并且 2) 滚轮时鼠标光标位于旋转框上方。
        • 好吧。使用 PySide 执行此操作会使 SpinBox 永远不会关注 wheelEvent。覆盖 focusIn/OutEvents 做到了!
        【解决方案6】:

        只是为了帮助任何有需要的人,它缺少一个小细节:

        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);
            }
        

        【讨论】:

        • 嗨,杰罗姆,欢迎来到 Stack Overflow。您能否稍微证实您的答案以提供它不起作用的原因?这有助于向其他人(尤其是对特定语言不太熟练的人)解释为什么这可以解决问题?谢谢!
        【解决方案7】:

        只是为了扩展,您可以使用 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);
        }
        

        【讨论】:

          猜你喜欢
          • 2011-11-04
          • 2011-02-18
          • 1970-01-01
          • 1970-01-01
          • 2016-04-09
          • 1970-01-01
          • 1970-01-01
          • 2013-05-25
          • 1970-01-01
          相关资源
          最近更新 更多