Qt中的触屏事件
1.启用触屏事件
当按下、松开或移动触摸设备(如触摸屏或触控板)上的一个或多个触摸点时,就会发生触摸事件。要接收触摸事件,部件必须设置 Qt::WA_AcceptTouchEvents 属性,图形项必须将 acceptTouchEvents 属性设置为 true。
使用基于 QAbstractScrollArea 的 widget 时,应在滚动区域的视口中启用 Qt::WA_AcceptTouchEvents 属性。
与 QMouseEvent 类似,Qt 会在 widget 首次按下时自动抓取每个触摸点,并且该 widget 将接收触摸点的所有更新,直到触摸点被释放。请注意,一个 widget 有可能接收多个触摸点的事件,而且多个 widget 可能同时接收触摸事件。
在需要捕捉触屏的控件中增加
setAttribute(Qt::WA_AcceptTouchEvents);
如果是图形项
acceptTouchEvents(true);
2.事件处理
所有触摸事件都属于 QEvent::TouchBegin, QEvent::TouchUpdate, QEvent::TouchEnd 或 QEvent::TouchCancel 类型。为部件重新实现 QWidget::event() 或 QAbstractScrollArea::viewportEvent() 以及为图形视图中的项目重新实现 QGraphicsItem::sceneEvent() 以接收触摸事件。
与窗口部件不同,QWindow 可始终接收触摸事件,无需选择是否接收。在直接使用 QWindow 时,只需重新实现 QWindow::touchEvent()即可。
QEvent::TouchUpdate 和 QEvent::TouchEnd 事件会发送到接受 QEvent::TouchBegin 事件的部件或项目。如果 QEvent::TouchBegin 事件未被接受,也未被事件过滤器过滤,那么在下一次 QEvent::TouchBegin 之前,不会再发送触摸事件。
某些系统可能会发送 QEvent::TouchCancel 类型的事件。收到该事件后,应用程序将被要求忽略整个活动触摸序列。例如,在合成系统中,合成器可能会决定将某些手势视为全系统手势。每当做出这样的决定(手势被识别)时,客户端就会收到 QEvent::TouchCancel 事件的通知,从而可以相应地更新自己的状态。
触屏事件类型:QEvent::TouchBegin, QEvent::TouchUpdate, QEvent::TouchEnd 或 QEvent::TouchCancel 类型。
要处理触屏事件,就要重写QWidget::event()
如果要控件可以处理触屏事件,就要重写控件,然后重写控件中的QWidget::event() 函数。
如果是QAbstractScrollArea的滑块类型要重写QAbstractScrollArea::viewportEvent() 这个函数。
QWindow 可始终接收触摸事件,无需选择是否接收。在直接使用 QWindow 时,只需重新实现 QWindow::touchEvent()即可
virtual bool event(QEvent *e) override
{
switch (e->type())
{
case QEvent::TouchBegin:
setTouchStyle();
break;
case QEvent::TouchUpdate:
setHoverStyle();
break;
case QEvent::TouchEnd:
setNormalStyle();
break;
default:
break;
}
return QPushButton::event(e);
}
touchPoints() 函数返回事件中包含的所有触摸点的列表。请注意,该列表可能是空的,例如在 QEvent::TouchCancel 事件中。可以使用 QTouchEvent::TouchPoint 类检索每个触摸点的信息。Qt::TouchPointState 枚举描述了触摸点可能具有的不同状态。
注意:touchPoints() 的列表永远不会是部分的: 触控事件将始终包含一个触控点,该触控点是针对发送事件的窗口或部件的每个现有物理触点。例如,假设所有触摸都针对同一个窗口或部件,那么条件为 touchPoints().count()==2 的事件保证意味着触摸触摸屏或触摸板的手指数正好是两个。
说白了就是touchPoints()函数可以获取到触控点的信息。每个触控点的信息都不一样,有自己独立的信息。
3.事件传递和传播
默认情况下,QGuiApplication 会将 QTouchEvent 中的第一个触摸点转换为 QMouseEvent。这使得在通常不处理 QTouchEvent 的现有部件上启用触摸事件成为可能。有关这样做时需要特别考虑的信息,请参阅下文。
QEvent::TouchBegin 是发送给 widget 的第一个触摸事件。QEvent::TouchBegin 事件包含一个特殊的接受标志,表示接收者是否需要该事件。默认情况下,事件被接受。如果您的 widget 未处理触摸事件,则应调用 ignore()。QEvent::TouchBegin 事件会沿着父窗口部件链向上传播,直到某个窗口部件使用 accept() 接受了该事件,或者某个事件过滤器消耗了该事件。对于 QGraphicsItems,QEvent::TouchBegin 事件会传播到鼠标下的项目(类似于 QGraphicsItems 的鼠标事件传播)。
4.触摸点分组
如上所述,多个部件有可能同时接收 QTouchEvents。但是,Qt 确保不会向同一个窗口部件发送重复的 QEvent::TouchBegin 事件,例如,如果用户触摸了 QGroupBox 中的两个不同窗口部件,而这两个窗口部件都忽略了 QEvent::TouchBegin 事件,那么在传播过程中理论上就会发生这种情况。
为了避免这种情况,Qt 将使用以下规则把新的触摸点分组:
当检测到第一个触摸点时,目的地部件首先由屏幕上的位置决定,其次由传播规则决定。
当检测到其他触摸点时,Qt 会首先查看新触摸点下的 widget 的任何祖先或后代是否有活动触摸点。如果有,新的触摸点将与第一个触摸点分组,新的触摸点将以单个 QTouchEvent 发送给处理第一个触摸点的 widget。(新触摸点下的部件不会收到事件)。
这样,同级部件就可以独立处理触摸事件,同时确保 QTouchEvents 的顺序始终正确。
5.鼠标事件和触摸事件合成
QTouchEvent 的传递独立于 QMouseEvent。应用程序标志 Qt::AA_SynthesizeTouchForUnhandledMouseEvents 和 Qt::AA_SynthesizeMouseForUnhandledTouchEvents 可用来启用或禁用触摸事件到鼠标事件和鼠标事件到触摸事件的自动合成。
6.注意事项
如上所述,启用触摸事件意味着多个部件可以同时接收触摸事件。结合默认的 QWidget::event() QTouchEvents 处理功能,这将为您设计触摸用户界面提供极大的灵活性。请注意其中的影响。例如,用户有可能用一只手指移动 QSlider,另一只手指按下 QPushButton。这些部件发出的信号将交错出现。
不支持使用 QTouchEvent 事件处理程序中的 exec() 方法(如 QDialog::exec() 或 QMenu::exec())递归到事件循环。由于存在多个事件接收者,递归可能会导致问题,包括但不限于事件丢失和意外的无限递归。
QTouchEvents 不受鼠标抓取或活动弹出窗口部件的影响。当打开弹出窗口或抓取鼠标时有多个活动触摸点,QTouchEvents 的行为是未定义的。
qt5版本中,触碰事件转为鼠标事件,当一根手指长按,另外一根手指再按下其他地方,依次松开后来手指和第一跟手指,会导致触碰事件转鼠标事件中的鼠标释放事件没法对应,手指开始按下的地方会保持按下状态。这个的解决方法只能是自己处理触碰事件,不依赖qt的自动转化。
bool event(QEvent *e) override
{
static int i=0;
switch (e->type()) {
case QEvent::TouchBegin:
{
auto a = static_cast<QTouchEvent*>(e);
auto pos = a->touchPoints().first().pos();
auto child = childAt(pos.toPoint());
if(child){
QPoint childPos = child->mapFrom(this, pos.toPoint());
// 创建鼠标按下事件
QMouseEvent pressEvent(
QEvent::MouseButtonPress,
childPos,
Qt::LeftButton,
Qt::LeftButton,
Qt::NoModifier
);
QCoreApplication::sendEvent(child, &pressEvent);
}
return true;
break;
}
case QEvent::TouchUpdate:{
//这里需要处理手指按下,滑动超出原先控件范围,还有多点触碰逻辑
}
case QEvent::TouchEnd:
//case QEvent::TouchCancel:
{
auto a = static_cast<QTouchEvent*>(e);
qDebug()<<"窗口触碰事件"<<i++<<e->type()<<a->touchPoints().size();
auto pos = a->touchPoints().first().pos();
auto child = childAt(pos.toPoint());
if(child){
QPoint childPos = child->mapFrom(this, pos.toPoint());
// 创建鼠标释放事件
QMouseEvent releaseEvent(
QEvent::MouseButtonRelease,
childPos,
Qt::LeftButton,
Qt::LeftButton,
Qt::NoModifier
);
// 发送事件给子部件
QCoreApplication::sendEvent(child, &releaseEvent);
}
return true;
break;
}
case QEvent::MouseButtonPress:
case QEvent::MouseButtonRelease:
case QEvent::MouseMove:{
qDebug()<<"窗口鼠标事件"<<i++<<e->type();
break;
}
case QEvent::Gesture:
{
auto gEvent = static_cast<QGestureEvent*>(e);
qDebug()<<"窗口手势事件"<<gEvent->gestures();
break;
}
}
return QWidget::event(e);
}
触屏的事件流转:
从子窗口到父窗口,一层一层的冒泡,只有开启了setAttribute(Qt::WA_AcceptTouchEvents);才会有touch事件,否则就是没有。touch的事件是一系列的事件,如果begin的事件不在event中接受,那么后面的QEvent::TouchUpdate, QEvent::TouchEnd 或 QEvent::TouchCancel 类型的事件就不会再次分发到这里,如果最后触屏事件都没有被接受,qt默认会处理,后面的都是触屏事件的更新都是由qt处理了,比如转化为鼠标事件等。如果想子窗口和父窗口同时收到触屏事件,这个是没有的,只能是子窗口接受之后,再把这个事件转发给父窗口。
本文详细介绍了在Qt中处理触屏事件的方法,包括如何启用触屏事件、事件类型与处理、事件传递和传播规则、触摸点分组以及鼠标事件与触屏事件的合成。还强调了处理多部件触屏事件时需要注意的事项。





