手机端网站需要多少钱,做株洲网站需要多少钱,海口网站制作案例,wordpress 如何修改浅谈Qt事件子系统——以可拖动的通用Widget为例子
这一篇文章是一个通过实现可拖动的通用Widget为引子简单介绍一下我们的事件对象子系统的事情 代码和所有的文档
1#xff1a;Qt侧的API介绍和说明
这个是每一个小项目的惯例#xff0c;我会介绍大部分Qt程序中使用到的…浅谈Qt事件子系统——以可拖动的通用Widget为例子
这一篇文章是一个通过实现可拖动的通用Widget为引子简单介绍一下我们的事件对象子系统的事情 代码和所有的文档
1Qt侧的API介绍和说明
这个是每一个小项目的惯例我会介绍大部分Qt程序中使用到的细节比如说本项目当中就是eventFilter和事件处理队列的Qt编程技术。这个也是我们编程Qt的一个重点。
本项目打算介绍的是——Qt的事件处理机制以及对象事件监听机制。如果您很熟悉了可以考虑直接跳过本篇。
所以Qt的事件处理机制
我喜欢写一个东西的时候直接说明我要写什么。很简单。
Qt是如何实现事件处理的技术的要点有哪些我们作为开发人员重点关心的接口有哪些如何监听甚至是拦截其他对象的事件处理呢【这个是本项目的实现要点】
Qt是如何实现事件处理的
毫无疑问事件驱动处理是GUI的一个命根子我们的GUI接受事件展示对应的变化同时我们的用户跟GUI交互将用户的意图传递给我们的后台。这就是GUI的一个最大的要点。
所以我们关心事件驱动的对象有哪些呢
我们的事件队列的处理主要依赖一个重要的概念叫“事件循环”Event Loop。事件循环是一个持续运行的循环它不断检测、分发并处理各种事件包括用户输入如键盘、鼠标事件、系统消息以及自定义事件。主要过程大致如下
事件产生当用户操作或系统状态变化时Qt会生成一个对应的事件对象QEvent的子类实例。事件队列事件对象被放入事件队列中等待处理。事件分发事件循环依次从队列中取出事件分发给相应的对象处理。事件响应目标对象在其事件处理函数中对事件作出响应更新界面或执行其他逻辑。
我们分析事件也是主要抓手这四个部分进行学习。
我们事件队列处理的开始在QApplication::exec上调用这个我们的全应用程序的事件队列就开始工作了。下面我们来看看一些API函数
对于框架层次你需要知道这员工的一些函数
QCoreApplication::notify
Qt的事件分发机制主要依赖于QCoreApplication类中的notify()方法。每当一个事件需要传递给某个QObject对象时都会经过该方法。其主要职责是
统一调度集中管理所有事件的发送和转发。异常处理对事件处理过程中可能出现的异常进行捕获和处理保证整个事件循环的稳定性。事件过滤在正式分发事件前提供预处理的机会见下文的事件过滤机制。
我们一般不会跑去重写notify至少笔者没见过特殊到要重写notify的
事件队列与异步处理
Qt支持将事件异步投递到目标对象中通过QCoreApplication::postEvent()方法将事件放入事件队列等待事件循环处理。这种方式使得事件发送和处理解耦避免在调用过程中产生阻塞提升了系统响应能力。
与之对应的同步事件发送方式为QCoreApplication::sendEvent()该方法直接调用目标对象的事件处理函数在调用者线程中立刻执行。这种方式适用于对时序和结果有严格要求的情况但需注意同步调用可能会引发递归调用或死锁问题。
事件循环Event Loop
每个Qt应用程序通常都有一个主事件循环通过调用QCoreApplication::exec()启动。事件循环在不断地检测、分发和处理事件的同时也会处理定时器、信号等异步任务。
阻塞与非阻塞事件循环既能阻塞等待事件也能在无事件时进入休眠状态保证资源利用率。嵌套事件循环在某些对话框或模态窗口中Qt会启动嵌套事件循环保证界面依然响应用户操作。需要注意的是嵌套循环可能会带来事件处理顺序和状态管理方面的复杂性。
讨论事件的类型
事件事件啥事件呢这就是事件的类型。Qt中的所有事件都以QEvent为基类其派生类涵盖了丰富的事件类型如
用户输入事件QMouseEvent、QKeyEvent、QWheelEvent等。窗口系统事件QResizeEvent、QCloseEvent等。自定义事件开发人员可以继承QEvent定义属于自己的事件类型实现特定业务逻辑的事件传递。
这些事件呢就在我们后面的开发接口上埋下了伏笔所以让我们马上进入第二个部分
开发人员关心的关键接口
我们的一个大头中的大头是QObject的一个重要的函数或者说QT元对象系统的一个重要的特化于事件处理的核心就是我们的一个虚函数event(QEvent *event),这是所有事件最终处理的入口函数。每个QObject子类都可以重写这个函数根据事件类型作出不同的响应。
我们需要注意的是——event()函数并不直接处理事件而是将这些事件对象按照它们不同的类型**分发给不同的事件处理器event handler。**重写一个event事件我们往往可能是要特化一部分操作。当然往往我们的功能是——需要在原先拥有事件处理的基础上进一步扩展通用事件处理的能力比如说要做薄记比如说统一的处理这个时候重写event就是一个很明智的选择了
例如在自定义控件中可以重写event()函数对特定事件如鼠标点击、键盘输入进行处理从而实现自定义行为。当然这只是一个例子实际上没人这样写我们会有专门的函数来处理这是我们下面会提到的议题
bool MyWidget::event(QEvent *event) {if (event-type() QEvent::MouseButtonPress) {my_process_of_mouseEvent(event);return true;}// 调用基类的事件处理保证其他事件正常分发return QWidget::event(event);
} 你需要注意的是——请看这里函数返回的是一个Bool值这个bool值的含义是什么呢答案是——当你返回了true的时候就说明你的事件已经处理结束Qt 将会检查这个函数的返回值如果是true说明这个事件已经被处理完成会转而取事件队列的下一个进行预取如果返回的是false那么会继续把这个事件传递给其他的组件让他们接着处理
专用事件处理函数
为了简化事件处理Qt为常见的事件提供了专用的虚函数举个例子看看
mousePressEvent(QMouseEvent *event)处理鼠标按下事件。keyPressEvent(QKeyEvent *event)处理键盘按下事件。resizeEvent(QResizeEvent *event)处理窗口尺寸变化事件。
这些函数通常在对应的控件类中重写目的是对特定事件进行精细控制。需要注意的是如果同时重写了event()函数和专用事件函数则通常应保证事件在其中一个函数中得到完整处理避免重复调用。这些在源码中的表先就是判断事件的Type然后依据事件的类型转发给对应的回调函数就是这样简单
事件发送接口
QCoreApplication::sendEvent()
同步事件发送接口sendEvent()直接调用目标对象的事件处理函数并返回处理结果。这种方式适用于需要立即获得事件处理结果的情况。但由于它是在当前线程中执行的因此要注意防止在事件处理过程中产生阻塞或递归调用。
QCoreApplication::postEvent()
异步事件投递接口postEvent()将事件放入目标对象所在线程的事件队列中由事件循环在合适的时机进行分发。常见的应用场景包括跨线程通信、延迟处理等。由于postEvent()并不会立即调用事件处理函数开发人员在设计逻辑时应考虑事件延时带来的影响。
自定义事件
在许多场景中内置的事件类型无法满足特定需求开发者可以通过继承QEvent来定义自定义事件。常见步骤如下
定义新的事件类型通常选用Qt::User 类型及之后的值。创建自定义事件类包含特定数据和处理逻辑。通过postEvent()或sendEvent()将自定义事件投递到目标对象中。在目标对象的event()函数中进行识别和处理。
这种方式提供了极大的扩展性使得复杂的应用逻辑可以通过事件机制进行模块化解耦。
事件过滤器
Qt还提供了事件过滤器机制使得开发人员可以在事件传递前拦截、监控或修改事件。关键接口是QObject的installEventFilter(QObject *filterObj)和eventFilter(QObject *watched, QEvent *event)函数。通过在某个对象上安装事件过滤器过滤器对象可以提前捕获并处理目标对象的事件。
例如在全局日志记录、调试或临时修改事件响应逻辑时事件过滤器是一种非常有效的手段。下列代码展示了如何为一个窗口安装事件过滤器
// 在构造函数中安装过滤器
myWidget-installEventFilter(this);// 重写eventFilter函数
bool MyClass::eventFilter(QObject *watched, QEvent *event) {if (watched myWidget event-type() QEvent::KeyPress) {// 对键盘事件进行特殊处理qDebug() 捕获到键盘事件;return true; // 返回true表示事件已经被处理不再传递}// 调用基类实现确保其他事件可以正常传递return QObject::eventFilter(watched, event);
}通过上述接口开发者可以在不改动原有对象代码的前提下实现对事件的监听和拦截。 如何监听和拦截其他对象的事件本次文档的重点
在实际开发中经常需要对已有控件或对象的事件进行监听、修改甚至拦截。Qt提供了非常方便的事件过滤机制使得这一需求得以高效实现。
事件过滤器的核心在于每个QObject对象都有一个内部列表用于存储安装到该对象上的过滤器。当事件到达目标对象前系统会先依次调用每个过滤器对象的eventFilter()方法。
如果某个过滤器返回true表示该事件已经被处理后续的过滤器和目标对象本身将不再接收到此事件。如果所有过滤器都返回false事件则继续传递给目标对象进行正常处理。
这种机制使得开发人员可以在不侵入原对象逻辑的情况下对事件进行预处理甚至阻断事件传递。
安装和使用事件过滤器
要实现对其他对象事件的监听和拦截主要步骤如下
编写过滤器类 通常通过继承QObject并重写eventFilter()方法编写自定义过滤器类。在该方法中根据watched参数判断当前捕获的事件属于哪个对象并根据事件类型进行处理。安装过滤器 在需要监控的对象上调用installEventFilter()方法将自定义过滤器对象注册到该对象上。一个对象可以安装多个过滤器调用顺序与安装顺序有关。事件拦截与传递控制 在eventFilter()中当检测到感兴趣的事件后可以选择返回true表示事件已处理不继续传递也可以返回false让目标对象继续处理。
例如假设我们需要拦截某个QLineEdit控件中的鼠标事件可以这样实现
class MyEventFilter : public QObject {Q_OBJECT
protected:bool eventFilter(QObject *watched, QEvent *event) override {if (watched-inherits(QLineEdit)) {if (event-type() QEvent::MouseButtonDblClick) {// 拦截双击事件qDebug() QLineEdit双击事件被拦截;return true; // 阻止事件继续传递}}// 其他情况继续传递事件return QObject::eventFilter(watched, event);}
};在程序初始化时为目标对象安装过滤器
QLineEdit *edit new QLineEdit(this);
MyEventFilter *filter new MyEventFilter();
edit-installEventFilter(filter);这样当用户对该QLineEdit进行双击操作时MyEventFilter将捕获并拦截该事件而QLineEdit本身不会收到双击事件。
动态监听与跨对象事件监控
有时我们不仅需要拦截单个对象的事件还需要在全局范围内对多个对象进行统一监控。例如在大型应用中调试或记录日志时可以为整个应用安装一个全局事件过滤器。通常的做法是将过滤器安装在QCoreApplication对象上这样所有事件都会先经过该过滤器的检测。
class GlobalEventFilter : public QObject {Q_OBJECT
protected:bool eventFilter(QObject *watched, QEvent *event) override {// 可以对所有对象和事件进行日志记录或特定处理qDebug() 全局过滤器捕获到事件: event-type() 来自对象: watched;// 根据需求选择是否拦截或继续传递return QObject::eventFilter(watched, event);}
};// 在main()函数中安装全局过滤器
int main(int argc, char *argv[]) {QApplication app(argc, argv);GlobalEventFilter *globalFilter new GlobalEventFilter();app.installEventFilter(globalFilter);// 后续创建的所有对象的事件均会经过globalFilter的检测// …return app.exec();
}这种全局过滤器的使用尤其适用于调试阶段对复杂交互过程中的事件进行全面记录和分析或在某些特殊情况下统一拦截某类事件。
注意事项与最佳实践
当然这里说一些重点的事情。在使用事件过滤器时还需要注意以下几点
性能问题全局事件过滤器会处理所有事件因此在实现中要避免执行过于耗时的操作防止影响界面响应。返回值控制返回true表示事件被完全拦截可能导致目标对象无法得到响应返回false则允许事件继续传递。开发人员需要仔细判断实际需求。层次关系如果一个对象安装了多个事件过滤器事件会按照安装顺序依次经过各过滤器过滤器之间可能存在相互影响因此在设计时要考虑好先后次序。安全释放当过滤器对象不再需要时必须及时调用removeEventFilter()方法或者在对象销毁时自动移除避免悬挂指针问题。
本项目的实现的重要文档思路
注意这个文档可能不会跟我们的源码有一定保证的同步只是提供一种参考
如何让Widgets跟随鼠标移动呢
一种办法是让我们创建一个SubWidget这个SubWidget负责一对一的维护一个目标控件。比如说一个按钮或者是任何一个其他的控件当我们的的目标事件传递到这个控件的时候会优先的投射到我们的这个widgets上来。通过调用控件的 installEventFilter() 方法将当前对象this作为过滤器安装到 holding_widget 上。安装事件过滤器后该控件产生的所有事件都会首先传递到当前对象的 eventFilter() 方法中进行预处理。如果在 eventFilter() 中返回了 true那么该事件就不会继续传递到控件自身的事件处理函数中如果返回 false则事件会继续传递。
这样我们就可以写自己的一个eventFilter来控制目标widget的行为。而不需要重载我们的对象添加一个Movable或者是其他任何的属性这样看就会非常的方便。
下面我们要做的就是准备处理我们的move行为
bool CCMovableWidget::eventFilter(QObject *watched, QEvent *event) {if (!holding_widget || watched ! holding_widget) {return false;}QMouseEvent *mouseEvent dynamic_castQMouseEvent *(event);if (!mouseEvent) {return false;}// here we handle the mouse events// this will promise the future extensionsswitch (event-type()) {case QEvent::MouseButtonPress:handling_mousePressEvent(mouseEvent);break;case QEvent::MouseButtonRelease:handling_mouseReleaseEvent(mouseEvent);break;case QEvent::MouseMove:handling_mouseMoveEvent(mouseEvent);break;default:break;}// back the default behaviorreturn QObject::eventFilter(watched, event);
} 这是笔者的处理方式依次对这个事件的MouseButtonPressMouseButtonRelease和MouseMove进行了传递。这也就意味着这里它的事件就传递进来了进行了处理当然处理结束后我们还希望让它做进一步的处理所以我们让他进一步维护其默认的实现。不要更改控件原来的行为。
剩下的内容
剩下的内容就没什么新鲜的了这里就让AI帮我代劳吧
// widget is pressed by the mouse, so this means we shell start our moving
void CCMovableWidget::handling_mousePressEvent(QMouseEvent *event) {qDebug() Mouse pressed;if (!holding_widget)return; // no widget to hold, reject processif (accept_buttons.size() 0 !accept_buttons.contains(event-button()))return; // the button is not acceptable, reject processwidget_state.lastPoint event-pos(); // memorize the last pointwidget_state.pressed true;
}handling_mousePressEvent(QMouseEvent *event) 是用户按下鼠标时触发的事件处理函数是整个拖动行为的起点。当鼠标点击到控件上时首先通过日志输出来表明事件已经被捕获。接着程序判断 holding_widget 是否存在如果为空则说明当前没有设置任何需要被移动的目标控件因此直接返回放弃此次操作。随后如果开发者为这个类设定了一个特定可接受的鼠标按钮列表 accept_buttons而当前触发事件的按钮不在该列表中也同样视为无效事件拒绝处理。只有当这些条件都满足后事件才被视为有效操作。此时程序记录当前鼠标点击的位置保存在 widget_state.lastPoint 中用于后续计算移动偏移量并将 widget_state.pressed 标志设为 true表明控件已被点击按住准备进行拖动。
void CCMovableWidget::handling_mouseReleaseEvent(QMouseEvent *event) {qDebug() Mouse released;if (!holding_widget)return; // no widget to hold, reject processwidget_state.pressed false;
}handling_mouseReleaseEvent(QMouseEvent *event) 则是用户释放鼠标按钮时调用的函数它的作用相对简单。同样以日志开始表示捕获了释放事件。随后依旧先检查是否存在 holding_widget如果当前并未绑定任何控件则此次释放事件无需处理。若控件存在则将 widget_state.pressed 设为 false这一行为本质上是标记当前已结束拖动操作后续的鼠标移动将不再引起控件的位置变化。
void CCMovableWidget::handling_mouseMoveEvent(QMouseEvent *event) {qDebug() Mouse moved;if (!holding_widget)return; // no widget to hold, reject processif (!widget_state.pressed)return; // the widget is not pressed, reject process// calculate the offsetint offsetX event-pos().x() - widget_state.lastPoint.x();int offsetY event-pos().y() - widget_state.lastPoint.y();// calculate the new positionint x holding_widget-x() offsetX;int y holding_widget-y() offsetY;// check if the widget should be in the parentif (widget_state.inParent) {QWidget *w dynamic_castQWidget *(holding_widget-parent());if (w (sizeIsOutlier(QPoint(x, y), w) || positionIsOutlier(QPoint(x, y)))) {return;}// move the widgetholding_widget-move(x, y);}
}handling_mouseMoveEvent(QMouseEvent *event) 是核心函数它在用户拖动鼠标时不断被调用从而持续地更新控件位置完成“随鼠标移动”的视觉效果。函数首先打印出“鼠标移动”的日志确认事件的发生。紧接着它做出两个防御性检查。第一是否存在 holding_widget否则自然不该响应移动。第二判断是否存在 widget_state.pressed 为真的状态这是防止控件在未被按住的情况下跟随鼠标移动确保只有在“鼠标按下后并且未释放”的情形下才进入后续逻辑。接下来程序通过当前位置与上次记录的鼠标按下点 lastPoint 计算出一个偏移量 offsetX 与 offsetY这是拖动过程中控件应该移动的距离。然后根据当前控件的原始位置加上偏移量计算出控件新的坐标 x 和 y。
但并非所有位置更新都是合理的因此函数中还加入了一道逻辑判断即如果当前设置了 widget_state.inParent 为真意味着控件应保持在其父组件内就需要判断新位置是否越界。这里调用了 sizeIsOutlier(QPoint(x, y), w) 与 positionIsOutlier(QPoint(x, y)) 两个函数前者大概是判断控件在给定位置上是否尺寸越界后者则可能是判断位置是否超出允许的边界。这一检查使得控件不能被拖出其父容器或显示区域之外。如果这两个函数判定位置无效则不执行移动操作函数直接返回。
最后如果所有条件都满足程序调用 holding_widget-move(x, y) 将控件平滑地移动到新位置上。这一行为便是“拖动”体验的实现者控件就随着鼠标游走而流畅移动。