Application 示例展示了如何实现标准的GUI应用程序(包括:菜单,工具栏,状态栏)。该示例本身是一个基于QPlainTextEdit的简单的文字编辑器程序。
几乎所有的代码都在MainWindow类中完成,它的基类为QMainWindow。QMainWindow为需要菜单、工具栏、停靠窗口、状态栏的窗口提供了一个框架。本示例在菜单栏中提供了File、Edit和Help入口。点开后分别可以看见下列弹出菜单:
状态栏在主窗口的底端,显示鼠标指向的菜单项或是工具栏按钮的描述。为了保持示例的简洁,在File菜单中没有包含最近访问过的文件列表功能,虽然该功能在90%的应用程序中都需要实现。此外,本示例每次只能打开一个文件。在SDI和MDI示例中,将展示如何去除这些限制,以及实现最近访问文件列表功能。
本示例还提供了个人喜好的记忆功能,会在电脑上为该应用存储一些信息。例如关闭时的位置,窗口的大小。下次打开的时候会自动读取这些信息,并使用。
本示例还展示了如何在QT应用程序中使用命令行参数。为了演示该功能,首先需要完成编译,生成可执行文件。并找到生成的执行文件所在的文件夹。例如在我的机器里,内置的实例生成后在下面的文件夹中:D:\Qt\Qt5.14.2\Examples\Qt-5.14.2\widgets\mainwindows\build-application-Desktop_Qt_5_14_2_MinGW_64_bit-Debug\debug。
确保可执行文件载文件夹中后,可以在doc窗口中输入命令,进行测试。
set path=D:\Qt\Qt5.14.2\5.14.2\mingw73_64\bin
application.exe --help
application.exe -v
application.exe test
其中test是通过Application应用程序,保存的文件。执行结果如下:
启动Creator4.11.1
文件->新建文件或项目
创建一个Qt Widgets Application项目
后面按要求一路点下去,给项目起一个自己的名字,例如:myApplication。
在details步骤里,取消Generate form选项,UI的内容我都将采用代码实现。入下图所示:
Kits步骤里,一定要选择一个已有的编译器,入下图所示。多选几个也可以。例如,在我机器里,没有安装MSVC2017,如果只选择了该选项,在编译的时候将无法编程生成源文件。这里我选择了QT内置的WinGW编译器
完成后,将会看到一些默认的源文件,点击运行试一试效果:
为了更直观的展示示例的内容,我们从main函数开始。
#include
//命令行参数所需的两个头文件。第二个在这里没有用到,可以删除。
#include
#include
#include "mainwindow.h"
int main(int argc, char *argv[])
{
//初始化资源文件
Q_INIT_RESOURCE(application);
QApplication app(argc, argv);
//为了记录个人的喜好,需要在本机为该应用创建一个“账户”,机构名称、应用名称
QCoreApplication::setOrganizationName("QtProject");
QCoreApplication::setApplicationName("Application Example");
//版本信息,如果在QCommandLineParser 开启了该项。通过-v或--help能够查询
QCoreApplication::setApplicationVersion(QT_VERSION_STR);
QCommandLineParser parser;
//为parser添加一个应用的描述,该描述会出现在--help中
parser.setApplicationDescription(QCoreApplication::applicationName());
//开启--help
parser.addHelpOption();
//开启--version
parser.addVersionOption();
//位置参数,在本示例中用于在启动时加载一个文件。
parser.addPositionalArgument("file", "The file to open.");
//前面已经把parser设置好了,现在使用parser去解析本应用程序的命令行参数。
parser.process(app);
MainWindow mainWin;
//对应前面的parser.addPositionalArgument,这里我们只需要第一个参数(文件名)
//if (!parser.positionalArguments().isEmpty())
// mainWin.loadFile(parser.positionalArguments().first());
mainWin.show();
return app.exec();
}
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include
//如果用户没有自定义的命名空间,该宏什么都不做。
//如果用户有自定义的命名空间,则将中间的内容放入Qt自己定义的命名空间,像下面这样。
//# define QT_BEGIN_NAMESPACE namespace QT_NAMESPACE {
QT_BEGIN_NAMESPACE
//加快编译速度
class QAction;
class QMenu;
class QPlainTextEdit;
class QSessionManager;
//# define QT_END_NAMESPACE }
QT_END_NAMESPACE
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
void loadFile(const QString &fileName);
//继承Qobject的类到都不需要自己写析构函数,继承自QObject析构函数就可以把子类删除
protected:
//当用户是否要关闭窗口时,如果内容有变更,则提醒用户保存。
void closeEvent(QCloseEvent *event) override;
private slots:
void newFile();
void open();
bool save();
bool saveAs();
void about();
void documentWasModified();
//session是一组正在运行的应用程序,每一个都有特定的状态。由session manager服务进行控制。
//seession里的应用程序称为session客户端
//session manager根据使用者的行为,向客户端发送命令。
//这些命令可能会导致客户端提交未保存的更改(例如保存打开的文件),
//为将来的session保留其状态,或正常终止。这些操作的集合称为会话管理。
//在常见情况下,session由用户在桌面上同时运行的所有应用程序组成。
//注意: 使用QSessionManager, 应该禁用回退会话管理
//这部分需要session 服务程序配合才能够演示功能。例如可以通过一个程序,发出命令关闭其他程序。
//session(会话):是另一种记录应用程序状态的机制
//如果没有特别指出,不需要SESSIONMANAGER
//那么我们就需要commitData函数
#ifndef QT_NO_SESSIONMANAGER
void commitData(QSessionManager &);
#endif
private:
void createActions();
void createStatusBar();
void readSettings();
void writeSettings();
bool maybeSave();
bool saveFile(const QString &fileName);
void setCurrentFile(const QString &fileName);
QString strippedName(const QString &fullFileName);
QPlainTextEdit *textEdit;
QString curFile;
};
#endif // MAINWINDOW_H
MainWindow 的定义部分,代码非常多,所以第一步我们只是把框架搭好,完成构造函数和一些一看就懂的简单函数。其他的只保留函数名,内容为空。返回值不为void类型的函数,我们随便给一个返回值,后面再修改。
#include "mainwindow.h"
#include
MainWindow::MainWindow(QWidget *)
//初始化textEditQPlain
: textEdit(new QPlainTextEdit)
{
//textEdit 将是占据主窗口中心区域,位于工具栏和状态栏之间
setCentralWidget(textEdit);
//设置用户交互界面
createActions();
createStatusBar();
//恢复用户的喜好选项
readSettings();
//我们在QPlainTextEdit的document对象和documentWasModified()之间建立了一个信号槽连接。
//每当用户修改QPlainTextEdit中的文本时,我们将更新标题栏以显示文件已被修改*。
connect(textEdit->document(), &QTextDocument::contentsChanged,
this, &MainWindow::documentWasModified);
#ifndef QT_NO_SESSIONMANAGER
QGuiApplication::setFallbackSessionManagementEnabled(false);
connect(qApp, &QGuiApplication::commitDataRequest,
this, &MainWindow::commitData);
#endif
//设置窗口标题。
setCurrentFile(QString());
//是否在macOS上使用统一的标题和工具栏外观
setUnifiedTitleAndToolBarOnMac(true);
}
void MainWindow::closeEvent(QCloseEvent *event)
{
}
void MainWindow::newFile()
{
}
void MainWindow::open()
{
}
bool MainWindow::save()
{
if (curFile.isEmpty()) {
return saveAs();
} else {
return saveFile(curFile);
}
}
bool MainWindow::saveAs()
{
return true;
}
void MainWindow::about()
{
QMessageBox::about(this, tr("About Application"),
tr("The Application example demonstrates how to "
"write modern GUI applications using Qt, with a menu bar, "
"toolbars, and a status bar."));
}
void MainWindow::documentWasModified()
{
}
void MainWindow::createActions()
{
}
void MainWindow::createStatusBar()
{
statusBar()->showMessage(tr("Ready"));
}
void MainWindow::readSettings()
{
}
void MainWindow::writeSettings()
{
}
bool MainWindow::maybeSave()
{
}
void MainWindow::loadFile(const QString &fileName)
{
}
bool MainWindow::saveFile(const QString &fileName)
{
return true;
}
void MainWindow::setCurrentFile(const QString &fileName)
{
}
//排除路径缩短文件名
QString MainWindow::strippedName(const QString &fullFileName)
{
return QFileInfo(fullFileName).fileName();
}
#ifndef QT_NO_SESSIONMANAGER
void MainWindow::commitData(QSessionManager &manager)
{
//如果允许交互则试图关闭顶层窗口,再保存该应用的session内容,否则直接保存。
if (manager.allowsInteraction()) {
if (!maybeSave())
manager.cancel();
} else {
// Non-interactive: save without asking
if (textEdit->document()->isModified())
save();
}
}
#endif
目前并没有个人喜好的记忆功能,只是能够输入文字,无法保存、加载等。运行效果如下,:
重复的地方比较多,我们只写出了一部分注释。其他部分对照一下,都差不多。
//QAction是表示一个用户操作的对象,例如保存文件或调用对话框。
//一个action可以放在QMenu或QToolBar中,或者两者都放,
//或者放在任何其他重新实现QWidget::actionEvent()的widget中。
void MainWindow::createActions()
{
//menuBar()获取菜单栏的指针,通过addMenu创建一个File菜单。返回File菜单的指针
QMenu *fileMenu = menuBar()->addMenu(tr("&File"));
//添加一个File的工具栏
QToolBar *fileToolBar = addToolBar(tr("File"));
//创建一个QIcon对象,使用资源文件中的图片。
//使用QIcon::fromTheme通过主题名称,从操作系统获取图标。
//第二个参数是备胎。苹果可以使用第一个参数,windows下只能用备胎。
const QIcon newIcon = QIcon::fromTheme("document-new", QIcon(":/images/new.png"));
//(icon, &text, *parent);
QAction *newAct = new QAction(newIcon, tr("&New"), this);
//依赖于平台的快捷方式列表,调用此函数的结果将取决于当前运行的平台。
//QKeySequence::New值为6,表示新建一个文档的快捷键
newAct->setShortcuts(QKeySequence::New);
newAct->setStatusTip(tr("Create a new file"));
connect(newAct, &QAction::triggered, this, &MainWindow::newFile);
//吧newAct分别加入到菜单和工具栏
fileMenu->addAction(newAct);
fileToolBar->addAction(newAct);
const QIcon openIcon = QIcon::fromTheme("document-open", QIcon(":/images/open.png"));
QAction *openAct = new QAction(openIcon, tr("&Open..."), this);
openAct->setShortcuts(QKeySequence::Open);
openAct->setStatusTip(tr("Open an existing file"));
connect(openAct, &QAction::triggered, this, &MainWindow::open);
fileMenu->addAction(openAct);
fileToolBar->addAction(openAct);
const QIcon saveIcon = QIcon::fromTheme("document-save", QIcon(":/images/save.png"));
QAction *saveAct = new QAction(saveIcon, tr("&Save"), this);
saveAct->setShortcuts(QKeySequence::Save);
saveAct->setStatusTip(tr("Save the document to disk"));
connect(saveAct, &QAction::triggered, this, &MainWindow::save);
fileMenu->addAction(saveAct);
fileToolBar->addAction(saveAct);
const QIcon saveAsIcon = QIcon::fromTheme("document-save-as");
//(&icon, &text, *receiver,*member,&shortcut = 0)
//这里第三个参数直接把member函数也写进去了,就不需要在写connection了
QAction *saveAsAct = fileMenu->addAction(saveAsIcon, tr("Save &As..."), this, &MainWindow::saveAs);
saveAsAct->setShortcuts(QKeySequence::SaveAs);
saveAsAct->setStatusTip(tr("Save the document under a new name"));
fileMenu->addSeparator();
const QIcon exitIcon = QIcon::fromTheme("application-exit");
QAction *exitAct = fileMenu->addAction(exitIcon, tr("E&xit"), this, &QWidget::close);
exitAct->setShortcuts(QKeySequence::Quit);
exitAct->setStatusTip(tr("Exit the application"));
QMenu *editMenu = menuBar()->addMenu(tr("&Edit"));
QToolBar *editToolBar = addToolBar(tr("Edit"));
#ifndef QT_NO_CLIPBOARD
const QIcon cutIcon = QIcon::fromTheme("edit-cut", QIcon(":/images/cut.png"));
QAction *cutAct = new QAction(cutIcon, tr("Cu&t"), this);
cutAct->setShortcuts(QKeySequence::Cut);
cutAct->setStatusTip(tr("Cut the current selection's contents to the "
"clipboard"));
connect(cutAct, &QAction::triggered, textEdit, &QPlainTextEdit::cut);
editMenu->addAction(cutAct);
editToolBar->addAction(cutAct);
const QIcon copyIcon = QIcon::fromTheme("edit-copy", QIcon(":/images/copy.png"));
QAction *copyAct = new QAction(copyIcon, tr("&Copy"), this);
copyAct->setShortcuts(QKeySequence::Copy);
copyAct->setStatusTip(tr("Copy the current selection's contents to the "
"clipboard"));
connect(copyAct, &QAction::triggered, textEdit, &QPlainTextEdit::copy);
editMenu->addAction(copyAct);
editToolBar->addAction(copyAct);
const QIcon pasteIcon = QIcon::fromTheme("edit-paste", QIcon(":/images/paste.png"));
QAction *pasteAct = new QAction(pasteIcon, tr("&Paste"), this);
pasteAct->setShortcuts(QKeySequence::Paste);
pasteAct->setStatusTip(tr("Paste the clipboard's contents into the current "
"selection"));
connect(pasteAct, &QAction::triggered, textEdit, &QPlainTextEdit::paste);
editMenu->addAction(pasteAct);
editToolBar->addAction(pasteAct);
menuBar()->addSeparator();
#endif // !QT_NO_CLIPBOARD
QMenu *helpMenu = menuBar()->addMenu(tr("&Help"));
QAction *aboutAct = helpMenu->addAction(tr("&About"), this, &MainWindow::about);
aboutAct->setStatusTip(tr("Show the application's About box"));
QAction *aboutQtAct = helpMenu->addAction(tr("About &Qt"), qApp, &QApplication::aboutQt);
aboutQtAct->setStatusTip(tr("Show the Qt library's About box"));
#ifndef QT_NO_CLIPBOARD
cutAct->setEnabled(false);
copyAct->setEnabled(false);
//确保必须有文字选择才可以剪切、复制
connect(textEdit, &QPlainTextEdit::copyAvailable, cutAct, &QAction::setEnabled);
connect(textEdit, &QPlainTextEdit::copyAvailable, copyAct, &QAction::setEnabled);
#endif // !QT_NO_CLIPBOARD
}
//如果有未保存的更改,它会弹出一个QMessageBox,提醒用户保存文档。
bool MainWindow::maybeSave()
{
if (!textEdit->document()->isModified())
return true;
const QMessageBox::StandardButton 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);
switch (ret) {
//如果点击save,则执行保存
case QMessageBox::Save:
return save();
case QMessageBox::Cancel:
return false;
default:
break;
}
return true;
}
maybeSave没有单独的界面入口,需要其他函数的调用。
//当用户试图关闭窗口时,调用maybeSave()
void MainWindow::closeEvent(QCloseEvent *event)
{
if (maybeSave()) {
writeSettings();
event->accept();
} else {
//如果用户选择 Cancel 按钮,则当什么都没发生好了。
event->ignore();
}
}
//在saveAs()中,我们首先弹出一个QFileDialog,要求用户提供一个名称。
//如果用户单击“取消”,则返回的文件名为空,直接退出。
////否则执行saveFile函数
bool MainWindow::saveAs()
{
QFileDialog dialog(this);
//设置为模态对话框,就是没有关闭它之前,用户不能再与同一个应用程序的其他窗口进行交互
dialog.setWindowModality(Qt::WindowModal);
dialog.setAcceptMode(QFileDialog::AcceptSave);
if (dialog.exec() != QDialog::Accepted)
return false;
return saveFile(dialog.selectedFiles().first());
}
可以点击File ==>Save As观看效果,目前只能弹出保存窗口,不能真正的完成保存功能。
//使用QSaveFile来确保所有数据都安全写入,并且在写入失败时不会损坏现有文件。
//我们使用QFile::Text标志来确保在Windows上,“\n”被转换为“\r\n”以符合Windows约定。
bool MainWindow::saveFile(const QString &fileName)
{
QString errorMessage;
//在当前状态下,改为 Qt::WaitCursor 光标
QGuiApplication::setOverrideCursor(Qt::WaitCursor);
QSaveFile file(fileName);
if (file.open(QFile::WriteOnly | QFile::Text)) {
QTextStream out(&file);
out << textEdit->toPlainText();
if (!file.commit()) {
errorMessage = tr("Cannot write file %1:\n%2.")
//返回路径名,其中“/”分隔符转换为适合基础操作系统的分隔符。
.arg(QDir::toNativeSeparators(fileName), file.errorString());
}
} else {
errorMessage = tr("Cannot open file %1 for writing:\n%2.")
.arg(QDir::toNativeSeparators(fileName), file.errorString());
}
//设置回之前的光标
QGuiApplication::restoreOverrideCursor();
if (!errorMessage.isEmpty()) {
QMessageBox::warning(this, tr("Application"), errorMessage);
return false;
}
setCurrentFile(fileName);
//(&message, timeout = 0)
//timeout的单位为milli-seconds。
//显示File Saved,持续时间2秒
statusBar()->showMessage(tr("File saved"), 2000);
return true;
}
现在我们可以通过File ==>Save As将对话框中的文字保存到一个文件里了。
//在加载、保存文件或用户开始编辑新文件(在这种情况下,文件名为空)时重置一些变量的状态。
void MainWindow::setCurrentFile(const QString &fileName)
{
//我们更新curFile变量
curFile = fileName;
textEdit->document()->setModified(false);
setWindowModified(false);
QString shownName = curFile;
if (curFile.isEmpty())
shownName = "untitled.txt";
//将widget与文件路径联系起来。
//如果没有设置标题,将会使用文件名作为标题
setWindowFilePath(shownName);
}
与saveFile类似,具体看代码吧:
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(QDir::toNativeSeparators(fileName), file.errorString()));
return;
}
QTextStream in(&file);
#ifndef QT_NO_CURSOR
QGuiApplication::setOverrideCursor(Qt::WaitCursor);
#endif
textEdit->setPlainText(in.readAll());
#ifndef QT_NO_CURSOR
QGuiApplication::restoreOverrideCursor();
#endif
setCurrentFile(fileName);
statusBar()->showMessage(tr("File loaded"), 2000);
}
void MainWindow::readSettings()
{
QSettings settings(QCoreApplication::organizationName(), QCoreApplication::applicationName());
//QByteArray类似const char * ,更高级
//value(key,默认值)
const QByteArray geometry = settings.value("geometry", QByteArray()).toByteArray();
if (geometry.isEmpty()) {
//设置默认状态
const QRect availableGeometry = screen()->availableGeometry();
resize(availableGeometry.width() / 3, availableGeometry.height() / 2);
//移动到正中心,计算出剩余空间的大小,除以2就得到合适的移动距离
move((availableGeometry.width() - width()) / 2,
(availableGeometry.height() - height()) / 2);
} else {
restoreGeometry(geometry);
}
}
//在closeEvent中被调用
void MainWindow::writeSettings()
{
//保存顶级widget的当前形状、大小和状态。
QSettings settings(QCoreApplication::organizationName(), QCoreApplication::applicationName());
settings.setValue("geometry", saveGeometry());
}
退出的状态将会被记录在本机,下次启动时会被调用。如果没有,则启用默认状态。
这些函数,主要功能是调用前面的函数。非常直观,就不写注释了。
void MainWindow::newFile()
{
if (maybeSave()) {
textEdit->clear();
setCurrentFile(QString());
}
}
void MainWindow::open()
{
if (maybeSave()) {
QString fileName = QFileDialog::getOpenFileName(this);
if (!fileName.isEmpty())
loadFile(fileName);
}
}
void MainWindow::documentWasModified()
{
setWindowModified(textEdit->document()->isModified());
}