МП
Jan. 22, 2019, 2:38 p.m.

Загрузка тяжелого файла через StackView

Добрый день!
При загрузке файла qml через StackView, приложение зависает и через некоторое время переходит на нужную страницу. Поставил BusyIndicator и попробовал сделать через Loader. Все работает: нажимаешь, появляется BusyIndicator, затем переходит на следующую страницу:

main.qml:

  1. ApplicationWindow {
  2. id: window
  3. visible: true
  4. width: 640
  5. height: 480
  6. Loader {
  7. anchors.fill: parent
  8. id: loader
  9. active: true
  10. asynchronous: true
  11. }
  12. BusyIndicator {
  13. id: busyIndicator
  14. anchors.centerIn: parent
  15. running: loader.status == Loader.Loading ? true : false
  16. }
  17.  

Так же есть две кнопки:

  1. Button {
  2. id:btn1
  3. text: qsTr("Назад")
  4. onClicked: stackView.pop()
  5. }
  6. Button {
  7. id:btn2
  8. text: qsTr("В начало")
  9. onClicked: stackView.push(homePage)
  10. }

и сама страница:

  1. HomePageForm {
  2. TitleRect.onAppClicked: {
  3. stackView.push(loader)
  4. loader.source = "8/HomePage.qml"
  5. }
  6. }

После нажатия кнопки btn2 из любой части программы, переходишь на homePage. Нажимаешь повторно TitleRect для перехода к файлу qml, пишет :
QML StackView: push: nothing to push

Не пойму, что я делаю не так.

2

Do you like it? Share on social networks!

22
Evgenii Legotckoi
  • Jan. 22, 2019, 2:45 p.m.

Добрый день.

Проблема в том, что StackView при выполнении метода pop уничтожает тот объект, который был в него добавлен через метод push() . В документации этот момент прописан.

Вам нужно создавать объект homePage каждый раз перед тем, как вы будете добавлять его в StackView через push.

    Я немного не понял. При нажатии кнопки btn2 я попадаю на homePage, с этим проблем нет. Однако, на homePage расположена кнопка для перехода в папку 8, на страницу HomePage.qml Так вот при нажатии на эту кнопку выдает ошибку QML StackView: push: nothing to push

      А где находится в коде stackView относительно loader?

        Они оба в main.qml

          Может всё-таки более подробный код покажите?

            МП
            • Jan. 22, 2019, 4:32 p.m.

            main.qml

            1. ApplicationWindow {
            2. id: window
            3. visible: true
            4. width: 640
            5. height: 480
            6. title: qsTr("111")
            7.  
            8. Loader {
            9. anchors.fill: parent
            10. id: loader
            11. active: true
            12. asynchronous: true
            13. // visible: status == Loader.Ready
            14. }
            15. BusyIndicator {
            16. id: busyIndicator
            17. anchors.centerIn: parent
            18. running: loader.status == Loader.Loading ? true : false
            19. }
            20.  
            21. header: ToolBar {
            22. Label {
            23. color: "#a0f649"
            24. text: stackView.currentItem.title
            25. font.pointSize: 10
            26. font.bold: true
            27. font.capitalization: Font.AllUppercase
            28. wrapMode: Text.WordWrap
            29. anchors.horizontalCenter: parent.horizontalCenter
            30. anchors.fill: parent
            31. horizontalAlignment: Text.AlignHCenter
            32. verticalAlignment: Text.AlignVCenter
            33. }
            34. }
            35.  
            36.  
            37.  
            38.  
            39. StackView {
            40. id: stackView
            41. initialItem: p0
            42. anchors.fill: parent
            43.  
            44.  
            45. Keys.onReleased: if (event.key === Qt.Key_Back && stackView.depth > 1) {
            46. stackView.pop();
            47. event.accepted = true;
            48. }
            49.  
            50. onCurrentItemChanged: {
            51.  
            52. if(stackView.currentItem.title==="000")
            53. {
            54. backButton.visible=false
            55. }
            56. else if(stackView.currentItem.title==="222")
            57. {
            58. homeButton.visible=false
            59. backButton.visible=false
            60. }
            61. else
            62. {
            63. homeButton.visible=true
            64. backButton.visible=true
            65. }
            66. }
            67. }
            68.  
            69. footer: ToolBar{
            70. id: toolBar
            71. position: ToolBar.Footer
            72. anchors.horizontalCenter: parent.horizontalCenter
            73.  
            74.  
            75. RowLayout{
            76. anchors.horizontalCenter: parent.horizontalCenter
            77. anchors.verticalCenter: parent.verticalCenter
            78. anchors.top: parent.top
            79.  
            80. HomeButton{
            81. id:homeButton
            82. }
            83.  
            84. BackButton{
            85. id:backButton
            86. }
            87. }
            88. }
            89.  
            90.  
            91. Component
            92. {
            93. id:p0
            94. Page0{
            95. }
            96. }
            97.  
            98. Component
            99. {
            100. id:homePage
            101. HomePage{
            102. }
            103. }
            104. }
              Evgenii Legotckoi
              • Jan. 22, 2019, 5:39 p.m.
              • (edited)

              Кажется знаю, в чём проблема. StackView по умолчанию должен содержать минимум один объект, если объектов больше 1, то метод pop удаляет последний компонент, если только один то он не удаляется. В вашем случае вы уже добавили loader, поэтому метод push не имеет эффекта, поскольку объект уже в стеке. При чём видимо в самом начале.

              В общем напишите код так, чтобы homePage был в стеке в самом начала на уровне 1, а остальное просто добавляйте или удаляйте. Если у вам есть ещё тяжёлые объекты, то добавьте ещё один свой loader для тех страниц и перезагружайте через тот, что уже есть. Нужно с чем-то одним определиться вам.

              Вот код, демонстрирующий проблему.

              1. import QtQuick 2.11
              2. import QtQuick.Controls 2.4
              3. import QtQuick.Window 2.11
              4.  
              5. Window {
              6. visible: true
              7. width: 50
              8. height: 50
              9. title: qsTr("Hello World")
              10.  
              11. StackView {
              12. id: stackView
              13. anchors.fill: parent
              14. }
              15.  
              16. Component {
              17. id: greenRect
              18.  
              19. Rectangle {
              20. color: "green"
              21. }
              22. }
              23.  
              24. Component {
              25. id: blueRect
              26.  
              27. Rectangle {
              28. color: "blue"
              29.  
              30. }
              31. }
              32.  
              33. Rectangle {
              34. anchors.bottom: parent.bottom
              35. anchors.left: parent.left
              36. width: 10
              37. height: 10
              38.  
              39. MouseArea {
              40. anchors.fill: parent
              41.  
              42. onClicked: {
              43. console.log(stackView.currentItem)
              44. // Если поменять на 0, то добавленный при инициализации компонент blueRect не бует удаляться, то есть pop работать не будет
              45. // Если будет 1, то будет добавлять и удаляться greenRect
              46. if (stackView.depth > 1)
              47. {
              48. console.log(stackView.pop())
              49. }
              50. else
              51. {
              52. console.log(stackView.push(greenRect))
              53. }
              54. }
              55. }
              56. }
              57.  
              58. Component.onCompleted: {
              59. stackView.push(blueRect)
              60. }
              61. }
              62.  

                Положил loader в homePage. Все работает как надо, спасибо. Теперь проблема с busyIndicator. Положил его вместе с loader. Теперь при загрузке большого файла просто темный экран. В чем может быть проблема?

                1. HomePageForm {
                2.  
                3. Loader {
                4. anchors.fill: parent
                5. id: loader
                6. active: true
                7. asynchronous: true
                8. }
                9. BusyIndicator {
                10. id: busyIndicator
                11. running: loader.status == Loader.Loading ? true : false
                12. anchors.centerIn: parent
                13. }
                14. TitleRect.onAppClicked: {
                15. stackView.push(loader)
                16. loader.source = "8/HomePage.qml"
                17. }
                18. }

                  Я бы этот индикатор оставил снаружи в окне приложения и запускал его через обработку сигнала о загрузке от лоадера.

                    Да, я примерно так и сделал. Разместил его в main.qml:

                    1. ApplicationWindow {
                    2.  
                    3. BusyIndicator {
                    4. id: busyIndicator
                    5. anchors.centerIn: parent
                    6. running: loader.status == Loader.Loading
                    7. }
                    8. }

                    Получается он запускается и работает постоянно. Или я ни от того сигнала запускаю loader?

                      Evgenii Legotckoi
                      • Jan. 22, 2019, 7:02 p.m.

                      Вот кусок документации на Loader

                      1. Loader {
                      2. id: loader
                      3. onStatusChanged: if (loader.status == Loader.Ready) console.log('Loaded')
                      4. }

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

                      Пожалуй, это должно выглядеть примерно так

                      1. Loader {
                      2. id: loader
                      3. onStatusChanged: {
                      4. if (loader.status == Loader.Loading) {
                      5. busyIndicator.running = true
                      6. } else {
                      7. busyIndicator.running = false
                      8. }
                      9. }
                      10. }
                        МП
                        • Jan. 22, 2019, 7:15 p.m.

                        Евгений, огромное спасибо, все работает!

                          МП
                          • Jan. 23, 2019, 5:51 p.m.
                          • (edited)

                          Добрый день! Еще один вопрос по работе louder. stackView.currentItem.title показывает в заголовке title страницы.

                          1. header: ToolBar {
                          2. Label {
                          3. color: "#a0f649"
                          4. text: stackView.currentItem.title
                          5. ....
                          6. }
                          7. }

                          Переходя на страницу через loader:

                          1. HomePageForm {
                          2. TitleRect.onAppClicked: {
                          3. stackView.push(loader)
                          4. loader.source = "8/HomePage.qml"
                          5. }
                          6. }

                          title HomePage.qml не показывает, выдает ошибку: Unable to assign [undefined] to QString
                          У лоудера есть свойство SetSourse. Пробовать через него?

                            Добрый день! Содержимое HomePage.qml покажите. Подозреваю, что у него или нет title или нужно alias на этот title делать.

                              МП
                              • Jan. 23, 2019, 9:21 p.m.
                              • (edited)
                              1. import QtQuick 2.10
                              2. import QtQuick.Controls 2.3
                              3. import QtQuick.Layouts 1.0
                              4.  
                              5.  
                              6. Flickable {
                              7. contentHeight: page.contentHeight+10
                              8. property string title: page.title
                              9. Page {
                              10. id:page
                              11. anchors.fill: parent
                              12. title: qsTr("Текст")
                              13.  
                              14. ColumnLayout {
                              15. anchors.fill: parent
                              16.  
                              17. MainButton{
                              18. text: qsTr("1")
                              19. onClicked: stackView.push(p1)
                              20. }
                              21. ...

                                ну понятно, в случае лоадера вы не получите доступ к title так, как делаете. Лоадер выступает в качестве промежутоного звена, тем более, что вы его пушите в StackView, чтобы достучаться до той property, нужно писать так

                                1. header: ToolBar {
                                2. Label {
                                3. color: "#a0f649"
                                4. text: stackView.currentItem.item.title
                                5. }
                                6. }

                                Как вариант, можно попробовать немного кастомизировать Loader, чтобы он отвечал вашему изначальному коду. То есть создаёте новый QML файл - CustomLoader.qml

                                CustomLoader.qml

                                А в нём уже сделать property alias и заменить таким кастомным лоадером стандартный.

                                1. Loader {
                                2. property alias title: item.title
                                3. }

                                Тогда по идее Loader будет реализовывать ваш обобщённый интерфейс и следующий код должен будет работать.

                                1. header: ToolBar {
                                2. Label {
                                3. color: "#a0f649"
                                4. text: stackView.currentItem.title
                                5. }
                                6. }
                                  МП
                                  • Jan. 23, 2019, 10:15 p.m.

                                  Ошибка:
                                  qrc:/CustomLoader.qml:4 Invalid alias reference. Unable to find id "item"

                                  1. import QtQuick 2.0
                                  2.  
                                  3. Loader{
                                  4. property alias title: item.title
                                  5. }
                                  6.  
                                    Evgenii Legotckoi
                                    • Jan. 24, 2019, 2:36 a.m.

                                    мда.. я как-то не подумал, что QML делает alias только по id, хотя логично.. а item это не id, а property.

                                    Ну тогда нужно слушать изменение currentItem у StackView и по его изменению реализовывать специфичную логику. Выглядеть может так.

                                    1. Connections {
                                    2. target: stackView
                                    3. onCurrentItemChanged: {
                                    4. labelTitle.text = stackView.currentItem.getTitle())
                                    5. }
                                    6. }

                                    Прокомментирую код выше.

                                    • labelTitle - это id вашего лейбла, в который вы устанавливали текст так
                                    1. header: ToolBar {
                                    2. Label {
                                    3. id: labelTitle
                                    4. color: "#a0f649"
                                    5. }
                                    6. }
                                    • stackView.currentItem.getTitle() - это уже поинтереснее, в каждой странице, которую вы добавляете в stackView, нужно будет определить функцию которая будет возвращать title
                                    1. Loader {
                                    2. id: root
                                    3.  
                                    4. function getTitle()
                                    5. {
                                    6. return root.item.title;
                                    7. }
                                    8. }

                                    Ну и для каждой страницы нужно реализовывать эту функцию соответсвующим образом, чтобы был единообразный интерфейс QML объекта.

                                      МП
                                      • Jan. 24, 2019, 1:15 p.m.

                                      Интрересно. А если сделать вообще без лоудера, через stackView загружать страницу:

                                      1. TitleRect.onAppClicked: {
                                      2. stackView.push("8/HomePage.qml")
                                      3. }

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

                                        Evgenii Legotckoi
                                        • Jan. 24, 2019, 2:35 p.m.
                                        • (edited)

                                        По моему опыту без лоадера чисто на StackView либо не получится, либо будет с очень большими костылями. StackView служит для организации структуры контента, но не для асинхронной загрузки.

                                        Но есть немного иной вариант, вы довольно близко находитесь со своей идеей в этом последнем коде

                                        1. TitleRect.onAppClicked: {
                                        2. stackView.push("8/HomePage.qml")
                                        3. }

                                        Сделайте так, но в HomePage поместите loader и busyIndicator. Внутрь лоадера поместите тяжёлую часть файла. Таким образом при помещении файла HomePage.qml внутрь StackView, часть контента, которая лёгкая, отобразится сразу, а тяжёлую часть начните загружать через лоадер в обработчике Component.onCompleted

                                        HomePage.qml

                                        1. Item {
                                        2. id: root
                                        3. title: "Home"
                                        4.  
                                        5. Loader {
                                        6. anchors.fill: parent
                                        7. id: loader
                                        8. active: true
                                        9. asynchronous: true
                                        10. }
                                        11.  
                                        12. BusyIndicator {
                                        13. id: busyIndicator
                                        14. anchors.centerIn: parent
                                        15. running: loader.status == Loader.Loading ? true : false
                                        16. }
                                        17.  
                                        18. Component.onCompleted: {
                                        19. loader.source = "8/HomePageHardPart.qml"
                                        20. }
                                        21. }
                                          МП
                                          • Jan. 25, 2019, 12:53 p.m.

                                          Попробуем идти в другую сторону.

                                          1. HomePageForm {
                                          2. TitleRect.onAppClicked: {
                                          3. stackView.push(loader)
                                          4. loader.source = "8/HomePage.qml"
                                          5. }
                                          6. }

                                          Возможно не засовывать в stackView loader, а при нажатии на TitleRect сразу обращаться к лоудеру? Файл в папке 8, около 800 страниц. Получается при нажатии сначала все эти 800 страниц скачиваются в кеш, а только потом осуществляется переход на HomePage. Затем переходы занимают 1-2 сек и внутри, и с главной страницы приложения. Конечно это неудобно. На слабых устройствах загрузка занимает порядка 2 минут.

                                            какие 800 страниц? я допустим понятия не имею, что вы туда загружаете.

                                            Что касается лоадера и stackView. Сделать-то это можно, только не понятно какой вы результат ожидаете. Если вам нужны странички в стиле как у Андроида, то нужен StackView. Это наиболее удобно в данном случае.

                                              Comments

                                              Only authorized users can post comments.
                                              Please, Log in or Sign up
                                              • Last comments
                                              • AK
                                                April 1, 2025, 11:41 a.m.
                                                Добрый день. В данный момент работаю над проектом, где необходимо выводить звук из программы в определенное аудиоустройство (колонки, наушники, виртуальный кабель и т.д). Пишу на Qt5.12.12 поско…
                                              • Evgenii Legotckoi
                                                March 9, 2025, 9:02 p.m.
                                                К сожалению, я этого подсказать не могу, поскольку у меня нет необходимости в обходе блокировок и т.д. Поэтому я и не задавался решением этой проблемы. Ну выглядит так, что вам действитель…
                                              • VP
                                                March 9, 2025, 4:14 p.m.
                                                Здравствуйте! Я устанавливал Qt6 из исходников а также Qt Creator по отдельности. Все компоненты, связанные с разработкой для Android, установлены. Кроме одного... Когда пытаюсь скомпилиров…
                                              • ИМ
                                                Nov. 22, 2024, 9:51 p.m.
                                                Добрый вечер Евгений! Я сделал себе авторизацию аналогичную вашей, все работает, кроме возврата к предидущей странице. Редеректит всегда на главную, хотя в логах сервера вижу запросы на правильн…
                                              • Evgenii Legotckoi
                                                Oct. 31, 2024, 11:37 p.m.
                                                Добрый день. Да, можно. Либо через такие же плагины, либо с постобработкой через python библиотеку Beautiful Soup