10.1 简单控件容器

本节介绍三种简单控件容器,Widget、Frame、Group Box,通常用于把多个控件分类显示,并不能直接扩展视图。这三个简单容器主要是用于子控件的分组排布,比如多种不同功能的单选框,使用这些容器包裹一下,就自动分成不同 的组别,单选框就不会全部是互斥的,而只是一个容器里单选框的互斥。下面我们逐个学习这三个简单控件容器。

10.1.1 Widget 部件

本章所学的就是设计师界面的几个控件容器,如图所示:
wid containers
Widget 容器就是 QWidget 类,与我们之前学习的窗口类是同一个类。QWidget 作为独立窗口时,有自己的标题栏和边框,但是作为窗口内部的控件容器时,没有边框,只是简单的一块背景板,里面放置一些子控件。在设计师界面能看到 Widget 容器的虚线边框,但是运行时,Widget 容器默认没有边框。
Widget 容器的典型应用一是将不同功能的单选框分组,不同容器的单选框自动分在各自的组别;
第二个应用举例,是放置在容器中的控件,子控件排布是跟随父级容器的,拖动父级容器,子控件总是与父级容器对齐,并且父级容器有自己独立的布局器。
第三个应用举例,例如控件太多了,希望使用滚动区域时,QScrollArea 必须指定唯一的 widget 作为滚动对象,那么我们将一堆子控件塞到一个 widget 容器里面,然后将包裹好的大容器 widget 作为 QScrollArea 的唯一滚动对象。
下面我们打开 Qt 设计师举例,选择 Widget 窗口模板:
wid1
点击“创建”按钮,进入界面编辑,将窗口大小设置为 480*400:
wid2
然后拖入三个单选按钮和一个 Widget 容器,三个单选按钮文本改为 “red”、“blue”、“green”,然后将 Widget 容器大小设置为 180*140,如下图所示:
wid3
这是我们按住 Ctrl 键,选中红蓝绿三个单选按钮,然后将按钮往虚线框的 Widget 容器里面拖入,看到拖入子控件的效果:
wid4
拖入子控件时,Widget 容器背景色变深,方格子点阵也临时隐藏,这时候松开鼠标,三个单选按钮就归属于 Widget 父级容器。如下图所示:
wid5
这种操作方式是先新建三个单选按钮、Widget容器,将三个单选按钮拖入父级容器。
下面我们再拖入一个 Widget 容器,大小也设置为 180*140,与上一个的容器放在不重叠位置:
wid6
然后在 Qt 设计师左边控件栏依次拖入三个单选按钮到窗口下半部分的 Widget 容器中,并设置文本为 “small”、“middle”、“large”:
wid7
从设计师左边栏目拖入控件进入下面容器之后,控件自动成为容器的子控件。我们点击容器的空白位置,然后可以拖动容器移动:
wid8
可以看到子控件自动锚定父容器,跟随父容器移动而移动。下面我们学习一个操作,就是为容器指定内部布局,我们点击上面容器内部的空白位置,或者在设计 师右上角 “对象查看器”直接点击选定该容器:
wid9
选中容器对象后,点击设计师上面的垂直布局按钮,然后可以看到容器内部变成带布局器的情况:
wid10
容器尺寸自动缩小了,因为布局器计算了子控件需要的最小长宽尺寸,父级容器自动缩小到最小的合适尺寸。
然后我们再选中窗口下面的容器,然后也是点击垂直布局按钮,形成如下的界面:
wid11
现在窗口就只有两个儿子控件,就是两个 Widget 容器,单选按钮就是孙子级的控件,孙子级控件由父级容器控制,而不需要主窗口管理布局。我们只需要对主窗口的两个儿子控件容器进行布局,我们选中根级的主窗口,然后再次点 击垂直布局按钮,就能完成窗口整体的布局:
wid12
完成整体布局后,我们点击设计师菜单“窗体”-->“预览”,或者按快捷键 Ctrl+R ,弹出预览窗口,我们点击六个单选按钮,可以看到前三个单选按钮互斥,后面三个单选按钮互斥,不同容器内部的单选按钮自动分为不同的组别:
wid13
Widget 容器的使用示范到这里,我们下面学习带边框 Frame 容器。

