动态数据(Dynamic Data)

动态数据包含了从模型中插入,移除,清除数据等。QAbstractListModel期望当条目被移除或者插入时有一个明确的行为。这个行为使用一个信号来表示,在操作调用前和调用后调用这个行为。例如向一个模型插入一行数据,你首先需要发送beginInsertRows信号,然后操作数据,最后发送endInsertRows信号。

我们将在头文件中加入后续的函数。这些使用Q_INVOKABLE函数定义使得可以在QML中调用它们。另一种方法是将它们定义为公共槽函数。

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

此外,我们定义了count属性来获取模型的大小和一个使用索引值的get方法来获取颜色。这些东西在QML中使用迭代器遍历模型时会用到。

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

实现插入数据首先要检查边界和插入值是否有效。在这之后我们开始插入数据。

  1. void DynamicEntryModel::insert(int index, const QString &colorValue)
  2. {
  3. if(index < 0 || index > m_data.count()) {
  4. return;
  5. }
  6. QColor color(colorValue);
  7. if(!color.isValid()) {
  8. return;
  9. }
  10. // view protocol (begin => manipulate => end]
  11. emit beginInsertRows(QModelIndex(), index, index);
  12. m_data.insert(index, color);
  13. emit endInsertRows();
  14. // update our count property
  15. emit countChanged(m_data.count());
  16. }

添加数据非常简单。我们使用模型大小并调用插入函数来实现。

  1. void DynamicEntryModel::append(const QString &colorValue)
  2. {
  3. insert(count(), colorValue);
  4. }

移除数据与插入数据类似,但是需要调用移除操作协议。

  1. void DynamicEntryModel::remove(int index)
  2. {
  3. if(index < 0 || index >= m_data.count()) {
  4. return;
  5. }
  6. emit beginRemoveRows(QModelIndex(), index, index);
  7. m_data.removeAt(index);
  8. emit endRemoveRows();
  9. // do not forget to update our count property
  10. emit countChanged(m_data.count());
  11. }

函数count不太重要,这里不再介绍,只需要知道它会返回数据总数。get函数也十分简单。

  1. QColor DynamicEntryModel::get(int index)
  2. {
  3. if(index < 0 || index >= m_data.count()) {
  4. return QColor();
  5. }
  6. return m_data.at(index);
  7. }

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

现在你可以在QML中使用模型并且可以从模型中插入,添加,移除条目。这里有一个小例子,它允许用户输入一个颜色名称或者颜色16进制值,并将这个颜色加入到模型中在链表视图中显示。代理上的红色圆圈允许用户从模型中移除这个条目。在条目被移除后,模型通知链表视图更新它的内容。

动态数据(Dynamic Data) - 图1

这里是QML代码。你可以在这章的资源里找到完整的源代码。这个例子使用了QtQuick.Controls和QtQuick.Layout模块使得代码更加紧凑。控制模块提供了QtQuick中一组与桌面相关的用户界面元素,布局模块提供了非常有用的布局管理器。

  1. import QtQuick 2.2
  2. import QtQuick.Window 2.0
  3. import QtQuick.Controls 1.2
  4. import QtQuick.Layouts 1.1
  5. // our module
  6. import org.example 1.0
  7. Window {
  8. visible: true
  9. width: 480
  10. height: 480
  11. Background { // a dark background
  12. id: background
  13. }
  14. // our dyanmic model
  15. DynamicEntryModel {
  16. id: dynamic
  17. onCountChanged: {
  18. // we print out count and the last entry when count is changing
  19. print('new count: ' + count);
  20. print('last entry: ' + get(count-1));
  21. }
  22. }
  23. ColumnLayout {
  24. anchors.fill: parent
  25. anchors.margins: 8
  26. ScrollView {
  27. Layout.fillHeight: true
  28. Layout.fillWidth: true
  29. ListView {
  30. id: view
  31. // set our dynamic model to the views model property
  32. model: dynamic
  33. delegate: ListDelegate {
  34. width: ListView.view.width
  35. // construct a string based on the models proeprties
  36. text: 'hsv(' +
  37. Number(model.hue).toFixed(2) + ',' +
  38. Number(model.saturation).toFixed() + ',' +
  39. Number(model.brightness).toFixed() + ')'
  40. // sets the font color of our custom delegates
  41. color: model.name
  42. onClicked: {
  43. // make this delegate the current item
  44. view.currentIndex = index
  45. view.focus = true
  46. }
  47. onRemove: {
  48. // remove the current entry from the model
  49. dynamic.remove(index)
  50. }
  51. }
  52. highlight: ListHighlight { }
  53. // some fun with transitions :-)
  54. add: Transition {
  55. // applied when entry is added
  56. NumberAnimation {
  57. properties: "x"; from: -view.width;
  58. duration: 250; easing.type: Easing.InCirc
  59. }
  60. NumberAnimation { properties: "y"; from: view.height;
  61. duration: 250; easing.type: Easing.InCirc
  62. }
  63. }
  64. remove: Transition {
  65. // applied when entry is removed
  66. NumberAnimation {
  67. properties: "x"; to: view.width;
  68. duration: 250; easing.type: Easing.InBounce
  69. }
  70. }
  71. displaced: Transition {
  72. // applied when entry is moved
  73. // (e.g because another element was removed)
  74. SequentialAnimation {
  75. // wait until remove has finished
  76. PauseAnimation { duration: 250 }
  77. NumberAnimation { properties: "y"; duration: 75
  78. }
  79. }
  80. }
  81. }
  82. }
  83. TextEntry {
  84. id: textEntry
  85. onAppend: {
  86. // called when the user presses return on the text field
  87. // or clicks the add button
  88. dynamic.append(color)
  89. }
  90. onUp: {
  91. // called when the user presses up while the text field is focused
  92. view.decrementCurrentIndex()
  93. }
  94. onDown: {
  95. // same for down
  96. view.incrementCurrentIndex()
  97. }
  98. }
  99. }
  100. }

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