本文翻译自QT的官方帮助文档
该应用程序示例展示了如何实现一个标准的GUI应用程序,这个程序带有菜单栏,工具栏和状态栏。这个例子本身是以QPlainTextEdit类而建立的一个简单文本编辑器。
该应用程序实例的所有代码几乎都在MainWindow类里,它继承于QMainWindow类。QMainWindow类提供了一个具有菜单,工具栏,停靠窗口和状态栏的框架。该应用程序在菜单栏中提供了文件,编辑,帮助操作,并且有着下面的弹出菜单:
在主窗口底部的状态栏上,会显示光标下的菜单项或工具栏按钮的说明。
为了保持例子简单化,最近打开的文件并未在文件菜单中显示,即使在90%的应用程序中都需要有该功能。在Recent Files示例中介绍了如何实现这一功能。此外,这个例子只能在同一时间内加载一个文件。 SDI和MDI的例子演示了如何突破这些限制。
MainWindow类定义
下面是类定义
class MainWindow :public QMainWindow
{
Q_OBJECT
public:
MainWindow(); //construction function
protected:
void closeEvent(QCloseEvent *event);
//when user attempts to close the window, warn the user about unsaved changes
private slots: //the slot when the signal send
void newFile();
void open();
bool save();
bool saveAs();
void about();
void documentWasModified();
private:
void createActions(); //create the menus, toolbars, statusbar
void createMenus();
void createToolBars();
void createStatusBar();
void readSettings();
void writeSettings();
bool maybeSave();
void loadFile(const QString &filename);
bool saveFile(const QString &filename);
void setCurrentFile(const QString &filename);
QString strippedName(const QString &fullFilename);
QPlainTextEdit *textEdit;
QString curFile;
QMenu *fileMenu; //the menus elements file, edit, help
QMenu *editMenu;
QMenu *helpMenu;
QToolBar *fileToolBar; //the toolbar elements file edit
QToolBar *editToolBar;
QAction *newAct; //the actions in the menus
QAction *openAct;
QAction *saveAct;
QAction *saveAsAct;
QAction *exitAct;
QAction *cutAct;
QAction *copyAct;
QAction *pasteAct;
QAction *aboutAct;
QAction *aboutQtAct;
};
公共的API限制为构造函数。在受保护的部分,我们重新实现QWidget::closeEvent()函数,当用户试图关闭窗口时,用这个函数来进行检测,并警告用户有未保存的文件。在私有插槽部分,我们宣布对应菜单项插槽,以及一个神秘的documentWasModified()槽。最后,在类的私有部分中,我们有各种部件,这些将在适当的时机进行说明。
MainWindow 类的实现
#include <QtWidgets>
#include "mainwindow.h"
我们首先包含<QtGui>,该头文件包含了核心的Qt和Qt图形用户界面模块中所有类的定义。这使我们省去了必须单独包括每个类的麻烦。我们还包含了mainwindow.h头文件。
你可能想知道,为什么我们不在mainwindow.h中包含<QtGui>,然后使用它。其原因在于,从另一头文件包括这样一个大的头文件会大大的降低性能。在这里,它不会造成任何损害,但是从另一头文件中只包括那些绝对必须的头文件,仍然是一个好主意。
MainWindow::MainWindow()
{
textEdit = new QPlainTextEdit; //the text show the file
setCentralWidget(textEdit); //occupies the central area of the main window
createActions(); //show the user interface
createMenus();
createToolBars();
createStatusBar();
readSettings();
connect(textEdit->document(), SIGNAL(contentsChanged()),
this, SLOT(documentWasModified()));
//update the title bar to show that the file was modified.
setCurrentFile("");
setUnifiedTitleAndToolBarOnMac(true);
}
在构造函数中,我们首先创建一个QPlainTextEdit部件作为主窗口(this对象)的child。然后我们调用的QMainWindow:: setCentralWidget()函数来说明这部件将占据在工具栏和状态栏之间的主窗口中心区域
然后我们调用createActions(),createMenus(),createToolBars(),和createStatusBar()函数,这四个私有函数建立了用户的接口。在这之后,我们调用readSettings()来设定用户的偏好。
我们在QPlainTextEdit的文档对象和documentWasModified()槽之间建立信号槽的连接。每当用户修改在QPlainTextEdit中的文本,我需要更新标题栏去显示该文件已经被修改。
最后,我们使用私有setCurrentFile()函数来设立窗口标题。我们在后面再回过来讨论这一点。
void MainWindow::closeEvent(QCloseEvent *event)
{
if (maybeSave()) { //give the user the possibility to save pending changes.
writeSettings();
event->accept();
} else {
event->ignore();
}
}
当用户试图去关闭窗口时,我们调用私有函数maybeSave()给用户保存当前修改的机会。如果用户想要关闭应用程序,该函数返回true; 否则,返回false。在第一种情况下,我们根据用户的偏好保存文件到磁盘并接受关闭事件; 在第二种情况下,我们忽略关闭事件,这意味着该应用程序将继续保持运行,就好像什么都没有发生过。
void MainWindow::newFile()
{
if (maybeSave()) {
textEdit->clear();
setCurrentFile("");
}
}
当用户在菜单栏里选择File|New时,newFile()槽将会被调用。我们调用maybeSave()函数去保存所有当前的更改,如果用户同意这样做,我们清除QPlainTextEdit和调用私有函数setCurrentFile()来更新窗口的标题,并清除windowModified标志。
void MainWindow::open() //We pop up a QFileDialog asking the user to choose a file
{
if (maybeSave()) {
QString fileName = QFileDialog::getOpenFileName(this);
if (!fileName.isEmpty())
loadFile(fileName);
}
}
当用户点击File|Opne时,会调用Open()槽。我们弹出一个QFileDialog去要求用户选择一个文件。如果用户选择了一个文件(例如文件名不是空字符串),我们调用私有函数loadFild()函数真正去做读取文件的工作。
bool MainWindow::save() //If the user hasn't provided a name for the file yet, we call saveAs()
{
if (curFile.isEmpty())
return saveAs();
else
return saveFile(curFile);
}
当用户点击File|Save时,会调用save()槽。如果用户没有提供为文件提供一个名字的话,我们调用saveAs()函数。否则,我们调用私有函数saveFile()去保存文件。
bool MainWindow::saveAs()
{
QFileDialog dialog(this);
dialog.setWindowModality(Qt::WindowModal);
dialog.setAcceptMode(QFileDialog::AcceptSave);
dialog.exec();
QStringList files = dialog.selectedFiles(); //asking the user to provide a name
if (files.isEmpty())
return false;
return saveFile(files.at(0));
}
在saveAs()函数里,我们开始于弹出一个QFileDialog要求用户提供一个名字。如果用户点击Cancel的话,返回的文件名为空,我们啥都不做。void MainWindow::about()
void MainWindow::about()
{
QMessageBox::about(this, tr("About Application"),
tr("The <b>Application</b> example demonstrates how to "
"write modern GUI applications using Qt, with a menu bar, "
"toolbars, and a status bar."));
}
应用程序中的About box使用一个语句来实现,这语句使用了QMessageBox::about()的静态函数和依赖于一个HTML子集的支持。
文字串周围的tr()函数标记该字符串要进行翻译。在所有用户可见的字符串上使用tr()是一个好习惯,以防你以后决定要把应用程序转换为其他语言时。Qt国际化概述中包含更多有关细节。
void MainWindow::documentWasModified()
{
setWindowModified(textEdit->document()->isModified()); // to make the title bar show that the file was modified
}
因为用户编辑的QPlainTextEdit变化的文字documentWasModified()槽在每次调用。我们调用QWidget:: setWindowModified()函数使标题栏显示该文件已经被修改了。在每个的平台上如何做到这一点都是不同的。
void MainWindow::createActions() // the interface actions
{
newAct = new QAction(QIcon(":/images/new.png"), tr("&New"), this);
newAct->setShortcuts(QKeySequence::New);
newAct->setStatusTip(tr("Create a new file"));
connect(newAct, SIGNAL(triggered()), this, SLOT(newFile()));
//! [19]
openAct = new QAction(QIcon(":/images/open.png"), tr("&Open..."), this);
openAct->setShortcuts(QKeySequence::Open);
openAct->setStatusTip(tr("Open an existing file"));
connect(openAct, SIGNAL(triggered()), this, SLOT(open()));
//! [18] //! [19]
saveAct = new QAction(QIcon(":/images/save.png"), tr("&Save"), this);
saveAct->setShortcuts(QKeySequence::Save);
saveAct->setStatusTip(tr("Save the document to disk"));
connect(saveAct, SIGNAL(triggered()), this, SLOT(save()));
saveAsAct = new QAction(tr("Save &As..."), this);
saveAsAct->setShortcuts(QKeySequence::SaveAs);
saveAsAct->setStatusTip(tr("Save the document under a new name"));
connect(saveAsAct, SIGNAL(triggered()), this, SLOT(saveAs()));
//! [20]
exitAct = new QAction(tr("E&xit"), this);
exitAct->setShortcuts(QKeySequence::Quit);
//! [20]
exitAct->setStatusTip(tr("Exit the application"));
connect(exitAct, SIGNAL(triggered()), this, SLOT(close()));
//! [21]
cutAct = new QAction(QIcon(":/images/cut.png"), tr("Cu&t"), this);
//! [21]
cutAct->setShortcuts(QKeySequence::Cut);
cutAct->setStatusTip(tr("Cut the current selection's contents to the "
"clipboard"));
connect(cutAct, SIGNAL(triggered()), textEdit, SLOT(cut()));
copyAct = new QAction(QIcon(":/images/copy.png"), tr("&Copy"), this);
copyAct->setShortcuts(QKeySequence::Copy);
copyAct->setStatusTip(tr("Copy the current selection's contents to the "
"clipboard"));
connect(copyAct, SIGNAL(triggered()), textEdit, SLOT(copy()));
pasteAct = new QAction(QIcon(":/images/paste.png"), tr("&Paste"), this);
pasteAct->setShortcuts(QKeySequence::Paste);
pasteAct->setStatusTip(tr("Paste the clipboard's contents into the current "
"selection"));
connect(pasteAct, SIGNAL(triggered()), textEdit, SLOT(paste()));
aboutAct = new QAction(tr("&About"), this);
aboutAct->setStatusTip(tr("Show the application's About box"));
connect(aboutAct, SIGNAL(triggered()), this, SLOT(about()));
//! [22]
aboutQtAct = new QAction(tr("About &Qt"), this);
aboutQtAct->setStatusTip(tr("Show the Qt library's About box"));
connect(aboutQtAct, SIGNAL(triggered()), qApp, SLOT(aboutQt()));
createActions()私有函数,是在主窗口的构造函数中被调用的,创建QActions的代码是有很大重复性的,所以我们只介绍对应于File|New,File|Open,Help|About Qt的操作。
QAction是表示一个用户操作的对象,如保存文件或调用一个对话框。一个action可以被放在QMenu或QToolBar,或两者之上,或在重新实现QWidget:: ActionEvent的任何其他部件上。
每一个action都有一个显示在菜单中的文本,图标,快捷键,工具提示,状态提示(在状态栏中显示),“这是什么?”文本,等等。每当用户调用actions时(例如,通过点击相应的菜单项或工具栏按钮),它都会发射triggered()信号。我们把这个信号连接到执行实际的工作的插槽中
上面的代码中包含一个以上idiom的必须加以解释。对于一些actions,我们在QAction构造函数中用QIcon为它指定一个图标。该QIcon构造函数接受一个我们尝试加载的图像的文件名。在这里,文件名的开头为:这样的文件名不是正常的文件名,而是存储资源的可执行文件路径。我们会回过头来探讨application.qrc文件,该文件是项目的一部分。
cutAct->setEnabled(false);
copyAct->setEnabled(false); // must be available only when the QPlainTextEdit contains selected text.
connect(textEdit, SIGNAL(copyAvailable(bool)), cutAct, SLOT(setEnabled(bool)));
connect(textEdit, SIGNAL(copyAvailable(bool)), copyAct, SLOT(setEnabled(bool)));
}
只有当QPlainTextEdit包含选择文本功能时,Edit | Cut 和Edit |Copy actions才必须提供。我们把它们默认值设置为不可用,然后把QPlainTextEdit::copyAvailable()信号和QAction::setEnabled()槽连接起来,这保证了当文件编辑器没有选择功能时,这些actions是不可用的。
void MainWindow::createMenus() //We create a File, an Edit, and a Help menu
{
fileMenu = menuBar()->addMenu(tr("&File"));
fileMenu->addAction(newAct);
fileMenu->addAction(openAct);
fileMenu->addAction(saveAct);
fileMenu->addAction(saveAsAct);
fileMenu->addSeparator();
fileMenu->addAction(exitAct);
editMenu = menuBar()->addMenu(tr("&Edit"));
editMenu-> addAction(cutAct);
editMenu->addAction(copyAct);
editMenu->addAction(pasteAct);
menuBar()->addSeparator();
helpMenu = menuBar()->addMenu(tr("&Help"));
helpMenu->addAction(aboutAct);
helpMenu->addAction(aboutQtAct);
}
创建actions不足以让他们提供给用户;我们也必须将它们添加到菜单系统。这就是createMenus()的工作。我们创建了一个File,Edit,和Help菜单。QMainWindow::menubar()允许我们访问该窗口的菜单栏widget。我们不必担心怎么去创建菜单栏; 我们第一次调用该函数时,QMenuBar就会被创建。
就像之前我们创建Help菜单一样,我们调用QMenuBar:: addSeparator()函数。这对于大多数widget的样式(例如,Windows和Mac OS X的风格)没有影响,但对于一些样式来说,要确保Help项在菜单栏的右侧。
现在让我们来回顾一下工具栏:
void MainWindow::createToolBars()
{
fileToolBar = addToolBar(tr("File"));
fileToolBar->addAction(newAct);
//! [29] //! [31]
fileToolBar->addAction(openAct);
//! [31]
fileToolBar->addAction(saveAct);
editToolBar = addToolBar(tr("Edit"));
editToolBar->addAction(cutAct);
editToolBar->addAction(copyAct);
editToolBar->addAction(pasteAct);
}
创建工具栏与创建菜单栏非常相似。我们在菜单栏上放置的actions同样可以被再用到工具栏上。
void MainWindow::createStatusBar()
{
statusBar()->showMessage(tr("Ready")); //the initial status information
}
QMainWindow::statusBar()返回一个指向主窗口QStatusBar widget的指针。就像QMainWindow::menuBar()一样,在第一次调用该函数时,widget会自动创建。
void MainWindow::readSettings()
{ // restoring the position and size of a window
QSettings settings("QtProject", "Application Example");
QPoint pos = settings.value("pos", QPoint(200, 200)).toPoint();
QSize size = settings.value("size", QSize(400, 400)).toSize();
resize(size);
move(pos);
}
在构造函数中调用的readSettings()用来加载用户的偏好和其他应用程序设置。该QSettings类提供了在磁盘上永久保存设置的一个高层次接口。在Windows上,它采用了著名的Windows注册表;在Mac OS X上,它采用了原生XML为基础的CFPreferences API;在Unix/ X11,它采用文本文件。
带参数的QSettings构造函数确认你的公司和产品的名称。这确保了对于不同的应用程序的设置是分隔开的。
我们使用QSettings::value()函数去获得“pos”和“size”的设置值。QSettings::value()的第二个参数是可选的,如果没有提供初始值的话,它将设置一个默认值。这个值在第一次运行应用程序时被使用。
在改变窗口的位置和大小时,在QWidget::move()之前调用QWidget::resize()是非常重要的。其原因在Window Geometry中有说明
void MainWindow::writeSettings()
//! [37] //! [39]
{
QSettings settings("QtProject", "Application Example");
settings.setValue("pos", pos());
settings.setValue("size", size());
}
writeSettings()函数在closeEvent()函数中被调用. 除了simpler,写设置跟读操作类似. QSettings 构造函数的参数必须和readSettings()函数的参数一样.
bool MainWindow::maybeSave()
{ //The maybeSave() function is called to save pending changes
if (textEdit->document()->isModified()) {
QMessageBox::StandardButton ret;
ret = QMessageBox::warning(this, tr("Application"),
tr("The document has been modified.\n"
"Do you want to save your changes?"),
QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel);
if (ret == QMessageBox::Save)
return save();
else if (ret == QMessageBox::Cancel)
return false;
}
return true;
}
maybeSave()函数被调用去保存未处理的变更。如果有未处理的变更,它会弹出一个QMessageBox让给用户可以去保存文档。选项包括QMessageBox::Yes,QMessageBox::No,和QMessageBox::Cancel。Yes按钮是QMessageBox::Default标志所使用的默认按钮(当用户按下Return键时调用此按钮); Cancel按钮是由退出按钮使用QMessageBox::Escape标志的escap按钮(当用户按下Esc键时调用此按钮)。
除非用户点击取消,不然的话maybeSave()函数在任何情况下都返回true。如果返回值是false的话,调用者必须检查返回值,无论它正在做什么,都要将它停止。
void MainWindow::loadFile(const QString &fileName)
{
QFile file(fileName);
if (!file.open(QFile::ReadOnly | QFile::Text)) {
QMessageBox::warning(this, tr("Application"),
tr("Cannot read file %1:\n%2.")
.arg(fileName)
.arg(file.errorString()));
return;
}
QTextStream in(&file);
#ifndef QT_NO_CURSOR
QApplication::setOverrideCursor(Qt::WaitCursor);
#endif
textEdit->setPlainText(in.readAll());
#ifndef QT_NO_CURSOR
QApplication::restoreOverrideCursor();
#endif
setCurrentFile(fileName);
statusBar()->showMessage(tr("File loaded"), 2000);
}
在loadFile()中,我们使用一个QFile和QTextStream进行数据的读入。QFile对象提供了方法去访问存储在文件中的字节。
我们首先以只读方式打开文件。QFile::Text标志表示该文件是一个文本文件,而不是二进制文件。在Unix和Mac OS X,这两种文件没有区别,但在Windows上,当读操作时,确保一系列的“\ r\ n”转换成“\ n”。
如果我们成功地打开了文件,我们使用QTextStream对象来读取数据。 QTextStream自动把8位的数据转换为Unicode QString,它支持各种编码。如果没有指定编码,QTextStream假设要写入的文件使用系统默认的8位编码(例如,Latin-1;详见 QTextCodec:: codecForLocale())。
因为调用QTextStream:: readAll()可能需要一些时间,当整个程序还在工作时,我们将光标设置为Qt:: WaitCursor。
最后,我们调用私有setCurrentFile()函数中,我们将讨论在某一时刻,我们在状态栏中显示字符串“Fileloaded” 2秒钟(2000毫秒)。
bool MainWindow::saveFile(const QString &fileName)
{
QFile file(fileName);
if (!file.open(QFile::WriteOnly | QFile::Text)) { //the QFile::Text flag ensures that on Windows
QMessageBox::warning(this, tr("Application"),
tr("Cannot write file %1:\n%2.")
.arg(fileName)
.arg(file.errorString()));
return false;
}
QTextStream out(&file);
#ifndef QT_NO_CURSOR
QApplication::setOverrideCursor(Qt::WaitCursor);
#endif
out << textEdit->toPlainText();
#ifndef QT_NO_CURSOR
QApplication::restoreOverrideCursor();
#endif
setCurrentFile(fileName);
statusBar()->showMessage(tr("File saved"), 2000);
return true;
}
保存文件跟加载非常相似。在这里,QFile::Text标志确保在Windows中,“\ n”被转换成“\ r\ n”以符合窗口的习惯。
void MainWindow::setCurrentFile(const QString &fileName)
{
curFile = fileName;
textEdit->document()->setModified(false);
setWindowModified(false);
QString shownName = curFile;
if (curFile.isEmpty())
shownName = "untitled.txt";
setWindowFilePath(shownName);
}
当一个文件被加载或保存,或者当用户开始编辑一个新文件(在这种情况下,文件名是空的)时,setCurrentFile()函数被调用去重置一些变量的状态。我们更新了curFile变量,清除了QTextDocument::modified标志和相关的QWidget:windowModified标志,并更新窗口标题去显示新的文件名(或untitled.txt)。
The strippedName() function call around curFile in theQWidget::setWindowTitle() call shortens the file name to exclude the path.功能的定义如下:
QString MainWindow::strippedName(const QString &fullFileName)
{
return QFileInfo(fullFileName).fileName();
}
Main()函数
这个应用程序的Main()函数是典型的应用程序main()函数,它包含了一个主窗口。
#include <QApplication>
#include "mainwindow.h"
int main(int argc, char **argv)
{
Q_INIT_RESOURCE(images); //initial the resource file
QApplication app(argc, argv);
MainWindow mainWin;
mainWin.show(); // shot the mainWin interface
return app.exec();
}
资源文件:
你可能会记得,在一些actions中,我们以:开始指定的图标与文件名,并提到这样的文件名不是普通的文件名,而是可执行文件的存储资源路径。这些资源会被编译
在.qrc文件中说明了与应用程序相关的资源,该文件基于XML的文件格式,它列出磁盘上的文件。下面是应用实例是所使用的application.qrc文件:
<!DOCTYPE RCC><RCC version="1.0">
<qresource>
<file>images/copy.png</file>
<file>images/cut.png</file>
<file>images/new.png</file>
<file>images/open.png</file>
<file>images/paste.png</file>
<file>images/save.png</file>
</qresource>
</RCC>
在application.qrc文件中列出的.png文件是应用程序实例的子资源树的一部分。Application.qrc文件位置是目录的绝对路径(the mainwindows/applicationdirectory).
资源文件必须在application.pro中说明,这样的话qmake才能知道这些资源。
RESOURCES = application.qrc
Qmake会生成make rules来生成一个称为qrc_application.cpp的文件,该文件链接到应用程序。该文件包含图像所有数据,other resources as static C++arrays of compressed binary data.查看The Qt Resource System有更多关于资源的信息。