向父母发出传播模拟鼠标事件(拖动目的)的问题

Posted

技术标签:

【中文标题】向父母发出传播模拟鼠标事件(拖动目的)的问题【英文标题】:Issue propagating simulated MouseEvents (Drag purpose) to parents 【发布时间】:2018-01-20 16:50:10 【问题描述】:

我正在开发一个 Qt/Qml 应用程序。

我目前正在尝试通过 Qt 在拖动 QML 元素(Listview、Flickable...)上模拟拖动行为以进行测试。

我有一个特定的问题,我想用最通用的解决方案来解决,我的组件是一个非交互式 ListView,嵌套在交互式 ListView 中,自身嵌套在 MouseArea 中:

ListView 
    anchors.fill: parent
    interactive: false
    ListView 
        anchors.fill: parent
        MouseArea 
             ...
        
    

所以。我的想法是:取一个 QML 对象,运动开始的局部坐标 (x, y),找到它在位置上最嵌套的子对象,然后将运动 (dx, dy) 应用于该子对象。如果我正确理解 QT/QML 的工作原理,它应该将事件发送给父级(如果子级不使用),并且 Flickable 组件应该能够检测到拖动并捕捉它们。

void xx::touchAndDrag(QObject *object, const int x, const int y, const int dx, const int dy)


    timer = new QTimer(this);
    timerIteration = 0;
    deltaPoint = new QPointF(dx, dy);
    parentItem = qobject_cast<QQuickItem *>(object);
    item = parentItem;
    startPoint = QPointF(x, y);
    QPointF tempPoint = startPoint;

    for( ;; ) 
        //Find the most nested child at coordinate
        QQuickItem* child = item->childAt(tempPoint.x(), tempPoint.y());
        if(child) 
            item = child;
            tempPoint = child->mapFromItem(parentItem, tempPoint);
            qDebug() << "child found " << item;
         else 
            break;
        
    

    timer->setInterval(movementDuration / nbIteration);
    qDebug() << "interval " << timer->interval();
    timer->setSingleShot(false);
    connect(timer, SIGNAL(timeout()), this, SLOT(mouseMove()));

    // Send press event at starting point
    QMouseEvent *e = new QMouseEvent(QEvent::MouseButtonPress, item->mapFromItem(parentItem, startPoint), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier );
    qDebug() << "press " << e;
    qApp->postEvent(item, e);

    timer->start();


void xx::mouseMove()

    timerIteration++;
    QMouseEvent *e;
    if(timerIteration < nbIteration) 
        int x = startPoint.x() + deltaPoint->x() * timerIteration / nbIteration;
        int y = startPoint.y() + deltaPoint->y() * timerIteration / nbIteration;

        QPointF point = QPointF(x, y);
        // Send moveEvent
        e = new QMouseEvent(QEvent::MouseMove, item->mapFromItem(parentItem, point), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier );
        qDebug() << "move! " << e ;
    else 
        //End reached, send end event
        QPointF point = QPointF(startPoint.x() + deltaPoint->x(), startPoint.y() + deltaPoint->y());
        e = new QMouseEvent(QEvent::MouseButtonRelease, item->mapFromItem(parentItem, point), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier );
        timer->stop();
        qDebug() << "end! " << e ;
    

    qApp->postEvent(item, e);

所以...它不工作。发生什么了?根据我的测试:

-如果我删除嵌套的子部分,并直接给交互式(拖动)QML 组件(这里是嵌套的ListView),结果是好的。但这对我来说不是一个好的解决方案,因为我必须确切地知道哪个组件应该做出反应。但是,这似乎覆盖了组件的“interactive: false”,当目的是……测试组件时,这是一个坏主意。

-鼠标区域接收所有事件。 mouseX 属性已更新。这是一个问题。对于非模拟事件,MouseArea 应该接收按下事件、一些移动事件,然后事件应该由 ListView / Flickable 捕获。更糟糕的是,即使 mousePress 和 mouseRelease 事件之间的位置不同(400px),Qt 也会检测到 MouseClicked 事件并在 QML 端触发它......

所以,不知道从那里去哪里。我可以做一些糟糕的孩子检测(检查嵌套孩子的类型,只接受好的孩子),但我对此不太满意。有什么想法吗?

【问题讨论】:

【参考方案1】:

好的,所以我更深入地研究了 QT 代码,我理解了我的错误: 要正确使用 sendEvent,您必须将它们传递给根视图(在我的项目中,它是 QQuickView,但也可以是 QWindow)。如果您不在根上发送事件,它不会按预期过滤 mouseEvent(通过 childMouseEventFilter() 方法)

我还发现 MouseRelease 事件需要一个 NoButton 参数。所以我的代码现在可以工作了,看起来像这样:

    //Set mouse timer
    mouseTimer = new QTimer(this);
    mouseTimer->setInterval(m_movementDuration / m_nbIteration);
    mouseTimer->setSingleShot(false);
    connect(mouseTimer, SIGNAL(timeout()), this, SLOT(mouseMove()));


void xx::pressAndDrag(QObject *object, const int x, const int y, const int dx, const int dy)

    m_pressAndDragCompleted = false;

    //Reset timer iteration
    m_timerIteration = 0;

    //Keep all coordinates
    startPoint = ((QQuickItem *) object)->mapToScene(QPointF(x, y));
    deltaPoint = new QPointF(dx, dy);

    QMouseEvent *e = new QMouseEvent(QEvent::MouseButtonPress, startPoint, Qt::LeftButton, Qt::LeftButton, Qt::NoModifier );
    qApp->postEvent(parent(), e);

    //Start timer
    mouseTimer->start();


void xx::mouseMove()

    m_timerIteration++;
    if (m_timerIteration < m_nbIteration + 2) 
        //Move mouse
        int x = startPoint.x() + deltaPoint->x() * m_timerIteration / m_nbIteration;
        int y = startPoint.y() + deltaPoint->y() * m_timerIteration / m_nbIteration;

        QMouseEvent *e = new QMouseEvent(QEvent::MouseMove, QPointF(x, y), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier );

        qApp->postEvent(parent(), e);
        // Let some time to not trigger a flick
     else if (m_timerIteration - m_nbIteration >= 400 / mouseTimer->interval()) 
        //End movement
        QPointF point = QPointF(startPoint.x() + deltaPoint->x(), startPoint.y() + deltaPoint->y());
        QMouseEvent *e = new QMouseEvent(QEvent::MouseButtonRelease, point, Qt::NoButton, Qt::NoButton, Qt::NoModifier );
        qApp->postEvent(parent(), e);

        //Stop timer
        mouseTimer->stop();
        m_pressAndDragCompleted = true;
    

如果有帮助的话。 :)

【讨论】:

以上是关于向父母发出传播模拟鼠标事件(拖动目的)的问题的主要内容,如果未能解决你的问题,请参考以下文章

如何模拟鼠标点击和拖动?

在 WPF 中向鼠标旋转图形(如模拟表盘)

c#全局鼠标事件以及鼠标事件模拟

在可拖动元素上模拟 3 像素拖动

易语言怎么webbrowser1中模拟鼠标单击?

鼠标单击并拖动事件 WPF