10.2 滚动区域和工具箱

本节介绍两种控件容器 Scroll Area、Tool Box,可以对窗口里面的控件视图扩展,容纳原本放不下的很多控件。Scroll Area 滚动区域,就是指该容器具有水平和垂直的滚动条,当容器内控件尺寸超出容器矩形区域时,将显示滚动条,通过拖动滚动条切换显示内部控件的位置。
Tool Box 工具箱是支持多个页面集合到一起,每次显示一个页面,点击页面标题可以切换各个页面。

10.2.1 Scroll Area 滚动区域

滚动区域的类是 QScrollArea,该类可以设置唯一的子控件,通过滚动条切换内部尺寸较大的控件视口(viewport)。 在不涉及滚动式,视口与控件矩形一样大。当子控件实际尺寸超过视口时,如下图所示:
viewport
黄色矩形是真实的子控件区域,比如一张很大的图片,而我们的 QScrollArea 矩形可能只有蓝色矩形那么大的尺寸,这时候 QScrollArea 就会显示水平和垂直的滚动条,滚动条往下移动时,对应的视口矩形就向下滑动,切换显示子控件的区域。视口就是一个可以实际看见的小窗口,通过不断地滑动,来轮流显示控件的 各个区域。
QScrollArea 设置唯一子控件的函数为:
void    setWidget(QWidget * widget)
设置子控件之后,还可以设置子控件的对齐显示方式:
void    setAlignment(Qt::Alignment)
这个对齐是子控件尺寸小于视口尺寸时才能看到效果,就是视口很大,有空余地方,决定小尺寸的子控件摆在哪个位置合适。对齐方式分为水平和垂直:

常量 数值 描述
Qt::AlignLeft  0x0001 水平左对齐。
Qt::AlignRight  0x0002 水平右对齐。
Qt::AlignHCenter  0x0004 水平居中。
Qt::AlignTop  0x0020 垂直顶部对齐。
Qt::AlignBottom  0x0040 垂直底部对齐。
Qt::AlignVCenter  0x0080 垂直居中。

水平和垂直的对齐方式可以用二进制或 | 同时设置,比如水平居中和垂直居中: 
Qt::AlignHCenter | Qt::AlignVCenter
一般显示图片的应用程序通常图片水平和垂直都居中。
QScrollArea 还可以设置 widgetResizable 属性:
bool    widgetResizable() const
void    setWidgetResizable(bool resizable)
widgetResizable 属性默认是 false,代表 QScrollArea 尊重子控件原本的尺寸设置,程序员可以通过函数 widget()->resize() 调整子控件的尺寸, QScrollArea 自动根据调整后的子控件调整自己的滚动条显示。
如果 widgetResizable 属性设置为 true,那么 QScrollArea 则可以自己做主调整子控件大小,尽量调整子控件尺寸,以避免使用到滚动条,从而有更多视口区域显示内部子控件。滚动条的绘制会占用一定的视口区 域,widgetResizable 为 true 时, QScrollArea 会尽量调整子控件尺寸避免绘制滚动条。

QScrollArea 通常有两种用途,一种是显示很大的图片,比如:
QLabel *imageLabel = new QLabel;
QImage image("happyguy.png");
imageLabel->setPixmap(QPixmap::fromImage(image));

scrollArea = new QScrollArea;
scrollArea->setBackgroundRole(QPalette::Dark);
scrollArea->setWidget(imageLabel);
如果图片太大,就用滚动条显示图片各个区域。
第二种是显示很大的子控件容器,比如:
    //控件容器
    QWidget *widContainer = new QWidget();
    //垂直布局器
    QVBoxLayout *layout = new QVBoxLayout();

    QLabel *curLabel;
    //一百个文本标签举例
    for(int i=0; i<100; i++)
    {
        QString strTemp = tr("Label %1").arg( i );
        curLabel = new QLabel( strTemp );
        layout->addWidget( curLabel );
    }
    //容器布局
    widContainer->setLayout( layout );
    //将容器设置为滚动区域唯一子控件
    ui->scrollArea->setWidget( widContainer );
我们将这 100 个标签控件放进布局器,然后布局器设置给容器对象,容器对象再设置给滚动区域,我们就通过滚动区域轮流显示100个标签控件。
针对两种场景,QScrollArea 可以通过两个函数来移动视口位置:
void    ensureVisible(int x, int y, int xmargin = 50, int ymargin = 50)
子控件的左上角为 x=0,顶部 y=0,ensureVisible() 默认显示 (x,y) 点位置及包含该点的50*50矩形区域。
改变 x 和 y 数值,视口就会移动到能够显示该坐标点的位置。
第二个函数是移动视口,确保某个子孙控件显示出来:
void    ensureWidgetVisible(QWidget * childWidget, int xmargin = 50, int ymargin = 50)
QScrollArea 总是显示直接子控件的,上面函数通常是显示孙子级或更低级别的重孙、玄孙等内部控件。

滚动条默认的显示策略是有必要时才显示,即子控件真实尺寸大于视口尺寸时才显示,这个策略可以设置:
Qt::ScrollBarPolicy    horizontalScrollBarPolicy() const       //获取水平滚动条的显示策略
void    setHorizontalScrollBarPolicy(Qt::ScrollBarPolicy)     //设置水平滚动条的显示策略
Qt::ScrollBarPolicy    verticalScrollBarPolicy() const            //获取垂直滚动条的显示策略
void    setVerticalScrollBarPolicy(Qt::ScrollBarPolicy)          //设置垂直滚动条的显示策略
Qt::ScrollBarPolicy 枚举常量包括如下数值:

常量 数值 描述
Qt::ScrollBarAsNeeded  0 内容超出视口区域才显示滚动条,否则不显示,默认策略就是该数值。
Qt::ScrollBarAlwaysOff  1 永远不显示滚动条,通过鼠标滚轮或键盘上下键盲滚动。
Qt::ScrollBarAlwaysOn  2 不管内容区域多大,总是显示滚动条。对于Mac10.7以上系统,滚动条是瞬态显示的,就会忽略该数值。

默认数值 Qt::ScrollBarAsNeeded 是比较合适的,有需要就显示,不需要就不显示。
通常 ensureVisible() 和 ensureWidgetVisible() 两个函数能够应付大多数的需要滚动的场景,如果有额外需求,那么还可以直接获取水平滚动条和垂直滚动条来设置:
QScrollBar * QAbstractScrollArea::​horizontalScrollBar() const
QScrollBar * QAbstractScrollArea::verticalScrollBar() const
这两个函数是从基类 QAbstractScrollArea 继承的,滚动条的类型是 QScrollBar,可以使用滚动条自己的函数来设置滚动到的位置:
void    QScrollBar::​setValue(int)        //设置滚动到参数值位置,只能介 于最小值和最大值之间
int    QScrollBar::value() const          //获取滚动条当前位置
int    QScrollBar::minimum() const   //最小位置
int    QScrollBar::maximum() const   //最大位置

当 QScrollArea 包裹控件容器时,有可能对子孙控件的显示缩放不合适,子孙控件由于缩放被隐藏了,这时候通过调用子控件的QWidget::setMinimumSize() 函数,设置子孙控件的最小尺寸,这样保证子孙控件的绘图区域,从而避免被隐藏。
使用布局器时,布局器的 sizeConstraint 属性也会影响子孙控件的缩放,该枚举常量数值如下表所示:

常量 数值 描述
QLayout::SetDefaultConstraint  0 控件的最小尺寸默认设置为 QLayout::​minimumSize() ,除非控件自己设置了最小尺寸。
QLayout::SetFixedSize  3 控件尺寸设置为 QLayoutItem::​sizeHint(),尺寸固定不能变化。
QLayout::SetMinimumSize  2 控件最小尺寸为 QLayout::​minimumSize(),不能更小。
QLayout::SetMaximumSize  4 控件最大尺寸为 QLayout::​maximumSize(),不能更大。
QLayout::SetMinAndMaxSize  5 控件尺寸位于 QLayout::​minimumSize() 和 QLayout::​maximumSize() 之间。
QLayout::SetNoConstraint  1 控件尺寸没有任何限制。

修改布局器的 sizeConstraint 属性,QScrollArea 也会根据这个属性变化改变子孙控件布局。布局器 sizeConstraint  属性的默认值为 QLayout::SetDefaultConstraint 。
QScrollArea 的内容介绍到这,下面我们学习一个滚动区域的示例。
我们先下载两个文件,一个是地图图片,另一个是省份在图片中像素位置的文本文件:
https://qtguide.ustclug.org/QtProjects/ch10/geography/china.png
https://qtguide.ustclug.org/QtProjects/ch10/geography/china.txt
下载好 china.png 、china.txt 备用。
我们打开 QtCreator,新建一个 Qt Widgets Application 项目,在新建项目的向导里填写:
①项目名称 geography,创建路径 D:\QtProjects\ch10,点击下一步;
②套件选择里面选择全部套件,点击下一步;
③基类选择 QWidget,点击下一步;
④项目管理不修改,点击完成。
我们把下载好的 china.png 、china.txt 复制到项目文件夹 D:\QtProjects\ch10\geography,然后我们在 QtCreator 界面项目视图,右击根节点项目名geography,在右键菜单选择“添加新文件”,弹出新建文件的对话框,在新建文件对话框中,左边选择 “Qt” ,右边选择“Qt Resource File”,如图所示:
newfile1
点击新建文件对话框的 “Choose...”按钮,进入下一步:
newfile2
在文件名称编辑框里填写: map.qrc
然后路径不变,点击“下一步” 按钮,进入下面界面:
newfile3
不用修改,点击“完成”按钮,回到 QtCreator 界面,这样我们就新建了 Qt 资源文件 map.qrc 。
我们在左边项目视图右击 map.qrc 文件,在右键菜单选择“添加现有文件...”,然后选择 china.png 、china.txt 两个文件添加到 map.qrc 资源文件里面。添加好之后,可以看到 map.qrc 现在包含两个资源:
newfile4
资源文件就编辑好了。这个例子我们显示一张中国地图,然后新建 34 个单选按钮,点击哪个按钮,就显示哪个省份地图位置。
地图较大,我们使用 QScrollArea 包裹地图以便于滚动显示;34个单选按钮也很多,我们使用容器包裹这些单选按钮,然后用 QScrollArea 包裹容器,这样可以滚动显示很多的按钮。
按钮特别多情况下,我们这里使用代码来创建界面,widget.ui 界面文件我们不修改,全都用代码来生成。
下面我们开始编辑头文件 widget.h 的内容:
#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>
#include <QScrollArea>  //滚动区域
#include <QLabel>       //标签用于显示图片
#include <QRadioButton> //单选按钮
#include <QHBoxLayout>  //水平布局器
#include <QVBoxLayout>  //垂直布局器
#include <QStringList>
#include <QList>
#include <QPoint>
#include <QPixmap>
#include <QSignalMapper> //信号高级映射

namespace Ui {
class Widget;
}

class Widget : public QWidget
{
    Q_OBJECT

public:
    explicit Widget(QWidget *parent = 0);
    ~Widget();
    //初始化控件
    void InitControls();

public slots:
    //信号映射将所有按钮的信号都发给该槽函数
    void ShowProvince(int index);

private:
    Ui::Widget *ui;
    //省份名称
    QStringList m_listProvinces;
    //像素点位置
    QList<QPoint> m_listPoints;
    //地图
    QPixmap m_map;

    //加载地图和文本数据,包含省份和像素点位置
    void LoadData();

    //信号映射对象指针
    QSignalMapper *m_pSigMapper;

    //标签显示图片
    QLabel *m_labelMap;
    //左边滚动区域显示绘图
    QScrollArea *m_saLeftMap;
    //右边滚动区域显示一大堆按钮
    QScrollArea *m_saRightButtons;

};

#endif // WIDGET_H
文件开头我们添加多个类的包含,然后为窗口类添加控件初始化函数 InitControls() ;
手动编辑一个公开槽函数 ShowProvince(int) ,这个参数是按钮映射的整数序号,通过序号区分 34 个单选按钮;
我们添加成员变量 m_listProvinces 保存省份名称,m_listPoints 保存省份在地图的坐标位置,
m_map 保存地图图片,LoadData() 函数用于初始化时从资源文件加载图片和文本文件,
m_pSigMapper 保存信号映射对象,专门批量处理 34 个单选按钮的点击信号,m_labelMap 保存标签对象,用于显示绘图,
m_saLeftMap 保存用于左边显示地图的滚动区域对象,m_saRightButtons 用于保存右边显示按钮的滚动区域对象。

下面我们分段编辑 widget.cpp 源文件,首先是构造函数部分:
#include "widget.h"
#include "ui_widget.h"
#include <QDebug>
#include <QFile>

Widget::Widget(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::Widget)
{
    ui->setupUi(this);
    //加载地图和文本数据,包含省份和像素点位置
    LoadData();
    //初始化控件
    InitControls();
}

Widget::~Widget()
{
    delete ui;
}
在构造函数添加了 LoadData() 从资源文件加载数据,然后调用 InitControls() 初始化界面控件。
我们下面编辑加载数据的函数:
//加载地图和文本数据,包含省份和像素点位置
void Widget::LoadData()
{
    //加载地图
    m_map.load( ":/china.png" );
    //读取文本文件
    QFile fileIn( ":/china.txt" );
    m_listProvinces.clear();
    m_listPoints.clear();
    //打开文件
    fileIn.open( QIODevice::ReadOnly|QIODevice::Text );
    while( ! fileIn.atEnd() )
    {
        QByteArray baLine = fileIn.readLine();
        QString strLine = QString::fromUtf8( baLine );
        QStringList liParts = strLine.split( '\t' );
        QPoint pt;
        m_listProvinces << liParts[0];
        pt.setX( liParts[1].toInt() );
        pt.setY( liParts[2].toInt() );
        m_listPoints<<pt;
    }
    //加载完毕
    qDebug()<<m_listProvinces.size()<<m_listProvinces;
    qDebug()<<m_listPoints.size()<<m_listPoints;
}
使用 m_map 的 load() 函数读取资源中的地图文件 ":/china.png" ;
根据文本文件 ":/china.txt" 新建文件读取对象 fileIn,清空 m_listProvinces 和 m_listPoints 两个列表;
以只读模式打开文件对象 fileIn;然后循环读取文件每一行到字节数组 baLine;
文件中的汉字使用 utf-8 编码,因此使用字符串的函数 QString::fromUtf8() 把字节数组转为字符串 strLine;
文本文件每一行都是三个数据,使用 '\t' 制表符分隔,因此使用切分函数,一行文本切成三段:
第 0 段是省份名称,第 1 段是 x 坐标,第 2 段是 y 坐标。
针对每行的三段文本,省份名称直接存到 m_listProvinces;
第 1 段和第 2 段转为整数之后存到 pt 点,然后把 pt 点存到 m_listPoints。
处理完文件后,我们打印省份个数和列表,打印点的个数和点列表。

接下里我们重点学习这个初始化控件函数:
//初始化控件
void Widget::InitControls()
{
    //使用QLabel绘图
    m_labelMap = new QLabel();
    m_labelMap->setPixmap( m_map );
    //构建滚动区域包裹标签
    m_saLeftMap = new QScrollArea();
    m_saLeftMap->setWidget( m_labelMap );

    //右边容器和布局器
    QWidget *pWidRight = new QWidget();
    QVBoxLayout *pLayoutRight = new QVBoxLayout();
    //用于循环新建单选按钮
    QRadioButton *curButton = NULL;
    m_pSigMapper = new QSignalMapper(this);
    //个数
    int nCount = m_listProvinces.size();
    for(int i=0; i<nCount; i++)
    {
        curButton = new QRadioButton( m_listProvinces[i] );
        //关联信号到信号映射对象
        connect(curButton, SIGNAL(clicked()),
                m_pSigMapper, SLOT(map()) );
        //设置映射参数,将当前按钮的编号设置为 i
        m_pSigMapper->setMapping(curButton, i);
        //添加到布局器
        pLayoutRight->addWidget( curButton );
    }
    //将信号映射对象的 mapped() 信号关联到统一的槽函数
    connect(m_pSigMapper, SIGNAL(mapped(int)),
            this, SLOT(ShowProvince(int)) );

    //布局器设置给右边容器
    pWidRight->setLayout( pLayoutRight );
    //新建右边滚动区域,将容器设置给右边滚动区域
    m_saRightButtons = new QScrollArea();
    m_saRightButtons->setWidget( pWidRight );

    //主界面布局器
    QHBoxLayout *pMainLayout = new QHBoxLayout();
    pMainLayout->addWidget( m_saLeftMap );
    pMainLayout->addWidget( m_saRightButtons );
    pMainLayout->setStretch( 0, 4); //左边拉伸因子 4
    pMainLayout->setStretch( 1, 1); //右边拉伸因子 1
    //主窗口布局器
    setLayout( pMainLayout );
    //窗口大小
    resize(800, 600);
    //最后一个单选按钮选中
    curButton->setChecked( true );
    //显示最后一个单选按钮
    m_saRightButtons->ensureWidgetVisible( curButton );
    //视口保证最后一个点会显示
    m_saLeftMap->ensureVisible( m_listPoints[nCount-1].x(), m_listPoints[nCount-1].y(), 200, 200 );
}
该函数先创建了标签对象存到 m_labelMap,并设置显示图片 m_map;
新建滚动区域存到 m_saLeftMap,设置滚动区域的子控件为 m_labelMap,用于显示大地图。
然后我们新建右边的容器 pWidRight、布局器 pLayoutRight;
curButton 指针用于临时保存后面循环中创建的每个按钮对象;
新建信号映射对象存到 m_pSigMapper,信号映射 QSignalMapper 的作用就是将大量有规律的信号映射到一个槽函数,批量处理。
然后获取省份计数,循环开始处理:
    新建单选按钮存到 curButton;
    关联按钮的点击信号到 m_pSigMapper 的槽函数 map() ;
    调用 m_pSigMapper->setMapping(curButton, i),设置当前按钮映射的规律序号为 i ;
    将按钮 curButton 添加到右边布局器 pLayoutRight;
