5.1 按钮类的控件

本节介绍图形程序里常见的按钮,包括普通的按钮按钮,之前都用过多次了,还有单选按钮、复选框、命令链接按钮等。本节安排两个简单例子,第一个例子是普通的按压按 钮和单选按钮使用示范,第二个例子主要是复选框和命令链接按钮使用示范。

5.1.1 按钮概述

我们在 Qt 设计师或 QtCreator 的设计界面编辑 ui 文件时,可以看到 Buttons 分类的按钮控件:
buttons
逐个解释一下各个用途:
(1)按压按钮 QPushButton
最基本的按钮,点击该按钮通常是通知程序进行一个操作,比如弹个窗、下一步、保存、退出等等,这是经常用到的,操作系统里的对话框里几乎全部都有这种按压按 钮,5.1.2 节详细讲解按压按钮。

(2)工具按钮 QToolButton
工具按钮通常都不是一个,是一排放置在工具栏 QToolBar 里面,作为快捷按钮来用,比如 Qt 设计师的工具栏:
QToolButton
工具按钮暂时不讲,等到后面讲带有菜单和工具栏的程序一块讲。

(3)单选按钮 QRadioButton
单选按钮通常是多个放在一块,表示互斥的一组选项,只能选择其中一个来填写,比如一个人性别有男、女,只能选择一个,而不能同时是男的又是女的,5.1.3 节详 细讲解单选按钮。

(4)复选框 QCheckBox
与单选按钮不同,复选框通常表示多个可以同时存在的选项,比如一个人可以同时多个爱好,比如读书、看电影、爬山、游泳等,5.1.4 节介绍复选框。

(5)命令链接按钮 QCommandLinkButton
这是 Qt 仿造 Vista 里的命令链接按钮引入的扁平风格按钮,点击这个按钮一般意味着打开新的功能窗口或网站链接等,5.1.5 节介绍这个命令链接按钮。

(6)标准按钮盒 QDialogButtonBox
标准按钮盒通常用于对话框程序,举例来说,我们常见的保存询问对话框里面有“保存”、“丢弃”、“取消”三个标准按钮,确认对话框有 “OK”“Cancel”等标准按钮,Qt 将这些典型的按钮做成标准按钮盒,并将相应的信号加以封装,方便程序员使用。这个标准按钮盒等到对话框相关章节再详细 介绍,这里只贴张图尝鲜看看:
QDialogButtonBox
概略浏览这些按钮控件之后,下面来进入按压按钮的学习。

5.1.2 QPushButton

按压按钮 QPushButton 我们之前用过多次了,点击之后弹出个消息框,打印调试信息等。QPushButton 常用的构造函数有两个:
QPushButton::​QPushButton(const QString & text, QWidget * parent = 0)
QPushButton::​QPushButton(const QIcon & icon, const QString & text, QWidget * parent = 0)
第一个构造函数有两个参数 text 是按钮显示的文本,第二个 parent 是父窗口指针。
第二个构造函数多了参数 icon,表示按钮图标,图标意味着有相关的图片资源,本章 5.6 节介绍使用自定义的图片作为按钮图标。

除了可以在构造函数里指定文本和图标,QPushButton 类还有对应的 get/set 函数,获取和设置文本的函数:
QString    text() const
void    setText(const QString & text)
获取和设置图标的函数:
QIcon    icon() const
void    setIcon(const QIcon & icon)
QPushButton 类另外有一种特殊用途,可以为按钮添加菜单,类似 Qt 设计师编辑样式表对话框里的:
button-menu
带菜单的按钮等到菜单相关章节再讲解,不能一口吃成胖子,本节我们先用简单的按钮。

按压按钮被点击之后会触发 clicked 信号:
void QAbstractButton::​clicked(bool checked = false)
QAbstractButton 是各种按钮类的抽象基类,QPushButton 从这个基类继承了点击按钮的信号 ​clicked。
按压按钮一般不会用到 checked 参数,因为按压按钮一般按下去就自动弹起来,没有两种状态。而下面的单选按钮和复选框默认都是有两种状态的,被选中和没被选中。下面来看看单选按钮和本节第一个例 子。

5.1.3 QRadioButton

单选按钮通常用于从一组选项里面选择一个,各个选项之间是互斥的,因此名字叫“单选按钮”。单选按钮的构造函数是比较简单的:
QRadioButton::​QRadioButton(const QString & text, QWidget * parent = 0)
text 是显示的文本,parent 是父窗口。文本操作也有类似的 text() 和 setText() 函数,不赘述了。

单选按钮有选中和非选中两种状态,获取当前状态通过函数:
bool isChecked() const
如果希望通过代码来改变单选按钮的状态,可以用如下函数:
void setChecked(bool)

