QML Book 第十五章 Qt 和 C++ 2

15.4 普通 Qt 课程

QObject 类构成了 Qt 的基础,但框架中还有更多的类。在我们继续关注 QML 和扩展它之前,我们将看一些有用的基本 Qt 类。

本节中显示的代码示例使用 Qt 测试库编写。它提供了一个很好的方式来探索 Qt API 并将其存储以供以后参考。QVERIFY,QCOMPARE 是由测试库提供的用于断言某些条件的函数。我们将使用 {} 范围来避免名称冲突。所以不要困惑。

15.4.1 QString

一般来说,Qt 中的文本处理是基于 unicode 的。为此,我们可以使用 QString 类。 它附带了许多伟大的功能,我们会期望从一个现代的框架。对于 8 位数据,我们将正常使用 QByteArray 类和 ASCII 标识符,以便 QLatin1String 保留内存。对于字符串列表,我们可以使用 QList 或简单的 QStringList 类(从 QList 派生)。

这里有一些例子介绍了如何使用 QString 类。QString 可以在栈上创建,但是它的数据存储在堆上。分配一个字符串数据到另一个上,不会产生拷贝操作,只是创建了数据的引用。这个操作非常廉价让开发者更专注于代码而不是内存操作。QString 使用引用计数的方式来确定何时可以安全的删除数据。这个功能叫做隐式共享,在 Qt 的很多类中都用到了它。

    QString data("A,B,C,D"); // create a simple string
    // split it into parts
    QStringList list = data.split(",");
    // create a new string out of the parts
    QString out = list.join(",");
    // verify both are the same
    QVERIFY(data == out);
    // change the first character to upper case
    QVERIFY(QString("A") == out[0].toUpper());

这里我们将展示如何将一个数字转换成一个字符串并返回。还有 float 或 double 和其他类型的转换函数。只需查找这里使用的 Qt 文档中的功能,你会发现其他的。

    // create some variables
    int v = 10;
    int base = 10;
    // convert an int to a string
    QString a = QString::number(v, base);
    // and back using and sets ok to true on success
    bool ok(false);
    int v2 = a.toInt(&ok, base);
    // verify our results
    QVERIFY(ok == true);
    QVERIFY(v = v2);

通常在文本中需要参数化文本。一个选项可能是使用 QString("Hello" + name) 的形式,但更灵活的方法是 arg 标记方法。当订单可能更改时,它也会在翻译期间保留订单。

    // create a name
    QString name("Joe");
    // get the day of the week as string
    QString weekday = QDate::currentDate().toString("dddd");
    // format a text using paramters (%1, %2)
    QString hello = QString("Hello %1. Today is %2.").arg(name).arg(weekday);
    // This worked on Monday. Promise!
    if(Qt::Monday == QDate::currentDate().dayOfWeek()) {
        QCOMPARE(QString("Hello Joe. Today is Monday."), hello);
    } else {
        QVERIFY(QString("Hello Joe. Today is Monday.") !=  hello);
    }

有时我们想直接在我们的代码中使用 unicode 字符。为此,我们需要记住如何为 QChar 和 QString 类标记它们。

    // Create a unicode character using the unicode for smile :-)
    QChar smile(0x263A);
    // you should see a :-) on you console
    qDebug() << smile;
    // Use a unicode in a string
    QChar smile2 = QString("\u263A").at(0);
    QVERIFY(smile == smile2);
    // Create 12 smiles in a vector
    QVector smilies(12);
    smilies.fill(smile);
    // Can you see the smiles
    qDebug() << smilies;

这给出了一些如何在 Qt 中轻松处理 unicode 感知文本的示例。对于非 unicode,QByteArray 类也有许多帮助函数进行转换。 请阅读Qt文档的 QString,因为它包含了很多很好的例子。

15.4.2 顺序容器

列表、队列、向量或链表是顺序容器。大多数时我们最常使用的顺序容器是 QList 类。它是一个基于模板的类,需要使用类型初始化。它也是隐式共享的,并在堆内部存储数据。应在堆栈上创建所有容器类。通常你永远不想使用 new QList (),这意味着永远不要使用 new 的容器。

QList 与 QString 类一样多功能,并提供了一个很好的 API 来探索数据。下面是一个小例子,如何使用和迭代列表使用一些新的 C++ 11 功能。

    // Create a simple list of ints using the new C++11 initialization
    // for this you need to add "CONFIG += c++11" to your pro file.
    QList list{1,2};

    // append another int
    list << 3;

    // We are using scopes to avoid variable name clashes

    { // iterate through list using Qt for each
        int sum(0);
        foreach (int v, list) {
            sum += v;
        }
        QVERIFY(sum == 6);
    }
    { // iterate through list using C++ 11 range based loop
        int sum = 0;
        for(int v : list) {
            sum+= v;
        }
        QVERIFY(sum == 6);
    }

    { // iterate through list using JAVA style iterators
        int sum = 0;
        QListIterator i(list);

        while (i.hasNext()) {
            sum += i.next();
        }
        QVERIFY(sum == 6);
    }

    { // iterate through list using STL style iterator
        int sum = 0;
        QList::iterator i;
        for (i = list.begin(); i != list.end(); ++i) {
            sum += *i;
        }
        QVERIFY(sum == 6);
    }


    // using std::sort with mutable iterator using C++11
    // list will be sorted in descending order
    std::sort(list.begin(), list.end(), [](int a, int b) { return a > b; });
    QVERIFY(list == QList({3,2,1}));


    int value = 3;
    { // using std::find with const iterator
        QList::const_iterator result = std::find(list.constBegin(), list.constEnd(), value);
        QVERIFY(*result == value);
    }

    { // using std::find using C++ lambda and C++ 11 auto variable
        auto result = std::find_if(list.constBegin(), list.constBegin(), [value](int v) { return v == value; });
        QVERIFY(*result == value);
    }

15.4.3 关联容器

map、dictionary 或 set 是关联容器的实例。他们使用键存储值。 他们以快速查找而闻名。我们演示了使用最常用的关联容器,QHash 还展示了一些新的 C++ 11功能。

    QHash hash({{"b",2},{"c",3},{"a",1}});
    qDebug() << hash.keys(); // a,b,c - unordered
    qDebug() << hash.values(); // 1,2,3 - unordered but same as order as keys

    QVERIFY(hash["a"] == 1);
    QVERIFY(hash.value("a") == 1);
    QVERIFY(hash.contains("c") == true);

    { // JAVA iterator
        int sum =0;
        QHashIterator i(hash);
        while (i.hasNext()) {
            i.next();
            sum+= i.value();
            qDebug() << i.key() << " = " << i.value();
        }
        QVERIFY(sum == 6);
    }

    { // STL iterator
        int sum = 0;
        QHash::const_iterator i = hash.constBegin();
        while (i != hash.constEnd()) {
            sum += i.value();
            qDebug() << i.key() << " = " << i.value();
            i++;
        }
        QVERIFY(sum == 6);
    }

    hash.insert("d", 4);
    QVERIFY(hash.contains("d") == true);
    hash.remove("d");
    QVERIFY(hash.contains("d") == false);

    { // hash find not successfull
        QHash::const_iterator i = hash.find("e");
        QVERIFY(i == hash.end());
    }

    { // hash find successfull
        QHash::const_iterator i = hash.find("c");
        while (i != hash.end()) {
            qDebug() << i.value() << " = " << i.key();
            i++;
        }
    }

    // QMap
    QMap map({{"b",2},{"c",2},{"a",1}});
    qDebug() << map.keys(); // a,b,c - ordered ascending

    QVERIFY(map["a"] == 1);
    QVERIFY(map.value("a") == 1);
    QVERIFY(map.contains("c") == true);

    // JAVA and STL iterator work same as QHash

15.4.4 文件 IO