循环处理完之后,我们将 m_pSigMapper 对象的信号 mapped(int) 关联到槽函数 ShowProvince(int),参数就是按钮映射的规律序号;
然后我们将右边布局器 pLayoutRight 设置给容器 pWidRight;
新建右边的滚动区域对象 m_saRightButtons,把容器 pWidRight 设置给右边滚动区域来显示;
接下来我们新建主界面布局器 pMainLayout,把两个滚动区域 m_saLeftMap、m_saRightButtons 添加给主界面布局器;
设置主界面布局器两个控件的拉伸因子分别为 4 和 1,这样左边地图滚动区域和右边按钮滚动区域的宽度显示比例 4:1 ;
将 pMainLayout 设置给主界面窗口;调整窗口大小为  800*600;
我们设置最后的单选按钮 curButton 为选中状态;
通过 m_saRightButtons->ensureWidgetVisible() 函数显示该按钮的区域;
通过 m_saLeftMap->ensureVisible() 函数显示该按钮对应的地图点所在区域。

这里说明一下 QSignalMapper ,信号映射类专门处理批量的信号,该类的用法就是三步走:
第一步,将批量控件的信号关联到 QSignalMapper 的槽函数 map() ;
第二步,将批量控件对象映射为整数序号或字符串:
void    setMapping(QObject * sender, int id)
void    setMapping(QObject * sender, const QString & text)
第三步,将 QSignalMapper 的信号 mapped() 关联到一个槽函数,槽函数的参数与该信号的参数一样:
void    mapped(int i)
void    mapped(const QString & text)
这些批量的控件发信号时,QSignalMapper 自动根据源对象信号,生成新的带参数映射信号,调用带参数的槽函数,槽函数可以通过参数的整数值或字符串来判断是谁发的信号,就根据源头控件进行处理。除了整数序号和字符串,QSignalMapper 也支持参数里为 QWidget * widget  或者QObject * object映射,可以将发射信号的控件指针直接传给槽函数。

对于大批量的控件信号,如果使用普通办法一个个新建槽函数,那么槽函数添加太多也容易出错,最好的办法是使用 QSignalMapper 映射,这样我们只需要一个槽函数就能处理大批量控件的信号。

最后我们来编辑槽函数 ShowProvince(int)代码:
//信号映射将所有按钮的信号都发给该槽函数
void Widget::ShowProvince(int index)
{
    m_saLeftMap->ensureVisible( m_listPoints[index].x(), m_listPoints[index].y(), 200, 200 );
}
这个槽函数根据参数 index 决定点的序号,然后使用 m_saLeftMap->ensureVisible() 滚动显示该点位置,就是省份所在位置。
例子代码讲解到这,我们生成项目,运行例子:
run1
示例程序启动时,左边地图显示了“澳门”区域,右边也滚动到了“澳门特别行政区”的单选按钮,就是代码里设置的效果,
单选按钮只显示了一半,因为滚动区域判断能看到该单选按钮就可以了,没有设置显示控件的全貌,属于能凑合显示就行的设置。
我们滚动右边按钮区域,点击“黑龙江省”的单选按钮,地图就自动切换显示黑龙江省的区域:
run2
如果点击的省份点坐标不在当前显示区域内,滚动区域就会滚动显示到该省份;
如果点击的省份点坐标在当前显示区域内,点击了就没有反应,因为已经显示了,不需要挪动。
本示例内容介绍到这,下面我们介绍工具箱容器。

10.2.2 Tool Box 工具箱

工具箱容器 QToolBox 可以容纳多个标签页,每个标签页就像柜子的抽屉一样堆叠在一起,通过点击多个标签页标题栏切换页面显示,每次显示一个标签页。
多个标签页可以按照功能分类,将同类型控件放在一个标签页内,多个标签页切换显示,就可以实现很多的功能。QToolBox 如下图所示:
toolbox1
Page1 是默认显示的标签页,标题栏是黄色背景的文本 "Page 1",标签页的背景色是绿色的,当我们点击 "Page 2" 标题栏时,就会切换显示第二个标签页,多个标签页切换像是单选按钮,每次启用一个:
toolbox2
QToolBox 将每个页面当做一个 item,每个 item 都有 widget 页面,并且 item 可以带有标题文本、序号、图标、工具提示(ToolTip),比较接近列表控件的条目,列表控件的条目是文本图标为主,而 QToolBox 条目都是一个带标题的标签页 widget。
QToolBox 添加标签页条目的函数如下:
int    addItem(QWidget * widget, const QIcon & iconSet, const QString & text)
int    addItem(QWidget * w, const QString & text)
int    insertItem(int index, QWidget * widget, const QIcon & icon, const QString & text)
int    insertItem(int index, QWidget * widget, const QString & text)
addItem() 函数将新标签页追加到所有标签页的末尾,是追加最后一个;insertItem() 是将新标签页插入到序号为 index 的位置,原来序号之后的标签页往后挪动一个。注意 widget 页面不能为空,文本一般不为空,表示该标签页的功能描述,图标可以没有。
删除条目的函数如下:
void    removeItem(int index)
注意 removeItem() 仅是将指定序号的标签页卸载下来,该页面不会被 delete,删除的是工具箱条目信息,而不会删除标签页 widget 控件本身。

