本章目录
创建QMainWindow的子类
创建菜单和工具栏
建立状态栏
实现File菜单
使用对话框
保存设置
多文档窗口
启动欢迎窗口
本章将教你如何使用Qt创建主窗口。最后,你将能够创建一个完整的用户界面应用程序,包括菜单,工具栏,状态栏和许多应用程序中要用到的对话框。
应用程序主窗口提供了构建应用程序用户界面的基本框架。图 3.1中的电子表格窗口将构成本章的基础。该电子表格程序使用了第二章中创建的查找,跳转到单元,及排序对话框。
图 3.1. 电子表格程序
在大多数GUI程序的背后都有大量的提供底层功能的代码,例如,用于读写文件或者处理用户界面中的数据表示的代码。在 Chapter 4 ,我们还将以这个电子表格程序为例来展示如何实现些类功能。
创建QmainWindow的子类
应用程序的主窗口通过继承QMainWindow类来创建的。我们在第二章中看到的创建对话框的许多技术也可用于创建主窗口,因为QDialog和QMainWindow都是从Qwidget类继承的。
主窗口可用Qt 设计师创建,但本章中我们将用代码做这些工作以演示它是如何做的。如果你更倾向于可视化方法,请看Qt设计师在线手册中的“在Qt设计师中创建主窗口”一章。
电子表格程序的主窗口的源代码包括mainwindow.h和mainwindow.cpp两个文件。现在从头文件开始看起:
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
class QAction;
class QLabel;
class FindDialog;
class Spreadsheet;
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow();
protected:
void closeEvent(QCloseEvent *event);
我们将类MainWindow 定义为QMainWindow的子类。由于它要提供自己的信号和槽,所以它包含了Q_OBJECT宏。
closeEvent()函数是QWidget中的一个虚函数,在用户关闭窗口的时候它被自动调用。它在MainWindow类中实现后,我们就能询问用户“你要保存修改吗”这样规范化的问题,然后就可以保存用户的参数设置到磁盘上。
private slots:
void newFile();
void open();
bool save();
bool saveAs();
void find();
void goToCell();
void sort();
void about();
一些菜单选项,如文件|新建和帮助|关于,则作为私有槽在MainWindow中实现。大多数槽的返回值都是 void , 但 save() 和saveAs() 返回的是 bool 值。该返回值在槽当作 信号的响应而执行时将被忽略,但是当我们把槽作为一个的函数调用时,我们可以得到它的返回值,正像我们调用任何普通C++函数一样。
void openRecentFile();
void updateStatusBar();
void spreadsheetModified();
private:
void createActions();
void createMenus();
void createContextMenu();
void createToolBars();
void createStatusBar();
void readSettings();
void writeSettings();
bool okToContinue();
bool loadFile(const QString &fileName);
bool saveFile(const QString &fileName);
void setCurrentFile(const QString &fileName);
void updateRecentFileActions();
QString strippedName(const QString &fullFileName);
为支撑些用户界面,主窗口还需要几个么有槽和几个私有函数。
Spreadsheet *spreadsheet;
FindDialog *findDialog;
QLabel *locationLabel;
QLabel *formulaLabel;
QStringList recentFiles;
QString curFile;
enum { MaxRecentFiles = 5 };
QAction *recentFileActions[MaxRecentFiles];
QAction *separatorAction;
QMenu *fileMenu;
QMenu *editMenu;
...
QToolBar *fileToolBar;
QToolBar *editToolBar;
QAction *newAction;
QAction *openAction;
...
QAction *aboutQtAction;
};
#endif
MainWindow除了私有槽和私有函数外,它还有一些私有变量。所有这些都会在我们用到的时候做解释。
现存来预览一下实现:
#include <QtGui>
#include "finddialog.h"
#include "gotocelldialog.h"
#include "mainwindow.h"
#include "sortdialog.h"
#include "spreadsheet.h"
我们包括了头文件<QtGui>,它包含MainWindow中用到的所有Qt 类的定义。其中还包括了一些定制头文件,如第二章中的finddialog.h, gotocelldialog.h, 和 sortdialog.h。
MainWindow::MainWindow()
{
spreadsheet = new Spreadsheet;
setCentralWidget(spreadsheet);
createActions();
createMenus();
createContextMenu();
createToolBars();
createStatusBar();
readSettings();
findDialog = 0;
setWindowIcon(QIcon(":/images/icon.png"));
setCurrentFile("");
}
在构造函数中,我们在开始的地方创建一个Spreadsheet物件,然后将它设置为方窗口的主物件。该主物件占据主窗口的中央位置(见图3.2)。Spreadsheet是类QtableWidget的含有电子表格功能的子类,如电子表格的计算功能。我们将在第四章中实现它。
图3.2 QmainWindow的区域图
我们调用私有函数createActions(), createMenus(), createContext-Menu(), createToolBars(), 和 createStatusBar()来创建主窗口的其他部分。另外还调用私有函数readSettings()来读取程序保存的设置。
我们将findDialog指针初始化成空指针。并在MainWindow::find()首次调用的时候创建FindDialog 对象。
在构造函数的最后,我们把窗口图标设成一个PNG图像文件,icon.png。Qt支持许多图像类型,如BMP, GIF,[*] JPEG, PNG, PNM, XBM, 和 XPM。调用Qwidget::setWindowIcon()设置显示在窗口左上角的图标。遗憾的是,现在还没有一种平台无关方法用于设置显示在桌面上的应用程序图标。平台相关处理详见http://doc.trolltech.com/4.1/appicon.html。
[*] GIF 支持在Qt默认设置中是关闭的,因为Qt中使用的GIF 解压方法在一些承认软件专利的国家是需要专利许可的。我们知道这一专利现存已经过期。要打开Qt的GIF支持,可以将命令行参数-qt-gif传递给配置脚本,或者在Qt安装程序中设置适当的参数。
GUI 程序一般会用到许多图像。有几种给程序提供图像方法。最常用的是:
将图像保存在文件中,并在运行时加载。
在源代码中包含XPM 文件。(这是可行的,因为XPM 文件是有效的C++文件。)
使用Qt资源机制。
这里我们使用Qt的资源机制,因为它比运行时加载文件更方便,并且它对任何支持的文件格式都有效。我们选择将图像存储在源代码树中的一个叫images的子文件夹中。
要想使用Qt的资源系统,我们必须创建一个资源文件,并在.pro 资源文件中加入一行。在本示例中,我们创建资源文件spreadsheet.qrc,然后把下面一行放到.pro文件中:
RESOURCES = spreadsheet.qrc
资源文件本身使用的是简单的XML格式。这是从我们曾经用过的文件中抽取出来的:
<!DOCTYPE RCC><RCC version="1.0">
<qresource>
<file>images/icon.png</file>
...
<file>images/gotocell.png</file>
</qresource>
</RCC>
资源文件被编译进可执行程序中,因而它们不会丢失。在引用资源的时候,我们使用路径前缀 :/ (冒号 斜线),这就是为什么图标被指定为:/images/icon.png。资源可以是任何类型的文件(不仅仅指图像),并且我们可以在Qt需要文件名的大多数地方使用它们。我们将在第12章做详细解释。
创建菜单和工具栏
大多数现代GUI应用程序会提供菜单,上下文菜单和工具栏。菜单能让用户探索应用程序和学习如何做新的事情,而上下文菜单和工具栏提供常用功能的快捷访问。
图3.3 电子表格程序的菜单
Qt通过它的操作概念简化菜单和工具栏编程。操作(action)是能添加到许多菜单与工具栏的项(item)。在Qt中创建菜单和工具栏包含下面一些步骤:
创建并建立操作(actions)。
创建菜单并把它们与操作绑在一起。
创建工具栏并把它们与操作绑在一起。
在此电子表格程序中,操作在createActions()中创建:
void MainWindow::createActions()
{
newAction = new QAction(tr("&New"), this);
newAction->setIcon(QIcon(":/images/new.png"));
newAction->setShortcut(tr("Ctrl+N"));
newAction->setStatusTip(tr("Create a new spreadsheet file"));
connect(newAction, SIGNAL(triggered()), this, SLOT(newFile()));
“Nww”操作有一个加速键(New),一个父窗口(主窗口),一个图标(new.png),一个快捷键(Ctrl+N)和一个小提示。我们把该操作的triggered()信号连接到主窗口的私有槽newFile()上,下一节将实现这个私有槽。该连接保证在用户选择“File|New”菜单项,点击“新建”工具栏按钮或者按下Ctrl+N的时候,newFile()槽会被调用。
“Open”,”Save”,”Save As”操作与“New”操作非常相似,因此我们直接跳到”File”菜单的”recently opened files”部分:
...
for (int i = 0; i < MaxRecentFiles; ++i) {
recentFileActions[i] = new QAction(this);
recentFileActions[i]->setVisible(false);
connect(recentFileActions[i], SIGNAL(triggered()),
this, SLOT(openRecentFile()));
}
我们把所有操作组装成recentFileActions 数组。每个操作都被隐藏并被连接到槽openRecentFile() 上。稍后,我们将看到最近的文件操作是如何显示和使用的。
现存我们可以跳到“ All action“了:
...
selectAllAction = new QAction(tr("&All"), this);
selectAllAction->setShortcut(tr("Ctrl+A"));
selectAllAction->setStatusTip(tr("Select all the cells in the "
"spreadsheet"));
connect(selectAllAction, SIGNAL(triggered()),
spreadsheet, SLOT(selectAll()));
槽selectAll()由QtableWidget的一个祖先QabstractItemView类提供,因而我们不必自己实现它了。
现存跳到更远一点的“Option”菜单的“Show Grid“操作:
...
showGridAction = new QAction(tr("&Show Grid"), this);
showGridAction->setCheckable(true);
showGridAction->setChecked(spreadsheet->showGrid());
showGridAction->setStatusTip(tr("Show or hide the spreadsheet's "
"grid"));
connect(showGridAction, SIGNAL(toggled(bool)),
spreadsheet, SLOT(setShowGrid(bool)));
“Show Grid”是一个可选中操作。它在菜单中被渲染出一个选中标记,在工具栏中实现为一个开关按钮。在该操作是选中的时候,Spreadsheet组件会显示一个网络。默认情况下,我们为Spreadsheet组件初始化这种状态,那么在启动的时候就能够操持同步。然后我们把”Show Grid”操作的toggled(bool)信号连接到Spreadsheet组件的setShowGrid(bool)槽,这个槽其实是从QtableWidget类继承来的。一旦这个操作被添加到菜单或者工具栏,用户就能开启或者关闭网络。
“Show Grid”和”Auto-Recalculate”操作两个无关的可选中操作。Qt还能通过QActionGroup类支持互斥的操作。
...
aboutQtAction = new QAction(tr("About &Qt"), this);
aboutQtAction->setStatusTip(tr("Show the Qt library's About box"));
connect(aboutQtAction, SIGNAL(triggered()), qApp, SLOT(aboutQt()));
}
对”About Qt”操作,我们使用QApplication对象的aboutQt()槽,它可以通过qApp全局变量来访问。
图3.4 关于Qt
既然创建了操作,我们就可以继续构建一个包含它们的菜单系统了:
void MainWindow::createMenus()
{
fileMenu = menuBar()->addMenu(tr("&File"));
fileMenu->addAction(newAction);
fileMenu->addAction(openAction);
fileMenu->addAction(saveAction);
fileMenu->addAction(saveAsAction);
separatorAction = fileMenu->addSeparator();
for (int i = 0; i < MaxRecentFiles; ++i)
fileMenu->addAction(recentFileActions[i]);
fileMenu->addSeparator();
fileMenu->addAction(exitAction);
在Qt中,菜单是Qmenu的实例。addMenu()函数调用创建一个带有指定文本的QMenu物件并将它添加到菜单条中。QMainWindow::menuBar()函数返回一个指向QMenuBar的指针。主菜单条在首次调用menuBar()时创建。
我们以创建“File”菜单开始,然后在菜单上添加”New”,”Open”,”Save”,”Save As”一系列操作。我们插入一个分隔符以形象地将相关的项排到一起。我们使用for循环从recentFileActions数组添加(初始化为隐藏)操作,然后在最后添加exitAction操作。
我们已经保存了指向其中一个分隔符的指针。这让我们能隐藏或者显示它,因为在中间没有东西时候我们并不想显示两个分隔符。
editMenu = menuBar()->addMenu(tr("&Edit"));
editMenu->addAction(cutAction);
editMenu->addAction(copyAction);
editMenu->addAction(pasteAction);
editMenu->addAction(deleteAction);
selectSubMenu = editMenu->addMenu(tr("&Select"));
selectSubMenu->addAction(selectRowAction);
selectSubMenu->addAction(selectColumnAction);
selectSubMenu->addAction(selectAllAction);
editMenu->addSeparator();
editMenu->addAction(findAction);
editMenu->addAction(goToCellAction);
现在我人来创建“Edit”菜单,就像我们已为”File”菜单所做的,使用QMenu::addAction()添加操作,使用QMenu::addMenu()在你放的位置添加子菜单。子菜单像它所属的菜单一样也是个QMenu。
toolsMenu = menuBar()->addMenu(tr("&Tools"));
toolsMenu->addAction(recalculateAction);
toolsMenu->addAction(sortAction);
optionsMenu = menuBar()->addMenu(tr("&Options"));
optionsMenu->addAction(showGridAction);
optionsMenu->addAction(autoRecalcAction);
menuBar()->addSeparator();
helpMenu = menuBar()->addMenu(tr("&Help"));
helpMenu->addAction(aboutAction);
helpMenu->addAction(aboutQtAction);
}
我们以相似的样式创建“Tools”,”Options”和”Help”菜单。在”Options”和”Help”菜单中间插入一个分隔符。在Motif和CDE风格中,分隔符把”Help”菜单挤压到了右侧。对于其他风格,分隔符将被忽略。
图3.5 Motif和Windows风格的菜单
void MainWindow::createContextMenu()
{
spreadsheet->addAction(cutAction);
spreadsheet->addAction(copyAction);
spreadsheet->addAction(pasteAction);
spreadsheet->setContextMenuPolicy(Qt::ActionsContextMenu);
}
任何Qt物件都可以拥有一系列与之关联的QAction。为给程序提供一个上下文菜单,我们把所需的操作添加到Spreadsheet物件,设置该物件的上下文菜单策略以显示包含这些操作的上下文菜单。上下文菜单将在右键点击或者按下一个平台相关的键的时候触发。
图3.6 电子表格程序的上下文菜单
一个更复杂的提供上下文菜单的方法是实现QWidget::contectMenuEvent()函数,创建一个QMenu物件,把所需的操作添加到该QMenu物件上,并调用它的exec()方法。
void MainWindow::createToolBars()
{
fileToolBar = addToolBar(tr("&File"));
fileToolBar->addAction(newAction);
fileToolBar->addAction(openAction);
fileToolBar->addAction(saveAction);
editToolBar = addToolBar(tr("&Edit"));
editToolBar->addAction(cutAction);
editToolBar->addAction(copyAction);
editToolBar->addAction(pasteAction);
editToolBar->addSeparator();
editToolBar->addAction(findAction);
editToolBar->addAction(goToCellAction);
}
创建工具栏与创建菜单类似。我们创建了一个“File”与工具栏和一个”Edit”工具栏。正如菜单一样,工具栏也可以有分隔符。
图3.7 电子表格程序的工具栏
|