当前位置: 首页 > 工具软件 > QML for Go > 使用案例 >

Qt6 QML Book/入门/应用程序类型

郎慎之
2023-12-01

Application Types

应用程序类型

This section is a run through of different application types one can write with Qt 6. It’s not limited to the selection presented here, but it will give you a better idea of what you can achieve with Qt 6 in general.

本节介绍了可以使用Qt 6编写的不同应用程序类型。Qt不限于这里提供的类型,但它将使您更好地了解使用Qt 6一般可以实现什么。

Console Application

控制台程序

A console application does not provide a graphical user interface, and is usually called as part of a system service or from the command line. Qt 6 comes with a series of ready-made components which help you create cross-platform console applications very efficiently. For example, the networking file APIs, string handling, and an efficient command line parser. As Qt is a high-level API on top of C++, you get programming speed paired with execution speed. Don’t think of Qt as being just a UI toolkit - it has so much more to offer!

控制台应用程序不提供图形用户界面,通常作为系统服务的一部分或从命令行调用。Qt 6附带了一系列现成的组件,可以帮助您非常高效地创建跨平台控制台应用程序。例如,网络文件api、字符串处理和一个高效的命令行解析器。由于Qt是c++之上的高级API,因此编程速度与执行速度相匹配。不要认为Qt只是一个UI工具包——它也提供了众多的功能库!

String Handling

字符串处理

This first example demonstrates how you could add 2 constant strings. Admittedly, this is not a very useful application, but it gives you an idea of what a native C++ application without an event loop may look like.

第一个示例演示了如何添加两个常量字符串。当然,这不是一个非常有用的应用程序,但它向人展示了,没有事件循环的本地c++应用程序会是什么样的。

// module or class includes 引入模块或类
include <QtCore>

// text stream is text-codec aware 文本流是支持文本编解码器的
QTextStream cout(stdout, QIODevice::WriteOnly);

int main(int argc, char** argv)
{
    // avoid compiler warnings 避免编译器警告提示
    Q_UNUSED(argc)
    Q_UNUSED(argv)
    QString s1("Paris");
    QString s2("London");
    // string concatenation 字符串拼接
    QString s = s1 + " " + s2 + "!";
    cout << s << endl;
}

Container Classes

容器类

This example adds a list, and list iteration, to the application. Qt comes with a large collection of container classes that are easy to use, and has the same API paradigms as other Qt classes.

这个示例向应用程序添加了一个列表和列表迭代。Qt提供了大量易于使用的容器类,并且这些容器类具有与其他Qt类相同的API范式。

QString s1("Hello");
QString s2("Qt");
QList<QString> list;
// stream into containers 将数据添加到容器中
list << s1 << s2;
// Java and STL like iterators 类似于Java和STL的迭代器
QListIterator<QString> iter(list);
while(iter.hasNext()) {
    cout << iter.next();
    if(iter.hasNext()) {
        cout << " ";
    }
}
cout << "!" << endl;

Here is a more advanced list function, that allows you to join a list of strings into one string. This is very handy when you need to proceed line based text input. The inverse (string to string-list) is also possible using the QString::split() function.

下面是一个更高级的列表函数,它允许您将一个字符串列表合并成一个字符串。当您需要进行基于行的文本输入时,这非常实用。反之(字符串转字符串列表),可以使用QString::split()函数。

QString s1("Hello");
QString s2("Qt");
// convenient container classes 方便的容器类
QStringList list;
list <<  s1 << s2;
// join strings 合并字符串
QString s = list.join(" ") + "!";
cout << s << endl;

File IO

文件读写

In the next snippet, we read a CSV file from the local directory and loop over the rows to extract the cells from each row. Doing this, we get the table data from the CSV file in ca. 20 lines of code. File reading gives us a byte stream, to be able to convert this into valid Unicode text, we need to use the text stream and pass in the file as a lower-level stream. For writing CSV files, you would just need to open the file in write mode, and pipe the lines into the text stream.

在下一个代码片段中,我们从本地目录读取一个CSV文件,并循环遍历行,并提取单元格数据。这样做,我们从约20行代码的CSV文件中获得表数据。文件读取为我们提供了一个字节流,为了能够将其转换为有效的Unicode文本,我们需要使用文本流,将其作为低级流传入。对于写入CSV文件,您只需要以写入模式打开文件,并将文本流依次传入。

