The way of Qt Quick debugging: a comprehensive guide to tracking, outputting and printing information

The way of Qt Quick debugging: a comprehensive guide to tracking, outputting and printing information

console.log("123");
console.log("a is ", a, "b is ", b);
打印代码块时间
console.time("wholeFunction");
//代码块
console.timeEnd("wholeFunction");
//打印执行次数
console.count("fun called")

1. Introduction to Qt Quick

1.1. Overview of Qt Quick

Qt Quick is a modern, lightweight technical framework that allows developers to use QML (Qt Meta-Object Language) for fast and efficient user interface (UI) design and development. Qt Quick Apps implemented in C++ offers strong performance benefits while supporting multi-platform deployment (such as Windows, macOS, Linux, Android and iOS).

At the core of Qt Quick there are several important components:

  • QML: A declarative JavaScript-based language for building dynamic, fluid, and easily extensible UIs.
  • Qt Quick Items: Provides 2D rendering capabilities and hardware-accelerated graphics rendering support.
  • State and transition: It can be understood by referring to state management in CSS or WPF.
  • Data model and view: Similar to the method of processing data in the MVC or MVVM design pattern.

1.2.Qt Quick入门 (Getting Started with Qt Quick)

To start using Qt Quick, you first need to install the Qt development environment. The official Qt website provides corresponding tutorials and download links: https://www.qt.io/download

Once installed, create a new Qt Quick project in Qt Creator. You can choose from different types of base templates, such as blank project, with list view, etc.

In the project structure, you will typically see the following files:

  • main.qml: the main QML file, which contains the entire user interface content;
  • qml.qrc: resource file, which contains references to all static resources (images, audio, etc.) required by the client;
  • main.cpp: C++ source file, instantiate and run QQmlApplicationEngine, then load the main.qml file;

Here is a simple example to illustrate how to use Qt Quick to build a window displaying "Hello, World!":

main.qml:

import QtQuick 2.12
import QtQuick.Window 2.12

Window {
    visible: true
    width: 640
    height: 480
    title: qsTr("Hello World")

    Text {
        anchors.centerIn: parent
        text: "Hello, World!"
    }
}

main.cpp:

#include <QGuiApplication>
#include <QQmlApplicationEngine>

int main(int argc, char *argv[])
{
    
    
    QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
    QGuiApplication app(argc, argv);

    QQmlApplicationEngine engine;
    const QUrl url(QStringLiteral("qrc:/main.qml"));
    QObject::connect(&engine, &QQmlApplicationEngine::objectCreated,
                     &app, [url](QObject *obj, const QUrl &objUrl) {
    
    
        if (!obj && url == objUrl)
            QCoreApplication::exit(-1);
    }, Qt::QueuedConnection);
    engine.load(url);

    return app.exec();
}

Next, click the run button through Qt Creator to view the created simple window. This is a basic introductory example for building programs with Qt Quick. On this basis, you can start to design your own user interface and application logic according to your needs.

1.3. Introduction to Qt Quick and QML

Qt Quick is a UI framework based on the declarative language QML. QML is a highly readable, component-oriented and easy-to-maintain language that provides integrated support for JavaScript code.

QML files have .qmlthe extension and contain the following elements:

  • Objects : Controls, models, etc. are represented as objects in QML.
  • Properties : Used to configure the appearance or behavior of an object (such as size, color, or position).
  • Signals and slots : Event handlers designed to respond to user actions or other external input.
  • Animations and Transitions : Used to define smooth property changes.

Here is a simple QML example:

import QtQuick 2.0

Rectangle {
    width: 200
    height: 200
    color: "blue"

    Text {
        anchors.centerIn: parent
        text: "Hello, World!"
    }
}

This example creates a rectangular box with a text label. As you can see, QML is designed to be very simple and intuitive.

With Qt Creator, you can easily create and edit QML files. Right-click on the project, select "Add New..." and find the ".qml" file type to quickly start writing QML code. At the same time, Qt Creator also supports the real-time preview function to view the immediate effect.

