# 动态数据(Dynamic Data) 动态数据包含了从模型中插入,移除,清除数据等。```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中使用模型并且可以从模型中插入,添加,移除条目。这里有一个小例子,它允许用户输入一个颜色名称或者颜色16进制值,并将这个颜色加入到模型中在链表视图中显示。代理上的红色圆圈允许用户从模型中移除这个条目。在条目被移除后,模型通知链表视图更新它的内容。 ![](http://qmlbook.github.io/_images/modelview.png) 这里是QML代码。你可以在这章的资源里找到完整的源代码。这个例子使用了QtQuick.Controls和QtQuick.Layout模块使得代码更加紧凑。控制模块提供了QtQuick中一组与桌面相关的用户界面元素,布局模块提供了非常有用的布局管理器。 ``` import QtQuick 2.2 import QtQuick.Window 2.0 import QtQuick.Controls 1.2 import QtQuick.Layouts 1.1 // 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中可用不仅仅是技术边界,也是从命令式编程到声明式编程的编程方法转变。所以准备好经历一些挫折并从中获取快乐吧。