通常需要从文件中读取和写入。QFile 实际上是一个 QObject,但在大多数情况下它是在堆栈上创建的。QFile 包含信号,以便在读取数据时通知用户。这允许异步读取数据块,直到读取整个文件。为了方便起见,它还允许以阻塞模式读取数据。这只能用于较小数据量而不是大文件。幸运的是,我们在这些例子中只使用少量的数据。

除了读取文件内容到内存中可以使用 QByteArray,你也可以根据读取数据类型使用 QDataStream 或者使用 QTextStream 读取 unicode 字符串。我们现在来看看如何使用。

    QStringList data({"a", "b", "c"});
    { // write binary files
        QFile file("out.bin");
        if(file.open(QIODevice::WriteOnly)) {
            QDataStream stream(&file);
            stream << data;
        }
    }
    { // read binary file
        QFile file("out.bin");
        if(file.open(QIODevice::ReadOnly)) {
            QDataStream stream(&file);
            QStringList data2;
            stream >> data2;
            QCOMPARE(data, data2);
        }
    }
    { // write text file
        QFile file("out.txt");
        if(file.open(QIODevice::WriteOnly)) {
            QTextStream stream(&file);
            QString sdata = data.join(",");
            stream << sdata;
        }
    }
    { // read text file
        QFile file("out.txt");
        if(file.open(QIODevice::ReadOnly)) {
            QTextStream stream(&file);
            QStringList data2;
            QString sdata;
            stream >> sdata;
            data2 = sdata.split(",");
            QCOMPARE(data, data2);
        }
    }

15.4.5 更多的类

Qt 是一个丰富的应用程序框架。因此,它有成千上万的类。需要一些时间来习惯所有这些类以及如何使用它们。幸运的是 Qt 有很好的文档,很多有用的例子包括在其中。大多数时候你搜索一个类,最常见的用例已经被提供了使用片段。这意味着我们只需复制和调整这些片段。Qt 的 Qt 源代码中的例子也是一个很大的帮助。确保我们可以使用它们并进行搜索,以使您的生活更有成效。不要浪费时间。Qt 社区总是有帮助的。当我们问问题的时候,确切地描述问题并提供一个简单的例子来显示我们的需求是非常有帮助的。这将大大提高他人的响应时间。所以多投入一点点时间,能够让别人想要帮助你更容易一些:-)。

这里有一些作者认为必须要阅读的文档:QObject, QString, QByteArray, QFile, QDir, QFileInfo, QIODevice, QTextStream, QDataStream, QDebug, QLoggingCategory, QTcpServer, QTcpSocket, QNetworkRequest, QNetworkReply, QAbstractItemModel, QRegExp, QList, QHash, QThread, QProcess, QJsonDocument, QJSValue。

这应该足以作为我们工作的开始。

15.5 C++ 中的模型

QML 中的模型用于将数据提供给 ListViews,PathView 和其他视图,该视图采用模型并为模型中的每个条目创建代理实例。该视图足够聪明,只会创建可见或缓存范围内的这些实例。这使得可以拥有数十个条目的大型模型,但仍然具有非常平滑的用户界面。代理行为像模板一样使用模型条目数据呈现。总而言之:视图使用代理作为模板从模型呈现条目。该模型是视图的数据提供者。

当我们不想使用 C++ 时,我们还可以在纯 QML 中定义模型。我们可以通过多种方式为视图提供模型。为了处理来自 C++ 或大量数据的模型,C++ 模型比这些纯 QML 方法更合适。但是,如果我们只需要几个条目,那么这些 QML 模型非常适合。

ListView {
    // using a integer as model
    model: 5
    delegate: Text { text: 'index: ' + index }
}

ListView {
    // using a JS array as model
    model: ['A', 'B', 'C', 'D', 'E']
    delegate: Text { 'Char['+ index +']: ' + modelData }
}

ListView {
    // using a dynamic QML ListModel as model
    model: ListModel {
        ListElement { char: 'A' }
        ListElement { char: 'B' }
        ListElement { char: 'C' }
        ListElement { char: 'D' }
        ListElement { char: 'E' }
    }
    delegate: Text { 'Char['+ index +']: ' + model.char }
}

QML 视图知道如何处理这些不同的模型。对于来自 C++ 世界的模型,该视图期望遵循特定的协议。该协议在 API(QAbstractItemModel) 中定义,并与动态行为的文档一起定义。该 API 是为桌面小部件世界开发的,并且具有足够的灵活性,可充当树或多列表以及列表的基础。在 QML 中,我们几乎只使用 API 的列表版本(QAbstractListModel)。API 包含一些必须执行的功能,一些是可选的。可选部分主要处理添加或删除数据的动态用例。

15.5.1 一个简单的模型

典型的 QML C++ 模型来源于 QAbstractListModel,并至少实现了数据和 rowCount 函数。在本例中,我们将使用 QColor 类提供的一系列 SVG 颜色名称,并使用我们的模型显示。数据存储在 QList 数据容器中。

我们的 DataEntryModel 是派生形式 QAbstractListModel 并实现强制性功能。我们可以忽略 rowCount 中的父类,因为它仅在树模型中使用。QModelIndex 类提供单元格的行和列信息,视图要为其检索数据。该视图是从行/列和角色基础上的模型中提取信息。QAbstractListModel 在 QtCore 中被定义,但在 QtGui 中定义了 QColor。这就是为什么我们有额外的 QtGui 依赖。对于 QML 应用,可以依赖于 QtGui,但通常不依赖于 QtWidgets。

#ifndef DATAENTRYMODEL_H
#define DATAENTRYMODEL_H

#include 
#include 

class DataEntryModel : public QAbstractListModel
{
    Q_OBJECT
public:
    explicit DataEntryModel(QObject *parent = 0);
    ~DataEntryModel();

public: // QAbstractItemModel interface
    virtual int rowCount(const QModelIndex &parent) const;
    virtual QVariant data(const QModelIndex &index, int role) const;
private:
    QList m_data;
};

#endif // DATAENTRYMODEL_H

在实现方面,最复杂的部分是数据功能。我们首先需要进行范围检查。然后我们检查显示角色。Qt::DisplayRole 是视图将要求的默认文本角色。在 Qt 中定义了一小组默认角色,可以使用它,但是通常一个模型将会为自己定义自己的角色。 所有不包含显示角色的调用都将被忽略,并返回默认值 QVariant()。

#include "dataentrymodel.h"

DataEntryModel::DataEntryModel(QObject *parent)
    : QAbstractListModel(parent)
{
    // initialize our data (QList) with a list of color names
    m_data = QColor::colorNames();
}

DataEntryModel::~DataEntryModel()
{
}

int DataEntryModel::rowCount(const QModelIndex &parent) const
{
    Q_UNUSED(parent);
    // return our data count
    return m_data.count();
}

QVariant DataEntryModel::data(const QModelIndex &index, int role) const
{
    // the index returns the requested row and column information.
    // we ignore the column and only use the row information
    int row = index.row();

    // boundary check for the row
    if(row < 0 || row >= m_data.count()) {
        return QVariant();
    }

    // A model can return data for different roles.
    // The default role is the display role.
    // it can be accesses in QML with "model.display"
    switch(role) {
        case Qt::DisplayRole:
            // Return the color name for the particular row
            // Qt automatically converts it to the QVariant type
            return m_data.value(row);
    }

    // The view asked for other data, just return an empty QVariant
    return QVariant();
}

下一步是使用 qmlRegisterType 调用向 QML 注册模型。这是在加载 QML 文件之前的 main.cpp 内完成的。

#include 
#include 

#include "dataentrymodel.h"

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

    // register the type DataEntryModel
    // under the url "org.example" in version 1.0
    // under the name "DataEntryModel"
    qmlRegisterType("org.example", 1, 0, "DataEntryModel");

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

    return app.exec();
}

现在,我们可以使用 QML import 语句 import org.example 1.0 访问 DataEntryModel,并像其他 QML 项 DataEntryModel {} 一样使用它。

