QML - Урок 007. ListView Qml. Динамическое создание и удаление элементов

Android, ListItem, ListModel, ListView, QML, Qt

В статье про динамическое создание виджетов в Qt я уже рассказывал, как создавать и удалять кнопки динамически, а также как с ними взаимодействовать. И там использовался Vertical Layout , а случае с Qml мы сможем использовать ListView Qml, как аналогично используется при программирование в Java под Android. Что, к слову говоря, также применимо при программировании на Qt под Android.

В статье про динамическое создание виджетов использовались для примера объекты класса QButton. В данной статье будут использоваться объекты Button Qml, о кастомизации которых было рассказано в следующей статье . Но кнопки будут размещаться в ListView Qml.

Структура проекта для работы с ListView Qml

На это раз обойдёмся проектом, созданным по умолчанию и даже без дизайнера интерфейсов. Тем более, что на момент написания статьи не все параметры можно было задать в дизайнере. А структура проекта следующая:

  • QmlDynamic.pro - профайл проекта;
  • deployment.pri - файл правил деплоя на целевую платформу;
  • main.cpp - основной файл запуска приложения;
  • main.qml - qml файл с исходными кодами программы

main.qml

Поскольку все остальные файлы не представляют для нас никакого интереса и создаются по умолчанию, то сразу перейдём к разбору полётов с файлом main.qml.

Алгоритм работы программы следующий. В верхней части окна приложения имеется объект Row, в котором располагается текстовое поле и две кнопки. Остальную часть окна приложения занимает объект ListView Qml. По нажатию одной из кнопок в Row создаётся динамический объект в ListView Qml, который содержит кнопку. По нажатию динамической кнопки в текстовое поле в Row передаётся индекс ListElement, в котором находится кнопку, по которой производилось нажатие. И далее нажатием второй кнопки в объекте Row по индексу из текстового поля удаляется элемент из ListView Qml, а значение в текстовом поле стирается.

Примечателен тот факт, что при удалении элемента, который располагается в середине списка, например, индексы всех элементов, следующих за этим элементом уменьшаются на один. То есть индексы элементов пересчитываются автоматически.

import QtQuick 2.5
import QtQuick.Controls 1.4

ApplicationWindow {
    visible: true
    width: 640
    height: 480
    title: qsTr("Hello World")

    /* Номер создаваемой кнопки, для её визуальной идентификации
     * при демонстрации проекта
     */
    property int number: 0

    /* Строка с полем, где отображается индекс нажатой динамической кнопки,
     * кнопкой для создания динамических кнопок,
     * и кнопкой для удаления динамических кнопок по индексу
     * */
    Row {
        id: row
        // Задаём размеры строки и прибиваем к верхней части окна приложения
        height: 50
        anchors.top: parent.top
        anchors.left: parent.left
        anchors.right: parent.right

        // Задаём размещение поля с индексом кнопки
        Rectangle {
            width: (parent.width / 5)
            height: 50

            // Устанавливаем текстовое поле для размещения индекса кнопки
            Text {
                id: textIndex
                anchors.fill: parent
                text: ""
                verticalAlignment: Text.AlignVCenter
                horizontalAlignment: Text.AlignHCenter
            }
        }

        // Кнопка для создания динамических кнопок
        Button {
            id: button1
            text: qsTr("Create Button")
            width: (parent.width / 5)*2
            height: 50

            /* По клику по кнопке добавляем в model ListView
             * объект, с заданными параметрами
             * */
            onClicked: {
                listModel.append({idshnik: "Button " + (++number)})

            }
        }

        // Кнопка для удаления динамических кнопок
        Button {
            id: button2
            text: qsTr("Delete Button")
            width: (parent.width / 5)*2
            height: 50

            // Удаляем кнопку по её индексу в ListView
            onClicked: {
                if(textIndex.text != ""){
                    listModel.remove(textIndex.text)
                    textIndex.text = "" // Обнуляем текстовое поле с индексом
                }
            }
        }
    }

    // ListView для представления данных в виде списка
    ListView {
        id: listView1
        // Размещаем его в оставшейся части окна приложения
        anchors.top: row.bottom
        anchors.bottom: parent.bottom
        anchors.left: parent.left
        anchors.right: parent.right

        /* в данном свойстве задаём вёрстку одного объекта
         * который будем отображать в списке в качестве одного элемента списка
         * */
        delegate: Item {
            id: item
            anchors.left: parent.left
            anchors.right: parent.right
            height: 40

            // В данном элементе будет находиться одна кнопка
            Button {
                anchors.fill: parent
                anchors.margins: 5

                /* самое интересное в данном объекте
                 * задаём свойству text переменную, по имени которой будем задавать
                 * свойства элемента
                 * */
                text: idshnik

                // По клику по кнопке отдаём в текстовое поле индекс элемента в ListView
                onClicked: {
                    textIndex.text = index
                }
            }
        }

        // Сама модель, в которой будут содержаться все элементы
        model: ListModel {
            id: listModel // задаём ей id для обращения
        }
    }
}

Итог

В результате Вы получите приложение, в котором динамически создаются и удаляются элементы с кнопками, которое будет выглядеть так, как показано на рисунке. Также Вы можете ознакомиться с демонстрацией работы приложения в видеоуроке.

Видеоурок

Рекомендуем хостинг TIMEWEB
Рекомендуем хостинг TIMEWEB
Стабильный хостинг, на котором располагается социальная сеть EVILEG. Для проектов на Django рекомендуем VDS хостинг.
Поддержать автора Donate
L

Здравствуйте,


Скажите, как реализовать следующее. Из объекта C++ посылается сигнал. Этот сигнал передает int ID - номер элемента.  В QML этот сигнал принимается и объект из ListModel с index == ID, например, меняет текст или свой  цвет.

Не хотелось бы повторяться. В статье по сигналам и слотам в QML есть вариант использования C++ объекта. Там используется тип Connections , который можно настроить на сигнал, который передаёт некое значение, тот же самый ID.

Connections {
    target: appCore 
    onSendToQml: {
        
    }
}
При этом сигнатура сигнала и обработчика не прописывается в этом соединении. Мы просто знаем, что в сигнале передаётся ID типа int, например с таким названием: sendedID.

Далее необходимо по этому ID изменить цвет. Тут есть такой момент, Изменения в свойствах делегата ListView необходимо делать через модель данных. То есть, модель данных должна содержать поле с указанием цвета. Можно сделать по умолчанию чёрный, а потом изменять его.
delegate: Text {
    anchors.left: parent.left
     anchors.right: parent.right
     height: 50
    horizontalAlignment: Text.AlignHCenter
    verticalAlignment: Text.AlignVCenter

    color: textColor // Роль с цветом
    text: textList // Роль с текстом
}
Тогда изменение цвета будет выглядеть так:
Connections {
    target: appCore 
    onSendToQml: {
          listModel.get(sendedID).textColor = "blue"
    }
}
L

Большое спасибо за ответ.

АК

А я не понимаю, как представление ссылается на модель? Нигде ведь не указано, что представление использует именно модель с id : listModel.

ps.
Ни JavaScript ни JSON не знаю, проблема в этом ? :)

Сама модель устанавливается в качестве property в представление.

model: ListModel {
    id: listModel // задаём ей id для обращения
}
Это для представления достаточно. По факту, даже этот id можно не указывать. Указание данного id было необходимо, чтобы другие объекты интерфейса, которые не относятся к модели, могли бы к ней обратиться.
АК

Точно.
Я почему-то подумал, что парент у ListModel  главное окно, а не представление.
Три закрывающие скобки сбили с толка :)
Спасибо.

Можно и окно парентом сделать, то есть написать код модели под представлением. Но тогда в property model потребуется передать id этой модели.

АК

Не могу понять, как мы добавляем новый элемент в listModel на строчке 53.
Как я понял из прочитанного: delegate дополняет listModel своими данными, чтобы было удобно ее отображать. В данном случае этими данными является idshnik. Сама listModel не хранит idshnik.
Возникает вопрос, как мы можем писать следующее  listModel.append({idshnik: "Button " + (++number)}).
Ведь что получается, что добавляя новый элемент в listModel мы передаем данные idshnik, а listModel их не хранит. Получается, они передаются сразу делегату? Или я запутался?

idshnik - это роль в модели данных.
Когда в QML в модель добавляются данные таким способом

listModel.append({idshnik: "Button " + (++number)})
То при попытке забрать данные из модели в QML вызывается во внутренностях ListView метод модели data() , который присутствует во всех моделях в Qt. Как я понимаю, в качестве роли передаётся QString ("idshnik") из делегата, и если в текущем объекте такая роль была найдена
{idshnik: "Button " + (++number)}
то возвращается некоторое значение, которое там было задано.
АК

строчки 111-114

        model: ListModel {
            id: listModel // задаём ей id для обращения
        }
Создается модель без каких-либо еще ролей, это так?

А где тот момент, когда роль задается?
Строчка 53?
listModel.append({idshnik: "Button " + (++number)})
При первом добавлении элемента задается роль?

Или на строчке 102 внутри создания делегата?
text: idshnik
Но ведь это всего лишь текст кнопки.

Не могу понять как это именно работает :)

Роли здесь определяются автоматически. В самой модели в данном случае роль не определяется, как например сделано в этой статье , где имена ролей указываются в методе roleNames. Так что да, в строка 111-114 создаётся модель без предопределённых ролей.


На строке 53 создаётся объект со свойствами, у которых есть имена. Эти имена и являются ролями.

На строке 102 по сути написано имя роли, которую нужно попытаться найти в объекте модели.

Как я понимаю, когда делегат пытается получить данные из модели, он отправляет в модель имя роли, по которому модель отыскивает нужное свойство в объекте, который был добавлен в строке 53.

Учитывая, что все кастомные типы данных в QML наследуются от QObject, то и создаваемый объект должен также наследоваться от этого класса. А QObject может иметь динамически создаваемые property, доступ к которым осуществляется через имя, в данном случае таким имененем выступает роль (idshnik).
АК

Отлично, теперь понял.

И изменить набор ролей уже будет нельзя, если мы их уже задали.

Что говорится в "Note that when creating content dynamically the set of available properties cannot be changed once set. Whatever properties are first added to the model are the only permitted properties in the model." на http://doc.qt.io/Qt-5/qml-qtqml-models-listmodel.html#append-method .

Благодарю, Евгений.

R

Приветствую. Подскажите пожалуйста, как сделать так что бы на элементе ListView изначально был activeFocus. Ни как не получается это сделать. Даже если добавить

    Component.onCompleted:
    {
            listView.forceActiveFocus();
    }

То все равно

onActiveFocusChanged: { console.log("activeFocusChanged", activeFocus) }

выдает сначала true, а потом сразу же false. Если же нажать Tab то актив фокус сразу же устанавливается на элементе ListView. Вроде доходчиво объяснил проблему. Помогите кто знает решение.

Наверное, у вас что-то ещё не успело загрузиться в тот момент, и это что-то забирает фокус на себя. Попробуйте найти тот виджет, который забирает фокус и после его инициализации верните фокус на ListView.

R

Дело в том, что на данный момент на странице нет ничего кроме этого ListView, а в нем лишь модель и делегат.

Не помню порядок инициализации в QML и вызова метода onCompleted, возможно, что объекты в делегате будут созданы последними в итоге.

Можете попробовать сделать задержку таймером установить фокус по сработке таймера.

R

Я нашел в чем проблема. Страница у меня эта не первая, и не единственная. И проблемы прилетали с основного файла где крутился StackView с моими страницами. И еще я реализовал разные страницы как Item, а не Page. То есть общий фон и на нем сменялись эти самые Item'ы. Перевел все на Page - и все проблемы исчезли.

Ну видите )) Было же ещё что-то кроме того ListView ))

Комментарии

Только авторизованные пользователи могут публиковать комментарии.
Пожалуйста, авторизуйтесь или зарегистрируйтесь
Ищу работу?
14,000.00 руб. - 40,000.00 руб.
Разработчик Qt
Annino, Moscow Oblast, Russia
5,000.00 руб. - 15,000.00 руб.
Дизайнер
Moskovskiy, Moscow, Russia
25,000.00 руб. - 30,000.00 руб.
Разработчик Qt/C++
Barnaul, Altai Krai, Russia

Для зарегистрированных пользователей на сайте присутствует минимальное количество рекламы

А
22 августа 2019 г. 23:24
Александр73

Qt - Тест 001. Сигналы и слоты

  • Результат:47баллов,
  • Очки рейтинга-6
21 августа 2019 г. 10:23
Андрей Ермошин

C++ - Тест 002. Константы

  • Результат:58баллов,
  • Очки рейтинга-2
21 августа 2019 г. 10:15
Андрей Ермошин

C++ - Тест 001. Первая программа и типы данных

  • Результат:86баллов,
  • Очки рейтинга6
Последние комментарии
19 августа 2019 г. 7:41
Андрей Янкович

это проблема дистрибутива, попробуйте установить через пакетный менеджер snap Суть проблемы: libQt5Core которая лежит в дистрибутиве требует версию glibc >= 2.25 у вас видимо …
b
18 августа 2019 г. 6:09
bbb116

cqtdeployer /home/aleks/CQtDeployer/bin/cqtdeployer: /lib/x86_64-linux-gnu/libc.so.6: version `GLIBC_2.25' not found (required by /home/aleks/CQtDeployer/lib/libQt5Core.so.5) linux mint …
D
17 августа 2019 г. 9:04
Damir

github ChekableTView Правой групповая смена значения при перетаскивании левой как обычно.
Сейчас обсуждают на форуме
24 августа 2019 г. 7:21
Евгений Легоцкой

Не помню, давно уже с QML не работал, по-моему, обычно пишет в консоль, что не находит файл. В любом случае какую-то ошибку в консоль выкидывает. Но если честно, если у вас проект будет ак…
БГ
24 августа 2019 г. 4:27
Брюс Глифф

Спасибо, вначале в документации было не понятно что к чему, теперь разобрался
I
21 августа 2019 г. 8:36
Intruder

Александр, мне не нужно перебирать. Вы говорите правильно, сначала я написал избыточный код просто не подумав. Задача такая, мне нужно просто переложить из QMap в атрибуты xml тега все, что там …
21 августа 2019 г. 4:46
IscanderChe

Спасибо! Получилось.
21 августа 2019 г. 3:16
nayk1982

Если Вы разрабатываете какую-то универсальную утилиту, которая вообще не привязана к логике, тогда как вариант: 1. Получить список таблиц через QSqlDatabase::tables 2. Для каждой табли…
EVILEG
О нас
Услуги
© EVILEG 2015-2019
Рекомендует хостинг TIMEWEB