在一个窗体里面,默认所有的单选按钮都是互斥的,然而我们单选按钮可能有多种用途,比如两个单选按钮表示性别选择,四个单选按钮表示年龄段选择。不同用途的单选按 钮需要进行分组管理,常见的手段有两种,一种是在图形界面放置一个
分组框 Group Box,将同组的几个单选按钮塞到分组框里面;另一种是手动编写代码,将单选按钮添加到虚拟的管理组 QButtonGroup 实例里面,后面我们会在例子里给出两种用法的代码。

因为单选按钮的工作原理与按压按钮不一样,多个按压按钮之间通常互不影响,但是同组的单选按钮,一旦点击一个,其他的都是非选中状态。因此单选按钮通常使用 toggled() 信号,无论单选按钮是变成选中状态(该按钮被点击)或非选中状态(同组其他按钮被点击),只要单选按钮的状态发生变化,这个信号就会触发:
void    toggled(bool checked)
参数里 checked 表示新的选中或非选中状态,当然,还可以利用公开函数 isChecked() 获取是否处于选中状态。

对于使用 QButtonGroup 实例管理的多个单选按钮,还有一个好处是可以进行数值映射,不需要给每一个单选按钮都配备槽函数,而只需要根据 QButtonGroup 实例发出的 buttonClicked() 信号来统一处理整组的单选按钮,向 QButtonGroup 实例添加按钮的函数为:
void QButtonGroup::​addButton(QAbstractButton * button, int id = -1)
可以为每个单选按钮设置唯一的 id (不要用负数,-1 是不设置序号的意思),然后当 QButtonGroup 分组里的任意一个单选按钮被点击时,都会触发信号:
void QButtonGroup::​buttonClicked(int id)
根据参数里的 id 就可以判断是哪个单选按钮被点击了。

下面来动手编写本节第一个例子,打开 QtCreator,新建一个 Qt Widgets Application 项目,在新建项目的向导里填写:
①项目名称 singleselection,创建路径 D:\QtProjects\ch05,点击下一步;
②套件选择里面选择全部套件,点击下一步;
③基类选择 QWidget,点击下一步;
④项目管理不修改,点击完成。
建好项目之后,打开窗体 widget.ui 文件,进入设计模式,按照下图排布先拖入两个标签控件和五个单选按钮:
radios
按照图上显示的文本修改控件 text 属性,并修改单选按钮的 objectName :
第一排单选按钮对象名称分别为 radioButtonMan 和 radioButtonWoman;
第二排单选按钮对象名称分别为 radioButtonBang、radioButtonMeng 和 radioButtonYao。
因为控件比较多,注意控件水平方向和垂直方向的对齐,可以手动拖拽控件或者选中控件然后按方向键调整各个控件位置,尽量像上图一样对齐。
如果选择控件,然后按键盘上的方向键,那么默认以 10 像素移动,对齐到 整十 的点阵格子里面,所以控件是比较好对齐的。正式的程序里面要么手动调整控件位置 对齐,或者用下一章的布局控件。我们这里先手动对齐。
这两组单选按钮将在代码里通过虚拟按钮组 QButtonGroup 来管理。

然后再向窗体里面拖入一个 Group Box 控件和一个按压按钮,调整它们的位置和大小,按压按钮放在主窗体最下方,按压按钮文本显示为“弹窗显示”,中间的大部分区域都归分组框。
这个分组框会在窗体里显示出来,是实际能看到的控件,而虚拟按钮组 QButtonGroup 只存在于内存,是没有界面的。
拖入分组框之后,再向分组框内部拖入四个单选按钮,如下图所示:
radios2
分组框的文本不是通过 text 属性,而是 title 属性来设置分组框的标题文本,按上图将分组框标题文本设置为“年龄段”,将分组框位置和大小调整一下,方便放置内部的单选按钮。分组框除了标题文本,它自动绘制自己的边框 线,就是上图分组框的四个边界线。
分组框内部的四个单选按钮文本按上图设置,四个单选按钮 objectName 分别为 radioButton0to19、radioButton20to39、radioButton40to59 和 radioButton60to 。尽量让四个单选按钮水平和垂直方向都对齐,这样界面看起来整洁一些。

然后就可以通过 QtCreator 菜单“工具”-->“Form Editor”-->“预览...”,或者按快捷键 Alt+Shift+R 预览窗体效果,预览时点击各个单选按钮,可以发现头两排按钮只能选中一个,下面分组框里可以选中一个:
radios3
头两排单选按钮父窗口都是主窗体,默认情况下是互斥的,所以只能选中一个。而分组框里的单选按钮,它们的父窗口是分组框控件,所以与上面两排的不冲突。下面我们开 始编写代码,调整上面两排单选按钮的分组。
保存修改后的界面文件,回到代码编辑模式,打开 widget.h 文件,添加虚拟分组指针和两个槽函数:
#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>
#include <QButtonGroup> //按钮分组类头文件

namespace Ui {
class Widget;
}

class Widget : public QWidget
{
    Q_OBJECT

public:
    explicit Widget(QWidget *parent = 0);
    ~Widget();

public slots:
    void RecvGenderID(int id); //接收性别单选按钮分组信号
    void RecvStatusID(int id); //接收状态单选按钮分组信号

private:
    Ui::Widget *ui;
    //按钮分组
    QButtonGroup *m_pGenderGroup;   //性别单选按钮分组
    QButtonGroup *m_pStatusGroup;   //状态单选按钮分组
};

#endif // WIDGET_H
头文件编辑好之后,下面编辑 widget.cpp 文件,添加两个虚拟分组的代码,并关联它们对应的槽函数:
#include "widget.h"
#include "ui_widget.h"
#include <QDebug>               //调试输出类
#include <QMessageBox> //消息框类

Widget::Widget(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::Widget)
{
    ui->setupUi(this);

    //新建性别虚拟分组,并添加按钮和id
    m_pGenderGroup = new QButtonGroup(this);
    m_pGenderGroup->addButton(ui->radioButtonMan, 0);   //男
    m_pGenderGroup->addButton(ui->radioButtonWoman, 1); //女

    //新建状态虚拟分组,并添加按钮和id
    m_pStatusGroup = new QButtonGroup(this);
    //不同分组的 id 是无关的,不冲突
    m_pStatusGroup->addButton(ui->radioButtonBang, 0);  //棒棒哒
    m_pStatusGroup->addButton(ui->radioButtonMeng, 1);  //萌萌哒
    m_pStatusGroup->addButton(ui->radioButtonYao, 2);   //该吃药了

    //关联两个分组的信号和槽
    connect(m_pGenderGroup, SIGNAL(buttonClicked(int)), this, SLOT(RecvGenderID(int)));
    connect(m_pStatusGroup, SIGNAL(buttonClicked(int)), this, SLOT(RecvStatusID(int)));
}

Widget::~Widget()
{
    delete ui;
}

//接收性别分组的id
void Widget::RecvGenderID(int id)
{
    switch (id) {
    case 0:
        qDebug()<<tr("性别:男");
        break;
    case 1:
        qDebug()<<tr("性别:女");
        break;
    default:
        break;
    }
}
//接收状态分组的id
void Widget::RecvStatusID(int id)
{
    switch (id) {
    case 0:
        qDebug()<<tr("状态:棒棒哒");
        break;
    case 1:
        qDebug()<<tr("状态:萌萌哒");
        break;
    case 2:
        qDebug()<<tr("状态:该吃药了");
        break;
    default:
        break;
    }
}
在构造函数里,首先新建了 m_pGenderGroup 分组,然后将 ui->radioButtonMan 作为序号 0 添加到该分组,将ui->radioButtonWoman 作为序号 1 添加到该分组。
接下来新建了第二个 m_pStatusGroup 分组,将 ui->radioButtonBang 作为序号 0 添加到该分组,不同分组之间的 id 没有关系,所以这里序号也是以 0 打头,然后将 ui->radioButtonMeng 作为序号 1 添加到该分组,将 ui->radioButtonYao 作为序号 2 添加到该分组。

两个分组建立之后,将各组的 buttonClicked(int) 信号分别关联到对应的 RecvGenderID(int) 槽函数和 RecvStatusID(int) 槽函数。

这两个槽函数的内容就是打印调试输出,RecvGenderID()函数内部就是根据参数 id 打印相应的性别信息,注意 switch-case 代码里,每一个 case 末尾都要加 break 跳出,否则后面的 case 会继续执行。RecvStatusID() 也是根据参数里的 id,打印状态信息。这样例子的前半部分代码就完成了。这时候可以生成运行例子试试看,先不截图了,下面进行分组框里面单选按钮部分的编程。

再打开 widget.ui 界面文件,进入设计模式,我们从图形界面为分组框里的单选按钮添加槽函数,右击分组框里的单选按钮,右键菜单里选择“转到槽...”:
slots
选择信号 toggled(bool) ,点击下面的 OK 按钮,然后就为分组框里面第一个按钮生成了槽函数
void on_radioButton0to19_toggled(bool checked);
为剩下三个单选按钮都如法炮制,添加 信号 toggled(bool) 对应的槽函数。

最后再为下面的 “弹窗显示” 按压按钮添加 clicked() 信号对应的槽函数,按压按钮的槽函数等会留着用。
如上添加四个单选按钮对应的槽函数和按压按钮的槽函数之后,现在完整的 widget.h 头文件内容如下:
#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>
#include <QButtonGroup> //按钮分组类头文件

namespace Ui {
class Widget;
}

class Widget : public QWidget
{
    Q_OBJECT

public:
    explicit Widget(QWidget *parent = 0);
    ~Widget();

public slots:
    void RecvGenderID(int id); //接收性别单选按钮分组信号
    void RecvStatusID(int id); //接收状态单选按钮分组信号

private slots:
    void on_radioButton0to19_toggled(bool checked);

    void on_radioButton20to39_toggled(bool checked);

    void on_radioButton40to59_toggled(bool checked);

    void on_radioButton60to_toggled(bool checked);

    void on_pushButton_clicked();

private:
    Ui::Widget *ui;
    //按钮分组
    QButtonGroup *m_pGenderGroup;   //性别单选按钮分组
    QButtonGroup *m_pStatusGroup;   //状态单选按钮分组
};

#endif // WIDGET_H
头文件现在内容完整了,我们先编辑 widget.cpp 里面四个年龄段单选按钮对应的槽函数,根据参数里 bool 变量打印不同信息:
//年龄段 0-19
void Widget::on_radioButton0to19_toggled(bool checked)
{
    if(checked)
    {
        qDebug()<<tr("年龄段:0-19");
    }
    else
    {
        qDebug()<<tr("不是 0-19");
    }
}
//年龄段 20-39
void Widget::on_radioButton20to39_toggled(bool checked)
{
    if(checked)
    {
        qDebug()<<tr("年龄段:20-39");
    }
    else
    {
        qDebug()<<tr("不是 20-39");
    }
}
//年龄段 40-59
void Widget::on_radioButton40to59_toggled(bool checked)
{
    if(checked)
    {
        qDebug()<<tr("年龄段:40-59");
    }
    else
    {
        qDebug()<<tr("不是 40-59");
    }
}
//年龄段 60 以上
void Widget::on_radioButton60to_toggled(bool checked)
{
    if(checked)
    {
        qDebug()<<tr("年龄段:60 以上");
    }
    else
    {
        qDebug()<<tr("不是 60 以上");
    }
}
注意,每次点击分组框里一个新的单选按钮,那么旧的按钮从选中变为非选中状态,会发出 toggled() 信号,参数值是 false;而新的单选按钮会从非选中状态变成选中状态,也会发出 toggled() 信号,参数值是 true。其他的两个状态不变,一直是非选中状态的单选按钮不会发信号,这点需要留心,等会运行会看到实际效果。

关于单选按钮配套的槽函数都编辑好了,从单选按钮对应的槽函数参数里,可以知道单选按钮的新状态。但是如果在其他函数里,如何获知各个单选按钮的状态呢?我们从按 压按钮的槽函数代码里,获取单选按钮的状态,并弹窗显示信息:
//在单选按钮信号对应的槽函数之外,来获取单选按钮的状态
void Widget::on_pushButton_clicked()
{
    //结果字符串
    QString strResult;

    //性别分组
    int nGenderID = m_pGenderGroup->checkedId();    //获取被选中的 id
    switch (nGenderID) {
    case 0:
        strResult += tr("性别:男\r\n");
        break;
    case 1:
        strResult += tr("性别:女\r\n");
        break;
    default:
        strResult += tr("性别:未选中\r\n");
        break;
    }

    //状态分组
    int nStatusID = m_pStatusGroup->checkedId();    //获取被选中的 id
    switch (nStatusID) {
    case 0:
        strResult += tr("状态:棒棒哒\r\n");
        break;
    case 1:
        strResult += tr("状态:萌萌哒\r\n");
        break;
    case 2:
        strResult += tr("状态:该吃药了\r\n");
        break;
    default:
        strResult += tr("状态:未选中\r\n");
        break;
    }

    //年龄段四个按钮判断
    if( ui->radioButton0to19->isChecked() )
    {
        strResult += tr("年龄段:0-19\r\n");
    }
    else if( ui->radioButton20to39->isChecked() )
    {
        strResult += tr("年龄段:20-39\r\n");
    }
    else if( ui->radioButton40to59->isChecked() )
    {
        strResult += tr("年龄段:40-59\r\n");
    }
    else if( ui->radioButton60to->isChecked() )
    {
        strResult += tr("年龄段:60 以上\r\n");
    }
    else
    {
        strResult += tr("年龄段:未选中\r\n");
    }

    //strResult 获取信息完毕,弹窗显示
    QMessageBox::information(this, tr("综合信息"), strResult);
}
对于虚拟分组里的按钮,可以用类似 m_pGenderGroup->checkedId() 获取处于选中状态的单选按钮序号,然后根据序号来确定不同的信息显示。QString 类的 += 运算符就是字符串追加的意思,字符串里结尾的 "\r\n" 是回车换行两个字符,用于字符串分行显示。
对于状态分组的单选按钮也是类似的处理方法,根据组内序号为 strResult 追加字符串。
而年龄段的四个单选按钮,没有虚拟分组,仅仅是作为分组框的子控件存在,默认互斥。要判断它们的选中状态就得一个个地调用它们的 isChecked() 函数判断是否处于选中状态,然后根据选中状态追加相应的字符串到结果 strResult 里面。

