Hello Qt -- model view in QtQuick

Posted by salomo on Sat, 05 Mar 2022 17:23:43 +0100

1, QtQuick model view Foundation

Model view is a technology that separates data from display. QtQuick provides a series of predefined models and views.

1,Repeater

The most basic way to separate data from the presentation layer in QtQuick is to use the repeater element. The repeater element can be used to display the data of an array and can be easily located in the user interface. Repeater's model ranges from an integer to network data, which can be used as its data model.

The simplest use of Repeater is to take an integer as the value of its model attribute. The integer represents the number of data in the model used by Repeater. For example, in the following code, model: 10 represents that the model of Repeater has 10 data items.

import QtQuick 2.6


Column
{
    spacing: 2
    Repeater
    {
        model: 10
        Rectangle
        {
            width: 100
            height: 20
            radius: 3
            color: "blue"
            Text
            {
                anchors.centerIn: parent
                text: index
            }
        }
    }
}

Set 10 data items, and then define a Rectangle for display. The width and height of each Rectangle are 100px and 20px respectively, with rounded corners and light blue background. A Text element in the Rectangle is its child element, and the Text text value is the index of the current item.

import QtQuick 2.6


Column
{
    spacing: 2
    Repeater
    {
        model: ListModel
        {
            ListElement { name: "Mercury"; surfaceColor: "gray" }
            ListElement { name: "Venus"; surfaceColor: "yellow" }
            ListElement { name: "Earth"; surfaceColor: "blue" }
            ListElement { name: "Mars"; surfaceColor: "orange" }
            ListElement { name: "Jupiter"; surfaceColor: "orange" }
            ListElement { name: "Saturn"; surfaceColor: "yellow" }
            ListElement { name: "Uranus"; surfaceColor: "lightBlue" }
            ListElement { name: "Neptune"; surfaceColor: "lightBlue" }
        }

        Rectangle
        {
            width: 100
            height: 20
            radius: 3
            color: "lightBlue"
            Text
            {
                anchors.centerIn: parent
                text: name
            }

            Rectangle
            {
                anchors.left: parent.left
                anchors.verticalCenter: parent.verticalCenter
                anchors.leftMargin: 2

                width: 16
                height: 16
                radius: 8
                border.color: "black"
                border.width: 1
                color: surfaceColor
            }
        }
    }
}

Each property of ListElement is bound to the instantiated display item by Repeater. The name and surfaceColor properties of ListElement can be accessed within the scope of each Rectangle used to display data.

Repeater is suitable for a small number of static data sets.

2. Dynamic view

Repeater is suitable for a small number of static data sets, but in practical application, the data model is very complex and the number is huge. Therefore, QtQuick provides two specialized view elements: ListView and GridView. Both ListView and GridView inherit from Flickable and allow users to move in a large data set. At the same time, ListView and GridView can reuse the created agents. ListView and GridView do not need to create a separate agent for each data, reducing the memory problem caused by the creation of a large number of agents.

ListView uses the data provided by the model to create proxy rendering data.

If the data contained in the data model cannot be fully displayed on one screen, the ListView will only display part of the whole list. However, as a default behavior of QtQuick, ListView cannot limit the display range. Just within the display area of the agent, the agent may be displayed outside the ListView. Setting the clip property to true enables agents beyond the ListView boundary to be trimmed.

import QtQuick 2.6


Rectangle
{
    width: 80
    height: 300
    color: "white"
    ListView
    {
        anchors.fill: parent
        anchors.margins: 20
        clip: true
        model: 100
        delegate: numberDelegate
        spacing: 5
    }

    Component
    {
        id: numberDelegate
        Rectangle
        {
            width: 40
            height: 40
            color: "lightGreen"
            Text
            {
                anchors.centerIn: parent
                font.pixelSize: 10
                text: index
            }
        }
    }
}

ListView is a scrollable area. ListView supports smooth scrolling and can scroll quickly and smoothly. By default, scrolling has a bounce effect when it reaches the bottom, which is controlled by the boundsBehavior property. The boundsBehavior property has three optional values: flickable Stopatbounds completely eliminates the rebound effect; Flickable.DragOverBounds has no rebound effect during free sliding, allowing users to drag beyond the boundary; Flickable.DragAndOvershootBounds is the default value. Users can not only drag to cross the boundary, but also slide freely to cross the boundary.