我们在这个例子中使用它来显示颜色条目的简单列表。

import org.example 1.0

ListView {
    id: view
    anchors.fill: parent
    model: DataEntryModel {}
    delegate: ListDelegate {
        // use the defined model role "display"
        text: model.display
    }
    highlight: ListHighlight { }
}

ListDelegate 是显示一些文本的自定义类型。ListHighlight 只是一个矩形。提取代码以保持示例的紧凑。

该视图现在可以使用 C++ 模型和模型的显示属性来显示字符串列表。它仍然非常简单,但已经可以在 QML 中使用。通常,数据从模型外部提供,模型将作为视图的接口。

15.5.2 更复杂的数据

实际上,模型数据往往复杂得多。因此,需要定义自定义角色,以便视图可以通过属性查询其他数据。例如,该模型不仅可以将颜色提供为十六进制字符串,还可以将 HSV 颜色模型的色调,饱和度和亮度提供为 QML 中的“model.hue”、“model.saturation” 和 “model.brightness”。

#ifndef ROLEENTRYMODEL_H
#define ROLEENTRYMODEL_H

#include 
#include 

class RoleEntryModel : public QAbstractListModel
{
    Q_OBJECT
public:
    // Define the role names to be used
    enum RoleNames {
        NameRole = Qt::UserRole,
        HueRole = Qt::UserRole+2,
        SaturationRole = Qt::UserRole+3,
        BrightnessRole = Qt::UserRole+4
    };

    explicit RoleEntryModel(QObject *parent = 0);
    ~RoleEntryModel();

    // QAbstractItemModel interface
public:
    virtual int rowCount(const QModelIndex &parent) const override;
    virtual QVariant data(const QModelIndex &index, int role) const override;
protected:
    // return the roles mapping to be used by QML
    virtual QHash roleNames() const override;
private:
    QList m_data;
    QHash m_roleNames;
};

#endif // ROLEENTRYMODEL_H

在标题中,我们添加了用于 QML 的角色映射。当 QML 现在尝试从模型访问属性(例如 “model.name”)时,listview 将查找 “name” 的映射,并使用 NameRole 向模型询问数据。用户定义的角色应该从 Qt::UserRole 开始,并且每个模型都必须是唯一的。

#include "roleentrymodel.h"

RoleEntryModel::RoleEntryModel(QObject *parent)
    : QAbstractListModel(parent)
{
    // Set names to the role name hash container (QHash)
    // model.name, model.hue, model.saturation, model.brightness
    m_roleNames[NameRole] = "name";
    m_roleNames[HueRole] = "hue";
    m_roleNames[SaturationRole] = "saturation";
    m_roleNames[BrightnessRole] = "brightness";

    // Append the color names as QColor to the data list (QList)
    for(const QString& name : QColor::colorNames()) {
        m_data.append(QColor(name));
    }

}

RoleEntryModel::~RoleEntryModel()
{
}

int RoleEntryModel::rowCount(const QModelIndex &parent) const
{
    Q_UNUSED(parent);
    return m_data.count();
}

QVariant RoleEntryModel::data(const QModelIndex &index, int role) const
{
    int row = index.row();
    if(row < 0 || row >= m_data.count()) {
        return QVariant();
    }
    const QColor& color = m_data.at(row);
    qDebug() << row << role << color;
    switch(role) {
    case NameRole:
        // return the color name as hex string (model.name)
        return color.name();
    case HueRole:
        // return the hue of the color (model.hue)
        return color.hueF();
    case SaturationRole:
        // return the saturation of the color (model.saturation)
        return color.saturationF();
    case BrightnessRole:
        // return the brightness of the color (model.brightness)
        return color.lightnessF();
    }
    return QVariant();
}

QHash RoleEntryModel::roleNames() const
{
    return m_roleNames;
}

现在的实施只有两个地方有所改变。首先在初始化。我们现在使用 QColor 数据类型初始化数据列表。另外我们定义我们的角色名称 map 可以访问 QML。此 map 稍后在 ::roleNames 函数中返回。

第二个变化是在 ::data 函数中。我们扩展开关以覆盖其他角色(例如色调,饱和度,亮度)。没有办法从颜色返回 SVG 名称,因为颜色可以采取任何颜色,SVG 名称有限。所以我们跳过这个。存储名称将需要创建一个结构 struct { QColor, QString } 以便能够识别命名的颜色。

注册类型后,我们可以在用户界面中使用模型及其条目。

ListView {
    id: view
    anchors.fill: parent
    model: RoleEntryModel {}
    focus: true
    delegate: ListDelegate {
        text: 'hsv(' +
              Number(model.hue).toFixed(2) + ',' +
              Number(model.saturation).toFixed() + ',' +
              Number(model.brightness).toFixed() + ')'
        color: model.name
    }
    highlight: ListHighlight { }
}

我们将返回的类型转换为 JS 数字类型,以便能够使用定点符号格式化数字。代码也可以没有 Number 调用(例如 model.saturation.toFixed(2))。选择哪种格式,取决于我们传入数据的数量。

15.5.3 动态数据

动态数据涵盖了从模型中插入,删除和清除数据的方面。当删除或插入条目时,QAbstractListModel 会期望某种行为。行为表现为在操纵之前和之后需要调用的信号。例如,要将一行插入到模型中,首先要发出信号 beginInsertRows,然后操作数据,然后最终发出 endInsertRows 信号。

我们将在我们的标题中添加以下函数。使用 Q_INVOKABLE 声明这些函数能够从 QML 调用它们。另一种方式是在公开槽部分声明它们。

// inserts a color at the index (0 at begining, count-1 at end)
Q_INVOKABLE void insert(int index, const QString& colorValue);
// uses insert to insert a color at the end
Q_INVOKABLE void append(const QString& colorValue);
// removes a color from the index
Q_INVOKABLE void remove(int index);
// clear the whole model (e.g. reset)
Q_INVOKABLE void clear();

另外,我们定义一个 count 属性来获取模型的大小和一个 get 方法来获得给定索引的颜色。当我们想从 QML 迭代模型读取内容时,这很有用。

// gives the size of the model
Q_PROPERTY(int count READ count NOTIFY countChanged)
// gets a color at the index
Q_INVOKABLE QColor get(int index);

插入检查的实现首先是边界,如果给定的值是有效的。只有这样,我们才开始插入数据。

void DynamicEntryModel::insert(int index, const QString &colorValue)
{
    if(index < 0 || index > m_data.count()) {
        return;
    }
    QColor color(colorValue);
    if(!color.isValid()) {
        return;
    }
    // view protocol (begin => manipulate => end]
    emit beginInsertRows(QModelIndex(), index, index);
    m_data.insert(index, color);
    emit endInsertRows();
    // update our count property
    emit countChanged(m_data.count());
}

附加方法是很简单的。我们重用插入函数与模型的大小。

void DynamicEntryModel::append(const QString &colorValue)
{
    insert(count(), colorValue);
}

删除类似于插入,但是根据删除操作协议来调用。

void DynamicEntryModel::remove(int index)
{
    if(index < 0 || index >= m_data.count()) {
        return;
    }
    emit beginRemoveRows(QModelIndex(), index, index);
    m_data.removeAt(index);
    emit endRemoveRows();
    // do not forget to update our count property
    emit countChanged(m_data.count());
}

辅助功能 count 是微不足道的。它只返回数据计数。get 函数也很简单。

QColor DynamicEntryModel::get(int index)
{
    if(index < 0 || index >= m_data.count()) {
        return QColor();
    }
    return m_data.at(index);
}

我们需要注意你只能返回一个 QML 可读取的值。如果它不是 QML 基础类型或者 QML 所知类型,我们需要使用 qmlRegisterType 或者 qmlRegisterUncreatableType 注册类型。如果是用户不能在QML中实例化对象的类型应该使用 qmlRegisterUncreatableType 注册。

现在我们可以使用 QML 中的模型,并从模型中插入,追加,删除条目。这是一个小例子,允许用户输入颜色名称或颜色十六进制值,然后将颜色附加到模型上并显示在列表视图中。代理中的红色圆圈允许用户从模型中删除该条目。在删除条目后,列表视图将被模型通知并更新其内容。

QML Book 第十五章 Qt 和 C++ 2_第1张图片
modelview

这里是 QML 代码。我们也可以在本章的资源中找到完整的源代码。该示例使用 QtQuick.Controls 和 QtQuick.Layout 模块来使代码更紧凑。这些控件模块在 QtQuick 中提供了一组与桌面相关的 ui 元素,并且布局模块提供了一些非常有用的布局管理器。

import QtQuick 2.5
import QtQuick.Window 2.2
import QtQuick.Controls 1.5
import QtQuick.Layouts 1.2

// our module
import org.example 1.0

Window {
    visible: true
    width: 480
    height: 480


    Background { // a dark background
        id: background
    }

    // our dyanmic model
    DynamicEntryModel {
        id: dynamic
        onCountChanged: {
            // we print out count and the last entry when count is changing
            print('new count: ' + count);
            print('last entry: ' + get(count-1));
        }
    }

    ColumnLayout {
        anchors.fill: parent
        anchors.margins: 8
        ScrollView {
            Layout.fillHeight: true
            Layout.fillWidth: true
            ListView {
                id: view
                // set our dynamic model to the views model property
                model: dynamic
                delegate: ListDelegate {
                    width: ListView.view.width
                    // construct a string based on the models proeprties
                    text: 'hsv(' +
                          Number(model.hue).toFixed(2) + ',' +
                          Number(model.saturation).toFixed() + ',' +
                          Number(model.brightness).toFixed() + ')'
                    // sets the font color of our custom delegates
                    color: model.name

                    onClicked: {
                        // make this delegate the current item
                        view.currentIndex = index
                        view.focus = true
                    }
                    onRemove: {
                        // remove the current entry from the model
                        dynamic.remove(index)
                    }
                }
                highlight: ListHighlight { }
                // some fun with transitions :-)
                add: Transition {
                    // applied when entry is added
                    NumberAnimation {
                        properties: "x"; from: -view.width;
                        duration: 250; easing.type: Easing.InCirc
                    }
                    NumberAnimation { properties: "y"; from: view.height;
                        duration: 250; easing.type: Easing.InCirc
                    }
                }
                remove: Transition {
                    // applied when entry is removed
                    NumberAnimation {
                        properties: "x"; to: view.width;
                        duration: 250; easing.type: Easing.InBounce
                    }
                }
                displaced: Transition {
                    // applied when entry is moved
                    // (e.g because another element was removed)
                    SequentialAnimation {
                        // wait until remove has finished
                        PauseAnimation { duration: 250 }
                        NumberAnimation { properties: "y"; duration: 75
                        }
                    }
                }
            }
        }
        TextEntry {
            id: textEntry
            onAppend: {
                // called when the user presses return on the text field
                // or clicks the add button
                dynamic.append(color)
            }

            onUp: {
                // called when the user presses up while the text field is focused
                view.decrementCurrentIndex()
            }
            onDown: {
                // same for down
                view.incrementCurrentIndex()
            }

        }
    }
}

模型-视图编程是 Qt 中最难的任务之一。对于正常的应用开发者,它是为数不多的需要实现接口的类。其它类你只需要正常使用就可以额。模型的草图通常在 QML 这边开始。你需要想象你的用户在 QML 中需要什么样的模型。通常建议创建协议时首先使用 ListModel 看看如何在 QML 中更好的工作。这种方法对于定义 QML 编程接口同样有效。使数据从 C++ 到 QML 中可用不仅仅是技术边界,也是从命令式编程到声明式编程的编程方法转变。所以准备好经历一些挫折并从中获取快乐吧。

本章完。

你可能感兴趣的:(QML Book 第十五章 Qt 和 C++ 2)