所有单选按钮代表的信息都处理之后,通过 QMessageBox::information() 函数弹窗显示综合信息。

下面我们来生成并运行这个例子,首先一个单选按钮都不点击,直接点最下面的“弹窗显示”按钮:
run1
代码里面没有设置三个组选中的初始按钮,所有单选按钮默认是非选中状态,所以弹窗显示都是未选中。
点击消息框的 “OK” 按钮关闭消息框。
然后我们分别点击三个组的第一个单选按钮,然后点击下方的“弹窗显示”,运行结果变为:
run2
头两个组的信号触发比较好理解,来看第三个组,因为只有 ui->radioButton0to19 状态发生变化,从未选中变成选中,所以只有一个信号。
点击消息框的 “OK” 按钮关闭消息框。
然后我们依次点击三个组的第二个单选按钮,然后再点击下方的“弹窗显示”按钮,结果如下:
run3
头两组的信号还是比较清楚,都各自打印一个。
而第三组的单选按钮,发生状态变化的是两个按钮,ui->radioButton0to19 从选中变为非选中,发一个信号打印一次;
ui->radioButton20to39 从未选中变成选中状态,也发一个信号,因此又打印了一条字符串。
以后在点击第三组的其他单选按钮,过程是类似的,都是状态变化的两个单选按钮发信号。

这样本例子示范就完成了。例子头两组通过 QButtonGroup 虚拟按钮组管理,这样一个分组只需要一个槽函数就搞定了,按钮加入虚拟组时分配 id,接收信号时就可以根据 id 来判断是哪个单选按钮被点击了。注意分配的序号至少要大于等于 0,不要用 -1 作为序号,-1 是不设置序号的意思。

例子第三组是通过将四个单选按钮塞到 QGroupBox 控件里面,四个单选按钮自动成为分组框子控件,这样就与其他分组的单选按钮无关了。QGroupBox 控件里的四个单选按钮没有设置虚拟组,所以需要通过各自的槽函数来处理,四个单选按钮配了四个槽函数,函数变多了,看起来复杂些。

一般情况下,建议使用 QButtonGroup 虚拟按钮组管理成组的单选按钮。当然,分组框与虚拟管理组是不冲突的,分组框里的按钮也可以添加到新的虚拟分组里面进行管理。分组框本身是控件,它有标题栏,可以 用于显示该组按钮的主题信息。

现在可能还剩下一个问题,如果我们希望三个组的单选按钮,在启动时都默认选中各自第一个按钮,那怎么办呢?
可以通过单选按钮的  void    setChecked(bool)  函数设置单选按钮的状态,将下面三句代码放置在主窗体的构造函数末尾即可:
    //初始化选中状态
    ui->radioButtonMan->setChecked(true);
    ui->radioButtonBang->setChecked(true);
    ui->radioButton0to19->setChecked(true);

5.1.4 QCheckBox

复选框的选项就是多选题,而之前单选框是单选题。这两种按钮应用都很广泛,遇到可以选择多个选项时,复选框就派上用场了。多个复选框之间是不冲突了,它们的信号是 无关的,各自顾各自的。
复选框构造函数如下:
QCheckBox::​QCheckBox(const QString & text, QWidget * parent = 0)
text 是显示文本,parent 是父窗口指针。文本获取和设置也是使用 text() 和 setText() 函数。

复选框也是有选中和非选中两种状态,通过如下函数获取当前状态:
bool    isChecked() const
通过代码设置复选框的状态, 使用如下函数:
void setChecked(bool)
复选框状态变化时,会发出信号:
void    toggled(bool checked)
信号里的参数 checked 是复选框新的状态值。

对于复选框,不要用虚拟组 QButtonGroup 管理多个复选框,因为虚拟组 QButtonGroup 里面按钮默认全是互斥的,多个复选框添加到虚拟组里,那么默认只有一个按钮能被选中,就强行变成单选按钮了。

可以从界面拖动分组框 QGroupBox 包裹多个复选框,分组框其实就是一个带标题的框,它不会改变复选框的特性,也不会改变其他按钮的特性,所以可以放心用 分组框。

复选框还有一种特殊用途,叫三态复选框,比如 WORD 办公软件,一段文字既可以是加粗、部分加粗、都不加粗三种状态,三态复选框就是用来干这类事情的。
将传统两种状态复选框变成三态复选框的函数是:
void setTristate(bool y = true)
如果要获知当前复选框是不是三态的,通过函数:
bool isTristate() const
三态复选框的状态枚举 enum Qt::​CheckState 有三个枚举常量:

枚举常量 数值 描述
Qt::Unchecked 0 条目未选中
Qt::PartiallyChecked 1 条目有部分选中
Qt::Checked 2 条目全选中

获知当前三态复选框的状态使用函数:
Qt::CheckState QCheckBox::​checkState() const
如果要通过代码改变三态复选框的状态,那使用函数:
void QCheckBox::​setCheckState(Qt::CheckState state)
三态复选框状态变化时触发信号:
void QCheckBox::​stateChanged(int state)
将 state 值与三种状态的枚举常量比较一下,就可以获知不同的状态了。
点击三态复选框,复选框就会在三种状态之间轮流切换,这三种状态的显示效果如下图:
TriState
复选框的例子放到本节最后编写,下面先看看命令链接按钮。

5.1.5 QCommandLinkButton

 QCommandLinkButton 是类似 Vista 风格的命令链接按钮,命令链接按钮相当于是单选按钮和普通按压按钮的结合体,用于从互斥的一堆选项里选择一个执行,并进入下一步操作或打开新的网页链接窗口。使用命 令链接按钮就可以将窗体里的多个单选按钮和“下一步”按钮省掉了,变成一排命令链接按钮,每个命令链接按钮对应一条执行路径。这样界面看起来更简洁直观,只需要点 击一次按钮,而不需要先点单选按钮,再点“下一步” 按钮。
命令链接按钮是扁平风格,默认带有一个向右的箭头图标,并允许附带一个描述文本。点击命令链接按钮,一般意味着进入下一个窗体或弹出网页浏览器访问网页链接。

命令链接按钮常用的构造函数:
QCommandLinkButton::​QCommandLinkButton(const QString & text, QWidget * parent = 0)
QCommandLinkButton::​QCommandLinkButton(const QString & text, const QString & description, QWidget * parent = 0)
text 是按钮显示的文本,parent 是父窗口指针。第二个构造函数 description 是额外的描述信息,额外信息会显示在按钮普通文本的下方。
按钮显示文本通过 text() 函数获取,setText() 函数设置。
命令链接按钮的额外描述可以通过下面两个函数获取或设置:
QString description() const
void setDescription(const QString & description)
命令链接按钮被点击的信号也是 clicked() 。其实命令链接按钮就是长相与普通按钮按钮不一样,多个默认图标和额外描述,其他功能与普通按压按钮是一样的。

点击按钮弹出消息框不是什么新鲜的了,但是如果点击命令链接按钮之后,自动调用操作系统默认的浏览器访问我们指定的网址,该如何实现呢?
看起来很复杂的样子,对于 Qt ,这其实是再简单不过的事了。通过静态函数:
bool QDesktopServices::​openUrl(const QUrl & url)
QUrl 是用于描述网页链接或本地文件链接的类,其常用的构造函数:
QUrl::​QUrl(const QString & url, ParsingMode parsingMode = TolerantMode)
构造函数里的字符串 url 就是网页链接或本地文件链接,parsingMode 就用默认的宽容模式 TolerantMode 即可。
如果要打开本教程的主页,先包含头文件 <QDesktopServices> 和 <QUrl>,然后调用:
QDesktopServices::openUrl( QUrl("https://lug.ustc.edu.cn/sites/qtguide/") );
如果要打开 D:\QtProjects 文件夹,那么调用:
QDesktopServices::openUrl( QUrl("file:///D:/QtProjects") );
注意 URL 里面都是用右斜杠,file:///  是三根右斜杠,表示打开本地文件系统里的文件夹或文件。

关于命令链接按钮的知识就讲到这,下面我们把复选框和命令链接按钮例子放一块来编写。