10.1.2 Frame 框架

Frame 容器的类名是 QFrame,是 QWidget 派生类,Frame 容器比基类就多了一个特点:拥有丰富的边框。QFrame 最常用的 4 个属性 如下:
(1)frameShape 边框类型
总共有 7 种类型,如下面表格所示:

常量 数值 描述
QFrame::NoFrame  0 QFrame 不绘制任何边框。
QFrame::Box  0x0001 QFrame 绘制盒子模型的边框。
QFrame::Panel  0x0002 QFrame 绘制一个面板,显示效果为凸起或者凹陷。
QFrame::StyledPanel  0x0006 根据当前 GUI 风格绘制一个矩形面板,可能是凸起或凹陷效果。默认类型为 StyledPanel 。
QFrame::HLine  0x0004 在矩形区域中间绘制一条水平线条,常用于分割线。
QFrame::VLine  0x0003 在矩形区域中间绘制一条垂直线条,常用于分割线。
QFrame::WinPanel  0x0003 绘制一个矩形面板,像Windows2000风格的边框,可能时凸起或凹陷效果。 line width 固定为 2 像素。 WinPanel 用于兼容旧系统,正常情况下应该使用 StyledPanel 类型。

(2)frameShadow 边框阴影类型

常量 数值 描述
QFrame::Plain  0x0010 简单绘制矩形边框没有 3D 阴影效果,使用的调色板是 QPalette::WindowText 颜色。
QFrame::Raised  0x0020 边框与中间内容呈现凸起效果,使用当前颜色组的亮色和暗色配色绘制 3D 凸起线条。
QFrame::Sunken  0x0030 边框与中间内容呈现凹陷效果,使用当前颜色组的亮色和暗色配色绘制 3D 凹陷线条。

(3)lineWidth 线条宽度
绘制边框线条的宽度,默认值为  1,不同的边框类型、阴影类型效果不同。注意如果边框类型是  HLine 和 VLine 分割线,那么分割线的宽度使用 frameWidth 来设置。

(4)midLineWidth 中线宽度
绘制边框条的中间线,默认值为 0 ,如果绘制边框条的中线,不同的边框类型、阴影类型效果不同。

QFrame 绘制边框的效果主要根据上面几个属性来决定,多种属性决定绘制效果的多样性,绘制边框的示例图如下所示:
draw
实际应用时,可以自行观察效果,然后决定这些属性怎样设置。
QFrame 作为控件容器,功能和上面的 QWidget 容器差不多,只是多了边框绘图效果。
下面我们通过一个简单示例程序,学习一下 QWidget 和 QFrame 两个容器的使用。
我们打开 QtCreator,新建一个 Qt Widgets Application 项目,在新建项目的向导里填写:
①项目名称 frameshow,创建路径 D:\QtProjects\ch10,点击下一步;
②套件选择里面选择全部套件,点击下一步;
③基类选择 QWidget,点击下一步;
④项目管理不修改,点击完成。
打开 widget.ui 文件进入图形化编辑界面,构造界面如下图所示:
ui1
界面左上角是一个 Widget 容器,容器对象名为 widget1,里面放置 4 个单选按钮:
“Null”单选按钮 radioButtonNull,“Red”单选按钮 radioButtonRed、“Blue”单选按钮 radioButtonBlue、“Green”单选按钮 radioButtonGreen 。
界面右上角也是一个 Widget 容器,容器对象名为 widget2,里面放置 8 个控件:
第一行是“LineWidth”标签、旋钮编辑框 spinBoxLineWidth;
第二行是“MidLineWidth”标签、旋钮编辑框 spinBoxMidLineWidth;
第三行是“FrameShape”标签、组合框 comboBoxFrameShape;
第四行是“FrameShadow”标签、组合框 comboBoxFrameShadow。
对于左上角的容器,我们选中 widget1 对象,然后点击上面的垂直布局按钮,进行垂直布局:
ui2
对于右上角的容器,我们选中 widget2 对象,然后点击上面的网格布局(即栅格布局)按钮,进行网格布局:
ui3
然后我们按Ctrl键,鼠标选中 widget1 和 widget2 两个容器,进行水平布局:
ui4
现在上半部分布局完成,界面下半部分是一个 Frame 框架,我们将该对象改名为 frameTest,
然后选中主窗口,点击数上面垂直布局,就完成了所有的布局工作:
ui5
我们将主窗口的尺寸设置为320*320,这样就完成了界面的布局和编辑。
接下来我们右击左上角 4 个单选按钮,为单选按钮添加 clicked 信号对应的槽函数:
slot1
然后依次右击 2 个线宽的旋钮编辑框 ,添加 valueChanged(int) 信号对应的槽函数:
slot2
然后依次右击 2 个组合框,添加 currentIndexChanged(int) 信号对应的槽函数:
slot3
总共添加了 8 个控件对应的槽函数。槽函数添加完成后,下面我们开始编辑头文件 widget.h 的代码:
#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>
#include <QFrame>
#include <QList>

namespace Ui {
class Widget;
}

class Widget : public QWidget
{
    Q_OBJECT

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

private slots:
    void on_radioButtonNull_clicked();

    void on_radioButtonRed_clicked();

    void on_radioButtonBlue_clicked();

    void on_radioButtonGreen_clicked();

    void on_spinBoxLineWidth_valueChanged(int arg1);

    void on_spinBoxMidLineWidth_valueChanged(int arg1);

    void on_comboBoxFrameShape_currentIndexChanged(int index);

    void on_comboBoxFrameShadow_currentIndexChanged(int index);

private:
    Ui::Widget *ui;
    //保存 FrameShape
    QList<QFrame::Shape> m_listFrameShape;
    //保存 FrameShadow
    QList<QFrame::Shadow> m_listFrameShadow;
};

#endif // WIDGET_H
我们添加了头文件包含,并为窗口类添加了一个初始化控件函数 InitControls(),添加两个列表变量,m_listFrameShape 用于存储框架形状的常量列表,m_listFrameShadow 用于存储框架阴影的常量列表。其他代码都是自动生成的,包含 8 个槽函数。
接下来我们编辑源文件 widget.cpp 的代码,首先是构造函数和初始化控件函数部分:
#include "widget.h"
#include "ui_widget.h"
#include <QMessageBox>
#include <QDebug>

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

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

//初始化控件
void Widget::InitControls()
{
    //单选框默认选择 Null
    ui->radioButtonNull->setChecked( true );
    // Line Width 旋钮框
    ui->spinBoxLineWidth->setRange( 0, 27 );
    ui->spinBoxLineWidth->setValue( 1 );
    // Mid Line Width旋钮框
    ui->spinBoxMidLineWidth->setRange( 0, 27 );
    ui->spinBoxMidLineWidth->setValue( 0 );
    //Frame Shape 列表
    m_listFrameShape<<QFrame::NoFrame<<QFrame::Box
        <<QFrame::Panel<<QFrame::WinPanel
        <<QFrame::HLine<<QFrame::VLine
        <<QFrame::StyledPanel;
    //Frame Shape组合框条目
    ui->comboBoxFrameShape->addItem( "NoFrame" );
    ui->comboBoxFrameShape->addItem( "Box" );
    ui->comboBoxFrameShape->addItem( "Panel" );
    ui->comboBoxFrameShape->addItem( "WinPanel" );
    ui->comboBoxFrameShape->addItem( "HLine" );
    ui->comboBoxFrameShape->addItem( "VLine" );
    ui->comboBoxFrameShape->addItem( "StyledPanel" );
    //默认是 StyledPanel
    ui->comboBoxFrameShape->setCurrentIndex( 6 );
    //Frame Shadow列表
    m_listFrameShadow<<QFrame::Plain
        <<QFrame::Raised<<QFrame::Sunken;
    //Frame Shadow组合框条目
    ui->comboBoxFrameShadow->addItem( "Plain" );
    ui->comboBoxFrameShadow->addItem( "Raised" );
    ui->comboBoxFrameShadow->addItem( "Sunken" );
    //默认 Raised
    ui->comboBoxFrameShadow->setCurrentIndex( 1 );
}
构造函数添加了一行代码,调用 InitControls() 函数。
InitControls() 函数内部,我们先设置勾选 “Null”单选框,默认就是没有设置背景颜色。四个单选按钮的功能就是设置 frameTest 对象的背景颜色,方便与窗口默认背景区分开来。
然后我们设置 spinBoxLineWidth 旋钮框的数值范围 0~27,默认值 1;
设置 spinBoxMidLineWidth 旋钮框的数值范围 0~27,默认值 0。
接下来我们填充 m_listFrameShape 列表,将 7 种形状的常量值都填充到列表,然后我们按照相同的顺序为组合框 comboBoxFrameShape 添加文本条目,与 m_listFrameShape 列表的 7 个形状常量顺序一致;设置组合框默认的形状为 6 号 "StyledPanel" 。
接下里我们填充 m_listFrameShadow 列表,将 3 个阴影的常量值填充到列表,然后我们按照相同的顺序为组合框 comboBoxFrameShadow 添加文本条目,与 m_listFrameShadow 列表中的 3 个阴影常量顺序一致,最后设置组合框默认的阴影为 1号 "Raised" 。这些默认值是设计界面拖入 QFrame 容器时的属性默认值。

下面我们编辑 4 个单选按钮对应的槽函数:
//无背景色
void Widget::on_radioButtonNull_clicked()
{
    ui->frameTest->setStyleSheet("");
}
//红色背景
void Widget::on_radioButtonRed_clicked()
{
    ui->frameTest->setStyleSheet("background-color: rgb(255, 0, 0);");
}
//蓝色背景
void Widget::on_radioButtonBlue_clicked()
{
    ui->frameTest->setStyleSheet("background-color: rgb(0, 0, 255);");
}
//绿色背景
void Widget::on_radioButtonGreen_clicked()
{
    ui->frameTest->setStyleSheet("background-color: rgb(0, 255, 0);");
}
单选按钮功能就是设置 frameTest 对象的背景色样式表,第一个是不设置样式表,第二个是红色,第三个是蓝色,第四个是绿色。
接下来我们编辑 2 个设置线宽的槽函数:
//设置 LineWidth
void Widget::on_spinBoxLineWidth_valueChanged(int arg1)
{
    ui->frameTest->setLineWidth( arg1 );
}
//设置 MidLineWidth
void Widget::on_spinBoxMidLineWidth_valueChanged(int arg1)
{
    ui->frameTest->setMidLineWidth( arg1 );
}
当旋钮编辑框的数值改变时,自动根据新数值设置 frameTest 的 LineWidth 和 MidLineWidth 两种线宽。
//设置 FrameShape
void Widget::on_comboBoxFrameShape_currentIndexChanged(int index)
{
    if( index < 0 ) return;
    //设置 Shape
    ui->frameTest->setFrameShape( m_listFrameShape[index] );
}
//设置 FrameShadow
void Widget::on_comboBoxFrameShadow_currentIndexChanged(int index)
{
    if( index < 0 ) return;
    //设置 Shadow
    ui->frameTest->setFrameShadow( m_listFrameShadow[index] );
}
注意组合框的 currentIndexChanged(int)  信号,参数里的序号可能出现 -1,属于非法序号,不能当做列表对象的元素序号,因此需要做判断,如果序号为负数,那么直接返回;如果是合法序号,那么根据序号获取列表对象中保存的常量数值,设置给 frameTest 对象。
组合框选择的条目变化时,程序自动设置 frameTest 对象的框架形状或框架阴影。
示例程序代码讲解到这,我们生成项目,运行该示例程序,读者可以改变界面的控件数值,测试 frameTest 显示的框架效果:
run
Widget 和 Frame 容器内容讲解到这,我们下面学习 QGroupBox 分组框。

10.1.3 Group Box 分组框

QGroupBox 分组框也是 QWidget 的派生类,绘制矩形边框,并添加专门的标题栏,尤其适合描述某一类功能, 将该类功能的名字显示到分组框标题,子控件放在分组框内部集中放置。分组框还带有 checkable 属性, 设置 checkable 为 True 之后,分组框标题带有一个复选框,如果选中标题复选框, 代表启用分组框和子控件,如果不选中标题复选框,那么分组框和子控件全部设置为禁用状态, 适合集体启用或集体禁用批量组件。以网络连接为例,连接接通了之后,我们可以启用发送数据、接收数据、关闭连接多个按钮, 这些按钮只能在连接接通时启用,而关闭连接后,这些按钮就必须禁用,避免误操作。可以使用分组框同时管理多个子控件的启用和禁用, 这样就不需要一个个去调用子控件的启用或禁用代码。
分组框的 5 个属性如下表所示:

属性 类型 描述
alignment  Qt::Alignment 标题文本的对齐方式:Qt::AlignLeft 左对齐,Qt::AlignRight 右对齐,Qt::AlignHCenter 水平居中对齐。
checkable  bool 是否显示标题栏的复选框,如果为true,显示标题复选框,并设置 checked 为 true,启用状态。
checked  bool 标题复选框的勾选状态,true表示启用复选框和子控件,false表示禁用复选框和子控件。
flat  bool 边框效果,如果flat 为true,平面效果,不绘制边框;如果为false,绘制凹陷效果的边框,默认状态是绘制边框。
title  QString 分组框标题栏文本。

注意 checked 复选框状态仅在 checkable 为 true 时生效;如果 checkable 为false,那么 checked 数值没有任何意义。
分组框的主要特性就是以上 5 个属性的设置,其他功能都是从 QWdiget 继承而来。
下面我们学习一个分组框使用的例子,模拟新建TCP连接,然后有发送数据、接收数据、关闭连接等功能,模拟连接的操作,并不真的建立网络连接,只是学习分组框的用法。
我们打开 QtCreator,新建一个 Qt Widgets Application 项目,在新建项目的向导里填写:
①项目名称 tcpgroupbox,创建路径 D:\QtProjects\ch10,点击下一步;
②套件选择里面选择全部套件,点击下一步;
③基类选择 QWidget,点击下一步;
④项目管理不修改,点击完成。
打开 widget.ui 文件进入图形化编辑界面,构造界面如下图所示:
ui1
在界面上部,拖入一个分组框,对象名 groupBoxIP,标题文本 “IP”,在该分组框内部拖入三个单选按钮:
“192.168.1.1”单选按钮 radioButtonIP1,“192.168.1.2”单选按钮 radioButtonIP2,“192.168.1.3”单选按钮 radioButtonIP3。
界面中部拖入一个分组框,对象名 groupBoxPort,标题文本 “Port”,在该分组框内部拖入三个单选按钮:
“80”单选按钮 radioButtonPort1,“443”单选按钮 radioButtonPort2,“8080”单选按钮 radioButtonPort3。
界面底部左边拖入一个按钮,“新建连接”按钮 pushButtonConnect;
底部右边拖入一个分组框,对象名 groupBoxConnection,文本为“连接操作”,在该分组框内部拖入三个按钮:
“发送数据”按钮 pushButtonSend,“接收数据”按钮 pushButtonRecv,“关闭连接”按钮 pushButtonClose。
拖入上面控件之后,我们开始布局操作。
在右边对象视图选中 groupBoxIP,然后点击上面水平布局按钮,实现 IP分组框的布局:
lay1
然后我们在右边对象视图选中 groupBoxPort,点击上面水平布局按钮,实现 Port 分组框布局:
lay2
然后我们选中 groupBoxConnection 对象,点击上面的垂直布局按钮,实现垂直布局:
lay3
接着我们按下 Ctrl 键,在右边对象视图依次选中 pushButtonConnect 和 groupBoxConnection,同时选中下部的按钮和分组框后, 我们点击上面的水平布局按钮,进行水平布局:
lay4
最后我们选中主窗口节点 Widget 对象,点击上面的垂直布局按钮,实现窗口整体布局:
lay5
主窗口大小设置为 400*300,这样界面布局工作完成。
我们右击各个单选按钮和按压按钮添加 clicked 信号的槽函数,界面 6 个单选按钮和 4 个按压按钮都添加对应的一个槽函数:
slots
槽函数添加完成后,我们开始编辑代码。
我们打开 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_radioButtonIP1_clicked();

    void on_radioButtonIP2_clicked();

    void on_radioButtonIP3_clicked();

    void on_radioButtonPort1_clicked();

    void on_radioButtonPort2_clicked();

    void on_radioButtonPort3_clicked();

    void on_pushButtonConnect_clicked();

    void on_pushButtonSend_clicked();

    void on_pushButtonRecv_clicked();

    void on_pushButtonClose_clicked();