QList<QStringList> data;
// file operations 文件操作
QFile file("sample.csv");
if(file.open(QIODevice::ReadOnly)) {
    QTextStream stream(&file);
    // loop forever macro 永远循环的宏
    forever {
        QString line = stream.readLine();
        // test for null string 'String()'测试空字符串对象' String ()'
        if(line.isNull()) {
            break;
        }
        // test for empty string 'QString("")' 测试空字符串'QString("")'
        if(line.isEmpty()) {
            continue;
        }
        QStringList row;
        // for each loop to iterate over containers 在每个循环中遍历容器
        foreach(const QString& cell, line.split(",")) {
            row.append(cell.trimmed());
        }
        data.append(row);
    }
}
// No cleanup necessary.没有必要清理。

This concludes the section about console based applications with Qt.

这是关于使用Qt的基于控制台的应用程序的部分。

C++ Widget Application

c++窗体应用程序

Console based applications are very handy, but sometimes you need to have a graphical user interface (GUI). In addition, GUI-based applications will likely need a back-end to read/write files, communicate over the network, or keep data in a container.

基于控制台的应用程序非常方便实现的,但有时需要图形用户界面(GUI)。此外,基于gui的应用程序可能需要一个后台来读写文件、通过网络进行通信或在容器中保存数据。

In this first snippet for widget-based applications, we do as little as needed to create a window and show it. In Qt, a widget without a parent is a window. We use a scoped pointer to ensure that the widget is deleted when the pointer goes out of scope. The application object encapsulates the Qt runtime, and we start the event loop with the exec() call. From there on, the application reacts only to events triggered by user input (such as mouse or keyboard), or other event providers, such as networking or file IO. The application only exits when the event loop is exited. This is done by calling quit() on the application or by closing the window.

在基于窗体应用程序的第一个片段中,我们只创建一个窗口并显示它。在Qt中,没有父组件的窗体部件是窗口。我们使用作用域指针来确保指针超出作用域时删除窗体。应用程序对象封装Qt运行时,我们使用exec()启动事件循环。然后,应用程序只对由用户输入(如鼠标或键盘)或其他事件提供者(如网络或文件读写)触发的事件作出反应。应用程序只在事件循环退出时才退出。这可以通过在应用程序上调用quit()或关闭窗口来退出。

When you run the code, you will see a window with the size of 240 x 120 pixels. That’s all.

当您运行该代码时,您将看到一个大小为240 x 120像素的窗口。这就是全部了。

include <QtGui>

int main(int argc, char** argv)
{
    QApplication app(argc, argv);
    QScopedPointer<QWidget> widget(new CustomWidget());
    widget->resize(240, 120);
    widget->show();
    return app.exec();
}

Custom Widgets

自定义窗体

When you work on user interfaces, you may need to create custom-made widgets. Typically, a widget is a window area filled with painting calls. Additionally, the widget has internal knowledge of how to handle keyboard and mouse input, as well as how to react to external triggers. To do this in Qt, we need to derive from QWidget and overwrite several functions for painting and event handling.

在处理用户界面时,可能需要创建定制的窗体。通常,窗体是一个充满绘制区域。此外,窗体还集成了处理键盘和鼠标事件,以及对外部事件响应。在Qt中,我们只要从QWidget中派生出来,并重载绘制函数和事件处理函数即可。

#pragma once

include <QtWidgets>

class CustomWidget : public QWidget
{
    Q_OBJECT
public:
    explicit CustomWidget(QWidget *parent = nullptr);
    void paintEvent(QPaintEvent *event);
    void mousePressEvent(QMouseEvent *event);
    void mouseMoveEvent(QMouseEvent *event);
private:
    QPoint m_lastPos;
};

In the implementation, we draw a small border on our widget and a small rectangle on the last mouse position. This is very typical for a low-level custom widget. Mouse and keyboard events change the internal state of the widget and trigger a painting update. We won’t go into too much detail about this code, but it is good to know that you have the possibility. Qt comes with a large set of ready-made desktop widgets, so it’s likely that you don’t have to do this.

在具体实现中,我们在窗体上绘制一个小边框,在最后的鼠标位置绘制一个小矩形。这是一个典型的低定制自定义窗体。鼠标和键盘事件改变窗体的内部状态并触发绘制更新。我们不会过多地讨论这段代码的细节,但是知道这些就可以了。Qt附带了一大堆现成的桌面窗体部件,所以一般不需要自定义绘制窗体。

include "customwidget.h"

CustomWidget::CustomWidget(QWidget *parent) :
    QWidget(parent)
{
}

void CustomWidget::paintEvent(QPaintEvent *)
{
    QPainter painter(this);
    QRect r1 = rect().adjusted(10,10,-10,-10);
    painter.setPen(QColor("#33B5E5"));
    painter.drawRect(r1);

    QRect r2(QPoint(0,0),QSize(40,40));
    if(m_lastPos.isNull()) {
        r2.moveCenter(r1.center());
    } else {
        r2.moveCenter(m_lastPos);
    }
    painter.fillRect(r2, QColor("#FFBB33"));
}

void CustomWidget::mousePressEvent(QMouseEvent *event)
{
    m_lastPos = event->pos();
    update();
}

void CustomWidget::mouseMoveEvent(QMouseEvent *event)
{
    m_lastPos = event->pos();
    update();
}

Desktop Widgets

桌面窗体

The Qt developers have done all of this for you already and provide a set of desktop widgets, with a native look on different operating systems. Your job, then, is to arrange these different widgets in a widget container into larger panels. A widget in Qt can also be a container for other widgets. This is accomplished through the parent-child relationship. This means we need to make our ready-made widgets, such as buttons, checkboxes, radio buttons, lists, and grids, children of other widgets. One way to accomplish this is displayed below.

Qt开发者已经为您完成了一组桌面窗体,它们在不同的操作系统上都具有本地化外观。所以,您的工作就是将窗体容器中的这些不同窗体,排列组合成较大的面板。Qt中的窗体也可以是其他窗体的容器。这是通过窗体父子关系来实现的。也就是说,我们可以使用现成的窗体组合显示,例如按钮、复选框、单选按钮、列表和网格布局,及其他窗体部件。具体方法如下。

Here is the header file for a so-called widget container.

下面是窗体的头文件。

class CustomWidget : public QWidget
{
    Q_OBJECT
public:
    explicit CustomWidget(QWidget *parent = nullptr);
private slots:
    void itemClicked(QListWidgetItem* item);
    void updateItem();
private:
    QListWidget *m_widget;
    QLineEdit *m_edit;
    QPushButton *m_button;
};

In the implementation, we use layouts to better arrange our widgets. Layout managers re-layout the widgets according to some size policies when the container widget is re-sized. In this example, we have a list, a line edit, and a button, which are arranged vertically and allow the user to edit a list of cities. We use Qt’s signal and slots to connect sender and receiver objects.

在类具体实现中,我们使用布局器(layout)更适合窗体布局。当窗体部件大小调整时,布局管理器根据策略重新布局。在本示例中,我们有一个列表、一个行编辑器和一个按钮,它们垂直排列,并允许用户编辑城市列表。我们使用Qt的信号(signal)和槽(slots)来连接发送对象和接收对象。

CustomWidget::CustomWidget(QWidget *parent) :
    QWidget(parent)
{
    QVBoxLayout *layout = new QVBoxLayout(this);
    m_widget = new QListWidget(this);
    layout->addWidget(m_widget);

    m_edit = new QLineEdit(this);
    layout->addWidget(m_edit);

    m_button = new QPushButton("Quit", this);
    layout->addWidget(m_button);
    setLayout(layout);

    QStringList cities;
    cities << "Paris" << "London" << "Munich";
    foreach(const QString& city, cities) {
        m_widget->addItem(city);
    }

    connect(m_widget, &QListWidget::itemClicked, this, &CustomWidget::itemClicked);
    connect(m_edit, &QLineEdit::editingFinished, this, &CustomWidget::updateItem);
    connect(m_button, &QPushButton::clicked, qApp, &QCoreApplication::quit);
}

void CustomWidget::itemClicked(QListWidgetItem *item)
{
    Q_ASSERT(item);
    m_edit->setText(item->text());
}

void CustomWidget::updateItem()
{
    QListWidgetItem* item = m_widget->currentItem();
    if(item) {
        item->setText(m_edit->text());
    }
}

Drawing Shapes

几何图形绘制

Some problems are better visualized. If the problem at hand looks remotely like geometrical objects, Qt graphics view is a good candidate. A graphics view arranges simple geometrical shapes in a scene. The user can interact with these shapes, or they are positioned using an algorithm. To populate a graphics view, you need a graphics view and a graphics scene. The scene is attached to the view and is populated with graphics items.

有些问题比较直观。如果遇到图形对象的问题,Qt图形视图是个很好的选择。图形视图在场景中放置多个简单图形项。用户可以与这些图形项交互,或者使用编程控制它们。要使用图形视图,您需要一个图形视图(graphics view)和一个图形场景(graphics scene)。场景附加到视图中,并使用图形项填充。

Here is a short example. First the header file with the declaration of the view and scene.

这里有一个简短的示例。首先是带有视图和场景声明的头文件。

class CustomWidgetV2 : public QWidget
{
    Q_OBJECT
public:
    explicit CustomWidgetV2(QWidget *parent = nullptr);
private:
    QGraphicsView *m_view;
    QGraphicsScene *m_scene;

};

In the implementation, the scene gets attached to the view first. The view is a widget and gets arranged in our container widget. In the end, we add a small rectangle to the scene, which is then rendered on the view.

在实现中,场景首先附加到视图。视图是一个窗体部件,它被布局在其他窗体部件中。最后,我们在场景中添加一个小矩形,然后渲染到视图中。

include "customwidgetv2.h"

CustomWidget::CustomWidget(QWidget *parent) :
    QWidget(parent)
{
    m_view = new QGraphicsView(this);
    m_scene = new QGraphicsScene(this);
    m_view->setScene(m_scene);

    QVBoxLayout *layout = new QVBoxLayout(this);
    layout->setMargin(0);
    layout->addWidget(m_view);
    setLayout(layout);

    QGraphicsItem* rect1 = m_scene->addRect(0,0, 40, 40, Qt::NoPen, QColor("#FFBB33"));
    rect1->setFlags(QGraphicsItem::ItemIsFocusable|QGraphicsItem::ItemIsMovable);
}

Adapting Data

自适应数据处理

Up to now, we have mostly covered basic data types and how to use widgets and graphics views. In your applications, you will often need a larger amount of structured data, which may also need to be stored persistently. Finally, the data also needs to be displayed. For this, Qt uses models. A simple model is the string list model, which gets filled with strings and then attached to a list view.

到目前为止,我们已经介绍了基本数据类型以及如何使用窗体部件和图形视图。在应用程序中,经常需要大量的结构化数据,这些数据可能还需要长时间存储。最后,还要显示出来。为此,Qt提供了模型。最简单的模型是字符串列表模型,它用字符串填充,然后附加到列表视图。

m_view = new QListView(this);
m_model = new QStringListModel(this);
view->setModel(m_model);

QList<QString> cities;
cities << "Munich" << "Paris" << "London";
m_model->setStringList(cities);

Another popular way to store and retrieve data is SQL. Qt comes with SQLite embedded, and also has support for other database engines (e.g. MySQL and PostgreSQL). First, you need to create your database using a schema, like this:

另一种普遍的存储和检索数据的方法是SQL。Qt内置了SQLite数据库,也支持其他数据库引擎(如MySQL和PostgreSQL)。使用前需要先创建数据表,如下所示:

CREATE TABLE city (name TEXT, country TEXT);
INSERT INTO city value ("Munich", "Germany");
INSERT INTO city value ("Paris", "France");
INSERT INTO city value ("London", "United Kingdom");

To use SQL, we need to add the SQL module to our .pro file

要使用SQL,我们需要将SQL模块添加到.pro文件中

QT += sql

And then we can open our database using C++. First, we need to retrieve a new database object for the specified database engine. With this database object, we open the database. For SQLite, it’s enough to specify the path to the database file. Qt provides some high-level database models, one of which is the table model. The table model uses a table identifier and an optional where clause to select the data. The resulting model can be attached to a list view as with the other model before.

然后我们可以使用c++打开数据库。首先,我们需要为特定的数据库引擎创建数据库对象。使用这个数据库对象,我们打开数据库。对于SQLite,指定数据库文件的路径就可以了。Qt提供了一些高级数据库模型,其中之一是表模型。表模型使用表标识符和可选的where子句来查询数据。最终结果是,表模型可以像其他模型一样,附加到列表视图中。

QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE");
db.setDatabaseName("cities.db");
if(!db.open()) {
    qFatal("unable to open database");
}

m_model = QSqlTableModel(this);
m_model->setTable("city");
m_model->setHeaderData(0, Qt::Horizontal, "City");
m_model->setHeaderData(1, Qt::Horizontal, "Country");

view->setModel(m_model);
m_model->select();

For a higher level model operations, Qt provides a sorting file proxy model that allows you sort, filter, and transform models.

对于更高级别的模型操作,Qt提供了一个排序代理模型,该代理模型可对模型进行排序、筛选和转换。

QSortFilterProxyModel* proxy = new QSortFilterProxyModel(this);
proxy->setSourceModel(m_model);
view->setModel(proxy);
view->setSortingEnabled(true);

