QT5.14.2自带Examples:Application

Application 示例功能概述

主要功能

Application 示例展示了如何实现标准的GUI应用程序(包括:菜单,工具栏,状态栏)。该示例本身是一个基于QPlainTextEdit的简单的文字编辑器程序。
QT5.14.2自带Examples:Application_第1张图片
几乎所有的代码都在MainWindow类中完成,它的基类为QMainWindow。QMainWindow为需要菜单、工具栏、停靠窗口、状态栏的窗口提供了一个框架。本示例在菜单栏中提供了File、Edit和Help入口。点开后分别可以看见下列弹出菜单:
QT5.14.2自带Examples:Application_第2张图片
状态栏在主窗口的底端,显示鼠标指向的菜单项或是工具栏按钮的描述。为了保持示例的简洁,在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应用程序,保存的文件。执行结果如下:
QT5.14.2自带Examples:Application_第3张图片
QT5.14.2自带Examples:Application_第4张图片
QT5.14.2自带Examples:Application_第5张图片

实现步骤

生成工程

启动Creator4.11.1
文件->新建文件或项目
创建一个Qt Widgets Application项目
QT5.14.2自带Examples:Application_第6张图片
后面按要求一路点下去,给项目起一个自己的名字,例如:myApplication。
在details步骤里,取消Generate form选项,UI的内容我都将采用代码实现。入下图所示:
QT5.14.2自带Examples:Application_第7张图片
Kits步骤里,一定要选择一个已有的编译器,入下图所示。多选几个也可以。例如,在我机器里,没有安装MSVC2017,如果只选择了该选项,在编译的时候将无法编程生成源文件。这里我选择了QT内置的WinGW编译器
QT5.14.2自带Examples:Application_第8张图片
完成后,将会看到一些默认的源文件,点击运行试一试效果:
QT5.14.2自带Examples:Application_第9张图片

添加资源文件

  1. 我们的菜单里需要一些logo图片,这些图片会放到资源文件里,流程如下:
    QT5.14.2自带Examples:Application_第10张图片
  2. 在新建面板中选择Qt->Qt Resource File点击Choose…,入下图所示:
    QT5.14.2自带Examples:Application_第11张图片
    3.给文件起一个名字,以及选择存放目录,如下所示:
    QT5.14.2自带Examples:Application_第12张图片
    后面继续点确定就可以了,在菜单中会看到一个Resources目录,下面有一个.qrc文件。我们还可以在右边的窗口中为其添加一些属性。如下所示:
    QT5.14.2自带Examples:Application_第13张图片
    右击.qrc文件的名称,选择添加现有文件:
    QT5.14.2自带Examples:Application_第14张图片
    找到存放图片的文件夹,加入图片即可。这里需要事先准备好图片,我将原版示例中的image文件夹拷贝到了和新建的资源文件相同的目录下:
    QT5.14.2自带Examples:Application_第15张图片
    后面我们就可以在代码里,使用资源文件中的图片了,图片路径可以通过右键图片名,从右键菜单中获取。
    QT5.14.2自带Examples:Application_第16张图片

main函数

为了更直观的展示示例的内容,我们从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();
}

MainWindow Class 定义

#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 Class 定义框架部分

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


目前并没有个人喜好的记忆功能,只是能够输入文字,无法保存、加载等。运行效果如下,:
QT5.14.2自带Examples:Application_第17张图片

MainWindow::createActions()实现

重复的地方比较多,我们只写出了一部分注释。其他部分对照一下,都差不多。

//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
}

运行效果如下:
QT5.14.2自带Examples:Application_第18张图片

MainWindow::maybeSave()实现

//如果有未保存的更改,它会弹出一个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没有单独的界面入口,需要其他函数的调用。

MainWindow::closeEvent(QCloseEvent *event)实现

//当用户试图关闭窗口时,调用maybeSave()
void MainWindow::closeEvent(QCloseEvent *event)
{
    if (maybeSave()) {
        writeSettings();
        event->accept();
    } else {
        //如果用户选择 Cancel 按钮,则当什么都没发生好了。
        event->ignore();
    }
}

运行结果如下:
QT5.14.2自带Examples:Application_第19张图片

MainWindow::saveAs()实现

//在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观看效果,目前只能弹出保存窗口,不能真正的完成保存功能。

MainWindow::saveFile(const QString &fileName)实现

//使用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将对话框中的文字保存到一个文件里了。

MainWindow::setCurrentFile(const QString &fileName)实现

//在加载、保存文件或用户开始编辑新文件(在这种情况下,文件名为空)时重置一些变量的状态。
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);
}

运行效果如下:
QT5.14.2自带Examples:Application_第20张图片

MainWindow::loadFile(const QString &fileName)实现

与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());
}

你可能感兴趣的:(QT5.14.2自带Examples:Application)