本文翻译自:“Getting Started Programming with Qt”:http://doc.qt.nokia.com/4.7/gettingstartedqt.html
翻译人员: habert [developer.qt.nokia.com]

Qt编程入门

欢迎加入Qt的大家庭,Qt是一个跨平台的图形用户界面(GUI)的集成开发工具。作为一篇初学者的入门学习指导,这里我们将通过一个记事本的小程序来向大家介绍Qt的一些基础知识。此后你可以参考我们的技术概述和API文档来进行开发。

Hello Notepad

在第一个例子中,我们创建了一个很简单的窗口,窗口中显示了“This is a QTextEdit”,这可以说是一个最简单的拥有用户图形界面的Qt程序了。

hello notepad

以下是代码:

  1. #include <QApplication>
  2. #include <QTextEdit>
  3.  
  4. int main(int argv, char **args)
  5. {
  6.     QApplication app(argv, args);
  7.  
  8.     QTextEdit textEdit;
  9.     textEdit.show();
  10.  
  11.     return app.exec();
  12. }

现在让我们来对上面的代码逐行解释一下,前两行是包含QApplication和QTextEdit类的头文件,这两个类是我们程序中待会需要用到的。所有的Qt类都一个以它们类名命名的头文件。

第六行创建了一个QApplication对象,这个对象管理着整个程序的资源,它是运行一个拥有用户图形界面的Qt程序所必需的。
由于Qt接受一些命令行参数,在构造QApplication对象需要传递argv和args两个参数。

第八行创建了一个QTextEdit对象。QTextEdit对象是用户图形界面(GUI)中一个可见元素。在Qt中,我们叫这类可见元素为widget,其他widget还有
滚动条(QScrollBar), 标签(QLabel),以及单选框(QRadioButton)等等。一个widget也可以作为其他的widget的容器,也就是说可以包含其他widget,例如一个对话框或是一个主程序窗口。

第九行是用来在窗口上显示QTextEdit对象。由于widget也可以作为容器,例如一个QMainWindow实例就包含有工具条,菜单,状态栏等其他一些widget,这时可以将其显示在它自己的窗口中。默认情况下,widget是不可见的,需要调用它的show()方法才能让其可见。

第十一行让QApplication对象进入它的事件循环中。当一个Qt程序运行时,事件被创建并被传递给相应的widget,比如鼠标按下的事件,键盘事件,都会被传递给对应的widget。当你在一个QTextEdit对象中输入文字时,它会接受键盘按下事件并显示相应的字符。

如果想运行程序,你需要打开命令窗口,进入到程序.cpp文件所在的目录。键入以下命令即可:

  1. qmake -project
  2. qmake
  3. make

以上命令会在part1目录下生成可运行程序(注意在windows下,你可能需要使用nmake命令而不是make命令,另外,可运行的程序也将会被放在part1/debug目录或是part1/release目录下)。
qmake是Qt的编译工具,它需要一个.qmake的配置文件,而当使用-project参数时就会生成这个.qmake的文件。qmake会根据工程配置文件,即.pro文件,来生成一个make文件以便编译程序。我们待会会进一步对.pro文件做详细说明。

增加一个退出按钮

在真正的程序中,通常来讲都需要一个以上的widget。这里我们将在QTextEdit文字输入框下面增加一个QPushButton按钮。当单击这个按钮时会退出这个记事本程序。

QPushButton

让我们先来看看代码部分:

  1. #include <QtGui>
  2.  
  3. int main(int argv, char **args)
  4. {
  5.     QApplication app(argv, args);
  6.  
  7.     QTextEdit textEdit;
  8.     QPushButton quitButton("Quit");
  9.  
  10.     QObject::connect(&quitButton, SIGNAL(clicked()), qApp, SLOT(quit()));
  11.  
  12.     QVBoxLayout layout;
  13.     layout.addWidget(&textEdit);
  14.     layout.addWidget(&quitButton);
  15.  
  16.     QWidget window;
  17.     window.setLayout(&layout);
  18.  
  19.     window.show();
  20.  
  21.     return app.exec();
  22. }

第一行包含了QtGui,QtGui包括了所有Qt的用户界面接口类。