下面来动手编写本节第二个例子,重新打开 QtCreator,新建一个 Qt Widgets Application 项目,在新建项目的向导里填写:
①项目名称 multiselection,创建路径 D:\QtProjects\ch05,点击下一步;
②套件选择里面选择全部套件,点击下一步;
③基类选择 QWidget,点击下一步;
④项目管理不修改,点击完成。
建好项目之后,打开窗体 widget.ui 文件,进入设计模式,如下图拖控件到窗体里:
ui
首先拖一个 Group Box 分组框到窗体上半部分,调整分组框大小,这样能装得下 6 个复选框,
设置分组框的 title 属性,将标题文本改为“兴趣爱好”。

然后向分组框内部拖 6 个复选框,多个兴趣爱好是不冲突的,所以用复选框,复选框的文本按图上设置即可,
6 个复选框的 objectName 依次修改为:
checkBoxRead、checkBoxClimb、
checkBoxSwim、checkBoxDaze、
checkBoxShopping、checkBoxEat。
注意调整 6 个复选框位置,尽量对齐,看起来工整。

接着向分组框外部的下面拖一个额外的复选框,文本为“三态显示”,objectName 为 checkBoxTristate;
与这个额外的复选框并排再放一个普通按压按钮,文本为“弹窗显示”,objectName 为 pushButton,调整按钮高度和位置,与左边复选框尽量对齐点。

最后向窗体底部拖两个 Command Link Button,第一个命令链接按钮的文本为“打开文件夹”,objectName 为 commandLinkButtonFolder,第二个命令链接按钮的文本为“打开主页”,objectName 为 commandLinkButtonWeb,并调整这两个按钮的宽度和位置,使界面看起来比较工整。

这样界面控件的设置就完成了,在编写代码之前,先说一下这个例子要实现的功能,在分组框里的兴趣爱好共 6 个,每一个复选框都可以选中或不选中。分组框外的三态复选框根据 6 个兴趣复选框状态自动变化:
按压按钮“弹窗显示”,会弹出消息框,显示选中了的兴趣爱好信息。
窗体下方两个命令链接按钮一个用于打开本地文件夹,另一个打开本教程主页。

下面先从设计模式为 6 个兴趣复选框的 toggled() 信号添加 6 个槽函数:
slot1
然后为普通按压按钮和两个命令链接按钮的 clicked() 信号添加槽函数,共三个:
slot2
槽函数添加好之后,保存界面文件,回到代码编辑模式,向头文件 widget.h 添加一个 CheckHobbies() 函数声明,其他的不用动, widget.h 完整内容为:
#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>

namespace Ui {
class Widget;
}

class Widget : public QWidget
{
    Q_OBJECT

public:
    explicit Widget(QWidget *parent = 0);
    ~Widget();

    //检查兴趣爱好,同步更新三态复选框的状态
    void CheckHobbies();

private slots:
    void on_checkBoxRead_toggled(bool checked);

    void on_checkBoxClimb_toggled(bool checked);

    void on_checkBoxSwim_toggled(bool checked);

    void on_checkBoxDaze_toggled(bool checked);

    void on_checkBoxShopping_toggled(bool checked);

    void on_checkBoxEat_toggled(bool checked);

    void on_pushButton_clicked();

    void on_commandLinkButtonFolder_clicked();

    void on_commandLinkButtonWeb_clicked();

private:
    Ui::Widget *ui;
};

#endif // WIDGET_H
6 个复选框的槽函数和 3 个按钮的槽函数都是通过设计模式“转到槽...”添加的,这些槽函数都能自动关联,省了许多事。

接下来我们打开 widget.cpp 文件,首先添加头文件包含,并在构造函数里添加代码:
#include "widget.h"
#include "ui_widget.h"
#include <QDebug>
#include <QMessageBox>
#include <QDesktopServices>
#include <QUrl>

Widget::Widget(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::Widget)
{
    ui->setupUi(this);

    //设置三态工作模式
    ui->checkBoxTristate->setTristate(true);

}

Widget::~Widget()
{
    delete ui;
}
构造函数里将 ui->checkBoxTristate 三态工作模式。这个三态按钮会根据 6 个兴趣复选框来显示状态,我们下面编写 CheckHobbies() 函数内容,检查选中的兴趣复选框个数,然后更新三态复选框状态:
//检查 6 个兴趣爱好复选框,根据兴趣复选框状态,更新三态复选框显示
void Widget::CheckHobbies()
{
    int count = 0;  //兴趣计数
    if( ui->checkBoxRead->isChecked() )
    {
        count++;
    }
    if( ui->checkBoxClimb->isChecked() )
    {
        count++;
    }
    if( ui->checkBoxSwim->isChecked() )
    {
        count++;
    }
    if( ui->checkBoxDaze->isChecked() )
    {
        count++;
    }
    if( ui->checkBoxShopping->isChecked() )
    {
        count++;
    }
    if( ui->checkBoxEat->isChecked() )
    {
        count++;
    }
    //根据兴趣计数设置三态按钮的状态
    if( count <= 0)
    {
        ui->checkBoxTristate->setCheckState(Qt::Unchecked);
        return;
    }
    else if( count <= 5 )
    {
        ui->checkBoxTristate->setCheckState(Qt::PartiallyChecked);
        return;
    }
    else
    {
        ui->checkBoxTristate->setCheckState(Qt::Checked);
        return;
    }
}
count 就是 6 个兴趣复选框选中的个数,可以使用复选框的 isChecked() 函数判断是否处于选中状态,然后更新计数 count 。
计数统计好之后,根据计数范围,如果小于等于 0,那么设置三态复选框为 Qt::Unchecked,并返回;
如果计数位于 1 到 5 之间,那么设置三态复选框为 Qt::PartiallyChecked,并返回;
如果计数达到 6 个,那么设置三态复选框为 Qt::Checked,并返回。