获取工具箱包含的标签页总数使用下面函数:
int    count() const
工具箱的标签页每次只能激活一个当前页面,获取当前页面的序号、标签页指针使用下面函数:
int    currentIndex() const         //获取当前页面的序号,如果工具箱没有任何标签页,返回 -1
QWidget *    currentWidget() const  ////获取当前页面的指针,如果工具箱没有任何标签页,返回 NULL
设置当前激活的页面是两个槽函数:
void    setCurrentIndex(int index)  //槽函数,根据序号设置新的当前页面
void    setCurrentWidget(QWidget * widget)  ////槽函数,根据页面指针设置新的当前页面
当前页面序号变化时,会触发序号变化的信号:
void    currentChanged(int index)   //当前页面切换时触发的信号

根据标签页的序号,可以修改标题文本、图标以及工具提示:
QIcon    itemIcon(int index) const           //获取页面标题图标
QString    itemText(int index) const        //获取页面标题文本
QString    itemToolTip(int index) const  //获取页面标题工具提示
void    setItemIcon(int index, const QIcon & icon)    //设置页面标题图标
void    setItemText(int index, const QString & text)  //设置页面标题文本
void    setItemToolTip(int index, const QString & toolTip)  //设置页面标题工具提示

可以根据序号查页面指针,也可以根据页面指针反查序号,使用下面一对函数:
QWidget *    widget(int index) const  //序号正确,返回正确的页面指针,如果序号错误或没有任何页面,返回 NULL
int    indexOf(QWidget * widget) const  //如果页面属于工具子页面,那么返回序号,如果不属于工具箱,返回 -1
使用工具箱的函数时,注意返回的序号数值可能是 -1,返回的指针可能是 NULL,要注意判断这些异常值,避免产生 bug。

每个标签页可以设置为启用状态或禁用状态,禁用的标签页里面子控件也都处于禁用状态:
bool    isItemEnabled(int index) const   //根据序号查询标签页是否启用
void    setItemEnabled(int index, bool enabled)  //根据序号设置标签页的启用或禁用状态
QToolBox 的基类是  QFrame,继承了边框绘制功能,可以参照 “10.1.2 Frame 框架”小节设置定制的边框。
工具箱类的内容介绍到这,下面我们学习一个示例,通过工具箱内容设置一个 Label 控件。
我们打开 QtCreator,新建一个 Qt Widgets Application 项目,在新建项目的向导里填写:
①项目名称 labeldesign,创建路径 D:\QtProjects\ch10,点击下一步;
②套件选择里面选择全部套件,点击下一步;
③基类选择 QWidget,点击下一步;
④项目管理不修改,点击完成。
我们打开 widget.ui 界面文件,向里面拖入一个标签 Label 和一个 Tool Box 容器:
ui1
我们将标签的对象名修改为 labelShow ,设置标签 sizePolicy 的水平和垂直策略都为 Expanding:
ui2
工具箱容器默认就有两个页面,将右边工具箱容器长宽调整拉大一些,方便后续拖入子控件。
点击右边工具箱的 Page 1 标题栏,修改 toolBox 当前页的属性,将 currentItemText 修改为“编辑文本”,将 currentItemName 修改为 pageText:
ui3
这样就修改了第 0 序号的页面标题和页面对象名。
我们点击“Page 2”标题栏,点击该标题后会展开第 1 序号页面,再修改 currentItemText 修改为“字体字号”,将 currentItemName 修改为 pageFont:
ui4
工具箱容器支持更多的页面,我们右击工具箱容器,在右键菜单选择“插入页”-->“在当前页之后”:
ui5
点击在当前页之后插入页,工具箱容器就会新增一个页面,序号为 2:
ui6
点击“页”标题栏,我们再修改工具箱容器的 currentItemText 修改为“颜色设置”,将 currentItemName 修改为 pageColor:
ui7
这样我们设置了三个标签页,如果希望删除某个页面,先点击该页的标题展开页面,然后右击该页面,在右键菜单“N 的页N”-->“删除页”即可,N代表该页面的序号数字。
如果希望调整页面排序,那么右击工具箱容器,点击右键菜单“改变页顺序...”,弹出对话框:
ui8
在该对话框可以用右边上下箭头按钮调整页面的排序,我们这里例子不需要修改排序,点击“Cancel”即可。
现在三个标签页是空的,未填充子控件,我们先点击“编辑文本”标题栏,展开第 0 序号标签,向其中拖入单行编辑器和一个按钮,
单行编辑器对象名修改为 lineEditText ,按钮对象名 pushButtonEditText,显示文本为“修改文本”:
pageText
工具箱内容的布局,总是以选中 toolBox 开始布局,而不是选中 pageText 子页面布局,子页面属于容器,在界面编辑时不算独立控件,不能以子页面操作布局器。
我们点击选中工具箱容器 toolBox,然后点击上方垂直布局按钮,对工具箱的当前页面实现布局操作:
pageTextLayout
无论是编辑工具箱页面标题还是布局器,都是以选中工具箱容器为基础,然后修改属性或进行布局,修改或布局操作会自动处理工具箱的当前页面。
我们再点击“字体字号”页面,展开该页面,我们拖入 Font Combo Box 和 Spin Box,字体组合框对象名 fontComboBox,字号的旋钮框对象名为 spinBoxSize:
pageFont
然后我们选中 toolBox 对象,点击上方垂直布局按钮,对当前的“字体字号”页面进行垂直布局:
pageFontLayout
接下来我们点击“颜色设置”标题栏,展开该页面,拖入两个标签和两个组合框,
第一行的标签文本“前景色”,组合框对象名为 comboBoxFGColor;第二行标签文本 “背景色”,组合框对象名 comboBoxBGColor:
pageColor
对于现在展开的“颜色设置”页面,我们选中工具箱 toolBox 对象,然后点击上方的栅格布局器按钮,进行网格布局:
pageColorLayout
这样我们完成了三个标签页的子控件拖入和布局。
对于主窗口,我们选中 Widget 根节点,点击水平布局按钮,将 labelShow 控件和工具箱容器按照水平布局:
mainLayout
主窗口大小修改为为 520*400,这样方便展示较宽的字体组合框。
下面我们为工具箱容器中的子控件添加槽函数:
点击“编辑文本”标题栏,展开该标签页,右击“修改文本”按钮,右键菜单选择“转到槽...”,为该按钮添加 clicked() 信号的槽函数:
slot1
点击“字体字号”标题栏,展开该标签页,右击字体组合框,右键菜单选择“转到槽...”,为该控件添加 currentIndexChanged(QString) 信号的槽函数:
slot2
右击 spinBoxSize 旋钮编辑框,右键菜单选择“转到槽...”,为该控件添加 valueChanged(int) 信号的槽函数:
slot3
然后我们点击“颜色设置”标题栏,展开该标签页,右击前景色组合框,为该控件添加 currentIndexChanged(QString) 信号的槽函数:
slot4
我们再右击背景色组合框,为该控件添加 currentIndexChanged(QString) 信号的槽函数:
slot5
添加好五个控件对应的槽函数之后,我们就可以保存并关闭 widget.ui 文件,开始编辑代码。
我们打开 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 InitControls();

private slots:
    void on_pushButtonEditText_clicked();

    void on_fontComboBox_currentIndexChanged(const QString &arg1);

    void on_spinBoxSize_valueChanged(int arg1);

    void on_comboBoxFGColor_currentIndexChanged(const QString &arg1);

    void on_comboBoxBGColor_currentIndexChanged(const QString &arg1);

private:
    Ui::Widget *ui;
};

#endif // WIDGET_H
我们在窗口类里面添加一个初始化控件的函数 InitControls() ,其他代码都是自动生成的,包括我们添加的 5 个槽函数。
下面我们分段编辑源文件 widget.cpp ,首先是初始化部分:
#include "widget.h"
#include "ui_widget.h"
#include <QMessageBox>
#include <QDebug>
#include <QFont>
#include <QColor>

Widget::Widget(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::Widget)
{
    ui->setupUi(this);
    //初始化控件
    InitControls();
}

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