When the list sliding ends, the list may stop at any position: an agent may only display part, and the other part may be cut off, which is controlled by the snapMode attribute. The default value of snapMode property is listview Nosnap, which can stop at any position; ListView.SnapToItem will stop sliding on the top of an agent; ListView.SnapOneItem stipulates that no more than one agent can be slid at a time, and only one agent can be slid at a time, which is especially effective when paging and scrolling.

By default, the list view is vertical. You can change it to landscape through the orientation attribute. The acceptable value of the property is listview Vertical or listview Horizontal. When the list view is arranged horizontally, the default elements are arranged in the order from left to right. You can modify it by using the layoutDirection attribute. The optional value of the attribute is QT Lefttoright or QT RightToLeft.

Using ListView in the touch screen environment, the default setting is sufficient. However, if you use the direction keys in an environment with a keyboard, you should generally highlight the current item. The view also supports the use of a proxy specifically for highlighting, which will only be instantiated once and will only move to the location of the current project.

import QtQuick 2.6


Rectangle
{
    width: 240
    height: 300
    color: "white"
    ListView
    {
        anchors.fill: parent
        anchors.margins: 20
        clip: true
        model: 100
        delegate: numberDelegate
        spacing: 5
        highlight: highlightComponent
        focus: true
    }

    Component
    {
        id: highlightComponent
        Rectangle
        {
            width: ListView.view.width
            color: "lightGreen"
        }
    }

    Component
    {
        id: numberDelegate
        Item
        {
            width: 40
            height: 40
            Text
            {
                anchors.centerIn: parent
                font.pixelSize: 10
                text: index
            }
        }
    }
}

Set the focus property to true to allow the ListView to receive keyboard focus. The highlight property is set to a highlight proxy to be used. When the up and down keys on the keyboard are pressed, the focus will move and the highlighted items will move. By default, the highlighted movement is the responsibility of the view. The movement speed and size are controllable. The attributes include highlightMoveSpeed, highlightMoveDuration, highlightResizeSpeed and highlightResizeDuration. The default speed is 400 pixels per second; The duration is set to - 1, which means that the duration is controlled by speed and distance. When the speed and duration are set at the same time, the system selects the faster one. More detailed settings for highlighting can be achieved by setting the highlightFollowCurrentItem property to false. This means that the view will no longer be responsible for the highlighted movement and will be completely left to the developer. In the following example, the y property of the highlighted agent is bound to ListView view. currentItem. y additional attributes. This ensures that the highlight follows the current project. However, we do not want the view to move and highlight, but to be fully controlled by ourselves, so we apply a Behavior on the y attribute. The following code divides the moving process into three steps: fade out, move and fade in. Note that SequentialAnimation and PropertyAnimation can be combined with NumberAnimation to achieve more complex mobility. The animation part will be introduced in detail in later chapters. Here is just a demonstration of this effect.

The header and footer of the ListView are actually added before the first element and after the last element. Headers and footers are often used to display additional elements, such as the "load more" button at the bottom.

import QtQuick 2.6


Rectangle
{
    width: 240
    height: 300
    color: "white"
    ListView
    {
        anchors.fill: parent
        anchors.margins: 20
        clip: true
        model: 100
        delegate: numberDelegate
        spacing: 5
        header:headerComponent
        footer:footerComponent
    }

    Component
    {
        id: headerComponent
        Rectangle
        {
            width: 40
            height: 20
            color: "yellow"
            Text{text:"up"}
        }
    }

    Component
    {
        id: footerComponent
        Rectangle
        {
            width: 40
            height: 20
            color: "red"
            Text{text:"down"}
        }
    }

    Component
    {
        id: numberDelegate
        Item
        {
            width: 40
            height: 40
            Text
            {
                anchors.centerIn: parent
                font.pixelSize: 10
                text: index
            }
        }
    }
}

GridView is used to display two-dimensional tables. The elements of the table do not depend on the size of the proxy and the interval between the proxies. Instead, a cell is controlled by the cellWidth and cellHeight attributes. Each delegate is placed in the upper left corner of the cell. GridView supports different display directions and is controlled by flow attribute. The optional value is GridView Lefttoright and GridView TopToBottom. The former is filled from left to right and then from top to bottom, and the scroll bar appears in the vertical direction; The latter is filled from top to bottom and then from left to right, and the scroll bar appears in the horizontal direction.

2, QtQuick view agent

1. View proxy

Agent plays an important role in user-defined user interface. Each data item in the model should be displayed to the user through an agent, and the visual part seen by the user is the agent.

