百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 热门文章 > 正文

Qt事件的接受与忽略 工程建设风险事件ⅰ级风险,较低,风险后果在一定条件下可以忽略

bigegpt 2024-10-12 05:26 11 浏览

上一章我们介绍了有关事件的相关内容。我们曾经提到,事件可以依情况接受和忽略。现在,我们就来了解下有关事件的更多的知识。

首先来看一段代码:

//!!! Qt5
// ---------- custombutton.h ---------- //
class CustomButton : public QPushButton
{
    Q_OBJECT
public:
    CustomButton(QWidget *parent = 0);
private:
    void onButtonCliecked();
};

// ---------- custombutton.cpp ---------- //
CustomButton::CustomButton(QWidget *parent) :
    QPushButton(parent)
{
    connect(this, &CustomButton::clicked,
            this, &CustomButton::onButtonCliecked);
}

void CustomButton::onButtonCliecked()
{
    qDebug() << "You clicked this!";
}

// ---------- main.cpp ---------- //
int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    CustomButton btn;
    btn.setText("This is a Button!");
    btn.show();

    return a.exec();
}

这是一段简单的代码,经过我们前面一段时间的学习,我们已经能够知道这段代码的运行结果:点击按钮,会在控制台打印出 “You clicked this!” 字符串。这是我们前面介绍过的内容。下面,我们向 CustomButton 类添加一个事件函数:

// CustomButton
...
protected:
    void mousePressEvent(QMouseEvent *event);
...

// ---------- custombutton.cpp ---------- //
...
void CustomButton::mousePressEvent(QMouseEvent *event)
{
    if (event->button() == Qt::LeftButton) {
        qDebug() << "left";
    } else {
        QPushButton::mousePressEvent(event);
    }
}
...

我们重写了 CustomButton 的 mousePressEvent() 函数,也就是鼠标按下。在这个函数中,我们判断如果鼠标按下的是左键,则打印出来 “left” 字符串,否则,调用父类的同名函数。编译运行这段代码,当我们点击按钮时,“You clicked this!” 字符串不再出现,只有一个 “left”。也就是说,我们把父类的实现覆盖掉了。由此可以看出,父类 QPushButton 的 mousePressEvent() 函数中肯定发出了 clicked() 信号,否则的话,我们的槽函数怎么会不执行了呢?

这暗示我们一个非常重要的细节:当重写事件回调函数时,时刻注意是否需要通过调用父类的同名函数来确保原有实现仍能进行!比如我们的 CustomButton 类,如果像我们这么覆盖函数,clicked() 信号永远不会发生,你连接到这个信号的槽函数也就永远不会被执行。这个错误非常隐蔽,很可能会浪费你很多时间才能找到。因为这个错误不会有任何提示。这一定程度上说,我们的组件 “忽略” 了父类的事件,但这更多的是一种违心之举,一种错误。

通过调用父类的同名函数,我们可以把 Qt 的事件传递看成链状:如果子类没有处理这个事件,就会继续向其父类传递。Qt 的事件对象有两个函数:accept() 和 ignore()。正如它们的名字一样,前者用来告诉 Qt,这个类的事件处理函数想要处理这个事件;后者则告诉 Qt,这个类的事件处理函数不想要处理这个事件。在事件处理函数中,可以使用 isAccepted() 来查询这个事件是不是已经被接收了。具体来说:如果一个事件处理函数调用了一个事件对象的 accept() 函数,这个事件就不会被继续传播给其父组件;如果它调用了事件的 ignore() 函数,Qt 会从其父组件中寻找另外的接受者。

事实上,我们很少会使用 accept() 和 ignore() 函数,而是像上面的示例一样,如果希望忽略事件(所谓忽略,是指自己不想要这个事件),只要调用父类的响应函数即可。记得我们曾经说过,Qt 中的事件都是 protected 的,因此,重写的函数必定存在着其父类中的响应函数,所以,这个方法是可行的。为什么要这么做,而不是自己去手动调用这两个函数呢?因为我们无法确认父类中的这个处理函数有没有额外的操作。如果我们在子类中直接忽略事件,Qt 会去寻找其他的接收者,该子类的父类的操作会被忽略(因为没有调用父类的同名函数),这可能会有潜在的危险。

为了避免自己去调用 accept() 和 ignore() 函数,而是尽量调用父类实现,Qt 做了特殊的设计:事件对象默认是 accept 的,而作为所有组件的父类 QWidget 的默认实现则是调用 ignore()。这么一来,如果你自己实现事件处理函数,不调用 QWidget 的默认实现,你就等于是接受了事件;如果你要忽略事件,只需调用 QWidget 的默认实现。这一点我们前面已经说明。下面可以从代码级别来理解这一点,我们可以查看一下 QWidget 的 mousePressEvent() 函数的实现:

【领QT开发教程学习资料,点击→「链接」←莬费领取,先码住不迷路~】