第十行使用Qt中的信号槽机制使得当退出按钮被按下时可以退出程序。槽是一个函数,在程序运行过程中,
它可以像普通函数一样调用。信号是一个函数,当被调用时即触发连接到此信号的所有槽,对此我们称之为连接槽到信号,发送信号。
quit()是一个QApplication类的一个槽,用来退出程序。click()是QPushButton的一个信号,当它被按下是即会发送一个click()信号。静态方法QObject::connect()用来连接信号和槽。SIGNALSLOT是两个宏,宏的参数是相应信号和槽的函数签名。另外在连接信号槽时我们也需要指定发送信号和接受信号的对象指针。

第十二行创建了一个QVBoxLayout对象,如上所述,widget可以包含其他widget,也可以直接设置它的子对象的的边界(位置以及大小),但一般来讲,使用布局类来管理这种位置关系会更方便,QVBoxLayout就是这样一个布局类,它可以将其子对象在竖直方向上进行排列。

第十三和第十四行将文字输入框和按钮加入布局对象中。第十七行我们将这个布局对象设置为这个widget的布局。

继承QWidget

当用户想退出程序时,你可能会想弹出一个对话框以便询问是否真的要退出。在下面这个例子中,我们继承自QWidget,增加一个连接到退出按钮的槽。

subclass

让我们来看看实现代码:

  1. class Notepad : public QWidget
  2. {
  3.     Q_OBJECT
  4.  
  5. public:
  6.     Notepad();
  7.  
  8. private slots:
  9.     void quit();
  10.  
  11. private:
  12.     QTextEdit *textEdit;
  13.     QPushButton *quitButton;
  14. };

Q_OBJECT宏必须放在类声明的最前面,它声明我们的类是一个QObject类(实际上,我们必须继承自QObject)。QObject在C++类基础上扩展很多能力,其中很重要的是可以在程序运行时查询类名和槽名,也可以查询一个槽的参数,然后调用它。

第十三行声明了一个quit()槽。使用slots宏很简单。quit()槽可以被连接到对应签名的信号(任何没有参数的信号都可以)。
与设计GUI然后在main()函数中连接槽所不同,这次我们在NotePad的构造函数中做这些:

  1. Notepad::Notepad()
  2. {
  3.     textEdit = new QTextEdit;
  4.     quitButton = new QPushButton(tr("Quit"));
  5.  
  6.     connect(quitButton, SIGNAL(clicked()), this, SLOT(quit()));
  7.  
  8.     QVBoxLayout *layout = new QVBoxLayout;
  9.     layout->addWidget(textEdit);
  10.     layout->addWidget(quitButton);
  11.  
  12.     setLayout(layout);
  13.  
  14.     setWindowTitle(tr("Notepad"));
  15. }

正如在类声明中看到的那样,我们使用指针指向我们的QObject对象(textEdit和quitButton)。记住,你应该总是堆上构造QObject对象,绝不要去拷贝它们。我们使用tr()函数处理用户可见的字符串。如果你需要支持多语言(例如英语和中文),那么这个函数是必需的,对此这里我们不做展开,但
你可以通过下面的Qt Linguist连接来了解更多。

创建一个.pro文件

这里我们没有使用qmake的-project选项,而是自己写了一个.pro文件。

  1. HEADERS =  notepad.h
  2. SOURCES =  notepad.cpp \
  3.            main.cpp

下面的命令将会编译我们的例子。
qmake
make

使用QMainWindow

很多程序都使用QMainWindow,这样很方便因为QMainWindow拥有自己的布局,你可以加入菜单工具栏,浮动的widget,工具条,状态栏。QMainWindow有一个中心区域,这个中心区域可以被任何一个widget占用,这里我们把我们的文字编辑框放在这个中心区域。

QMainWindow

让我们来看看Notepad类的声明:

  1. #include <QtGui>
  2.  
  3. class Notepad : public QMainWindow
  4. {
  5.     Q_OBJECT
  6.  
  7. public:
  8.     Notepad();
  9.  
  10. private slots:
  11.     void open();
  12.     void save();
  13.     void quit();
  14.  
  15. private:
  16.     QTextEdit *textEdit;
  17.  
  18.     QAction *openAction;
  19.     QAction *saveAction;
  20.     QAction *exitAction;
  21.  
  22.     QMenu *fileMenu;
  23. };

我们增加了两个槽以便来保存和打开文档,我们会在下面实现这两个槽。
在一个主窗口中,同一个槽会经常被多个widget调用,如菜单项,工具条上的按钮。为了更方便,Qt中提供了QAction,它可以被连接到某个槽上,并加入到一些widget中。例如QMenu和QToolBar都可以使用QAction创建菜单项以及工具按钮.我们将在下面对此做进一步说明。如上所述,我们使用Notepad的构造函数来配置用户图形接口:

  1. Notepad::Notepad()
  2. {
  3.     saveAction = new QAction(tr("&Open"), this);
  4.     saveAction = new QAction(tr("&Save"), this);
  5.     exitAction = new QAction(tr("E&xit"), this);
  6.  
  7.     connect(openAction, SIGNAL(triggered()), this, SLOT(open()));
  8.     connect(saveAction, SIGNAL(triggered()), this, SLOT(save()));
  9.     connect(exitAction, SIGNAL(triggered()), qApp, SLOT(quit()));
  10.  
  11.     fileMenu = menuBar()->addMenu(tr("&File"));
  12.     fileMenu->addAction(openAction);
  13.     fileMenu->addAction(saveAction);
  14.     fileMenu->addSeparator();
  15.     fileMenu->addAction(exitAction);
  16.  
  17.     textEdit = new QTextEdit;
  18.     setCentralWidget(textEdit);
  19.  
  20.     setWindowTitle(tr("Notepad"));
  21. }

QAction可以使用文字来构造,这些文字会显示在拥有这些QAction对象的widget上(在这个例子中是菜单项)。如果我们将QAction加入一个工具条,我们还可以给它们增加一些小图标。

保存和加载

在这个例子中,我们将实现上面例子中所增加的open()槽和save()槽的具体功能。

saveandload

先开始open()槽:

  1. QString fileName = QFileDialog::getOpenFileName(this, tr("Open File"), "",
  2.              tr("Text Files (*.txt);;C++ Files (*.cpp *.h)"));
  3.  
  4.          if (fileName != "") {
  5.              QFile file(fileName);
  6.              if (!file.open(QIODevice::ReadOnly)) {
  7.                  QMessageBox::critical(this, tr("Error"),
  8.                      tr("Could not open file"));
  9.                  return;
  10.              }
  11.              QString contents = file.readAll().constData();
  12.              textEdit->setPlainText(contents);
  13.              file.close();
  14.          }

第一步是询问用户打开的文件名。Qt中使用QFileDialog来弹出一个对话框来让用户选择文件。上图显示了在Kubuntu平台上的文件选择对话框。静态的getOpenFileName()函数显示一个模态的文件选择对话框,直到用户选择了某个文件才会返回,它会返回用户选择文件所在的路径,如果用户取消则返回空字符串。

如果我们有一个文件名,可以使用open()方法来尝试打开文件,如果文件可以被打开则返回true。这里我们不对其中的错误处理做说明,但你可以通过以下learn more部分来了解更多。如果文件不能打开,我们会使用QMessageBox来显示一个错误信息的对话框(请参考QMessageBox类的说明以便了解更多)。

实际上使用readAll()函数来读取数据是微不足道的,她会返回一个QByteArray,这个QByteArray包含了文件中所有的数据。constData()方法返回包含所有的数据的一个常数组。这些内容都可以用一个文字编辑框来显示。然后我们可以用close()方法来关闭打开的文件来返回打开的系统中。

现在让我们来继续save()槽部分:

  1. QString fileName = QFileDialog::getSaveFileName(this, tr("Save File"), "",
  2.              tr("Text Files (*.txt);;C++ Files (*.cpp *.h)"));
  3.  
  4.          if (fileName != "") {
  5.              QFile file(fileName);
  6.              if (!file.open(QIODevice::WriteOnly)) {
  7.                  // error message
  8.              } else {
  9.                  QTextStream stream(&file);
  10.                  stream << textEdit->toPlainText();
  11.                  stream.flush();
  12.                  file.close();
  13.              }
  14.          }

当我们将一个文字编辑框中的内容写到一个文件时,我们使用QTextStream类,这个类包含了一个QFile对象。QTextStream类可以直接将QString类的字符内容写到文件中。QFile只接受QIODevice类的write()方法的char*的原始数据。

更多Qt中文资料请访问“Qt中文论坛”:http://www.thisisqt.com/forum

Categories: