Evgenii Legotckoi
26 жовтня 2015 р. 23:25

QML - Урок 007. ListView. Динамічне створення та видалення елементів

У статті про динамічне створення віджетів у Qt я вже розповідав, як створювати та видаляти кнопки динамічно, а також як з ними взаємодіяти. І там використовувався Vertical Layout , а у випадку з Qml ми зможемо використовувати ListView Qml, як аналогічно використовується при програмуванні в Java під Android. Що, до речі кажучи, також можна застосувати при програмуванні на Qt під Android.

У статті про динамічне створення віджетів використовувалися, наприклад, об'єкти класу QButton. /post/64/). Але кнопки розміщуватимуться в 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, а значення в текстовому полі стирається.

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

  1. import QtQuick 2.5
  2. import QtQuick.Controls 1.4
  3.  
  4. ApplicationWindow {
  5. visible: true
  6. width: 640
  7. height: 480
  8. title: qsTr("Hello World")
  9.  
  10. /* Номер создаваемой кнопки, для её визуальной идентификации
  11. * при демонстрации проекта
  12. */
  13. property int number: 0
  14.  
  15. /* Строка с полем, где отображается индекс нажатой динамической кнопки,
  16. * кнопкой для создания динамических кнопок,
  17. * и кнопкой для удаления динамических кнопок по индексу
  18. * */
  19. Row {
  20. id: row
  21. // Задаём размеры строки и прибиваем к верхней части окна приложения
  22. height: 50
  23. anchors.top: parent.top
  24. anchors.left: parent.left
  25. anchors.right: parent.right
  26.  
  27. // Задаём размещение поля с индексом кнопки
  28. Rectangle {
  29. width: (parent.width / 5)
  30. height: 50
  31.  
  32. // Устанавливаем текстовое поле для размещения индекса кнопки
  33. Text {
  34. id: textIndex
  35. anchors.fill: parent
  36. text: ""
  37. verticalAlignment: Text.AlignVCenter
  38. horizontalAlignment: Text.AlignHCenter
  39. }
  40. }
  41.  
  42. // Кнопка для создания динамических кнопок
  43. Button {
  44. id: button1
  45. text: qsTr("Create Button")
  46. width: (parent.width / 5)*2
  47. height: 50
  48.  
  49. /* По клику по кнопке добавляем в model ListView
  50. * объект, с заданными параметрами
  51. * */
  52. onClicked: {
  53. listModel.append({idshnik: "Button " + (++number)})
  54.  
  55. }
  56. }
  57.  
  58. // Кнопка для удаления динамических кнопок
  59. Button {
  60. id: button2
  61. text: qsTr("Delete Button")
  62. width: (parent.width / 5)*2
  63. height: 50
  64.  
  65. // Удаляем кнопку по её индексу в ListView
  66. onClicked: {
  67. if(textIndex.text != ""){
  68. listModel.remove(textIndex.text)
  69. textIndex.text = "" // Обнуляем текстовое поле с индексом
  70. }
  71. }
  72. }
  73. }
  74.  
  75. // ListView для представления данных в виде списка
  76. ListView {
  77. id: listView1
  78. // Размещаем его в оставшейся части окна приложения
  79. anchors.top: row.bottom
  80. anchors.bottom: parent.bottom
  81. anchors.left: parent.left
  82. anchors.right: parent.right
  83.  
  84. /* в данном свойстве задаём вёрстку одного объекта
  85. * который будем отображать в списке в качестве одного элемента списка
  86. * */
  87. delegate: Item {
  88. id: item
  89. anchors.left: parent.left
  90. anchors.right: parent.right
  91. height: 40
  92.  
  93. // В данном элементе будет находиться одна кнопка
  94. Button {
  95. anchors.fill: parent
  96. anchors.margins: 5
  97.  
  98. /* самое интересное в данном объекте
  99. * задаём свойству text переменную, по имени которой будем задавать
  100. * свойства элемента
  101. * */
  102. text: idshnik
  103.  
  104. // По клику по кнопке отдаём в текстовое поле индекс элемента в ListView
  105. onClicked: {
  106. textIndex.text = index
  107. }
  108. }
  109. }
  110.  
  111. // Сама модель, в которой будут содержаться все элементы
  112. model: ListModel {
  113. id: listModel // задаём ей id для обращения
  114. }
  115. }
  116. }

Підсумок

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

Відеоурок

Вам це подобається? Поділіться в соціальних мережах!

L
  • 14 червня 2017 р. 15:09

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


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

Не хотелось бы повторяться. В статье по сигналам и слотам в 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
  • 15 червня 2017 р. 14:21

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

АК
  • 27 жовтня 2017 р. 17:08

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

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

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

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

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

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

АК
  • 10 січня 2018 р. 17:00

Не могу понять, как мы добавляем новый элемент в 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
  • 27 листопада 2018 р. 14:21

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

  1. Component.onCompleted:
  2. {
  3. listView.forceActiveFocus();
  4. }

То все равно

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

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

Evgenii Legotckoi
  • 27 листопада 2018 р. 14:32

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

R
  • 27 листопада 2018 р. 16:26

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

Evgenii Legotckoi
  • 28 листопада 2018 р. 14:06

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

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

R
  • 29 листопада 2018 р. 09:48

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

Evgenii Legotckoi
  • 29 листопада 2018 р. 14:47

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

juvf
  • 03 листопада 2022 р. 14:20

Добрый день.
Очень полезная статья. Спасибо.

Вопрос такой: 1) нужно "взять" кнопку 2 пальцем (прикаснулись пальцем к кнопке 2, держим, через 2-3 кнопка оторвалась от ListView) и перетащить её между 7 и 8. Как такое в Qml реализовать? куда копать?
2) можно удалить кнопку "викинув" её? Т.е. горизонтально чиркнули по кнопке слева направо, кнопка визуально улетела и соседние кнопки сомкнулись. Наподобе как в андроиде удаляются уведомления.

Evgenii Legotckoi
  • 12 грудня 2022 р. 14:51

Давно уже не работал с QML. Qt поддерживает во вторых котролах Material Design, но не знаю, насколько он реализует данный функционал. Felgo - QML based фреймворк реализует подобные вещи.
Но что касается drag and drop фунционала, то тут нужно создавать плавающий rectangle, который будет летать за пальцем, правильно высчитывать все позиции всех элементов и т.д. В общем достаточное количество кода будет.

Коментарі

Only authorized users can post comments.
Please, Log in or Sign up
  • Останні коментарі
  • Evgenii Legotckoi
    16 квітня 2025 р. 17:08
    Благодарю за отзыв. И вам желаю всяческих успехов!
  • IscanderChe
    12 квітня 2025 р. 17:12
    Добрый день. Спасибо Вам за этот проект и отдельно за ответы на форуме, которые мне очень помогли в некоммерческих пет-проектах. Профессиональным программистом я так и не стал, но узнал мно…
  • AK
    01 квітня 2025 р. 11:41
    Добрый день. В данный момент работаю над проектом, где необходимо выводить звук из программы в определенное аудиоустройство (колонки, наушники, виртуальный кабель и т.д). Пишу на Qt5.12.12 поско…
  • Evgenii Legotckoi
    09 березня 2025 р. 21:02
    К сожалению, я этого подсказать не могу, поскольку у меня нет необходимости в обходе блокировок и т.д. Поэтому я и не задавался решением этой проблемы. Ну выглядит так, что вам действитель…
  • VP
    09 березня 2025 р. 16:14
    Здравствуйте! Я устанавливал Qt6 из исходников а также Qt Creator по отдельности. Все компоненты, связанные с разработкой для Android, установлены. Кроме одного... Когда пытаюсь скомпилиров…