//!!! Qt5
void QWidget::mousePressEvent(QMouseEvent *event)
{
    event->ignore();
    if ((windowType() == Qt::Popup)) {
        event->accept();
        QWidget* w;
        while ((w = QApplication::activePopupWidget()) && w != this){
            w->close();
            if (QApplication::activePopupWidget() == w)
                w->hide(); // hide at least
        }
        if (!rect().contains(event->pos())){
            close();
        }
    }
}

这段代码在 Qt4 和 Qt5 中基本一致(区别在于 activePopupWidget() 一行,Qt4 的版本是 qApp->activePopupWidget())。注意函数的第一个语句:event->ignore(),如果子类都没有重写这个函数,Qt 会默认忽略这个事件,继续寻找下一个事件接收者。如果我们在子类的 mousePressEvent() 函数中直接调用了 accept() 或者 ignore(),而没有调用父类的同名函数,QWidget::mousePressEvent() 函数中关于 Popup 判断的那段代码就不会被执行,因此可能会出现默认其妙的怪异现象。

针对 accept() 和 ignore(),我们再来看一个例子:

class CustomButton : public QPushButton
{
    Q_OBJECT
public:
    CustomButton::CustomButton(QWidget *parent)
        : QPushButton(parent)
    {
    }
protected:
    void mousePressEvent(QMouseEvent *event)
    {
        qDebug() << "CustomButton";
    }
};

class CustomButtonEx : public CustomButton
{
    Q_OBJECT
public:
    CustomButtonEx::CustomButtonEx(QWidget *parent)
        : CustomButton(parent)
    {
    }
protected:
    void mousePressEvent(QMouseEvent *event)
    {
        qDebug() << "CustomButtonEx";
    }
};

class CustomWidget : public QWidget
{
    Q_OBJECT
public:
    CustomWidget::CustomWidget(QWidget *parent)
        : QWidget(parent)
    {
    }
protected:
    void mousePressEvent(QMouseEvent *event)
    {
        qDebug() << "CustomWidget";
    }
};

class MainWindow : public QMainWindow
{
    Q_OBJECT
public:
    MainWindow::MainWindow(QWidget *parent = 0)
        : QMainWindow(parent)
    {
        CustomWidget *widget = new CustomWidget(this);
        CustomButton *cbex = new CustomButton(widget);
        cbex->setText(tr("CustomButton"));
        CustomButtonEx *cb = new CustomButtonEx(widget);
        cb->setText(tr("CustomButtonEx"));
        QVBoxLayout *widgetLayout = new QVBoxLayout(widget);
        widgetLayout->addWidget(cbex);
        widgetLayout->addWidget(cb);
        this->setCentralWidget(widget);
    }
protected:
    void mousePressEvent(QMouseEvent *event)
    {
        qDebug() << "MainWindow";
    }
};

这段代码在一个 MainWindow 中添加了一个 CustomWidget,里面有两个按钮对象:CustomButton 和 CustomButtonEx。每一个类都重写了 mousePressEvent() 函数。运行程序点击 CustomButtonEx,结果是

CustomButtonEx

这是因为我们重写了鼠标按下的事件,但是并没有调用父类函数或者显式设置 accept() 或 ignore()。下面我们在 CustomButtonEx 的 mousePressEvent() 第一行增加一句 event->accept(),重新运行,发现结果不变。正如我们前面所说,QEvent 默认是 accept 的,调用这个函数并没有什么区别。然后我们将 CustomButtonEx 的 event->accept() 改成 event->ignore()。这次运行结果是

CustomButtonEx
CustomWidget

ignore() 说明我们想让事件继续传播,于是 CustomButtonEx 的父组件 CustomWidget 也收到了这个事件,所以输出了自己的结果。同理,CustomWidget 又没有调用父类函数或者显式设置 accept() 或 ignore(),所以事件传播就此打住。这里值得注意的是,CustomButtonEx 的事件传播给了父组件 CustomWidget,而不是它的父类 CustomButton。事件的传播是在组件层次上面的,而不是依靠类继承机制。

接下来我们继续测试,在 CustomWidget 的 mousePressEvent() 中增加 QWidget::mousePressEvent(event)。这次的输出是

CustomButtonEx
CustomWidget
MainWindow

如果你把 QWidget::mousePressEvent(event) 改成 event->ignore(),结果也是一样的。这正如我们前面说的,QWidget 的默认是调用 event->ignore()。

在一个特殊的情形下,我们必须使用 accept() 和 ignore() 函数,那就是窗口关闭的事件。对于窗口关闭 QCloseEvent 事件,调用 accept() 意味着 Qt 会停止事件的传播,窗口关闭;调用 ignore() 则意味着事件继续传播,即阻止窗口关闭。回到我们前面写的简单的文本编辑器。我们在构造函数中添加如下代码:

//!!! Qt5
...
textEdit = new QTextEdit(this);
setCentralWidget(textEdit);
connect(textEdit, &QTextEdit::textChanged, [=]() {
    this->setWindowModified(true);
});

setWindowTitle("TextPad [*]");
...

void MainWindow::closeEvent(QCloseEvent *event)
{
    if (isWindowModified()) {
        bool exit = QMessageBox::question(this,
                                      tr("Quit"),
                                      tr("Are you sure to quit this application?"),
                                      QMessageBox::Yes | QMessageBox::No,
                                      QMessageBox::No) == QMessageBox::Yes;
        if (exit) {
            event->accept();
        } else {
            event->ignore();
        }
    } else {
        event->accept();
    }
}

setWindowTitle() 函数可以使用 [ ] 这种语法来表明,在窗口内容发生改变时(通过 setWindowModified(true) 函数通知),Qt 会自动在标题上面的 [ ] 位置替换成 * 号。我们使用 Lambda 表达式连接 QTextEdit::textChanged() 信号,将 windowModified 设置为 true。然后我们需要重写 closeEvent() 函数。在这个函数中,我们首先判断是不是有过修改,如果有,则弹出询问框,问一下是否要退出。如果用户点击了 “Yes”,则接受关闭事件,这个事件所在的操作就是关闭窗口。因此,一旦接受事件,窗口就会被关闭;否则窗口继续保留。当然,如果窗口内容没有被修改,则直接接受事件,关闭窗口。

相关推荐

Go语言泛型-泛型约束与实践(go1.7泛型)

来源:械说在Go语言中,Go泛型-泛型约束与实践部分主要探讨如何定义和使用泛型约束(Constraints),以及如何在实际开发中利用泛型进行更灵活的编程。以下是详细内容:一、什么是泛型约束?**泛型...

golang总结(golang实战教程)

基础部分Go语言有哪些优势?1简单易学:语法简洁,减少了代码的冗余。高效并发:内置强大的goroutine和channel,使并发编程更加高效且易于管理。内存管理:拥有自动垃圾回收机制,减少内...

Go 官宣:新版 Protobuf API(go pro版本)

原文作者:JoeTsai,DamienNeil和HerbieOng原文链接:https://blog.golang.org/a-new-go-api-for-protocol-buffer...

Golang开发的一些注意事项(一)(golang入门项目)

1.channel关闭后读的问题当channel关闭之后再去读取它,虽然不会引发panic,但会直接得到零值,而且ok的值为false。packagemainimport"...

golang 托盘菜单应用及打开系统默认浏览器

之前看到一个应用,用go语言编写,说是某某程序的windows图形化客户端,体验一下发现只是一个托盘,然后托盘菜单的控制面板功能直接打开本地浏览器访问程序启动的webserver网页完成gui相关功...

golang标准库每日一库之 io/ioutil

一、核心函数概览函数作用描述替代方案(Go1.16+)ioutil.ReadFile(filename)一次性读取整个文件内容(返回[]byte)os.ReadFileioutil.WriteFi...

文件类型更改器——GoLang 中的 CLI 工具

我是如何为一项琐碎的工作任务创建一个简单的工具的,你也可以上周我开始玩GoLang,它是一种由Google制作的类C编译语言,非常轻量和快速,事实上它经常在Techempower的基准测...

Go (Golang) 中的 Channels 简介(golang channel长度和容量)

这篇文章重点介绍Channels(通道)在Go中的工作方式,以及如何在代码中使用它们。在Go中,Channels是一种编程结构,它允许我们在代码的不同部分之间移动数据,通常来自不同的goro...

Golang引入泛型:Go将Interface「」替换为“Any”

现在Go将拥有泛型:Go将Interface{}替换为“Any”,这是一个类型别名:typeany=interface{}这会引入了泛型作好准备,实际上,带有泛型的Go1.18Beta...

一文带你看懂Golang最新特性(golang2.0特性)

作者:腾讯PCG代码委员会经过十余年的迭代,Go语言逐渐成为云计算时代主流的编程语言。下到云计算基础设施,上到微服务,越来越多的流行产品使用Go语言编写。可见其影响力已经非常强大。一、Go语言发展历史...

Go 每日一库之 java 转 go 遇到 Apollo?让 agollo 来平滑迁移

以下文章来源于GoOfficialBlog,作者GoOfficialBlogIntroductionagollo是Apollo的Golang客户端Apollo(阿波罗)是携程框架部门研...

Golang使用grpc详解(golang gcc)

gRPC是Google开源的一种高性能、跨语言的远程过程调用(RPC)框架,它使用ProtocolBuffers作为序列化工具,支持多种编程语言,如C++,Java,Python,Go等。gR...

Etcd服务注册与发现封装实现--golang

服务注册register.gopackageregisterimport("fmt""time"etcd3"github.com/cor...

Golang:将日志以Json格式输出到Kafka

在上一篇文章中我实现了一个支持Debug、Info、Error等多个级别的日志库,并将日志写到了磁盘文件中,代码比较简单,适合练手。有兴趣的可以通过这个链接前往:https://github.com/...

如何从 PHP 过渡到 Golang?(php转golang)

我是PHP开发者,转Go两个月了吧,记录一下使用Golang怎么一步步开发新项目。本着有坑填坑,有错改错的宗旨,从零开始,开始学习。因为我司没有专门的Golang大牛,所以我也只能一步步自己去...