Filtering is done based on the column that is to be filters, and a string as filter argument.

过滤是基于要过滤器列和过滤器参数(字符串)来完成的。

proxy->setFilterKeyColumn(0);
proxy->setFilterCaseSensitive(Qt::CaseInsensitive);
proxy->setFilterFixedString(QString)

The filter proxy model is much more powerful than demonstrated here. For now, it is enough to remember it exists.

过滤器代理模型的功能比这里演示的要强大。当前,知道有这样的东西就可以了。

!!! note

注意!

This has been an overview of the different kind of classic applications you can develop with Qt 5. The desktop is moving, and soon the mobile devices will be our desktop of tomorrow. Mobile devices have a different user interface design. They are much more simplistic than desktop applications. They do one thing and they do it with simplicity and focus. Animations are an important part of the mobile experience. A user interface needs to feel alive and fluent. The traditional Qt technologies are not well suited for this market.

本文概述了使用Qt 5可以开发的各种经典应用程序。桌面系统正在变迁,很快移动设备将成为我们明天的桌面。移动设备有不同的用户界面设计。它们比桌面应用程序简洁。他们只做一件事,简单而专注。动画是移动端体验的重要部分。用户界面需要生动和流畅。传统的Qt技术并不适合做这个。

Coming next: Qt Quick to the rescue.

接下来:Qt Quick的到来。

Qt Quick Application

Qt Quick应用程序

There is an inherent conflict in modern software development. The user interface is moving much faster than our back-end services. In a traditional technology, you develop the so-called front-end at the same pace as the back-end. This results in conflicts when customers want to change the user interface during a project, or develop the idea of a user interface during the project. Agile projects, require agile methods.

在现代软件开发中存在着一种内在的冲突。用户界面的发展速度比我们的后端服务快得多。在传统技术中,开发的前端与后端步调相同。若开发时,客户希望更改用户界面,或者有新的界面想法时,会导致两者冲突。快速软件项目,需要快捷的方法。

Qt Quick provides a declarative environment where your user interface (the front-end) is declared like HTML and your back-end is in native C++ code. This allows you to get the best of both worlds.

Qt Quick提供了一个声明性环境,其中用户界面(前端)像HTML一样声明的,而后端是原生c++代码。这可以让你得到两者的好处。

This is a simple Qt Quick UI below

下面是一个简单的Qt Quick UI

import QtQuick

Rectangle {
    width: 240; height: 240
    Rectangle {
        width: 40; height: 40
        anchors.centerIn: parent
        color: '#FFBB33'
    }
}

The declaration language is called QML and it needs a runtime to execute it. Qt provides a standard runtime called qml. You can also write a custom runtime. For this, we need a quick view and set the main QML document as a source from C++. Then you can show the user interface.

这种声明式语言称为QML,它需要一个运行时来执行它。Qt提供了一个称为qml的标准运行时。您还可以编写自定义运行时。为此,我们需要快速查看视图,并将主QML文件设置为来自c++的源。然后就可以显示用户界面了

#include <QtGui>
#include <QtQml>

int main(int argc, char *argv[])
{
    QGuiApplication app(argc, argv);
    QQmlApplicationEngine engine("main.qml");
    return app.exec();
}

Let’s come back to our earlier examples. In one example, we used a C++ city model. It would be great if we could use this model inside our declarative QML code.

让我们回到前面的示例。在一个示例中,我们使用了c++城市模型。如果我们能在QML代码中使用这个模型,那就好了。

To enable this, we first code our front-end to see how we would want to use a city model. In this case, the front-end expects an object named cityModel which we can use inside a list view.

为了实现这点,我们首先编写前端代码,看下我们如何使用城市模型。在本示例中,前端需要一个名为cityModel的对象,以方便在列表视图中使用它。

import QtQuick

Rectangle {
    width: 240; height: 120
    ListView {
        width: 180; height: 120
        anchors.centerIn: parent
        model: cityModel
        delegate: Text { text: model.city }
    }
}

To enable the cityModel, we can mostly re-use our previous model, and add a context property to our root context. The root context is the other root-element in the main document.

要使用cityModel,我们可以主要重用以前的模型,并向根上下文添加一个属性。根上下文是主文档中的另一个根元素。

m_model = QSqlTableModel(this);
... // some magic code
QHash<int, QByteArray> roles;
roles[Qt::UserRole+1] = "city";
roles[Qt::UserRole+2] = "country";
m_model->setRoleNames(roles);
engine.rootContext()->setContextProperty("cityModel", m_model);
 类似资料: