Qt 4开发实践
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

3.5 控件

本节简单介绍一下常用的控件,以便对Qt的控件有一个初步认识,其具体的用法在后面章节用到的时候会详细介绍。

3.5.1 按钮组(Buttons)

按钮组(Buttons)如图3.1所示。

图3.1 按钮组(Buttons)

按钮组(Buttons)中各按钮的名称依次是:

● Push Button:按钮

● Tool Button:工具按钮

● Radio Button:单选按钮

● Check Box:复选框

● Command Link Button:命令链接按钮

● Button Box:按钮盒

下面以QPushButton为例介绍按钮的用法,其他按钮的用法与此类似。

(1) 建立一个工程。在创建过程中,在“Qt4 Gui Application”界面中,在“Name”后边的文本框中输入“PushButtonTest”,选择工程要保存的路径(注意,路径中不要有中文字符),单击“Next”按钮,再单击“Next”按钮,在“Base Class”下拉列表框中选择“QWidget”选项,在“Class name”后面的文本框中输入“MyWidget”,取消“Gernerate form”复选框的选中状态,单击“Next”按钮,单击“Finish”按钮。

(2) 在头文件mywidget.h中的具体代码如下:

        #ifndef MYWIDGET_H
        #define MYWIDGET_H
        #include <QtGui/QWidget>
        class MyWidget : public QWidget
        {
            Q_OBJECT
        public:
            MyWidget(QWidget *parent = 0);
            ~MyWidget();
        };
        #endif // MYWIDGET_H

(3) 在源文件mywidget.cpp中的具体代码如下:

        #include "mywidget.h"
        #include <qapplication.h>
        #include <qpushbutton.h>
        #include <qfont.h>
        MyWidget::MyWidget(QWidget *parent)
            : QWidget(parent)
        {
              setMinimumSize( 200, 120 );
              setMaximumSize( 200, 120 );
              QPushButton *quit = new QPushButton( "Quit", this);
              quit->setGeometry( 62, 40, 75, 30 );
              quit->setFont( QFont( "Times", 18, QFont::Bold ) );
              connect( quit, SIGNAL(clicked()), qApp, SLOT(quit()) );
        }
        MyWidget::~MyWidget()
        {
        }

(4) 在源文件main.cpp中的具体代码如下:

        #include <QtGui/QApplication>
      #include "mywidget.h"
      int main(int argc, char *argv[])
      {
          QApplication a(argc, argv);
          MyWidget w;
          w.setGeometry( 100, 100, 200, 120 );
          w.show();
          return a.exec();
      }

(5) 运行结果如图3.2所示。

图3.2 QPushButton实例

3.5.2 输入部件组(Input Widgets)

输入部件组(Input Widgets)如图3.3所示。

图3.3 输入部件组(Input Widgets)

输入部件组(Input Widgets)中各部件的名称依次是:

● Combo Box:组合框

● Font Combo Box:字体组合框

● Line Edit:行编辑

● Text Edit:文本编辑

输入部件组(Input Widgets)中各部件的名称依次是:

● Plain Text Edit:纯文本编辑

● Spin Box:数字显示框 (自旋盒)

● Double Spin Box:双自旋盒

● Time Edit:时间编辑

● Date Edit:日期编辑

● Date/Time Edit:日期/时间编辑

● Dial:拨号

● Horizontal Acroll Bar:横向滚动条

● Vertial Scroll Bar:垂直滚动条

● Horizontal Slider:横向滑块

● Vertial Slider:垂直滑块

下面介绍其中几个。

1.QDateTime类

在Qt4中,可以用QDateTime类来获得系统时间。通过QDateTime::currentDateTime ()来获取本地系统的时间和日期信号。可以通过date()和time()来返回datetime中的日期和时间部分。以下是一个测试程序的例子:

        QLabel * datalabel =new QLabel();
        QDateTime *datatime=new QDateTime(QDateTime::currentDateTime());
        datalabel->setText(datatime->date().toString());
        datalabel->show();

2.QTimer类

定时器(Qtimer)的使用非常简单,只需要以下几个步骤就可以完成定时器的应用:

(1) 产生一个定时器。

        QTimer *time_clock=new QTimer(parent);

(2) 连接这个定时器的信号和槽,利用定时器的timeout()。

        connect(time_clock,SIGNAL(timeout()),this,SLOT(slottimedone()));

即定时时间一到就会发送timeout()信号,从而触发slottimedone()槽去完成某件事情。

(3) 开始定时器,并设定定时周期。

定时器定时有两种:start(int time)和setSingleShot(true)。其中start(int time)表示每隔“time”秒就会重启定时器,可以重复触发定时,利用stop()将定时器关掉;而setSingleShot(true)则是仅仅启动定时器一次。工程中常用的是前者:

        time_clock->start(2000);

3.5.3 显示控件组(Display Widgets)

显示控件组(Display Widgets)如图3.4所示。

图3.4 显示控件组(Display Widgets)

显示控件组(Display Widgets)中各控件的名称依次为:

● Label:标签

● Text Browser:文本浏览器

● Graphics View:图形视图

● Calendar:日历

● LCD Number:液晶数字

● Progress Bar:进度条

● Horizontal Line:水平线

● Vertical Line:垂直线

● QWebView:Web视图

下面介绍其中几个。

(1) Graphics View对应于QGraphicsView类,其具体用法将在第8章详细介绍。

(2) Text Browser对应于QTextBrowser类。QTextBrowser继承自QTextEdit,而且仅仅是只读的,对里面的内容并不能进行更改,但是相对于QTextEdit来讲,它还具有链接文本的作用。QTextBrowser的属性有以下几点:

        modified : const bool         //通过布尔值来说明其内容是否被修改
        openExternalLinks : bool
        openLinks : bool
        readOnly : const bool
        searchPaths : QStringList
        source : QUrl
        undoRedoEnabled : const bool

通过以上的属性设置,可以设定QTextBrowser是否允许外部链接,是否为只读属性、外部链接的路径以及链接的内容,是否可以进行撤销等操作。

QTextBrowser还提供了几种比较有用的槽(SLOTS)。即

        virtual void backward ()
        virtual void forward ()
        virtual void home ()

可以通过链接这几个槽来达到人们常说的“翻页”效果。

3.5.4 空间间隔组(Spacers)

空间间隔组(Spacers)如图3.5所示。

图3.5 空间间隔组(Spacers)

空间间隔组(Spacers)中各控件的名称依次为:

● Horizontal Spacer:水平间隔

● Vertical Spacer:垂直间隔

具体应用见3.5.9小综合例子。

3.5.5 布局管理组(Layout)

布局管理组(Layouts)如图3.6所示。

图3.6 布局管理组(Layouts)

布局管理组(Layouts)中各控件的名称依次为:

● Vertical Layout:垂直布局

● Horizontal Layout:横向(水平)布局

● Grid Layout:网格布局

● Form Layout:表格布局

具体应用见3.5.9小综合例子。

3.5.6 容器组(Containers)

容器组(Containers)如图3.7所示。

图3.7 容器组(Containers)

容器组(Containers)中各控件的名称依次为:

● Group Box:组框

● Scroll Area:滚动区域

● Tool Box:工具箱

● Tab Widget:标签小部件

● Atacked Widget:堆叠部件

● Frame:帧

● Widget:小部件

● MdiArea:MDI的地区

● Dock Widget:码头部件

下面介绍一下Widget对应QWidget类的用法。Widget是使用Qt编写的图形用户界面(GUI)应用程序的基本生成块。每个GUI组件,如按钮、标签或文本编辑器,都是一个Widget,并可以放置在现有的用户界面中或作为单独的窗口显示。每种类型的组件都是由QWidget的特殊子类提供的,而QWidget自身又是QObject的子类。

QWidget是所有Qt GUI界面类的基类,它接受鼠标、键盘以及其他窗口事件,并在显示器上绘制自己。

通过传入QWidget构造函数的参数(或者调用QWidget::setWindowFlags()和QWidget::setParent()函数)可以指定一个窗口部件的窗口标志(window flags)和父窗口部件。

窗口部件的窗口标志(window flags)定义了窗口部件的窗口类型和窗口提示(hint)。窗口类型指定了窗口部件的窗口系统属性(window-system properties),一个窗口部件只有一个窗口类型。窗口提示定义了顶层窗口的外观,一个窗口可以有多个提示(提示能够进行按位或操作)。

没有父窗口部件的Widget对象是一个窗口,窗口通常具有一个窗口边框(frame)和一个标题栏。QMainWindow和所有的QDialog对话框子类都是经常使用的窗口类型。而子窗口部件通常处在父窗口部件的内部,没有窗口边框和标题栏。

QWidget窗口部件的构造函数:

        QWidget(QWidget *parent=0,Qt::WindowFlags f=0)

其中:

参数parent:指定了窗口部件的父窗口部件,如果parent=0(默认值),新建的窗口部件将是一个窗口;否则,新建的窗口部件是parent的子窗口部件(是否是一个窗口还需要第2个参数决定)。如果新窗口部件不是一个窗口,它将会出现在父窗口部件的界面内部。

参数f:指定了新窗口部件的窗口标志,默认值是0,即Qt::Widget。

QWidget定义的窗口类型为Qt::WindowFlags枚举类型,它们的可用性依赖于窗口管理器是否支持它们。

QWidget不是一个抽象类,它可用做其他Widget的容器,并很容易作为子类使用来创建定制Widget。它经常用来创建放置其他Widget的窗口。

至于QObject,可使用父对象创建Widget以表明其所属关系,这可确保删除不再使用的对象。使用Widget,这些父子关系就有了更多的意义,每个子类都显示在其父级所拥有的屏幕区域内。也就是说,当删除窗口时,其包含的所有Widget也都自动删除。

1.创建窗口

如果widget未使用父级进行创建,则在显示时视为窗口或顶层Widget。由于顶层Widget没有父级对象类来确保在其不再使用时就删除,因此需要开发人员在应用程序中对其进行跟踪。

在下例中,使用QWidget创建和显示具有默认大小的窗口:

        QWidget *window = new QWidget();
        window->resize(320, 240);
        window->show();
        QPushButton *button = new QPushButton(tr("Press me"), window);
        button->move(100, 100);
        button->show();

其中:

● 通过将window作为父级传递给其构造器来向窗口添加子Widget:button。在这种情况下,向窗口添加按钮并将其放置在特定位置。

● 该按钮现在为窗口的子项,并在删除窗口时一同删除。请注意,隐藏或关闭窗口不会自动删除该按钮。

2.使用布局

通常,子Widget是通过使用布局对象在窗口中进行排列的,而不是通过指定位置和大小进行排列的。在此,构造一个要并排排列的标签和行编辑框Widget:

        QLabel *label = new QLabel(tr("Name:"));
        QLineEdit *lineEdit = new QLineEdit();
        QHBoxLayout *layout = new QHBoxLayout();
        layout->addWidget(label);
        layout->addWidget(lineEdit);
        window->setLayout(layout);

构造的布局对象管理通过addWidget()函数提供Widget的位置和大小。布局本身是通过调用setLayout()提供给窗口的。布局仅可通过其对所管理的Widget(和其他布局)的效果才可显示。

在上文示例中,每个Widget的所属关系并不明显。由于未使用父级对象构造Widget和布局,会看到一个空窗口和两个包含了标签与行编辑框的窗口。如果通过布局来管理标签和行编辑框,并在窗口中设置布局,两个Widget与布局本身就都会成为窗口的子项。

由于Widget可包含其他Widget,布局可用来提供按不同层次分组的Widget。这里,要在显示查询结果的表视图上方、窗口顶部的行编辑框旁,显示一个标签:

        QLabel *queryLabel = new QLabel(tr("Query:"));
        QLineEdit *queryEdit = new QLineEdit();
        QTableView *resultView = new QTableView();
        QHBoxLayout *queryLayout = new QHBoxLayout();
        queryLayout->addWidget(queryLabel);
        queryLayout->addWidget(queryEdit);
        QVBoxLayout *mainLayout = new QVBoxLayout();
        mainLayout->addLayout(queryLayout);
        mainLayout->addWidget(resultView);
        window->setLayout(mainLayout);

除了QHBoxLayout和QVBoxLayout,Qt还提供了QGridLayout和QFormLayout类来协助实现更复杂的用户界面。

3.5.7 项目视图组(Item Views)

项目视图组(Item Views)如图3.8所示。

图3.8 项目视图组(Item Views)

项目视图组(Item Views)中各控件的名称依次为:

● List View:清单视图

● Tree View:树视图

● Table View:表视图

● Column View:专栏视图

下面介绍一下此处的Table View与下一节中的Table Widget的区别。具体区别如表3.10所示。

表3.10 QTableView与QTableWidget的具体区别

Qt4中引入了模型/视图框架来完成数据与表现的分离,这在Qt4中称为InterView框架,类似于常用的MVC设计模式。

MVC设计模式是起源于Smalltalk的一种与用户界面相关的设计模式。MVC包括3个元素:模型(Model)表示数据,视图(View)是用户界面,控制(Controler)定义了用户在界面上的操作。通过使用MVC模式,有效的分离了数据和用户界面,使得设计更为灵活,更能适应变化。

模型:所有的模型都基于QAbstractItemModel类,该类是抽象基类。

视图:所有的视图都从抽象基类QAbstractItemView继承。

InterView框架提供了一些常见的模型类和视图类,例如QStandardItemModel、QDirModel、QStringListModel、QProxyModel和QColumnView、QHeaderView、QListView、QTableView、QTreeView。其中,QDirModel可以以树状方式显示某个目录下的所有子目录,以及其相关信息;QProxyModel用来将旧的Model类型过渡到新类型上;QStandardItemModel是最简单的Grid方式显示Model。另外,还可以自己从QAbstractListModel、 QAbstractProxyModel、 QAbstractTableModel来继承出符合自己要求的model。其具体的用法将在第9章详细讲解。

相对于使用现有的模型和视图,Qt还提供了更为便捷的类来处理常见的一些数据模型。它们将模型和视图合一,便于处理一些常规的数据类型。使用这些类型虽然简单方便,但也失去了模型视图结构的灵活性,所以要根据具体情况来选择。

QTableWidget继承自QTableView。QSqlTableModel能与QTableView绑定,但不能与QTableWidget绑定。如下:

        QSqlTableModel *model = new QSqlTableModel;
        model->setTable("employee");
        model->setEditStrategy(QSqlTableModel::OnManualSubmit);
        model->select();
        model->removeColumn(0); // don't show the ID
        model->setHeaderData(0, Qt::Horizontal, tr("Name"));
        model->setHeaderData(1, Qt::Horizontal, tr("Salary"));
      QTableView *view = new QTableView;
      view->setModel(model);
      view->show();

视图与模型绑定时,模型必须使用new来创建,否则视图不能随着模型的改变而改变。

下面是错误的写法:

      QStandardItemModel model(4,2);
      model.setHeaderData(0, Qt::Horizontal, tr("Label"));
      model.setHeaderData(1, Qt::Horizontal, tr("Quantity"));
      ui.tableView->setModel(&model);
      for (int row = 0; row < 4; ++row)
      {
            for (int column = 0; column < 2; ++column)
            {
                  QModelIndex index = model.index(row, column, QModelIndex());
                  model.setData(index, QVariant((row+1) * (column+1)));
      }
      }

下面是正确的写法:

      QStandardItemModel *model;
      model = new QStandardItemModel(4,2);
      ui.tableView->setModel(model);
      model->setHeaderData(0, Qt::Horizontal, tr("Label"));
      model->setHeaderData(1, Qt::Horizontal, tr("Quantity"));
      for (int row = 0; row < 4; ++row)
      {
          for (int column = 0; column < 2; ++column)
          {
              QModelIndex index = model->index(row, column, QModelIndex());
              model->setData(index, QVariant((row+1) * (column+1)));
          }
      }

3.5.8 项目控件组(Item Widgets)

项目控件组(Item Widgets)如图3.9所示。

图3.9 项目控件组(Item Widgets)

项目控件组(Item Widgets)中各控件的名称依次为:

● List Widget:清单控件

● Tree Widget:树形控件

● Table Widget:表控件

下面以如何创建具有复选框的树形控件为例说明以上用法。在Qt中树形控件的名称叫做QTreeWidget,而控件里的树节点的名称叫做QTreeWidgetItem。这种控件其实有时很有用处,如飞信群发短信时的选择联系人的界面中就使用了有复选框的树形控件等(如图3.10所示)。

图3.10 有复选框的树形控件(QtreeWidget)

当选中顶层的树形节点时,子节点全部被选中;当取消顶层树形节点时,子节点全部被取消选中状态,而当选中子节点时,父节点显示部分选中的状态。

要实现这种界面其实很简单。在Qt的设计器中,拖出一个QTreeWidget,然后在主窗口中写一个函数init初始化界面,连接树形控件的节点改变信号itemChanged(QTreeWidgetItem* item, int column),实现这个信号即可。

具体步骤如下:

(1) 建立一个工程。在创建过程中,在“Qt4 Gui Application”界面中,在“Name”后边的文本框中输入“TreeWidget”,选择工程要保存的路径(注意,路径中不要有中文字符),单击“Next”按钮,再单击“Next”按钮,在“Base Class”下拉列表框中选择“QWidget”选项,在“Class name”后面的文本框中输入“Widget”,保持“Gernerate form”复选框的选中状态,单击“Next”按钮,单击“Finish”按钮。

(2) 双击widget.ui文件,打开Qt的设计器,拖出一个QTreeWidget。

(3) 在头文件widget.h中添加:

        #include <QTreeWidgetItem>

以及在类Widget声明中添加代码:

        public:
        void init();
            void updateParentItem(QTreeWidgetItem* item);
        public slots:
            void treeItemChanged(QTreeWidgetItem* item, int column);

(4) 在源文件widget.cpp中,在类Widget构造函数中添加代码:

        init();
        connect(ui->treeWidget,SIGNAL(itemChanged(QTreeWidgetItem*, int)),
                  this, SLOT(treeItemChanged(QTreeWidgetItem*, int)));

在此文件中实现各个函数的具体代码如下:

        void Widget::init()
        {
            ui->treeWidget->clear();
            //第一个分组
            QTreeWidgetItem *group1 = new QTreeWidgetItem(ui->treeWidget);
            group1->setText(0, "group1");
            group1->setFlags(Qt::ItemIsUserCheckable|Qt::ItemIsEnabled|Qt::
    ItemIsSelectable);
            group1->setCheckState(0, Qt::Unchecked);
            QTreeWidgetItem *subItem11 = new QTreeWidgetItem(group1);
            subItem11->setFlags(Qt::ItemIsUserCheckable|Qt::ItemIsEnabled|Qt::
    ItemIsSelectable);
            subItem11->setText(0, "subItem11");
            subItem11->setCheckState(0, Qt::Unchecked);
            QTreeWidgetItem *subItem12 = new QTreeWidgetItem(group1);
            subItem12->setFlags(Qt::ItemIsUserCheckable|Qt::ItemIsEnabled|Qt::
    ItemIsSelectable);
            subItem12->setText(0, "subItem12");
            subItem12->setCheckState(0, Qt::Unchecked);
            QTreeWidgetItem *subItem13 = new QTreeWidgetItem(group1);
            subItem13->setFlags(Qt::ItemIsUserCheckable  |  Qt::ItemIsEnabled  |
    Qt::ItemIsSelectable);
            subItem13->setText(0, "subItem13");
            subItem13->setCheckState(0, Qt::Unchecked);
            QTreeWidgetItem *subItem14 = new QTreeWidgetItem(group1);
            subItem14->setFlags(Qt::ItemIsUserCheckable | Qt::ItemIsEnabled | Qt::
    ItemIsSelectable);
            subItem14->setText(0, "subItem14");
            subItem14->setCheckState(0, Qt::Unchecked);
            //第二个分组
            QTreeWidgetItem *group2 = new QTreeWidgetItem(ui->treeWidget);
            group2->setText(0, "group2");
            group2->setFlags(Qt::ItemIsUserCheckable | Qt::ItemIsEnabled | Qt::
    ItemIsSelectable);
            group2->setCheckState(0, Qt::Unchecked);
            QTreeWidgetItem *subItem21 = new QTreeWidgetItem(group2);
            subItem21->setFlags(Qt::ItemIsUserCheckable | Qt::ItemIsEnabled | Qt::
    ItemIsSelectable);
          subItem21->setText(0, "subItem21");
          subItem21->setCheckState(0, Qt::Unchecked);
          QTreeWidgetItem *subItem22 = new QTreeWidgetItem(group2);
          subItem22->setFlags(Qt::ItemIsUserCheckable | Qt::ItemIsEnabled | Qt::
  ItemIsSelectable);
          subItem22->setText(0, "subItem22");
          subItem22->setCheckState(0, Qt::Unchecked);
          QTreeWidgetItem *subItem23 = new QTreeWidgetItem(group2);
          subItem23->setFlags(Qt::ItemIsUserCheckable | Qt::ItemIsEnabled | Qt::
  ItemIsSelectable);
          subItem23->setText(0, "subItem23");
          subItem23->setCheckState(0, Qt::Unchecked);
      }

函数treeItemChanged()的具体实现代码如下:

      void Widget::treeItemChanged(QTreeWidgetItem* item, int column)
      {
          QString itemText = item->text(0);
          //选中时
          if (Qt::Checked == item->checkState(0))
          {
                QTreeWidgetItem* parent = item->parent();
                int count = item->childCount();
                if (count > 0)
                {
                    for (int i = 0; i < count; i++)
                    {
                        //子节点也选中
                        item->child(i)->setCheckState(0, Qt::Checked);
                    }
                }
                else
                {
                    //是子节点
                    updateParentItem(item);
                }
          }
          else if (Qt::Unchecked == item->checkState(0))
          {
                int count = item->childCount();
                if (count > 0)
                {
                    for (int i = 0; i < count; i++)
                    {
                    item->child(i)->setCheckState(0, Qt::Unchecked);
                    }
            }
            else
            {
                updateParentItem(item);
            }
      }
  }

函数updateParentItem()的具体实现代码如下:

    void Widget::updateParentItem(QTreeWidgetItem* item)
    {
        QTreeWidgetItem *parent = item->parent();
        if (parent == NULL)
        {
            return;
        }
        //选中的子节点个数
        int selectedCount = 0;
        int childCount = parent->childCount();
        for (int i = 0; i < childCount; i++)
        {
            QTreeWidgetItem *childItem = parent->child(i);
            if (childItem->checkState(0) == Qt::Checked)
            {
                selectedCount++;
            }
        }
        if (selectedCount <= 0)
        {
            //选中状态
            parent->setCheckState(0, Qt::Unchecked);
        }
        else if (selectedCount > 0 && selectedCount < childCount)
        {
            //部分选中状态
            parent->setCheckState(0, Qt::PartiallyChecked);
        }
        else if (selectedCount == childCount)
        {
            //未选中状态
            parent->setCheckState(0, Qt::Checked);
        }
        //changeFromUser = true;
    }

(5) 运行结果如图3.10所示。

3.5.9 小综合例子

下面通过一个简单的例子介绍如何将上面的几个控件综合起来使用,其具体步骤如下:

(1) 建立一个工程。创建过程中,在“Qt4 Gui Application”界面中,“Name”后边的文本框中输入“Test”,选择工程要保存的路径(注意,路径中不要有中文字符),单击“Next”按钮,再单击“Next”按钮,在“Base Class”下拉列表框中选择“QDialog”选项,在“Class name”后面的文本框中输入“Dialog”,保持“Gernerate form”复选框的选中状态,单击“Next”按钮,单击“Finish”按钮。

(2) 双击dialog.ui文件,打开Qt的设计器,中间的空白视窗即为一个parent widget,接着需要建立一些child widget。在左边的工具框中找到所需要的widget:即拖出一个label、一个line editor(输入文字之用)、一个horizontal spacer以及两个push button。现在不需要花太多时间在那些widget的位置摆设上,以后可利用Qt的layout manage来做位置的编排。

(3) 设置widget的属性:

● 选择label,确定objectName属性为“label”,并且设定text属性为“&Cell Location”。

● 选择line editor,确定objectName属性为“lineEdit”。

● 选择第一个按钮,将其objectName属性设定为“okButton”,enabled属性设为“false”, text属性设为“OK”,并将default属性设为“true”。

● 选择第二个按钮,将其objectName属性设为“cancelButton”,并将text属性设为“Cancel”。

● 将表单背景的window Title属性设为“Go To Cell”。

如图3.11所示。

图3.11 编辑(E)|编辑元件模式

(4) 运行工程,此时看到界面的label会显示一个“&”。为了解决这个问题,选择Edit(E)|Edit buddies,在此模式下,可以设定同伴。按住label并拖拉至line editor,然后放开,此时会有一个红色箭头由label指向line editor,如图3.12所示。

图3.12 编辑(E)|编辑同伴模式

此时,再次运行该程序,label的“&”不再出现,如图3.13所示,此时label与line editor这两个widget互为同伴了。选择Edit(E)|Edit widgets,即可离开此模式,回到原本的编辑模式。

图3.13 运行结果

(5) 对widget进行位置编排的布局(layout):

● 利用Ctrl键一次选取多个widget,首先选取label与line editor;接着单击上方的工具列“”水平布局按钮,如图3.14所示。

图3.14 水平布局后的效果

● 与上面的步骤类似,选取spacer与两个push button,接着单击上方工具列的表单“”水平布局按钮即可,如图3.14所示。

● 接着选取整个form(不选取任何项目),单击上方工具列的“”垂直布局按钮。

● 选择上方工具列的“”调整大小按钮,整个表单就会自动调整为合适的大小。此时,会出现红色的线将widget框起来,被框起来的widget表示已经被选定为某种布局了,如图3.15所示。

图3.15 垂直布局和调整大小后的效果

(6) 单击“”编辑定位点顺序按钮,widget上会出现一个方框显示数字,这就是表示按下Tab键的顺序,调整到需要的顺序,如图3.16所示。单击“”编辑元件按钮,即可离开此模式,回到原本的编辑模式。此时,运行该程序后的效果如图3.17所示。

图3.16 编辑(E)|编辑元件模式

图3.17 布局后的运行结果

(7) 在头文件dialog.h中的Dialog类声明中添加语句:

        private slots:
            void on_lineEdit_textChanged();

(8) 在源文件dialog.cpp中的构造函数中添加代码如下:

        QRegExp regExp("[A-Za-z][1-9][0-9]{0,2}");
          ui->lineEdit->setValidator(new QRegExpValidator(regExp,this));
            connect(ui->okButton,SIGNAL(clicked()),this,SLOT(accept()));
            connect(ui->cancelButton,SIGNAL(clicked()),this,SLOT(reject()));

其中:

● 在构造函数中使用ui->setupUi(this)语句进行初始化。在产生界面之后,setupUi()会根据naming convention对slot进行连接,即连接on_objectName_signalName()与objectName的signalName()的signal。在此,setupUi()会自动建立下列的signal-slot连接:

        connect(ui->lineEdit,SIGNAL(textChanged(QString)),this,SLOT(on_lineEdit
    _textChanged()));

QRegExp regExp("[A-Za-z][1-9][0-9]{0,2}"):限制了输入字元的范围。

ui->lineEdit->setValidator(new QRegExpValidator(regExp,this)):使用QRegExpValidator并且搭配正规表示法"[A-Za-z][1-9][0-9]{0,2}"。这样,只允许第一个字元输入大小写英文字母之后,后面接着一位非0的数字,再接着0~2位可为0的数字。

connect(…):连接了OK按钮至QDialog的accept()槽函数;以及Cancel按钮至QDialog的reject()槽函数。这两个槽函数都会关闭dialog视窗,但是accept()会设定dialog的结果至QDialog::Accepted(结果设为1),而reject()则会设定为QDialog:: Rejected(结果设为0),所以可以根据这个结果来判断按下的是“OK”按钮还是“Cancel”按钮。

注意:parent-child机制:当建立一个物件(widget,validator或其他元件),若此物件有伴随着一个parent,则此parent就将此物件加入他的children list。而当parent被消除时,会根据children list将这些child给消除掉,若这些children也有其children,也会连同一起被消除。这个机制大大简化了记忆体管理,降低了memory leak的风险。所以,惟有那些没有parent的物件才使用delete来消除。所以,对widget来说,parent有着另外的意义:child widget是显示在parent范围之内的。当消除了parent widget,不只是child从记忆体中消失,整个视窗都会消失。

实现槽函数on_lineEdit_textChanged():

        void Dialog::on_lineEdit_textChanged()
         {
        ui->okButton->setEnabled(ui->lineEdit->hasAcceptableInput());
    }

此槽函数会根据line editor中输入的文字是否有效来启用或停用OK按钮, QLineEdit::hasAcceptableInput()中有使用到构造函数中的validator。

(9) 运行此工程。当在line editor中输入A12后,单击“OK”按钮自动变为可执行状态,当单击“Cancel”按钮时则会关闭视窗,如图3.18所示。

图3.18 最终运行结果