【技术】Qt对话框讲解

前言

Qt中对话框QDialog是几乎每个项目都会用到的GUI窗口对象。本文通过讲解QDialog及其子类在项目中经常被用到的功能点,帮助小伙伴们理解和掌握其使用方法。

QDialog

QDialog是Qt对话框类树中的基类,Qt为我们提供了很多QDialog子类,即标准对话框,实现了丰富的对话框功能。下面我们先看一下QDialog本身需要关注的一些问题。

模态对话框

一句话描述模态对话框:

除模态对话框外,用户无法选择并操作其他窗口,只有关闭了模态对话框,用户才能操作其他窗口。

下面的代码,用QDialog::exec函数实现了模态对话框(请结合Qt中QDialog的类帮助文档阅读下面的代码):

QDialog dlg;
dlg.exec(); // 执行模态对话框

等效于

QDialog dlg;
dlg.setModel(true); // 设置为模态
dlg.show(); // 显示对话框

原理上,exec函数是阻塞执行的,直到模态对话框关闭才返回,可以看出,exec内部有一个自己的消息循环,一直在循环处理消息。用代码模拟如下:

class QDialog
{
public:
	int exec()
    {
        show(); // 显示窗口
        m_event_loop.exec(); // 事件循环
        hide(); // 隐藏窗口
        return result(); // 返回退出码
    }
    
private:
	QEventLoop m_event_loop; // 事件循环对象
}

而调用show函数是不需要对话框关闭就会立即返回的,相当于setVisible(true),仅仅设置了允许窗口显示的标记而已。先调用setModel(true),再调用show()仍然是立即返回的,这种情况下,模态对话框使用的消息循环是main函数中app.exec中的启动的应用全局消息循环。原理上尝试理解即可,暂时不需要掌握,在后面的文章中会有对消息循环(QEventLoop)的专门讲解。

使用场景

exec阻塞执行方式,不会将本来可以在一个函数内实现的业务代码,分散到多个函数中去。

比如下面的例子,弹出对话框让用户输入名称,然后校验用户输入的用户名是否正确,在一个函数内即可完成此功能:

class Test 
{
public:
    void checkUsername()
    {
        QString name;
        MyDialog dlg; // MyDialog为我们自定义的QDialog子类

		// 用户点击取消按钮,直接返回
        if (dlg.exec() == QDialog::Rejected) 
        {
            return;
        }

        name = dlg.getName(); // 获取用户名
        if (name != "ABC") // 校验用户名
        {
            qDebug() << "Name Error!"; // 用户名校验错误
            return;
        }
        else
        {
            qDebug() << "Pass"; // 用户名校验正确
        }
    }
}

如果使用show会怎么样呢?使用show实现同样的功能,代码如下:

class Test : public QObject
{
    Q_OBJECT
public:
    void showNameInputDialog()
    {   
        // 下面额外监听对话框关闭信号
        connect(&m_dlg, SIGNAL(finished(int)), this, SLOT(slotDialogFinished(int)));
        m_dlg.setModel(true);
        m_dlg.show();
    }

    // 响应对话框关闭信号的槽
    void slotDialogFinished(int code)
    {
        if (code == QDialog::Rejected) // 用户点击取消按钮,直接返回
        {
            return;
        }

        name = m_dlg.getName(); // 获取用户名
        if (name != "ABC") // 校验用户名
        {
            qDebug() << "Name Error!";
            return;
        }
        else
        {
            qDebug() << "Pass"; // 用户名校验正确
        }
    }

private:
    // MyDialog为我们自定义的QDialog子类
    // 需要延长dlg的生命周期,把exec例子中的函数内变量,写成类的成员变量
    MyDialog m_dlg; 
}

可以看到,使用show来显示对话框,不仅要使用信号槽,还要添加成员变量、添加槽函数,非常麻烦,而且一个连续的业务流程代码被分散到了多个函数中。

exec()是开发中最常用的模块对话框调用方法。由上面的例子可知,模态对话框一般用来在某个处理过程中,请求用户输入内容,或配置必要的处理参数。而且,这样可以保持业务处理代码连续不分散。

非模态对话框

一句话描述非模态对话框:

非模态对话框不会独占用户鼠标和键盘输入,用户可以随时在其他窗口和非模态窗口之间切换操作。

非模态对话框一般用来实现悬浮窗口。非模态对话框会悬浮在父窗口上层,父窗口通过构造函数的parent参数设置。即使点击了父窗口,非模态对话框也会保持在父窗口上层,变化仅仅是失去焦点。

非模态对话框的典型例子是,使用文本编辑软件如notepad.exe,进行文本编辑时悬浮的文本搜索框,就是一种非模态对话框。

非模态对话框使用比较简单,定义好对话框变量后,直接调用成员函数show即可显示,这里不做过多讲解。

QDialog子类

说完了QDialog,下面开始介绍QDialog子类。

Qt标准对话框

根据使用频率大致对QDialog子类排序,见下表,读者可根据此顺序学习使用:

子类描述
QMessageBox信息对话框,最常用,用于显示提示信息、警告信息、询问信息、致命信息,并接受用户选择下一步操作。
QFileDialog文件对话框,常用,用于选择文件、文件夹。
QInputDialog输入对话框,用于获取字符串输入、数值输入、以及选择列表中的某一项。
QColorDialog、QFontDialog颜色和字体对话框。
QProgressDialog进度显示对话框。
QErrorMessage错误信息显示对话框,提供不再显示某条内容的消息的选项。
QWizard向导对话框,用于引导用户进行某个操作。

自定义QDialog子类

自定义QDialog子类是必备技能。很多情况下,标准对话框不满足使用要求,此时就需要我们自定义对话框实现响应的功能。

自定义对话框有以下几点需要说明。

1. done、accept、reject

这三个函数是自定义函数一定需要用到的函数,用于控制以什么样的状态码退出对话框。
done函数用来退出对话框并设置退出码。
对于模态对话框,done相当于:

void QDialog::done(int code)
{
    setResult(code); // 设置退出码
    
    if (isModel()) // 如果是模态对话框
    {
        m_event_loop.exit(code); // 退出消息循环
    }
    else
    {
        hide(); // 隐藏窗口
    }
}

done结束对话框内部消息循环,done函数退出,随后下一个消息循环中,exec函数中的消息循环返回,exec函数也退出,最后返回到调用对话框的代码处。

accept相当于

done(QDialog::Accepted);

reject相当于

done(QDialog::Rejected);

2. 不要用exec的返回值来传递内容信息

exec的返回值可以用来传递退出码。由上述内容可知,done、accept、reject可以用来退出对话框并设置退出码。

笔者在初次自定义对话框时,总想着将对话框中的输入内容,用exec返回。例如,将用户在对话框中选择的列表项索引,用done(index)来返回。这里明确告诉小伙伴们这样的用法是不提倡的。因为如果是字符串,那怎么返回呢?

正确的做法是,使用done返回用户对编辑的确认状态,即确认设置内容还是取消设置,确认内容用accept,取消设置用reject。这样一来,自定义的QDialog类需要提供对话框数据获取接口,当exec返回后,再使用对话框对象实例,调用数据获取接口获取用户编辑的数据。

举个简单的例子:

class MyDialog : public QDialog
{
public:
	explicit MyDialog(QWidget *parent = NULL);
	QString getName()
	{
		// 从界面输入框中获取用户输入内容
		return ui->lineEdit_name->text();
	}
	QString getPassword()
	{
		// 从界面输入框中获取用户输入内容
		return ui->lineEdit_password->text();
	}

private:
	// 点击确认按钮响应
	void slotOkButtonClicked()
	{
		accept();
	}
	
	// 点击取消按钮响应
	void slotCancelButtonClicked()
    {
    	reject();
    }
}

结语

本文代码略多,粗略地讲解了Qt中对话框的类型和理解对话框工作原理的关键问题。细节的部分需要读者自己去实践,文章中无法讲解得太过详细。讲解太过详细,第一,读起来费时费力,倒不如花时间自己写代码尝试,第二,学习容易出现问题的地方是较难理解的地方,细节不需要面面俱到 ,用到的时候再查就可以。学习Qt某个模块,要先把握住某一个功能模块的运作机理、框架,而不是一头钻进庞杂的内容里,没有方向地去学习,效率很低也很痛苦。

本文是Qt基础文章,笔者想尽快写完Qt的基础部分,除了基础还有很多东西需要掌握,不能一直在基础上转圈圈。小伙伴们如果有疑问,欢迎留言。如果问题很多,笔者会考虑建一个讨论群,尽自己能力帮助想要学习Qt的同学,大家一起进步。


本文首发于微信公众号“Qt未来工程师”。

posted @ 2022-05-22 11:22  撬动未来的支点  阅读(524)  评论(0编辑  收藏  举报