Each agent can access a series of properties and additional properties. Some of these attributes and additional attributes come from the data model and some from the view. The former provides the agent with the data information of each data item; The latter is the status information about the view.

The most commonly used attribute in the agent is the additional attribute listview from the view Iscurrent item and listview view. The former is a Boolean value used to indicate whether the data item represented by the agent is the current data item displayed by the view; The latter is a read-only property that represents the view to which the agent belongs. By accessing the relevant data of the view, you can create a general reusable agent to adapt to the size and presentation characteristics of the view.

import QtQuick 2.0


Rectangle
{
    width: 120
    height: 300
    gradient: Gradient
    {
        GradientStop { position: 0.0; color: "#f6f6f6" }
        GradientStop { position: 1.0; color: "#d7d7d7" }
    }

    ListView
    {
        anchors.fill: parent
        anchors.margins: 20
        clip: true
        model: 100
        delegate: numberDelegate
        spacing: 5
        focus: true
    }

    Component
    {
        id: numberDelegate
        Rectangle
        {
            width: ListView.view.width
            height: 40
            color: ListView.isCurrentItem?"#157efb":"#53d769"
            border.color: Qt.lighter(color, 1.1)
            Text
            {
                anchors.centerIn: parent
                font.pixelSize: 10
                text: index
            }
        }
    }
}

This example shows that the width of each agent is bound to the width of the view, and the background color of the agent is based on the additional attribute listview Iscurrentitem varies with the.

If each data item of the model is associated with an action, such as responding to the click operation of the data item, the data item association action in the model should be a part of each agent. This separates event management from the view. Views mainly deal with the navigation and switching between sub views, while agents deal with the events of a specific data item. The most common way to accomplish this is to create a MouseArea for each view and then respond to its onClicked signal.

2. Event handling in agent

The data items in a view are not fixed and need to be added and removed dynamically. The addition and removal of data items in the view is actually a response to the modification of the underlying model. Adding animation effects can make users clearly understand what data has changed.

QML provides two signals for each agent, onAdd and onRemove, to add and remove data items.

import QtQuick 2.0


Rectangle
{
    width: 480
    height: 300
    gradient: Gradient
    {
        GradientStop { position: 0.0; color: "#dbddde" }
        GradientStop { position: 1.0; color: "#5fc9f8" }
    }

    ListModel
    {
        id: theModel
        ListElement { number: 0 }
        ListElement { number: 1 }
        ListElement { number: 2 }
        ListElement { number: 3 }
        ListElement { number: 4 }
        ListElement { number: 5 }
        ListElement { number: 6 }
        ListElement { number: 7 }
        ListElement { number: 8 }
        ListElement { number: 9 }
    }

    Rectangle
    {
        anchors.left: parent.left
        anchors.right: parent.right
        anchors.bottom: parent.bottom
        anchors.margins: 20
        height: 40
        color: "#53d769"
        border.color: Qt.lighter(color, 1.1)
        Text
        {
            anchors.centerIn: parent
            text: "Add item!"
        }

        MouseArea
        {
            anchors.fill: parent
            onClicked:
            {
                theModel.append({"number": ++parent.count});
            }
        }
        property int count: 9
    }

    GridView
    {
        anchors.fill: parent
        anchors.margins: 20
        anchors.bottomMargin: 80
        clip: true
        model: theModel
        cellWidth: 45
        cellHeight: 45
        delegate: numberDelegate
    }

    Component
    {
        id: numberDelegate
        Rectangle
        {
            id: wrapper
            width: 40
            height: 40
            gradient: Gradient
            {
                GradientStop { position: 0.0; color: "#f8306a" }
                GradientStop { position: 1.0; color: "#fb5b40" }
            }

            Text
            {
                anchors.centerIn: parent
                font.pixelSize: 10
                text: number
            }

            MouseArea
            {
                anchors.fill: parent
                onClicked:
                {
                    if (!wrapper.GridView.delayRemove)
                        theModel.remove(index);
                }
             }
            GridView.onRemove: SequentialAnimation
            {
                PropertyAction { target: wrapper; property: "GridView.delayRemove"; value: true }
                NumberAnimation { target: wrapper; property: "scale"; to: 0; duration: 250; easing.type: Easing.InOutQuad }

                PropertyAction { target: wrapper; property: "GridView.delayRemove"; value: false }
             }

            GridView.onAdd: SequentialAnimation
            {
                NumberAnimation { target: wrapper; property: "scale"; from: 0; to: 1; duration: 250; easing.type: Easing.InOutQuad }
            }
        }
    }
}