To run a QML file, make sure you have properly set up main.cppand linked to the appropriate QML file. Here is a common main.cppexample :

#include <QGuiApplication>
#include <QQmlApplicationEngine>

int main(int argc, char *argv[])
{
    
    
    QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
    QGuiApplication app(argc, argv);

    QQmlApplicationEngine engine;
    const QUrl url(QStringLiteral("qrc:/main.qml"));
    QObject::connect(&engine, &QQmlApplicationEngine::objectCreated,
                     &app, [url](QObject *obj, const QUrl &objUrl) {
    
    
        if (!obj && url == objUrl)
            QCoreApplication::exit(-1);

2. Basic Techniques for Printing Information in Qt Quick

2.1. console.log() Detailed explanation (Understanding console.log())

console.log() is the recommended way to output debugging information in QML. This function sends text messages to the application's standard output (for example, on the command line, or otherwise captures and logs them). This allows the programmer to visualize what is happening in the current block of code execution.

2.1.1 Basic usage of console.log (Basic Usage)

Here is a simple example showing how to use console.log() to print messages:

import QtQuick 2.12

Item {
    
    
    id: root
    width: 640
    height: 480

    Component.onCompleted: {
    
    
        console.log("Root item completed")
    }

    MouseArea {
    
    
        id: mouse
        anchors.fill: parent
        onClicked: {
    
    
            console.log("Mouse clicked")
        }
    }
}

When the Item is loaded, Component.onCompleted will print a "Root item completed" log. When the TODO area is clicked, onClicked will print a "Mouse clicked" log.

2.1.2 Advanced Features and Tips

In addition to simple strings, console.log() supports a variety of value types and formatting options, such as:

  • Use floats and integers:console.log('The answer is' , 42)
  • format string:console.log('%1 plus %2 equals %3', 5, 6, 5 + 6)
  • Print properties of objects and arrays:console.log(JSON.stringify(obj))
  • Add the module name to the output: define a variable in each module, for example var modName = 'MyModule', and then include it when console.log() is called:console.log(modName, ':', 'Message...')

Precautions:

  • The '%'-sign may cause unexpected format conversions if the output text contains dashes ('٪'). In this case, escape with two consecutive %%.

To sum up, the powerful but easy-to-understand console.log() method provides a convenient and fast debugging method for QML programmers. Although it is only the basic article among many printing information skills, it can meet most of the daily development needs.

2.2 Correct posture of using qDebug(), qInfo(), qWarning(), qCritical(), qFatal() in QML (Using qDebug(), qInfo(), qWarning(), qCritical(), qFatal() in QML )

2.2.1 Basic Examples

In Qt Quick applications, we usually encounter situations where debugging information needs to be output. Although console.log can meet most needs, sometimes you may need more functions and formatted output. At this time, you need to use qDebug(), qInfo(), qWarning(), qCritical() and qFatal() in QML.

In order to use these functions in QML, you first need to import Qt.loigcthe module:

import QtQuick 2.0
import Qt.logic 1.0

Rectangle {
    id: rectangle
    Component.onCompleted: {
        console.log("Hello, World!")
        
        // 使用相关方法进行日志输出
        Debugging.qDebug("This is a debug message.")
        Debugging.qInfo("This is an info message.")
        Debugging.qWarning("This is a warning message.")
        Debugging.qCritical("This is a critical error message.")
        // Debugging.qFatal("This will cause the program to terminate")
    }
}

Next, define the Debugging class in the C++ source file and expose it to QML:

#include <QObject>
#include <QDebug>

class Debugging : public QObject
{
    
    
    Q_OBJECT
public:
    explicit Debugging(QObject *parent = nullptr) : QObject(parent) {
    
    }

    Q_INVOKABLE void qDebug(const QString &message)
    {
    
    
        ::qDebug().noquote() << message;
    }

    Q_INVOKABLE void qInfo(const QString &message)
    {
    
    
        ::qInfo().noquote() << message;
    }

    Q_INVOKABLE void qWarning(const QString &message)
    {
    
    
        ::qWarning().noquote() << message;
    }

    Q_INVOKABLE void qCritical(const QString &message)
    {
    
    
        ::qCritical().noquote() << message;
    }
};

After that, you need to register this type in the main method (main.cpp):

#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include "debugging.h"

int main(int argc, char *argv[])
{
    
    
    QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);

    QGuiApplication app(argc, argv);

    qmlRegisterType<Debugging>("com.example.debugging", 1, 0, "Debugging");

    QQmlApplicationEngine engine;
    const QUrl url(QStringLiteral("qrc:/main.qml"));
    QObject::connect(&engine, &QQmlApplicationEngine::objectCreated,
                     &app, [url](QObject *obj, const QUrl &objUrl) {
    
    
        if (!obj && url == objUrl)
            QCoreApplication::exit(-1);
    }, Qt::QueuedConnection);
    engine.load(url);

    return app.exec();
}

After passing the above steps, qDebug(), qInfo(), qWarning(), qCritical(), qFatal() can be called normally in QML.

2.2.2 More Practical Scenarios

In addition to basic usage, there are some other practical scenarios where functions such as qDebug() can be used. for example:

  • Output concise, readable and efficient log information: You can use the string formatting function provided by Qt to output more readable and efficient log information.
  • Record the operation process: You can use methods such as qDebug() to record multiple stages of user operations and program execution, so that you can clearly understand the situation of each link when a problem occurs.
  • Analyze function performance: Can be combined with the QElapsedTimer class to easily print and measure the running time of a code block.

So we have completed the second part 2.2 Introduction to Qt Quick in QML using qDebug() and other related debug output functions.

2.3. Using OutputDebugString in Windows and syslog in Linux (Using OutputDebugString in Windows and syslog in Linux)

2.3.1 Overview and Applications of OutputDebugString

OutputDebugString is a function to send a text message to the debugger under Windows platform. By combining Qt, we can print out more detailed log information and perform real-time monitoring. Here's how to use OutputDebugString in Qt:

#include <windows.h>

void printToOutputDebug(const QString& message) {
    
    
    std::wstring wideMessage = message.toStdWString();
    OutputDebugStringW(wideMessage.c_str());
}

This simple C++ method converts a character to a wide string format and outputs it using OutputDebugStringW.

2.3.2 Printing Information Using Syslog

The syslog (system log) function of the Linux system provides a means of recording log information during program operation. Compared to OutputDebugString, it has a similar effect, but for a different operating system. To use this method with Qt Quick, follow the steps below:

First, include the following header file (<syslog.h>) in your C++ file:

#include <syslog.h>

Then create a custom function that handles the conversion between QString objects and syslog():

void writeToSysLog(const QString &message){
    
    
   // Convert the QString to a char array
   QByteArray messageByteArray = message.toLatin1();

   // Use the char array as input for syslog()
  openlog("myApplicationName", LOG_PID | LOG_CONS, LOG_USER);
  syslog(LOG_INFO, "%s", messageByteArray.constData())
  closelog();
}

Through the above method, you can use syslog() in the QML project to output debugging, warning and error information.

3. QDiving into the Underlying Principles of Printing Information in Qt Quick

3.1 Application of Signals & Slots Mechanism in Printing Information

Qt provides a unique signal and slots (Signals and slots) mechanism to handle events, which is a method of communication between objects. When an event occurs, the sender will receive a signal about the event and broadcast it to the system. The slots connected to this signal are then automatically called to perform the operations required to deliver the given signal. We can use this powerful function to easily trace and output debugging information in Qt Quick.

3.1.1 Using signals and slots to print QML object property changes

First, let's explore signals and slots in action for Qt Quick debugging by monitoring QML object property changes and logging them to the console using signals and slots.

Suppose we have a simple Rectangle:

import QtQuick 2.0

Rectangle {
    
    
    id: rect
    width: 100; height: 50
    color: "red"
}

To recttrack color or size changes of , we can define signals and corresponding slot handlers:

import QtQuick 2.0

Rectangle {
    
    
    id: rect
    width: 100; height: 50
    color: "red"

    signal colorChanged(string newColor)
    signal sizeChanged(real newWidth, real newHeight)

    onColor: {
    
    
        console.log("Color changed to:", newColor);
        colorChanged(color); // 发送信号,传递新颜色给槽处理器
    }

    onSizeChanged: {
    
    
        console.log("Size changed. New width and height:", newWidth, newHeight);
        sizeChanged(newWidth, newHeight); // 发送信号,传递宽度和高度给槽处理器
    }
}

This way, when the size or color of the rectangle changes, the new value will be automatically printed to the console.

3.1.2 Using signals and slots to capture error information in components

In addition to logging property changes, we can use signal and slot handling to catch errors that occur in QML components and provide detailed debugging information in their output. For example:

import QtQuick 2.0

Component {
    
    

    id: comp
    Rectangle {
    
    
        width: 100; height: 50
        color: "blue"
    }

    onError: {
    
     // 当组件加载失败时触发
        console.error("Error in component:", error.text, "at line", error.line, "column", error.column);
        comp.errorMessage = error.message;
    }

3.2 Customize the Qt C++ type and register it to the QML engine (Exposing Custom Qt C++ Types to the QML Engine)

In order to combine C++ code with QML more efficiently, we need to customize Qt C++ types and use these types in QML. This section discusses how to print information by registering and implementing a custom C++ class.

3.2.1 Creating a Custom C++ Class (Creating a Custom C++ Class)

To create a custom class and make it usable in QML, you should first inherit QObject, and add properties, signals and slots on this basis.

For example:

#include <QObject>
#include <QDebug>

class Printer : public QObject
{
    
    
    Q_OBJECT

public:
    explicit Printer(QObject *parent = nullptr);

public slots:
    void printInfo(const QString &message);
};

In the printInfo() function we can perform some custom logging actions.

3.2.2 Registering a custom type and exposing it to QML (Registering and Exposing the Custom Type to QML)

First, we need to register the Printer class we just created in the main.cpp file:

#include "printer.h"
#include <QQmlApplicationEngine>
#include <QtQml>

int main(int argc, char *argv[])
{
    
    
    QApplication app(argc, argv);

    qmlRegisterType<Printer>("com.example.printer", 1, 0, "Printer");

    QQmlApplicationEngine engine;
    engine.load(QUrl(QStringLiteral("qrc:/main.qml")));

    return app.exec();
}

Note the qmlRegisterType<>() function. We used "com.example.printer" as the URI namespace, specifying a major version number 1 and a minor version number 0 of the library. Also, we will use the "Printer" name when referring to the type in QML.

3.2.3 Using a custom class in a QML file (Using the Custom C++ Class in a QML File)

After registration, we can import the namespace mentioned above in any QML file and instantiate the C++ object:

import QtQuick 2.6
import com.example.printer 1.0

Rectangle {
    width: 360
    height: 360

    Printer {
        id: myPrinter
    }

    Text {
        text: qsTr("Hello World")
        anchors.centerIn: parent
        MouseArea {
            anchors.fill: parent
            onClicked: {
                myPrinter.printInfo("This message is printed from QML");
            }
        }

3.3 Cross-thread update UI and synchronous log printing (Multithreading and Synchronizing Log Printing)

In Qt Quick applications, operations involving multiple threads are generally involved. Especially when dealing with complex tasks or frequently updating the UI interface, it is likely that some work needs to be performed asynchronously to improve the response speed and performance of the program. However, logging output and UI updates in a multi-threaded environment may bring many potential problems and challenges.

3.3.1 Thread Safety

  • When multiple threads write to the same log file at the same time, in order to prevent data confusion and reduce the risk of errors, mechanisms such as mutual exclusion locks (mutex) need to be used to ensure exclusive access to functions and resources.
  • Follow the event-driven model and use signals and slots instead of direct calls to avoid potential critical section problems.

3.3.2 Passing information between different threads (Log Messages Handling between Threads)

A simple method is to transmit log information between threads through Qt's message queue system (QueuedConnection), which can ensure that each record can reach the receiving end safely from the sending end. The specific method is as follows: Create a custom C++ class (for example: LogManager) and implement the following functions:

  • Declare a signal (syncPrintSignal) that can be exposed to the QML language;
  • Provide a slot function (syncPrintSlot) for processing log output from different threads;
  • Establish a connection between the signal and the slot to ensure the correct transmission of the message queue;

Register the C++ class and create a single global object with new LogManager instance in QML.

3.3.3 Updating UI across Threads

In Qt, it is easy to cause errors to directly operate GUI across threads. The problem can be avoided by:

  • Use Qt::QueuedConnection with MainWindow and WorkerThread to realize the non-blocking behavior of the main thread.
  • Use implicit asynchronous Invokable objects or Callable interfaces instead of explicit blocking calls (such as wait()) to request data or perform tasks; define these indicators as additional attributes or elements added to each return message.

4. Advanced Use Cases of Qt Quick in Application

4.1 Use MessageHandler to customize capture log output (Using a custom MessageHandler to intercept log output)

4.1.1 The practice of listening for QML log outputs

In some cases, we need to monitor and debug QML log information in more detail. At this time, you can capture and process log output by customizing MessageHandler. To get started, first redirect a standard function like qDebug() to a custom function.

First, create a new MessageHandler in Qt C++. One way is to write a function called "myCustomMessageHandler" and list all possible QtMsgType types:

void myCustomMessageHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg) {
    
    
    QByteArray local_msg = msg.toLocal8Bit();
    switch (type) {
    
    
        case QtDebugMsg:
            fprintf(stderr, "Debug: %s\n", local_msg.constData());
            break;
        case QtInfoMsg:
            fprintf(stderr, "Info:  %s\n", local_msg.constData());
            break;
        case QtWarningMsg:
            fprintf(stderr, "Warning: %s\n", local_msg.constData());
            break;
        case QtCriticalMsg:
            fprintf(stderr, "Critical: %s\n", local_msg.constData());
            break;
        case QtFatalMsg:
            fprintf(stderr, "Fatal: %s\n", local_msg.constData());
            abort();
    }
}

Next, set up this message handler in code to redirect its method to the custom function you just created. Add the following code to the Qt C++ main function:

int main(int argc, char *argv[]) {
    
    
    // ...
    qInstallMessageHandler(myCustomMessageHandler);
    // ...
}

Now, all logging output (including QML) will be captured and forwarded through the handler set above.

4.1.2 Advanced scenarios: Customizable Operations and Multiple-file Logging

Besides basic usage, we can apply some advanced features to MessageHandler:

  1. Add timestamps or other meta information, and retrieve additional source code location information from the context parameter.
  2. Perform specific actions based on the message type, such as warning notifications, sound effects, etc.
  3. Store different types of record information in separate files on disk.

To implement the third advanced function, here is an example:

First, create the different log files you need, and define global variables fileDebug, fileInfo, fileWarning, etc. to refer to the file save name,

FILE* fileDebug = fopen("debug.log", "w");
FILE* fileInfo  = fopen("info.log", "w");
FILE* fileWarning = fopen("warning.log", "w");
//...

Then, myCustomMessageHandler()inside the function, write data to a different file for each type:

void myCustomMessageHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg) {
    
    
    QByteArray local_msg = msg.toLocal8Bit();
    switch (type) {
    
    
        case QtDebugMsg:
            // ...
            fprintf(fileDebug, "%s\n", local_msg.constData());
            fflush(fileDebug);
            break;
        case QtInfoMsg:
            //...
            fprintf(fileInfo,  "%s\n", local_msg.constData());fflush(fileInfo);
            break;
        case QtWarningMsg:
            // ...
            fprintf(fileWarning, "%s\n", local_msg.constData());
            fflush(fileWarning);
            break;
          // ...
}});

NOTE: Make sure to always close the files you are using to avoid resource leaks.

4.2 Use GammaRay to debug and optimize the performance of QtQuick programs (Debugging and Profiling QtQuick Applications with GammaRay Tool)

4.2.1 Install and configure GammaRay in QtCreator (Installing and Setting up GammaRay in Qt Creator)

To start using GammaRay, you first need to install the tool in Qt Creator. Here are the brief steps:

1) Download the GammaRay source code: Visit the official website https://www.kdab.com/development-resources/qt-tools/gammaray/ and download the corresponding version of the GammaRay source code.

2) Compile and build GammaRay. Enter the main directory after decompression, and execute the following command.

