【QT 教程】Application Example(Text Editor)

本文翻译自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有更多关于资源的信息。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值