This example demonstrates adding animation effects to dynamically modify ListModel. There is a button for adding data items. Clicking the button will add a data item to the model by calling the append function, which will trigger the view to create a new agent and issue GridView Onadd signal. GridView. The onadd signal is associated with a SequentialAnimation type animation, which uses the change of the scale attribute to zoom the agent to the view. When a data item in the view is clicked, it will be removed by calling the remove function of the view, and the gridview.com command will be issued Onremove signal to trigger another SequentialAnimation type animation. When adding an agent, the agent must be created before the animation starts. When deleting an agent, the agent cannot be destroyed until the animation ends. Use the PropertyAction element to add GridView. Before the animation starts Set the delayremove property to true and set it to false after the animation is completed to ensure that the animation can be completed smoothly before the agent is destroyed.

3. Agent customization

When presenting a list, there is often a mechanism: when data items are selected, the selected data items will become larger to fill the screen. This behavior can place the activated data item in the center of the screen or display more detailed information for the user.

In this example, each data item of the ListView will fill the whole list view when clicked, and the extra space is used to display more information. You can use states to implement this mechanism. In this process, many properties of the list will change.

First, the height of the wrapper will be set to the height of the ListView; The thumbnail becomes larger and moves from the previous position to a larger position. In addition, two hidden components, factsView and closeButton, will be displayed in the appropriate location. Finally, the contentsY property of the ListView will be reset to the y value of the proxy. The contentsY attribute is actually the top distance of the visible part of the view. The interactive property of the view is set to false, which prevents the user from moving the view by dragging the scroll bar. When the data item is clicked for the first time, it will enter the expanded state. Its agent fills the whole ListView and rearranges the content. When the close button is clicked, the expanded state is cleared, the agent returns to the original state, and the interaction of ListView is allowed again.

3, QtQuick model view advanced technology

1,PathView

PathView is the most powerful and complex view in QtQuick. PathView allows you to create a more flexible view. The data items in the view are not square, but can be laid out along any path. Along the same layout path, the properties of the data items can be set in more detail, such as zoom, transparency, etc.

To use PathView, you first need to define a proxy and a path. In addition, PathView can also set many other properties, the most common of which is pathItemCount, which is used to set the number of visual data items; preferredHighlightBegin, preferredHighlightEnd and highlightRangeMode can set the highlight range, that is, the data items that can be displayed along the path.

Before delving into the highlighted range, you must first understand the path attribute. Path accepts a path element that defines the path required by the agent in the PathView. The path is defined using the startX and startY attributes in combination with PathLine, PathQuad, PathCubic and other path elements.

Once the path definition is complete, you can adjust it using the PathPercent and PathAttribute elements. These elements are used between two path elements to better control the path and the agent above the path. PathPercent controls how large the path portion between two elements is. It controls the distribution of agents on the path, which are distributed according to their defined percentage.

The PathAttribute element is placed between elements in the same way as PathPercent. Some attribute values are allowed to be inserted along the path of the element. These attribute values are attached to the agent and can be used for any attribute that can be used.

The following example demonstrates how to use PathView to pop in cards. Here are some techniques used to achieve this. Its path contains three PathLine elements. There is enough space in the middle of the element to avoid being obscured by other elements. The rotation, size scaling, and Z axis of the element are all controlled by the PathAttribute. In addition to defining the path, we also set the pathItemCount property of PathView. This attribute is used to specify the number of elements expected by the path. Finally, the PathView in the proxy Onpath uses the preferredHighlightBegin and preferredHighlightEnd properties to control the visibility of the proxy.

2. Load model from XML

XML is a common data format. QML provides XmlListModel elements to support the conversion of XML data into models. XmlListModel can load local or remote XML documents and process data using XPath expressions.

Background.qml file:

import QtQuick 2.0


Rectangle 
{
    width: 320
    height: 320
    gradient: Gradient 
    {
        GradientStop { position: 0.0; color: "#f6f6f6" }
        GradientStop { position: 1.0; color: "#d7d7d7" }
    }
}

Box.qml file:

import QtQuick 2.0


Rectangle
{
    id: root
    width: 64
    height: 64
    color: "#ffffff"
    border.color: Qt.darker(color, 1.2)
    property alias text: label.text
    property color fontColor: '#1f1f1f'
    Text
    {
        id: label
        anchors.centerIn: parent
        font.pixelSize: 14
        color: root.fontColor
    }
}