$ mkdir build
$ cd build
$ cmake .. -DCMAKE_INSTALL_PREFIX=/path/to/install/gammaray
$ make install

This will create and install an executable called gammaray.

3) Configure Qt Creator to use GammaRay

Open your Qt Creator, add GammaRay in the "Options" -> "Analyzer" dialog:

  • Add the GammaRay path to the "Startup" input box (for example: "/path/to/install/gammaray/bin/gammaray")

After that, you can find GammaRay by selecting the "Debug With..." option in the drop-down menu next to the "Run" button every time you debug.

4.2.2 Analysis of qDebug() and other information integration methods in QtQuick applications (Methods of Integrating Information such as debug() into Qt Quick Application Analysis)

When we use GammaRay to debug a compiled QtQuick application, we may see log output as shown below:

Starting program: /path/to/your/app
[Resetting GL shader cache]
GammaRay connected.
Debugging process finished.

To parse QtQuick log messages such as qDebug(), follow these steps:

1) Add in the constructor of MainWindow: QApplication::setAttribute(Qt::AA_EnableHighDpiScaling);

2) Use qDebug(), qWarning(), qCritical() or other similar functions to print logs in your QML classes and C++ classes.

For example, within your QML Window, use a method like this to send log messages to the console:

function displayLogMessage(logText) {
    
    
  console.log("Logging:", logText);
}

In the corresponding C++ code, you also need to define the corresponding slot:

void MyClass::handleQmlSignal(QString logText) {
    
    
    qDebug() << "Received" << logText;
}

4.3 Print out more detailed log information in QmlProfiler (Printing More Detailed Log Messages in QmlProfiler)

QmlProfiler is a powerful profiling and debugging tool for QtQuick applications. It can collect, display, and analyze various data in real time, such as events generated during JavaScript engine execution, rendering performance, and memory usage. However, by default, QmlProfiler only provides basic log information, which may not meet the detailed information we need for debugging and optimization during the development phase.

In order to enable QmlProfiler to output more detailed log information, we need to modify its configuration by the following methods:

  1. Open Qt Creator's settings and navigate to the "Analyzer" tab, then select "QML Profiler".
  2. On this page find the "Show debug messages for bindings" option and check it. This option will allow us to see messages output by functions like qDebug, qWarning, etc. in QML binding expressions.
  3. (Optional) You can also enable other related options such as "Trace timer events" to monitor how often timers fire in QtQuick; or use "Visualize QML Data Model" to inspect the QML documentation tree structure, which can help find performance Bottleneck or potential problem.
  4. Press "OK" to save the settings, and restart Qt Creator.

Now, when your program is running, QmlProfiler will display more detailed log information. This information can help you discover and resolve various performance issues and improve application performance.

It should be noted that during the release phase, do not keep too much debugging information in the user environment. For security and efficiency reasons, please remember to delete or comment out debug output statements that are no longer needed in time to reduce the package file size and optimize product performance.

Guess you like

Origin blog.csdn.net/qq_21438461/article/details/130664183