private:
    Ui::Widget *ui;
    //保存IP和端口
    QString m_strIP;
    int m_nPort;
};

#endif // WIDGET_H
我们为窗口类添加 InitControls() 函数用于初始化各个控件;
添加 m_strIP 和 m_nPort 保存TCP连接需要的IP地址和端口号,其他代码都是自动生成的,包括 10 个槽函数。
下面我们分段编辑源文件 widget.cpp 的代码,首先是初始化部分:
#include "widget.h"
#include "ui_widget.h"
#include <QMessageBox>
#include <QDebug>

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

Widget::~Widget()
{
    delete ui;
}
//初始化控件
void Widget::InitControls()
{
    //默认使用 IP 192.168.1.1
    m_strIP = tr("192.168.1.1");
    ui->radioButtonIP1->setChecked(true);
    //默认使用端口 80
    m_nPort = 80;
    ui->radioButtonPort1->setChecked(true);

    //显示分组框标题的复选框,默认启用IP分组框、Port分组框
    ui->groupBoxIP->setCheckable( true );
    ui->groupBoxPort->setCheckable( true );
    //显示连接操作分组框的复选框
    ui->groupBoxConnection->setCheckable( true );
    //禁用连接操作分组框
    ui->groupBoxConnection->setChecked( false );
}
构造函数里添加了初始化函数 InitControls() 调用,在 InitControls() 里面,我们设置默认的 IP 地址字符串为 "192.168.1.1",并勾选对应的IP单选按钮,设置默认端口 80,勾选对应的单选按钮;
然后我们设置 IP分组框、Port分组框、连接操作分组框的 checkable 属性为 true,显示三个分组框标题栏复选按钮;
然后我们设置 连接操作分组框的 checked 属性为 false,默认禁用了连接操作分组框及其子控件,因为连接没有建立,这些操作无法执行。

下面我们编辑 6 个单选按钮对应的槽函数:
//三个IP单选框,设置IP地址,分组框内部的单选按钮自动成为一个分组,分组内的单选按钮互斥
void Widget::on_radioButtonIP1_clicked()
{
    m_strIP = tr("192.168.1.1");
}

void Widget::on_radioButtonIP2_clicked()
{
    m_strIP = tr("192.168.1.2");
}

void Widget::on_radioButtonIP3_clicked()
{
    m_strIP = tr("192.168.1.3");
}
//三个Port单选框,设置端口,分组框内部的单选按钮自动成为一个分组,分组内的单选按钮互斥
void Widget::on_radioButtonPort1_clicked()
{
    m_nPort = 80;
}

void Widget::on_radioButtonPort2_clicked()
{
    m_nPort = 443;
}

void Widget::on_radioButtonPort3_clicked()
{
    m_nPort = 8080;
}
单选按钮的槽函数功能很简单,就是设置各自的 IP 字符串、端口号。
接下来我们编辑“新建连接”按钮对应的槽函数:
//模拟新建一个连接
void Widget::on_pushButtonConnect_clicked()
{
    QString strInfo = tr("新建连接成功:\r\nIP:%1\r\nPort:%2").arg(m_strIP).arg(m_nPort);
    QMessageBox::information(this, tr("新建连接"), strInfo);

    //禁用IP和端口分组框
    ui->groupBoxIP->setChecked( false );
    ui->groupBoxPort->setChecked( false );
    //禁用新建连接按钮
    ui->pushButtonConnect->setEnabled( false );

    //启用连接操作分组框
    ui->groupBoxConnection->setChecked( true );
}
我们构造信息字符串,提示新建连接成功,并提示IP地址和端口号信息,弹出信息框显示出来;
然后我们设置 IP 分组框为禁用、Port 分组框为禁用、pushButtonConnect 按钮为禁用,因为建立连接后,不能再修改IP和端口号,在关闭连接之前,我们也不使用第二个连接,所以把“新建连接”按钮也禁用了。
最后我们把连接操作分组框启用,建立连接之后,我们就可以进行发送数据、接收数据和关闭连接操作。
接下来我们编辑“发送数据”、“接收数据”按钮对应的槽函数:
//模拟发送数据,弹出信息框显示
void Widget::on_pushButtonSend_clicked()
{
    QMessageBox::information(this, tr("发送数据"), tr("已发送数据。"));
}
//模拟接收数据,弹出信息框提示
void Widget::on_pushButtonRecv_clicked()
{
    QMessageBox::information(this, tr("接收数据"), tr("已接收数据。"));
}
这两个槽函数只是简单提示信息,不进行实际的网络收发操作。
最后我们编辑“关闭连接”按钮对应的槽函数代码:
//关闭连接的操作模拟
void Widget::on_pushButtonClose_clicked()
{
    //显示
    QMessageBox::information(this, tr("关闭连接"), tr("已关闭连接。"));
    //启用IP和Port分组框
    ui->groupBoxIP->setChecked( true );
    ui->groupBoxPort->setChecked( true );
    //启用新建连接按钮
    ui->pushButtonConnect->setEnabled( true );

    //禁用连接操作分组框
    ui->groupBoxConnection->setChecked( false );
}
我们弹出信息框,显示关闭连接;
然后设置IP分组框、Port分组框、pushButtonConnect 按钮为启用状态;
设置连接操作分组框为禁用状态,这样恢复到初始的未建立连接状态。
代码编辑好之后,我们生成项目,运行示例程序,点击“新建连接”按钮:
run1
程序弹出信息框显示新建连接成功,我们点击“OK”按钮关闭信息框,可以看到主界面的变化:
run2
建立连接后,IP分组框、Port分组框、“新建连接”按钮都自动禁用了,而连接操作分组框就启用了。
当我们点击“关闭连接”按钮,关闭信息框之后,主界面就恢复到初始的未连接状态:
run3

其他按钮功能请读者自行测试,这里专门讲解一下启用和禁用控件的问题。
示例程序中,“新建连接”按钮和“关闭连接”按钮代码都对界面的三个分组框和“新建连接”按钮的可用状态进行设置,“新建连接”按钮和“关闭连接”按钮的功能正好相反。
实际应用中可以将 三个分组框和“新建连接”按钮的启用或禁用功能写成一个函数,例如:
//设置新建连接按钮启用,或禁用
void Widget::setConButtonEnabled(bool bEnable)
{
    //以 bEnable  true 启用时为例,如果是禁用那么输入参数设置为 false 即可
    //启用IP和Port分组框
    ui->groupBoxIP->setChecked( bEnable );
    ui->groupBoxPort->setChecked( bEnable );
    //启用新建连接按钮
    ui->pushButtonConnect->setEnabled( bEnable );

    //禁用连接操作分组框
    ui->groupBoxConnection->setChecked( ! bEnable );
}
将启用或禁用多个控件的代码集中到一个函数的好处有两个:
第一,简化代码,“新建连接”按钮和“关闭连接”按钮代码只需要调用 setConButtonEnabled() 就可以修改多个控件的状态,不需要穷举多个控件;
第二,不会出现遗漏,因为“新建连接”按钮和“关闭连接”按钮两个地方都设置这些控件的状态,并且正好是取反的操作,在多处出现功能相同或取反的代码时,使用同一函数进行操作,避免修改代码时遗漏其他地方需要同步修改的代码,减少程序 bug。
本节内容讲解到这,我们下一节学习容纳能力更强的控件容器。



prev
contents
next