You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

自定义触摸屏驱动引发QScrollArea移动抖动问题排查

问题描述

数年前为一款运行在Scientific Linux 6.4(基于CentOS 6)的应用编写了自定义触摸屏驱动,该系统无原生触摸支持,但内核支持触摸事件,因此直接从/dev/input/event*读取触摸屏原始数据,通过Qt生成鼠标事件模拟多点触摸控制应用。近期迁移至RedHat 8.4,因原生Qt触摸事件无法提供同等控制度,禁用了原生触摸驱动。

测试中发现,使用触摸屏操作应用内的图像显示区域(内置QImage、隐藏滚动条的QScrollArea)时,移动出现严重抖动。该区域通过mouseMoveEvent根据鼠标事件的delta值调整滚动条实现拖动,右键拖拽时功能正常。

相关代码如下:

void PanArea::mouseMoveEvent(QMouseEvent* e)
{

    SafeStr str;
    if (m_pw)
    {
        if (m_disable_panning == false &&
            e->buttons().testFlag(Qt::RightButton))
        {
            QPoint delta = e->pos() - m_last_pos;
            horizontalScrollBar()->setValue(horizontalScrollBar()->value() -
                                            delta.x());
            verticalScrollBar()->setValue(verticalScrollBar()->value() -
                                          delta.y());

            m_last_pos = e->pos();

            emit signalScrollPositionChanged(getScrollPosition());

            // This force update has been added because
            // when fast panning cause black to appear within the image
            // because some pan widget updates were being skipped.
            // Performance seems acceptable with this here.

            m_pw->update();
        }
        else if (...)
        {
            // irrelevant code removed to save space
        }
    }
}
void InputHandler::panStart(TouchPoint* tp, QWidget* widget)
{
    QPoint pos(tp->cx(), tp->cy());
    QWidget* target = widget;
    if (target == NULL) target = QApplication::widgetAt(pos);
    if (target != NULL)
    {
        QPoint local = target->mapFromGlobal(pos);
        QMouseEvent* press =
            new QMouseEvent(QEvent::MouseButtonPress, local, pos,
                            Qt::RightButton, Qt::RightButton, Qt::NoModifier);
        QApplication::postEvent(widget, press);
    }
}

void InputHandler::panMove(TouchPoint* tp, QWidget* widget)
{
    QPoint pos(tp->cx(), tp->cy());
    QWidget* target = widget;
    if (target == NULL) target = QApplication::widgetAt(pos);
    if (target != NULL)
    {
        QPoint local = target->mapFromGlobal(pos);
        QMouseEvent* move =
            new QMouseEvent(QEvent::MouseMove, local, pos, Qt::NoButton,
                            Qt::RightButton, Qt::NoModifier);
        QApplication::postEvent(widget, move);
    }
}
问题分析与解决方案

可能的原因及修复方案

1. 动态控件查找导致坐标映射错误

在panStart和panMove中,使用QApplication::widgetAt(pos)动态查找目标控件,当滚动区域移动后,widgetAt返回的控件可能不是预期的PanArea,导致局部坐标计算偏差,最终引发delta值跳变。

修复:直接使用传入的widget(即目标PanArea)进行坐标映射,避免动态查找:

void InputHandler::panStart(TouchPoint* tp, QWidget* widget)
{
    if (widget == NULL) return;
    QPoint pos(tp->cx(), tp->cy());
    QPoint local = widget->mapFromGlobal(pos);
    QMouseEvent* press =
        new QMouseEvent(QEvent::MouseButtonPress, local, pos,
                        Qt::RightButton, Qt::RightButton, Qt::NoModifier);
    QApplication::postEvent(widget, press);
}

void InputHandler::panMove(TouchPoint* tp, QWidget* widget)
{
    if (widget == NULL) return;
    QPoint pos(tp->cx(), tp->cy());
    QPoint local = widget->mapFromGlobal(pos);
    QMouseEvent* move =
        new QMouseEvent(QEvent::MouseMove, local, pos, Qt::NoButton,
                        Qt::RightButton, Qt::NoModifier);
    QApplication::postEvent(widget, move);
}

2. Qt5事件队列特性导致的事件延迟/乱序

Qt5与旧版Qt(如Scientific Linux 6.4默认的Qt4)的事件队列处理逻辑存在差异,postEvent将事件放入队列异步处理,可能导致事件延迟或顺序错乱,进而使坐标计算基于过时的控件状态。

修复:改用sendEvent同步发送事件,确保事件立即处理:

// panStart中替换postEvent
QApplication::sendEvent(widget, press);
// panMove中替换postEvent
QApplication::sendEvent(widget, move);

注意:sendEvent不需要手动删除事件对象,Qt会自动回收。

3. 局部坐标系随滚动变化导致delta计算错误

在mouseMoveEvent中,使用控件局部坐标计算delta,但调整滚动条后,控件的局部坐标系会发生变化,m_last_pos记录的是滚动前的坐标,下一次delta计算会出现偏差。

修复:改用全局坐标计算delta,全局坐标不受控件滚动影响:

// 在PanArea类中添加成员变量QPoint m_last_global_pos;

void PanArea::mousePressEvent(QMouseEvent* e)
{
    if (e->button() == Qt::RightButton)
    {
        m_last_global_pos = e->globalPos();
    }
    // 保留原有逻辑
}

void PanArea::mouseMoveEvent(QMouseEvent* e)
{
    SafeStr str;
    if (m_pw)
    {
        if (m_disable_panning == false &&
            e->buttons().testFlag(Qt::RightButton))
        {
            QPoint delta = e->globalPos() - m_last_global_pos;
            horizontalScrollBar()->setValue(horizontalScrollBar()->value() - delta.x());
            verticalScrollBar()->setValue(verticalScrollBar()->value() - delta.y());

            m_last_global_pos = e->globalPos();

            emit signalScrollPositionChanged(getScrollPosition());
            m_pw->update();
        }
        else if (...)
        {
            // 无关代码省略
        }
    }
}

4. 触摸屏原始数据噪声

RedHat 8.4下的触摸屏驱动可能输出更多原始数据噪声,导致生成的鼠标事件坐标跳变。

修复:在读取/dev/input/event*数据时添加滤波处理,比如:

  • 丢弃与上一次坐标偏差超过阈值的点;
  • 使用滑动平均算法平滑连续的坐标点;
  • 对原始触摸数据做去抖处理。

内容的提问来源于stack exchange,提问作者Darin Beaudreau

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.16 22:55:48