//初始化控件
void Widget::InitControls()
{
    //字号旋钮框的范围
    ui->spinBoxSize->setRange(4, 100);
    ui->spinBoxSize->setValue( 9 );
    //颜色组合框的设置
    //获取常见颜色
    QStringList colorNames = QColor::colorNames();
    //设置前景色组合框
    ui->comboBoxFGColor->addItems( colorNames );
    //默认前景色 黑色
    ui->comboBoxFGColor->setCurrentText( "black" );
    //设置背景色组合框
    ui->comboBoxBGColor->addItems( colorNames );
    //默认背景色,浅灰
    ui->comboBoxBGColor->setCurrentText( "lightgray" );

    //修改工具箱本身的样式表
    QString strCSS = "::tab{ background-color: magenta; }"
            "QWidget#pageText{ background-color: green; }"
            "QWidget#pageFont{ background-color: cyan; }"
            "QWidget#pageColor{ background-color: yellow; }";
    ui->toolBox->setStyleSheet( strCSS );
}
我们添加了 QFont、QColor 等类的头文件包含,在构造函数里调用 InitControls() 函数初始化控件。
InitControls() 函数首先设置字号旋钮框的取值范围 4~100,当前数值为 9 。
然后对两个颜色组合框进行设置,我们使用静态函数 QColor::colorNames() 获取常见颜色的字符串列表;
将颜色列表添加给前景色组合框,每个颜色名字符串都是一个组合框条目,设置默认的前景色为 "black" ;
然后同样地将颜色列表添加给背景色组合框,每个颜色名字符串都是一个组合框条目,设置默认的背景色为 "lightgray" 。
最后我们为工具箱容器定制样式表:
::tab 表示选中工具箱的所有标签页标题栏,设置背景色为 magenta;
QWidget#pageText 表示选中类名(基类名也生效)为 QWidget,井号代表同时选定对象名为 pageText  的控件对象,这样能确保我们选中唯一的对象,而不会对页面内部的以 QWidget 为基类的子控件生效,设置唯一匹配的 pageText  对象的背景色为 green;
后面两行同理,选中唯一对象 pageFont 页面,设置背景色 cyan;
选中唯一对象 pageColor 页面,设置背景色 yellow;最后将样式表字符串设置给 ui->toolBox 。

下面我们编辑修改文本按钮对应的槽函数:
//修改左边标签控件的文本
void Widget::on_pushButtonEditText_clicked()
{
    QString strText = ui->lineEditText->text();
    ui->labelShow->setText( strText );
}
我们直接获取单行编辑器的文本,设置给左边 ui->labelShow 标签即可。

接下来我们编辑字体和字号控件对应的槽函数:
//字体设置
void Widget::on_fontComboBox_currentIndexChanged(const QString &arg1)
{
    QFont txtFont( arg1, ui->spinBoxSize->value() );
    ui->labelShow->setFont( txtFont );
}

//字号设置
void Widget::on_spinBoxSize_valueChanged(int arg1)
{
    QFont txtFont( ui->fontComboBox->currentText(), arg1 );
    ui->labelShow->setFont( txtFont );
}
两个函数代码类似的,根据字体组合框的当前字体名称文本和字号旋钮框的当前值,构造字体对象 txtFont;然后设置给 ui->labelShow 标签。

最后我们编辑两个颜色组合框对应的槽函数:
//前景色修改
void Widget::on_comboBoxFGColor_currentIndexChanged(const QString &arg1)
{
    QString strFGColor = arg1; //前景色
    QString strBGColor = ui->comboBoxBGColor->currentText(); //背景色
    //样式表
    QString strCSS = QString("color: %1; background-color: %2;").arg( strFGColor ).arg( strBGColor );
    ui->labelShow->setStyleSheet( strCSS );
}
//背景色修改
void Widget::on_comboBoxBGColor_currentIndexChanged(const QString &arg1)
{
    QString strFGColor = ui->comboBoxFGColor->currentText(); //前景色
    QString strBGColor = arg1; //背景色
    //样式表
    QString strCSS = QString("color: %1; background-color: %2;").arg( strFGColor ).arg( strBGColor );
    ui->labelShow->setStyleSheet( strCSS );
}
我们从前景色组合框获取前景色文本,从背景色组合框获取背景色文本,然后构造样式表字符串,color 就是前景色,background-color 就是背景色,分别填充各自的颜色文本,设置样式表给 ui->labelShow 标签。
示例代码讲解到这,我们生成项目,运行该示例:
run1
我们可以看到右边工具箱容器的标题和页面内部颜色都变了,说明设置的样式表生效了。
我们修改前景色、背景色,可以看到左边标签的变化:
run2
我们点击“字体字号”标题栏,展开该页面,可以修改字体和字号,如下图所示:
run3
其他功能请读者自行测试,本节示例内容到这,我们下一节学习功能更强大的容器。


prev
contents
next