{"id":21877,"date":"2023-06-05T05:00:00","date_gmt":"2023-06-05T03:00:00","guid":{"rendered":"https:\/\/sii.pl\/blog\/?p=21877"},"modified":"2023-06-07T10:16:06","modified_gmt":"2023-06-07T08:16:06","slug":"model-view-example-in-qml-based-on-the-true-story","status":"publish","type":"post","link":"https:\/\/sii.pl\/blog\/en\/model-view-example-in-qml-based-on-the-true-story\/","title":{"rendered":"Model View example in QML \u2013 based on the true story"},"content":{"rendered":"\n<p>In this article, I will address the problem of preparing a tree model for any data containing mixed types and show you how to solve it.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Problem<\/strong><\/h2>\n\n\n\n<p>The generic problem is to prepare a tree model for any data containing mixed types. To make the example more appealing let\u2019s assume we want to read JSON data from the backend and display it on the GUI. Moreover, it would be nice to have the possibility to collapse all lists and objects. JSON could be constructed in any way so the app should be flexible. Properties could have different names which are unknown.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Preconditions<\/strong><\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Qt 6.2,<\/li>\n\n\n\n<li>QML frontend,<\/li>\n\n\n\n<li>JSON string in the backend.<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Ideas<\/strong><\/h2>\n\n\n\n<p>If there would be no preconditions an option would be to use <strong>QTreeView<\/strong> class, but there are projects that do not use widgets. For GUI we would go with QML, and for the model, there are still some options. If we are open for an MIT-licensed code there is something like <strong>JSONListModel<\/strong> (QML) or <strong>QJsonModel<\/strong> (cpp). But what if we are using only pure qt libs? It\u2019s not a simple list model so initial thoughts could lead to the cpp model, like using an abstract interface of <strong>QAbstractItemModel<\/strong>. It will go well with <strong>TreeView<\/strong> but after Qt 6.3 or before 6.0, since there is a gap in implementation.<\/p>\n\n\n\n<p>But this model sounds like a solid cannon that will grow together with a couple of classes down on the backend side. Moreover, <strong>TreeView<\/strong> is not available in our case (Qt 6.2). Maybe the model is not that complicated and could be handled on the QML side. Let\u2019s try this approach.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Solution<\/strong><\/h2>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\nimport QtQuick\n\nListModel {\n    id: root\n\n    function createAndLoadComponent(item) {\n        var newObj = Qt.createComponent(&quot;CustomDataModel.qml&quot;).createObject(root)\n        newObj.parseObject(item)\n        return newObj\n    }\n\n    function extractValue(value) {\n        if (Array.isArray(value))\n            return parseArray(value)\n        else if (value.constructor === ({}).constructor)\n            return createAndLoadComponent(value)\n        else\n            return value\n    }\n\n    function parseArray(value) {\n        var arr = &#x5B;]\n        for (var i = 0; i &amp;lt; value.length; i++)\n            arr.push({&#x5B;&quot;&quot;]: extractValue(value&#x5B;i])})\n        return arr\n    }\n\n    function parseObject(json) {\n        for (var key in json)\n            root.append({&#x5B;key]: extractValue(json&#x5B;key])})\n    }\n}\n<\/pre><\/div>\n\n\n<h3 class=\"wp-block-heading\"><strong>First step \u2013 CustomDataModel.qml creating<\/strong><\/h3>\n\n\n\n<p>Let\u2019s create <strong>CustomDataModel.qml<\/strong> which will represent our model. It will be based on <strong>ListModel<\/strong>. JSON file will be loaded into the front end as a <strong>QString<\/strong>. From there it will be parsed to the JavaScript value or object described by the string and then passed to the <strong>parseObject<\/strong> function inside <strong>CustomDataModel<\/strong>.<\/p>\n\n\n\n<p>It could be an array; it could be a simple key value or it could be also an object with many properties. We need to identify what is it for each key. Inside a for loop there is a call to <strong>extractValue<\/strong> where check: <strong>Array.isArray<\/strong> that will do just fine. The action name is self-explanatory. If it\u2019s not the case, the second task is to distinguish if the value is an object. It is done with the usage of the <strong>constructor<\/strong> property. If we have an object, we would need to read all properties of it and add those to the model.<\/p>\n\n\n\n<p>Here a recursive approach is taken by creating the object of a parent class and calling <strong>parseObject<\/strong> again. If all nested parameters are loaded, we can go further. <strong>Append<\/strong> in <strong>ListModel<\/strong> needs <strong>jsobject<\/strong> in the parameter, and in our example, we need to pass a key in there. To achieve that we are using syntax: <strong>append({[key]: value})<\/strong>. If the item is just a value the simple <strong>append<\/strong> to the ListModel would be enough.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>The parseArray function<\/strong><\/h3>\n\n\n\n<p>Now let\u2019s get back to the array case and investigate the <strong>parseArray<\/strong> function. Elements here are added in a loop. The <strong>length<\/strong> parameter is used to determine how many elements are there and before appending to the <strong>ListModel<\/strong>, an array is created where all items are pushed. Each element of an array could be an object, or value, or\u2026 an array so here we go again with the recursive approach. It is done with the help of the function <strong>extractValue<\/strong> . And that\u2019s it. The whole model class has less than 35 lines of code. I would say it is pretty concise.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>CustomDataView.qml creating<\/strong><\/h3>\n\n\n\n<p>Now to the view, let\u2019s create <strong>CustomDataView.qml<\/strong><\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\nimport QtQuick\nimport QtQuick.Controls\n\nListView {\n    id: root\n    property var customModel\n    function createObjectElement(prop, rootId) {\n        return Qt.createQmlObject(`\n              import QtQuick\n              import QtQuick.Controls\n              import QtQuick.Layouts\n              GridLayout {\n                  property alias childsId: childsId\n                  columns: 2\n                  Button { text: childsId.visible ? &quot;-&quot; : &quot;+&quot;\n                           onClicked: childsId.visible = !childsId.visible }\n                  Text { text: &quot;${prop}:&quot; }\n                  Item {}\n                  ColumnLayout { id: childsId }}`, rootId)\n    }\n\n    function prepareNestedElements(prop, rootId, value, ifArray) {\n        var child = createObjectElement(prop, rootId)\n        for (var i = 0; i &amp;lt; value.count; i++)\n            createView(value.get(i), child.childsId, ifArray ? null : i)\n    }\n\n    function createView(item, rootId, index) {\n        let count = 0;\n        for (var prop in item)\n            if ((index !== undefined &amp;amp;&amp;amp; index !== null &amp;amp;&amp;amp; index !== count++) || !item&#x5B;prop]) {\n            } else if (item&#x5B;prop] instanceof ListModel &amp;amp;&amp;amp; !(item&#x5B;prop] instanceof CustomDataModel)) {\n                prepareNestedElements(prop, rootId, item&#x5B;prop], true)\n            } else if (item&#x5B;prop].constructor === ({}).constructor) {\n                prepareNestedElements(prop, rootId, item&#x5B;prop], false)\n            } else {\n                var middle = prop === &quot;&quot; ? &quot;&quot; : &quot;: &quot;\n                Qt.createQmlObject(`\n                    import QtQuick\n                    Row {\n                        Item { width: 20; height: 10 }\n                        Text { text: &quot;${prop}${middle}${item&#x5B;prop]}&quot; }}`, rootId\n                )\n            }\n    }\n    model: customModel\n    delegate: Column {\n        id: colId\n        Component.onCompleted: root.createView(customModel.get(index), colId)\n    }\n}\n<\/pre><\/div>\n\n\n<p>It is based on the <strong>ListView<\/strong> component and has a simple <strong>Column<\/strong> delegate which will be dynamically created in <strong>Component.onCompleted<\/strong>, where simply the <strong>createView<\/strong> function is called.<\/p>\n\n\n\n<p>This function goes through all properties of an item and checks what it is. It will decode once created model items are. So, starting from the bottom we have created plain <strong>Text: key: value<\/strong> with some indentation done by the <strong>Item<\/strong>. Going up there is again check if the constructor property points to the object. In this case, the <strong>prepareNestedElements<\/strong> function is called. It\u2019s because an object can have multiple properties so it needs to be nested.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>CreateObjectElement function<\/strong><\/h3>\n\n\n\n<p>Moving inside this function, another one is called: <strong>createObjectElement<\/strong>. It will create a QML object by calling <strong>Qt.createQmlObject<\/strong> and putting it inside <strong>rootId<\/strong> which is the param here. The object will be created based on <strong>GridLayout<\/strong>, and inside there will be a button to collapse nested items. The alias to the <strong>childsId<\/strong> is so the access is provided to outside. In that way, we add new elements inside, directly to <strong>childsId<\/strong>.<\/p>\n\n\n\n<p>Going back to <strong>prepareNestedElements<\/strong>, after creating the object we need to fill it in. So recursive call to create a view will take care of all that\u2019s inside: arrays, objects, and simple elements. Mentioned <strong>child.childsId<\/strong> is passed as a param, so it will be created inside that <strong>ColumnLayout<\/strong> which is collapsible by the button. One important thing worth mentioning here is the third param in <strong>createView.<\/strong><\/p>\n\n\n\n<p>If it\u2019s not an array but an object, we are passing the index here. It will extract the correct parameter from the model because each item has a different key and on the other hand, all items in <strong>CustomListModel<\/strong> has all parameter names present. In simple words, if we add <strong>item[\u201ca\u201d]<\/strong> and <strong>item[\u201cb\u201d]<\/strong> to a <strong>ListModel<\/strong>, each item will have both params, one with the value and the other empty. So, from each one, we would like to extract only valid values.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>An array cases<\/strong><\/h3>\n\n\n\n<p>The one case that is left is an array. To identify this, we can check if the value is <strong>ListModel<\/strong> and not a <strong>CustomListModel<\/strong>. Reason for that we can find in a documentation of a <strong>ListModel<\/strong> in a <strong>get<\/strong> function: \u201cproperties of the returned object that are themselves objects will also be models\u201d. If we check the type, it is <strong>QQmlListModel<\/strong>. And since we do not explicitly make it <strong>CustomListModel<\/strong> we have a solution how to distinguish that we in fact have an array.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Summary<\/strong><\/h2>\n\n\n\n<p>A similar problem occurred in real life project I had. The thing worth mentioning is that from Qt6.3 we have a <strong>TreeView<\/strong> that could ease some things out. All of this is like 100 lines of code in QML. Sounds good to me. Presented code example could be extended using <strong>dynamicRoles<\/strong> property from <strong>ListModel <\/strong>to also support property names with different types, however according to documentation it could hit performance so it\u2019s 4-6 times slower.<\/p>\n\n\n\n<p>If this would not be JSON but some other tree like data structure it would be clearer that this model has to be custom, and with that in mind, I hope this article could show you the possible approach in such a situation. The same goes for the QML view, which is more popular nowadays and embedded-friendly. The whole example would be attached somewhere, so go ahead and check it out.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Application files<\/strong><\/h2>\n\n\n\n<div class=\"wp-block-file\"><a id=\"wp-block-file--media-adcdcdcf-3158-472f-a28f-2e3375c01e0e\" href=\"https:\/\/sii.pl\/blog\/wp-content\/uploads\/2023\/06\/CMakeLists.zip\">CMakeLists<\/a><\/div>\n\n\n\n<div class=\"wp-block-file\"><a id=\"wp-block-file--media-8b373acd-ebfa-4159-be3e-a977416a940a\" href=\"https:\/\/sii.pl\/blog\/wp-content\/uploads\/2023\/06\/JsonHandler.zip\">JsonHandler<\/a><\/div>\n\n\n\n<div class=\"wp-block-file\"><a id=\"wp-block-file--media-8b373acd-ebfa-4159-be3e-a977416a940a\" href=\"https:\/\/sii.pl\/blog\/wp-content\/uploads\/2023\/06\/main-qml.zip\">Main.qml<\/a><\/div>\n\n\n\n<div class=\"wp-block-file\"><a id=\"wp-block-file--media-8b373acd-ebfa-4159-be3e-a977416a940a\" href=\"https:\/\/sii.pl\/blog\/wp-content\/uploads\/2023\/06\/main-cpp.zip\">Main.cpp<\/a><\/div>\n\n\n\n<div class=\"wp-block-file\"><a id=\"wp-block-file--media-ab506f5a-5468-439a-a7e7-71af80acfaba\" href=\"https:\/\/sii.pl\/blog\/wp-content\/uploads\/2023\/06\/modelExampleFinal.7z\">modelExampleFinal<\/a><\/div>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Sources<\/strong><\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li><a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/JavaScript\/Reference\/Global_Objects\/JSON\/parse\" target=\"_blank\" aria-label=\" (opens in a new tab)\" rel=\"noreferrer noopener\" class=\"ek-link\" rel=\"nofollow\" >Developer.mozilla.org \u2013 JSON.parse()<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/JavaScript\/Reference\/Global_Objects\/Object\/constructor\" target=\"_blank\" aria-label=\" (opens in a new tab)\" rel=\"noreferrer noopener\" class=\"ek-link\" rel=\"nofollow\" >Developer.mozilla.org \u2013 Object.prototype.constructor<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/doc.qt.io\/qt-6\/qml-qtqml-models-listmodel.html\" class=\"ek-link\" rel=\"nofollow\" >Doc.qt.io \u2013 ListModel QM<\/a><a href=\"https:\/\/doc.qt.io\/qt-6\/qml-qtqml-models-listmodel.html\" target=\"_blank\" aria-label=\"L (opens in a new tab)\" rel=\"noreferrer noopener\" class=\"ek-link\" rel=\"nofollow\" >L<\/a><a href=\"https:\/\/doc.qt.io\/qt-6\/qml-qtqml-models-listmodel.html\" class=\"ek-link\" rel=\"nofollow\" > Type<\/a><\/li>\n<\/ul>\n\n\n\n<p>***<\/p>\n\n\n\n<p>If you are interested in the topic of QML, <a href=\"https:\/\/sii.pl\/blog\/en\/communication-methods-between-c-qt-and-qml\/?category=hard-development&amp;tag=cpp-en,embedded-en,qml\" target=\"_blank\" aria-label=\"read also the author's other article (opens in a new tab)\" rel=\"noreferrer noopener\" class=\"ek-link\">read also the author&#8217;s other article<\/a>.<\/p>\n\n\n<div class=\"kk-star-ratings kksr-auto kksr-align-left kksr-valign-bottom\"\n    data-payload='{&quot;align&quot;:&quot;left&quot;,&quot;id&quot;:&quot;21877&quot;,&quot;slug&quot;:&quot;default&quot;,&quot;valign&quot;:&quot;bottom&quot;,&quot;ignore&quot;:&quot;&quot;,&quot;reference&quot;:&quot;auto&quot;,&quot;class&quot;:&quot;&quot;,&quot;count&quot;:&quot;2&quot;,&quot;legendonly&quot;:&quot;&quot;,&quot;readonly&quot;:&quot;&quot;,&quot;score&quot;:&quot;5&quot;,&quot;starsonly&quot;:&quot;&quot;,&quot;best&quot;:&quot;5&quot;,&quot;gap&quot;:&quot;11&quot;,&quot;greet&quot;:&quot;&quot;,&quot;legend&quot;:&quot;5\\\/5 ( votes: 2)&quot;,&quot;size&quot;:&quot;18&quot;,&quot;title&quot;:&quot;Model View example in QML \u2013 based on the true story&quot;,&quot;width&quot;:&quot;139.5&quot;,&quot;_legend&quot;:&quot;{score}\\\/{best} ( {votes}: {count})&quot;,&quot;font_factor&quot;:&quot;1.25&quot;}'>\n            \n<div class=\"kksr-stars\">\n    \n<div class=\"kksr-stars-inactive\">\n            <div class=\"kksr-star\" data-star=\"1\" style=\"padding-right: 11px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 18px; height: 18px;\"><\/div>\n        <\/div>\n            <div class=\"kksr-star\" data-star=\"2\" style=\"padding-right: 11px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 18px; height: 18px;\"><\/div>\n        <\/div>\n            <div class=\"kksr-star\" data-star=\"3\" style=\"padding-right: 11px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 18px; height: 18px;\"><\/div>\n        <\/div>\n            <div class=\"kksr-star\" data-star=\"4\" style=\"padding-right: 11px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 18px; height: 18px;\"><\/div>\n        <\/div>\n            <div class=\"kksr-star\" data-star=\"5\" style=\"padding-right: 11px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 18px; height: 18px;\"><\/div>\n        <\/div>\n    <\/div>\n    \n<div class=\"kksr-stars-active\" style=\"width: 139.5px;\">\n            <div class=\"kksr-star\" style=\"padding-right: 11px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 18px; height: 18px;\"><\/div>\n        <\/div>\n            <div class=\"kksr-star\" style=\"padding-right: 11px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 18px; height: 18px;\"><\/div>\n        <\/div>\n            <div class=\"kksr-star\" style=\"padding-right: 11px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 18px; height: 18px;\"><\/div>\n        <\/div>\n            <div class=\"kksr-star\" style=\"padding-right: 11px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 18px; height: 18px;\"><\/div>\n        <\/div>\n            <div class=\"kksr-star\" style=\"padding-right: 11px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 18px; height: 18px;\"><\/div>\n        <\/div>\n    <\/div>\n<\/div>\n                \n\n<div class=\"kksr-legend\" style=\"font-size: 14.4px;\">\n            5\/5 ( votes: 2)    <\/div>\n    <\/div>\n","protected":false},"excerpt":{"rendered":"<p>In this article, I will address the problem of preparing a tree model for any data containing mixed types and &hellip; <a class=\"continued-btn\" href=\"https:\/\/sii.pl\/blog\/en\/model-view-example-in-qml-based-on-the-true-story\/\">Continued<\/a><\/p>\n","protected":false},"author":394,"featured_media":21881,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"_editorskit_title_hidden":false,"_editorskit_reading_time":0,"_editorskit_is_block_options_detached":false,"_editorskit_block_options_position":"{}","inline_featured_image":false,"footnotes":""},"categories":[1320],"tags":[1709,1506,1342],"class_list":["post-21877","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-hard-development","tag-json-2","tag-qml","tag-embedded-en"],"acf":[],"aioseo_notices":[],"republish_history":[],"featured_media_url":"https:\/\/sii.pl\/blog\/wp-content\/uploads\/2023\/05\/Model-View-example-in-QML-\u2013-based-on-the-true-story.jpg","category_names":["Hard development"],"_links":{"self":[{"href":"https:\/\/sii.pl\/blog\/en\/wp-json\/wp\/v2\/posts\/21877"}],"collection":[{"href":"https:\/\/sii.pl\/blog\/en\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/sii.pl\/blog\/en\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/sii.pl\/blog\/en\/wp-json\/wp\/v2\/users\/394"}],"replies":[{"embeddable":true,"href":"https:\/\/sii.pl\/blog\/en\/wp-json\/wp\/v2\/comments?post=21877"}],"version-history":[{"count":3,"href":"https:\/\/sii.pl\/blog\/en\/wp-json\/wp\/v2\/posts\/21877\/revisions"}],"predecessor-version":[{"id":22146,"href":"https:\/\/sii.pl\/blog\/en\/wp-json\/wp\/v2\/posts\/21877\/revisions\/22146"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/sii.pl\/blog\/en\/wp-json\/wp\/v2\/media\/21881"}],"wp:attachment":[{"href":"https:\/\/sii.pl\/blog\/en\/wp-json\/wp\/v2\/media?parent=21877"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/sii.pl\/blog\/en\/wp-json\/wp\/v2\/categories?post=21877"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/sii.pl\/blog\/en\/wp-json\/wp\/v2\/tags?post=21877"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}