浅谈Qt事件子系统——以可拖动的通用Widget为例子

news2025/3/26 20:59:40

浅谈Qt事件子系统——以可拖动的通用Widget为例子

这一篇文章是一个通过实现可拖动的通用Widget为引子简单介绍一下我们的事件对象子系统的事情
代码和所有的文档

1:Qt侧的API介绍和说明

​ 这个是每一个小项目的惯例,我会介绍大部分Qt程序中使用到的细节,比如说,本项目当中就是eventFilter和事件处理队列的Qt编程技术。这个也是我们编程Qt的一个重点。

​ 本项目打算介绍的是——Qt的事件处理机制,以及对象事件监听机制。如果您很熟悉了,可以考虑直接跳过本篇。

所以,Qt的事件处理机制

​ 我喜欢写一个东西的时候,直接说明我要写什么。很简单。

  • Qt是如何实现事件处理的?技术的要点有哪些?
  • 我们作为开发人员,重点关心的接口有哪些?
  • 如何监听,甚至是拦截其他对象的事件处理呢【这个是本项目的实现要点】

Qt是如何实现事件处理的

​ 毫无疑问,事件驱动处理是GUI的一个命根子,我们的GUI接受事件,展示对应的变化;同时我们的用户跟GUI交互,将用户的意图传递给我们的后台。这就是GUI的一个最大的要点。

所以,我们关心事件驱动的对象有哪些呢?

​ 我们的事件队列的处理主要依赖一个重要的概念,叫“事件循环”(Event Loop)。事件循环是一个持续运行的循环,它不断检测、分发并处理各种事件,包括用户输入(如键盘、鼠标事件)、系统消息以及自定义事件。主要过程大致如下:

  1. 事件产生:当用户操作或系统状态变化时,Qt会生成一个对应的事件对象(QEvent的子类实例)。
  2. 事件队列:事件对象被放入事件队列中等待处理。
  3. 事件分发:事件循环依次从队列中取出事件,分发给相应的对象处理。
  4. 事件响应:目标对象在其事件处理函数中对事件作出响应,更新界面或执行其他逻辑。

​ 我们分析事件,也是主要抓手这四个部分进行学习。

​ 我们事件队列处理的开始,在QApplication::exec上,调用这个,我们的全应用程序的事件队列就开始工作了。下面,我们来看看一些API函数:

​ 对于框架层次,你需要知道这员工的一些函数:

QCoreApplication::notify

Qt的事件分发机制主要依赖于QCoreApplication类中的notify()方法。每当一个事件需要传递给某个QObject对象时,都会经过该方法。其主要职责是:

  1. 统一调度:集中管理所有事件的发送和转发。
  2. 异常处理:对事件处理过程中可能出现的异常进行捕获和处理,保证整个事件循环的稳定性。
  3. 事件过滤:在正式分发事件前,提供预处理的机会(见下文的事件过滤机制)。

​ 我们一般不会跑去重写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来定义自定义事件。常见步骤如下:

  1. 定义新的事件类型(通常选用Qt::User 类型及之后的值)。
  2. 创建自定义事件类,包含特定数据和处理逻辑。
  3. 通过postEvent()或sendEvent()将自定义事件投递到目标对象中。
  4. 在目标对象的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,事件则继续传递给目标对象进行正常处理。

这种机制使得开发人员可以在不侵入原对象逻辑的情况下,对事件进行预处理,甚至阻断事件传递。

安装和使用事件过滤器

要实现对其他对象事件的监听和拦截,主要步骤如下:

  1. 编写过滤器类
    通常通过继承QObject并重写eventFilter()方法,编写自定义过滤器类。在该方法中,根据watched参数判断当前捕获的事件属于哪个对象,并根据事件类型进行处理。
  2. 安装过滤器
    在需要监控的对象上调用installEventFilter()方法,将自定义过滤器对象注册到该对象上。一个对象可以安装多个过滤器,调用顺序与安装顺序有关。
  3. 事件拦截与传递控制
    在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 计算出一个偏移量 offsetXoffsetY,这是拖动过程中控件应该移动的距离。然后,根据当前控件的原始位置加上偏移量,计算出控件新的坐标 xy

