马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?立即注册
×
浅谈Qt事故子体系——以可拖动的通用Widget为例子
这一篇文章是一个通过实现可拖动的通用Widget为引子简单先容一下我们的事故对象子体系的事变
代码和全部的文档
编辑1:非常感谢Higgins995提出的编译报错的Issue!编译堕落的朋侪可从前来查询办理方案!
1:Qt侧的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_cast<QMouseEvent *>(event);
- if (!mouseEvent) {
- return false;
- }
- // here we handle the mouse events
- // this will promise the future extensions
- switch (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 behavior
- return QObject::eventFilter(watched, event);
- }
复制代码 这是笔者的处置惩罚方式,依次对这个事故的MouseButtonPress,MouseButtonRelease和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 process
- if (accept_buttons.size() > 0 && !accept_buttons.contains(event->button()))
- return; // the button is not acceptable, reject process
- widget_state.lastPoint = event->pos(); // memorize the last point
- widget_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 process
- widget_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 process
- if (!widget_state.pressed)
- return; // the widget is not pressed, reject process
- // calculate the offset
- int offsetX = event->pos().x() - widget_state.lastPoint.x();
- int offsetY = event->pos().y() - widget_state.lastPoint.y();
- // calculate the new position
- int x = holding_widget->x() + offsetX;
- int y = holding_widget->y() + offsetY;
- // check if the widget should be in the parent
- if (widget_state.inParent) {
- QWidget *w = dynamic_cast<QWidget *>(holding_widget->parent());
- if (w && (sizeIsOutlier(QPoint(x, y), w) || positionIsOutlier(QPoint(x, y)))) {
- return;
- }
- // move the widget
- holding_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) 将控件平滑地移动到新位置上。这一举动便是“拖动”体验的实现者,控件就随着鼠标游走而流通移动。
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |