Qt Quick Text Editor Guide - Logic

This part of the guide is about adding logic and backend to the text editor example. At this stage, the user interface is set up from the previous stage.

Implementing the Logic and C++ Backend

Text Editor has a QML user interface and a C++ backend to implement the document handling. To connect QML and C++, we need to create actions associated to the tool buttons, which will call the document handling logic in C++.

Creating the Document Handler

The document handler implements the file loading and file saving logic with Qt's C++ APIs. First, we need to create the header file and the implementation file in Qt Creator's Edit mode.

  • Right-click a folder, and select Add New.
  • Follow the wizard and create a new C++ Class.
  • Create a class called DocumentHandler and select Inherits QObject in the Type information.
  • You can use default values for the rest and finish the wizard.

The wizard creates a DocumentHandler class in two files, documenthandler.h and documenthandler.cpp.

There are two functionalities we can expose to QML, the file loading and saving. We can do this by creating properties and binding them to C++ functions through the Qt Property System.

In the documenthandler.h header file, add the following functions with their respective access modifier:

     Q_PROPERTY(QUrl fileUrl READ fileUrl WRITE setFileUrl NOTIFY fileUrlChanged)
     Q_PROPERTY(QString text READ text WRITE setText NOTIFY textChanged)
     Q_PROPERTY(QString documentTitle READ documentTitle WRITE setDocumentTitle NOTIFY documentTitleChanged)

 public:
     QUrl fileUrl() const;
     QString text() const;
     QString documentTitle() const;

 public slots:
     void setFileUrl(const QUrl &arg);
     void setText(const QString &arg);
     void setDocumentTitle(QString arg);

 signals:
     void fileUrlChanged();
     void textChanged();
     void documentTitleChanged();

The lines with Q_PROPERTY() macro declares the property and its write and read methods as well as its notify signal. For example, setting the fileUrl property calls setFileUrl() and reading the property calls the fileUrl() function. Similarly, when the value of fileUrl changes the fileUrlChanged() function is called.

Internally, the properties are represented by private member variables. For our needs, here are the three variables in documenthandler.h which correspond to the properties:

 private:
     QUrl m_fileUrl;
     QString m_text;
     QString m_documentTitle;

Implementing the read functions is straightforward. They simply return the private member variables. For example, the implementation of documentTitle() in documenthandler.cpp is:

 QString DocumentHandler::documentTitle() const
 {
     return m_documentTitle;
 }

Implementing the write (setText(), for example) functions is also straightforward as they simply assign a value to a private member variable. They also handle basic error handling and they emit their respective notify signals. For example, the setDocumentTitle() function is implemented in documenthandler.cpp as:

 void DocumentHandler::setDocumentTitle(QString arg)
 {
     if (m_documentTitle != arg) {
         m_documentTitle = arg;
         emit documentTitleChanged();
     }
 }

The opening of the file is done in the setFileUrl() function:

 void DocumentHandler::setFileUrl(const QUrl &arg)
 {
     if (m_fileUrl != arg) {
         m_fileUrl = arg;
         QString fileName = arg.fileName();
         QFile file(arg.toLocalFile());
         if (file.open(QFile::ReadOnly)) {
             setText(QString(file.readAll()));
             if (fileName.isEmpty())
                 m_documentTitle = QStringLiteral("untitled");
             else
                 m_documentTitle = fileName;
             emit textChanged();
             emit documentTitleChanged();
         }
         emit fileUrlChanged();
     }
 }

Note how the function emits the notify signals with the emit keyword.

Similarly, we use QFile and text streams to save files. The function signature in documenthandler.h is placed under public slots because that is one way to expose functions to the QML engine. saveFile() is called from the QML file during saving.

 public slots:

     Q_INVOKABLE void saveFile(const QUrl &arg) const;

The implementation of saveFile() is in documenthandler.cpp:

 void DocumentHandler::saveFile(const QUrl &arg) const
 {
     QFile file(arg.toLocalFile());
     if (file.open(QFile::WriteOnly | QFile::Truncate)) {
         QTextStream out(&file);
         out << text();
     }
 }

For information about reading files and data storage, visit the QFile and the Data Storage documentation.

Registering the DocumentHandler Class

We now need to let the QML engine know about the DocumentHandler and its type. The qmlRegisterType() function is called in the application's main() function in main.cpp:

 qmlRegisterType<DocumentHandler>("org.qtproject.example", 1, 0, "DocumentHandler");

The org.qtproject.example is the library with the version 1.0 and the QML type registered is DocumentHandler. The import statement for the DocumentHandler QML type is then

 import org.qtproject.example 1.0

Note: The qmlRegisterType() function should be called before the engine loads the QML file.

Using the DocumentHandler QML type

With the basic loading implemented, we can use the functionalities in the QML file by creating an instance of the DocumentHandler class and by accessing its properties.

The next page is about using these C++ functions in QML files.

Example Files

The accompanying examples files are found in the following page: