Search code examples
qtqwidget

Simulate mouse click on QSlider does not work


I am trying to simulate mouse clicks on my widget using

QCoreApplication::sendEvent();

and it works fine for QPushButton and some other components.

For QSlider, (the Horizontal Slider in designer) I tried the below code which does not work.

QMouseEvent mousePressEvent(QEvent::MouseButtonPress,QPoint(50, 5), Qt::LeftButton, 0, 0);
QCoreApplication::sendEvent((QObject*)ui->horizontalSlider, &mousePressEvent);

QMouseEvent mouseReleaseEvent(QEvent::MouseButtonRelease, QPoint(50, 5), Qt::LeftButton, 0, 0);
QCoreApplication::sendEvent((QObject*)ui->horizontalSlider, &mouseReleaseEvent);

but with QTest it works fine.

QTest::mouseClick(ui->horizontalSlider, Qt::LeftButton, Qt::NoModifier, QPoint(50, 5));

Any suggestion for correcting same would be a great help. Thanks in advance.


Solution

  • Short answer would be the following:

    // The following code should be changed
    QMouseEvent mousePressEvent(QEvent::MouseButtonPress,QPoint(50, 5), Qt::LeftButton, 0, 0);
    QCoreApplication::sendEvent((QObject*)ui->horizontalSlider, &mousePressEvent);
    
    QMouseEvent mouseReleaseEvent(QEvent::MouseButtonRelease, QPoint(50, 5), Qt::LeftButton, 0, 0);
    QCoreApplication::sendEvent((QObject*)ui->horizontalSlider, &mouseReleaseEvent);
    
    // To the following one. Also, note that (QObject*) is not necessary, since QSlider is in the hierarchy with QObject, i.e. QObject is ancestor for the QSlider.
    QMouseEvent mousePressEvent(QEvent::MouseButtonPress,QPoint(50, 5), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier);
    QCoreApplication::sendEvent(ui->horizontalSlider, &mousePressEvent);
    
    QMouseEvent mouseReleaseEvent(QEvent::MouseButtonRelease, QPoint(50, 5), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier);
    QCoreApplication::sendEvent(ui->horizontalSlider, &mouseReleaseEvent);
    

    This would be the short answer. The longer version of it can be used by anyone who would like it is possible to deduce which events need to be used in order to simulate some sort of behavior on Qt elements.

    First of all we need to investigate what type of event actually is introduced on QSlider when pressing mouse button on it. Then, we would be able to simulate it actually. In order to do that, we can use event filter. For simplicity, let's use this one at first:

    class QSliderAnalyser
            : public QObject
    {
        public:
            QSliderAnalyser()
            {
            }
    
            virtual ~QSliderAnalyser()
            {
            }
    
        protected:
            bool eventFilter(QObject* object, QEvent* event) override
            {
                qDebug() << event->type();
    
                return QObject::eventFilter(object, event);
            }
    };
    

    Then, we install this filter on your QSlider instance:

    // Just keep in mind that memory is not managed in correct way here, and you should ensure proper cleanup of your elements on destruction.
    ui->horizontalSlider->installEventFilter(new QSliderAnalyser());
    

    After running an application, we can see lots of various of events. Example is below (after a couple of interactions with QSlider instance):

    QEvent::Type(HoverMove)
    QEvent::Type(HoverMove)
    QEvent::Type(HoverMove)
    QEvent::Type(HoverMove)
    QEvent::Type(MouseButtonPress)
    QEvent::Type(Paint)
    QEvent::Type(MouseButtonRelease)
    QEvent::Type(Paint)
    QEvent::Type(HoverMove)
    

    The most interesting part here is the following: QEvent::Type(MouseButtonPress) and QEvent::Type(MouseButtonRelease). In other words, QMouseEvent from different type is being simulated. Up to this point, you have probably understood yourself what to send, i.e. you already try to send correct events. However, maybe parameters are wrong? It might be so. So, let's take a look into QMouseEvent class constructor arguments:

    QMouseEvent(QEvent::Type type, const QPointF &localPos, const QPointF &windowPos, const QPointF &screenPos, Qt::MouseButton button, Qt::MouseButtons buttons, Qt::KeyboardModifiers modifiers, Qt::MouseEventSource source)
    

    Based on constructor that has most arguments, we can deduce that event filter above could be rewritten a bit in the following way:

            // Change "qDebug() << event->type();" to the following code.
            if (event->type() == QEvent::MouseButtonPress || event->type() == QEvent::MouseButtonRelease)
            {
                auto mouseEvent = static_cast<QMouseEvent*>(event);
                qDebug() << mouseEvent->type();
                qDebug() << mouseEvent->localPos();
                qDebug() << mouseEvent->windowPos();
                qDebug() << mouseEvent->screenPos();
                qDebug() << mouseEvent->button();
                qDebug() << mouseEvent->buttons();
                qDebug() << mouseEvent->modifiers();
                qDebug() << mouseEvent->source();
            }
    

    After running the app again, on my device the following values are shown for mouse button press and release events accordingly:

    QEvent::Type(MouseButtonPress)
    QPointF(37,8)
    QPointF(67,160)
    QPointF(2747,550)
    1
    QFlags<Qt::MouseButtons>(LeftButton)
    QFlags<Qt::KeyboardModifiers>(NoModifier)
    Qt::MouseEventSource(MouseEventNotSynthesized)
    
    QEvent::Type(MouseButtonRelease)
    QPointF(37,8)
    QPointF(67,160)
    QPointF(2747,550)
    1
    QFlags<Qt::MouseButtons>(NoButton)
    QFlags<Qt::KeyboardModifiers>(NoModifier)
    Qt::MouseEventSource(MouseEventNotSynthesized)
    

    So, what can be done here? I think we can just try simulating the event exactly as possible and just remove values one by one to see the minimum required event that would get processed as behavior wanted to be observed from Qt. So, initially the following code might be used:

    QMouseEvent mousePressEvent(QEvent::MouseButtonPress, QPoint(37, 8), QPoint(67, 160), QPoint(2747, 550), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier, Qt::MouseEventNotSynthesized);
    QMouseEvent mouseReleaseEvent(QEvent::MouseButtonPress, QPoint(37, 8), QPoint(67, 160), QPoint(2747, 550), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier, Qt::MouseEventNotSynthesized);
    
    QCoreApplication::sendEvent((QObject*)ui->horizontalSlider, &mousePressEvent);
    QCoreApplication::sendEvent((QObject*)ui->horizontalSlider, &mouseReleaseEvent);
    

    And after some observations, we can deduce the following:

    1. Screen position is not required.
    2. Window position is not needed, too.
    3. Mouse event source is not needed, too.
    4. You are not event obliged to use mouse button press AND release events. You can just either press or release your mouse. Qt will process even this type of behavior.
    5. Basically, what is needed, is the following: Mouse event type (i.e. press and release), local position, button that has been pressed and released, and keyboard modifiers.

    Thus, the code could be rewritten in the following ways and would be still working:

    // Option One
    QMouseEvent mousePressEvent(QEvent::MouseButtonPress, QPoint(50, 5), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier);
    QMouseEvent mouseReleaseEvent(QEvent::MouseButtonPress, QPoint(50, 5), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier);
    
    QCoreApplication::sendEvent(ui->horizontalSlider, &mousePressEvent);
    QCoreApplication::sendEvent(ui->horizontalSlider, &mouseReleaseEvent);
    
    // Option Two
    QMouseEvent mousePressEvent(QEvent::MouseButtonPress, QPoint(50, 5), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier);
    
    QCoreApplication::sendEvent(ui->horizontalSlider, &mousePressEvent);
    
    // Option Three
    QMouseEvent mousePressEvent(QEvent::MouseButtonPress, QPoint(50, 5), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier);
    
    QCoreApplication::sendEvent(ui->horizontalSlider, &mousePressEvent);
    

    Thus, I have shown possibilities of how to deduce what type of events result in behavior that Qt processes on widgets. Hopefully, my answer was clear and helpful enough.