Main.qml file:

When the XML data is downloaded, it is processed into the data items and roles of the model. The query attribute is an XPath expression language used to create model data items. In this example, the attribute value is / rss/channel/item. Each item tag in each channel tag under the rss tag will generate a data item. Each data item can define a series of roles, which are represented by XmlRole. Each role has a name, and the agent can access its value using the attachment property. The value of the role is obtained using an XPath expression. For example, the value of the title attribute is determined by the title/string() expression, which returns the text between the < title > and < / Title > tags. The result of the imageSource function is not the value of a string, but the result of a series of XML operations. In the returned XML, some items contain pictures, which are represented by < img SRC = tags. Using the substring after and substring before XPath functions, you can find the address of each picture and return it. Therefore, the imageSource attribute can be directly used as the source attribute value of the Image element.

3. Group list

In order to use grouping, you need to set section Property and section Criteria has two attributes. section.property defines which property to use for grouping. Before grouping, you need to ensure that the model is well ordered so that each part can contain continuous elements. Otherwise, the name of the same attribute may appear in multiple places. section. The optional value of criteria is viewsection Fullstring or viewsection FirstCharacter. The former is the default value, which is suitable for models with obvious grouping, such as music sets; The latter is grouped by attribute initials and means that all attributes apply. A common example is the address book list in the phonebook.

Once the grouping is defined, the additional attribute listview can be used in each data item section,ListView.previousSection and listview Nextsection accesses the group. Using this attribute, we can find the first and last element of a group, so as to realize some special functions.

We can also give the section of ListView The attribute of the delegate can be customized to display the attribute of the delegate. This inserts a proxy to display the grouping before the data item of a group. This agent can access the name of the current group using additional properties.

Group a group of people by country. The country is set to section The value of the property property. section. The delegate component, that is, sectionDelegate, is used to display the name of each group, that is, the country name. The names of people in each group are displayed using spaceManDelegate.

import QtQuick 2.0

import QtQuick.XmlListModel 2.0


Background
{
    width: 300
    height: 480
    Component
    {
        id: imageDelegate
        Box
        {
            width: listView.width
            height: 220
            color: '#333'
            Column
            {
                Text {text: title;color: '#e0e0e0'}
                Image
                {
                    width: listView.width
                    height: 200
                    fillMode: Image.PreserveAspectCrop
                    source: imageSource
                }
            }
        }
    }

    XmlListModel
    {
        id: imageModel
        source: "http://www.padmag.cn/feed"
        query: "/rss/channel/item"
        XmlRole { name: "title"; query: "title/string()" }
        XmlRole { name: "imageSource"; query: "substring-before(substring-after(description/string(), 'img src=\"'), '\"')"}

    }

    ListView
    {
        id: listView
        anchors.fill: parent
        model: imageModel
        delegate: imageDelegate
    }
}

4. Performance of model views

The performance of model views depends largely on the cost of creating new agents. For example, if the clip property is set to false, when scrolling down the ListView, the system will create a new agent at the end of the list and remove the agent that is no longer visible at the top of the list. When initializing agents takes a lot of time, initializing agents and deleting non displayable agents will have a certain impact when users quickly drag the scroll bar.

In order to avoid the consumption caused by creating an agent, you can adjust the value of the outer border of the scrolled view. This can be achieved by modifying the cacheBuffer attribute. In the example of vertical scrolling described above, this attribute affects how many pixels are above and below the list. These pixels affect whether they can accommodate these agents. For example, by combining the asynchronous loading of pictures with this, it can be displayed only after the pictures are actually loaded.

More agents means more memory consumption, which affects the user's operation fluency and the time of agent initialization. For complex agents, modifying the cacheBuffer attribute can not fundamentally solve the problem. Once the agent is initialized, the agent content will be recalculated, which will consume time. If it takes a long time, it will significantly reduce the user experience. The number of surrogate sub elements also has an impact, because it takes more time to move more elements. In order to solve the problem of agent consumption, the Loader element is recommended. The Loader element allows additional elements to be loaded late. For example, an expandable agent displays detailed information, including a large picture, only when the user clicks. Using the Loader element, you can load only when it is displayed, otherwise it will not be loaded. Similarly, each agent should contain as little JavaScript code as possible, and it is best to call complex JavaScript code outside the agent. This reduces the time it takes to compile JavaScript when the agent is created.

Topics: Qt