Qt QML ComboBox override wheel events

2019-05-10 05:51发布

问题:

Is there any way to override ComboBox MouseArea to ignore wheel event instead of changing current index? ComboBox itself has no option to change wheel focus behaviour. So far I've tried to override onWheel from CB MouseArea with code like this:

ComboBox {
  Component.onCompleted: {
    for (var i = 0; i < combobox_ctrl.children.length; ++i) {
      console.log(combobox_ctrl.children[i])
      console.log(combobox_ctrl.children[i].hasOwnProperty('onWheel'))

      if (combobox_ctrl.children[i].hasOwnProperty('onWheel')) {
        console.log(combobox_ctrl.children[i]['onWheel'])
        combobox_ctrl.children[i]['onWheel'] = function() { console.log("CB on wheel!") }
        //combobox_ctrl.children[i]onWheel = function() { console.log("CB on wheel!") 
        //combobox_ctrl.children[i].destroy()
      }
    }
  }
}

But I get

TypeError: Cannot assign to read-only property "wheel"

Did anyone was able to disable wheel events on ComboBox in Qml?

// EDIT

for example in Slider control I was able to remove wheel event handling like this:

Slider {
  Component.onCompleted: {
    for (var i = 0; i < slider.children.length; ++i) {
      console.log(slider.children[i])
      if (slider.children[i].hasOwnProperty("onVerticalWheelMoved") && slider.children[i].hasOwnProperty("onHorizontalWheelMoved")) {
        console.log("Found wheel area!")
        slider.children[i].destroy()
      }
    }
  }
}

But in slider WheelArea is not responsible for handling "click" events.

回答1:

You can place MouseArea over ComboBox and steel wheel event.

ComboBox {
    anchors.centerIn: parent
    model: [ "Banana", "Apple", "Coconut" ]
    MouseArea {
        anchors.fill: parent
        onWheel: {
            // do nothing
        }
        onPressed: {
            // propogate to ComboBox
            mouse.accepted = false;
        }
        onReleased: {
            // propogate to ComboBox
            mouse.accepted = false;
        }
    }
}


回答2:

It's not currently possible, as ComboBox is not derived from MouseArea, but FocusScope, which has no support for these kinds of events.

A similar problem was mentioned in a suggestion recently:

Disable mouse wheel scroll event on QtQuick.Controls


If you're after a hacky way of doing it, it seems like the only option you have left is to apply a patch to ComboBox.qml that removes the onWheel handler:

diff --git a/src/controls/ComboBox.qml b/src/controls/ComboBox.qml
index 4e29dfe..3413cac 100644
--- a/src/controls/ComboBox.qml
+++ b/src/controls/ComboBox.qml
@@ -407,13 +407,6 @@ Control {
                 popup.toggleShow()
             overridePressed = false
         }
-        onWheel: {
-            if (wheel.angleDelta.y > 0) {
-                __selectPrevItem();
-            } else if (wheel.angleDelta.y < 0){
-                __selectNextItem();
-            }
-        }
     }

Another alternative that doesn't involve modifying Qt code would be to add an intermediate MouseArea above ComboBox's, and then somehow only forward specific events through to ComboBox's MouseArea. Or, create a custom C++ item that does the equivalent. You may have more control that way.



回答3:

Ok. After hacking around I've managed to come with solution that is acceptable for me but may introduce some regressions in some situations. pressed and hovered properties are no longer usable

import QtQuick.Controls.Private 1.0

ComboBox {
  Component.onCompleted: {
    for (var i = 0; i < combobox_ctrl.children.length; ++i) {
      if (combobox_ctrl.children[i].hasOwnProperty('onWheel') && combobox_ctrl.children[i] !== mouseArea) {
        combobox_ctrl.children[i].destroy()
      }
    }
  }

  MouseArea {
    id: mouseArea
    anchors.fill: parent

    onPressed: {
      if (combobox_ctrl.activeFocusOnPress)
        forceActiveFocus()
      if (!Settings.hasTouchScreen)
        combobox_ctrl.__popup.toggleShow()
    }

    onClicked: {
      if (Settings.hasTouchScreen)
        combobox_ctrl.__popup.toggleShow()
    }
  }
}

This way we can mimic mouse area that was originaly inside the ComboBox. Popup is shown as it was (at least I didn't see any regresion in it yet). However two properties are inaccesible right now



回答4:

I created a separate file called NonScrollingComboBox.qml with the following code following this post: https://stackoverflow.com/a/33080217/969016

Now I can just use NonScrollingComboBox as a component instead of ComboBox on places where I don't want the mouse scroll to change the value

import QtQuick 2.0
import QtQuick.Controls 1.4

ComboBox {
    id: combobox_ctrl
    Component.onCompleted: {
        for (var i = 0; i < combobox_ctrl.children.length; ++i) {
            if (combobox_ctrl.children[i].hasOwnProperty('onWheel')
                    && combobox_ctrl.children[i] !== mouseArea) {
                combobox_ctrl.children[i].destroy()
            }
        }
    }
    MouseArea {
        id: mouseArea
        anchors.fill: parent

        onPressed: {
            if (combobox_ctrl.activeFocusOnPress)
                forceActiveFocus()
            combobox_ctrl.__popup.toggleShow()
        }

        onClicked: {
            combobox_ctrl.__popup.toggleShow()
        }
    }
}

usage:

NonScrollingComboBox {
    anchors.verticalCenter: parent.verticalCenter
    model: ["item one", "item 2"]
}


回答5:

This seems to apply only to Qt Quick Controls 1 ComboBox. On Qt Quick Controls 2 ComboBox the wheel mouse event is not enabled by default and can be enabled manually by setting to true the property wheelEnabled (documented in the base class Control). Also the combobox won't keep a "focus" on mouse events so you can freely use the wheel on other mouse areas by just entering them.