CheckHobbies() 只是普通函数,当每个兴趣复选框状态发出信号 toggled() 时,我们都要调用这个函数检查一下兴趣爱好计数,重新设置三态按钮状态。6 个兴趣复选框的槽函数内容都是类似的,如下所示:
void Widget::on_checkBoxRead_toggled(bool checked)
{
    if(checked)
    {
        qDebug()<<tr("读书");
    }
    CheckHobbies(); //更新三态复选框
}

void Widget::on_checkBoxClimb_toggled(bool checked)
{
    if(checked)
    {
        qDebug()<<tr("爬山");
    }
    CheckHobbies(); //更新三态复选框
}

void Widget::on_checkBoxSwim_toggled(bool checked)
{
    if(checked)
    {
        qDebug()<<tr("游泳");
    }
    CheckHobbies(); //更新三态复选框
}

void Widget::on_checkBoxDaze_toggled(bool checked)
{
    if(checked)
    {
        qDebug()<<tr("发呆");
    }
    CheckHobbies(); //更新三态复选框
}

void Widget::on_checkBoxShopping_toggled(bool checked)
{
    if(checked)
    {
        qDebug()<<tr("逛街");
    }
    CheckHobbies(); //更新三态复选框
}

void Widget::on_checkBoxEat_toggled(bool checked)
{
    if(checked)
    {
        qDebug()<<tr("吃货");
    }
    CheckHobbies(); //更新三态复选框
}
6 个复选框的槽函数首先根据 checked 判断是否打印文本,打印文本说明该复选框处于选中状态。
然后调用 CheckHobbies() 函数更新三态复选框。

普通的按压按钮槽函数会根据选中的兴趣复选框,弹窗显示选中的兴趣爱好信息:
//弹窗显示选中的兴趣爱好
void Widget::on_pushButton_clicked()
{
    QString strResult = tr("兴趣爱好为:\r\n");
    if( ui->checkBoxRead->isChecked() )
    {
        strResult += tr("读书\r\n");
    }
    if( ui->checkBoxClimb->isChecked() )
    {
        strResult += tr("爬山\r\n");
    }
    if( ui->checkBoxSwim->isChecked() )
    {
        strResult += tr("游泳\r\n");
    }
    if( ui->checkBoxDaze->isChecked() )
    {
        strResult += tr("发呆\r\n");
    }
    if( ui->checkBoxShopping->isChecked() )
    {
        strResult += tr("逛街\r\n");
    }
    if( ui->checkBoxEat->isChecked() )
    {
        strResult += tr("吃货\r\n");
    }
    //弹窗显示
    QMessageBox::information(this, tr("兴趣爱好"), strResult);
}
普通按压按钮的槽函数工作原理很简单,如果某兴趣复选框处于选中状态,就添加一行对应的兴趣爱好字符串,最后弹窗显示就行了。

关于复选框的代码就是上面的部分。接下来是两个命令链接按钮对应的槽函数:
void Widget::on_commandLinkButtonFolder_clicked()
{
    QDesktopServices::openUrl( QUrl("file:///D:/QtProjects") );
}

void Widget::on_commandLinkButtonWeb_clicked()
{
    QDesktopServices::openUrl( QUrl("https://lug.ustc.edu.cn/sites/qtguide/") );
}

第一个命令链接按钮的槽函数会打开本地文件夹,第二个命令链接按钮的槽函数会打开本教程主页。
widget.cpp 里面的函数代码按上面添加完成就行了。
可以生成运行例子,看看效果,下面两张运行效果图显示的是三态复选框的部分选中和全选中的状态:
run1
run2
至于两个命令链接按钮点击之后的运行效果,大家自己点击试试看。本节的内容就到这里,下一节学习单行编辑控件。



prev
contents
next