但并非所有位置更新都是合理的,因此函数中还加入了一道逻辑判断,即如果当前设置了 widget_state.inParent 为真(意味着控件应保持在其父组件内),就需要判断新位置是否越界。这里调用了 sizeIsOutlier(QPoint(x, y), w)positionIsOutlier(QPoint(x, y)) 两个函数,前者大概是判断控件在给定位置上是否尺寸越界,后者则可能是判断位置是否超出允许的边界。这一检查使得控件不能被拖出其父容器或显示区域之外。如果这两个函数判定位置无效,则不执行移动操作,函数直接返回。

最后,如果所有条件都满足,程序调用 holding_widget->move(x, y) 将控件平滑地移动到新位置上。这一行为便是“拖动”体验的实现者,控件就随着鼠标游走而流畅移动。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2320505.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

QT Quick(C++)跨平台应用程序项目实战教程 2 — 环境搭建和项目创建

目录 引言 1. 安装Qt开发环境 1.1 下载Qt安装包 1.2 安装Qt 1.3 安装MSVC编译器 2. 创建Qt Quick项目 2.1 创建新项目 2.2 项目结构 2.3 运行项目 3. 理解项目代码 3.1 main.cpp文件 3.2 Main.qml文件 引言 在上一篇文章中&#xff0c;我们介绍了本教程的目标和结…

登山第二十梯:无人机实时自主探索——我是一只小小小鸟

文章目录 一 摘要 二 资源 三 内容 一 摘要 自主探索是无人机 &#xff08;UAV&#xff09; 各种应用的基本问题。最近&#xff0c;基于 LiDAR 的探索因其能够生成大规模环境的高精度点云地图而受到广泛关注。虽然点云本身就为导航提供了信息&#xff0c;但许多现有的勘探方…

word插入Mathtype公式居中和自动更新

word插入公式自动更新 前提&#xff1a;安装Mathtype 1.word中查看页的宽度 出现如下 2.设置样式 出现这个窗口 给样式随便起个名字 3.修改样式 3.1 设置两个制表位 第二个 3.2 修改公式字体 如下所示 4. 修改公式格式 4.1在word中打开 Mathtype 4.2 修改公式的格式 变成…

网络层之IP协议

在讨论传输层时, 我们都只讨论了发送方和接收方的问题, 而没有讨论中间的网络形态的问题. 也就是数据包如何从主机传送到主机的? 如图, 主机B发送数据到主机C, 发送报文需要进行路径选择, 主机B-> F-> G-> H-> C-> D -> 主机C 这条路径是如何被选择出来的?…

基于springboot的旅游网站(013)

摘 要 随着科学技术的飞速发展&#xff0c;各行各业都在努力与现代先进技术接轨&#xff0c;通过科技手段提高自身的优势&#xff0c;旅游网站当然也不能排除在外&#xff0c;随着旅游网站的不断成熟&#xff0c;它彻底改变了过去传统的旅游网站方式&#xff0c;不仅使旅游管理…

人工智能 - 在 Spring Boot 中调用 AnythingLLM+DeepSeek 的知识库获取消息接口

整体逻辑: 自建系统的web UI界面调用接口: 1.SpringBoot接口&#xff1a;/anything/chatMessageAnything 2.调用anythingLLM - 调用知识库deepseek r1 . 部署 AnythingLLM DeepSeek 本地知识库 的环境要求如下&#xff1a; 一、硬件要求 CPU 最低&#xff1a;4核&#x…

体育直播模板nba英超直播欧洲杯直播模板手机自适应

源码名称&#xff1a;体育直播模板nba英超直播欧洲杯直播模板手机自适应帝国cms 7.5模板 开发环境&#xff1a;帝国cms7.5 空间支持&#xff1a;phpmysql 带软件采集&#xff0c;可以挂着自动采集发布&#xff0c;无需人工操作&#xff01; 模板特点&#xff1a; 程序伪静态…

STM32-ARM

一、体系架构 ARM里有37个寄存器ALU算数逻辑单元PC程序计数器&#xff1a;指向哪里执行哪里SP栈指针寄存器LR链接寄存器&#xff1a;保存函数入口地址CPSR(current program status register)当前程序状态寄存器&#xff1a;SOSR(CPSR的备份)MMU(内存管理单元)Cache高速缓冲(iCa…

ripro 主题激活 问题写入授权Token失败,可能无文件写入权限

ripro 主题激活 问题 写入授权Token失败&#xff0c;可能无文件写入权限 找到主题下面的functions.php文件&#xff0c;给其他写入权限。就好了。

计算机网络——通信基础和传输介质

物理层任务&#xff1a;实现相邻节点之间比特&#xff08;0或1&#xff09;的传输 到了数据链路层之后&#xff0c;它会以帧为单位&#xff0c;把若干个比特交给物理层&#xff0c;物理层需要把这些比特信息转化成信号&#xff0c;在物理传输媒体上进行传输 通信基础基本概念 信…

python-selenium 爬虫 由易到难

本质 python第三方库 selenium 控制 浏览器驱动 浏览器驱动控制浏览器 推荐 edge 浏览器驱动&#xff08;不容易遇到版本或者兼容性的问题&#xff09; 驱动下载网址&#xff1a;链接: link 1、实战1 &#xff08;1&#xff09;安装 selenium 库 pip install selenium&#…

Execution failed for task ‘:path_provider_android:compileDebugJavaWithJavac‘.

What went wrong: Execution failed for task ‘:path_provider_android:compileDebugJavaWithJavac’. Could not resolve all files for configuration ‘:path_provider_android:androidJdkImage’. Failed to transform core-for-system-modules.jar to match attributes {…

T113-i开发板的休眠与RTC定时唤醒指南

​​在嵌入式系统设计中&#xff0c;休眠与唤醒技术是优化电源管理、延长设备续航的关键。飞凌嵌入式基于全志T113-i处理器开发设计的OK113i-S开发板提供了两种休眠模式&#xff1a;freeze和mem&#xff0c;以满足不同应用场景下的功耗与恢复速度需求。本文将详细介绍如何让OK1…

基于Spring Boot的公司资产网站的设计与实现(LW+源码+讲解)

专注于大学生项目实战开发,讲解,毕业答疑辅导&#xff0c;欢迎高校老师/同行前辈交流合作✌。 技术范围&#xff1a;SpringBoot、Vue、SSM、HLMT、小程序、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、安卓app、大数据、物联网、机器学习等设计与开发。 主要内容&#xff1a;…

基于CVX优化器的储能电池调峰调频算法matlab仿真

目录 1.课题概述 2.系统仿真结果 3.核心程序与模型 4.系统原理简介 4.1 原理概述 4.2 CVX工具箱概述 5.完整工程文件 1.课题概述 基于CVX优化器的储能电池调峰调频算法matlab仿真。CVX 是一种用于求解凸优化问题的强大工具。凸优化问题具有良好的数学性质&#xff0c;能…

SpringBoot3+Vue3开发学生成绩管理系统

系统介绍 此系统功能包含&#xff1a;首页、课程管理、成绩查询、成绩详情、班级管理、专业管理、用户管理等功能。用户管理又细分为账号管理、学生管理、教师管理、管理员管理。 基础功能包含&#xff1a;登录、退出、修改登录人信息、修改登录人密码。 分为4种角色&#x…

正则魔法:解码 return /^\d+$/.test(text) ? text : ‘0‘ 的秘密

&#x1f680; 正则魔法&#xff1a;解码 return /^\d$/.test(text) ? text : 0 的秘密 &#x1f31f; 嘿&#xff0c;技术探险家们&#xff01;&#x1f44b; 今天我们要破解一段看似简单的代码&#xff1a;return /^\d$/.test(text) ? text : 0。它藏在一个 Vue 前端组件中…

基于BClinux8部署Ceph 19.2(squid)集群

#作者&#xff1a;闫乾苓 文章目录 1.版本选择Ceph版本发布历史目前官方在维护的版本 2.部署方法3.服务器规划4.前置配置4.1系统更新4.2配置hosts cat >> /etc/hosts << EOFssh-keygenssh-copy-id ceph01ssh-copy-id ceph02ssh-copy-id ceph034.5 Python34.6 Syst…

CVPR2025 | 对抗样本智能安全方向论文汇总 | 持续更新中~

汇总结果来源&#xff1a;CVPR 2025 Accepted Papers 若文中出现的 论文链接 和 GitHub链接 点不开&#xff0c;则说明还未公布&#xff0c;在公布后笔者会及时添加. 若笔者未及时添加&#xff0c;欢迎读者告知. 文章根据题目关键词搜索&#xff0c;可能会有遗漏. 若笔者出现…

磁盘清理工具-TreeSize Free介绍

TreeSizeFree是一个磁盘空间管理工具&#xff0c;主要用于分析磁盘使用情况&#xff0c;帮助用户找到占用空间大的文件和文件夹: 特点&#xff1a;按大小排序&#xff1a;快速找到占用空间最大的文件或文件夹 一般可以删除: 扫描 C:\Users\XXX\AppData\Local\Temp 或 C:\Window…