Privacy policyContactsAbout siteOpinionsGitHubDonate
© EVILEG 2015-2018
Recommend hosting
TIMEWEB
МП
Jan. 22, 2019, 8:38 a.m.

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

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

main.qml:

ApplicationWindow {
    id: window
    visible: true
    width: 640
    height: 480     
    Loader {
        anchors.fill: parent
        id: loader
        active: true
        asynchronous: true
       }
    BusyIndicator {
        id: busyIndicator
        anchors.centerIn: parent        
        running: loader.status == Loader.Loading ? true : false
       }

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

Button {  
    id:btn1
    text: qsTr("Назад")
    onClicked: stackView.pop()
}
Button {   
    id:btn2
    text: qsTr("В начало")
    onClicked: stackView.push(homePage)
}

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

HomePageForm {
TitleRect.onAppClicked: {
        stackView.push(loader)
        loader.source = "8/HomePage.qml"
        }
}

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

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

22

Добрый день.

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

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

0
МП

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

0

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

0
МП

Они оба в main.qml

0

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

0
МП

main.qml

ApplicationWindow {
    id: window
    visible: true
    width: 640
    height: 480
    title: qsTr("111")

    Loader {
        anchors.fill: parent
        id: loader
        active: true
        asynchronous: true
//        visible: status == Loader.Ready
       }
    BusyIndicator {
        id: busyIndicator
        anchors.centerIn: parent        
        running: loader.status == Loader.Loading ? true : false
       }

    header: ToolBar {
        Label {
            color: "#a0f649"
            text: stackView.currentItem.title
            font.pointSize: 10
            font.bold: true
            font.capitalization: Font.AllUppercase
            wrapMode: Text.WordWrap
            anchors.horizontalCenter: parent.horizontalCenter
            anchors.fill: parent
            horizontalAlignment: Text.AlignHCenter
            verticalAlignment: Text.AlignVCenter
        }
    }




    StackView {
        id: stackView
        initialItem: p0
        anchors.fill: parent        


        Keys.onReleased: if (event.key === Qt.Key_Back && stackView.depth > 1) {
                             stackView.pop();
                             event.accepted = true;
                         }

        onCurrentItemChanged:  {

            if(stackView.currentItem.title==="000")
            {
                backButton.visible=false
            }
            else if(stackView.currentItem.title==="222")
            {
                homeButton.visible=false
                backButton.visible=false
            }
            else
            {
                homeButton.visible=true
                backButton.visible=true
            }
        }
    }

    footer: ToolBar{
        id: toolBar
        position: ToolBar.Footer
        anchors.horizontalCenter: parent.horizontalCenter


        RowLayout{
            anchors.horizontalCenter: parent.horizontalCenter
            anchors.verticalCenter: parent.verticalCenter
            anchors.top: parent.top

            HomeButton{
                id:homeButton
            }

            BackButton{
                id:backButton
            }
        }
    }


    Component
    {
        id:p0
        Page0{
        }
    }

    Component
    {
        id:homePage
        HomePage{
        }
    }
}
0

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

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

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

import QtQuick 2.11
import QtQuick.Controls 2.4
import QtQuick.Window 2.11

Window {
    visible: true
    width: 50
    height: 50
    title: qsTr("Hello World")

    StackView {
        id: stackView
        anchors.fill: parent
    }

    Component {
        id: greenRect

        Rectangle {
            color: "green"
        }
    }

    Component {
        id: blueRect

        Rectangle {
            color: "blue"

        }
    }

    Rectangle {
        anchors.bottom: parent.bottom
        anchors.left: parent.left
        width: 10
        height: 10

        MouseArea {
            anchors.fill: parent

            onClicked: {
                console.log(stackView.currentItem)
                // Если поменять на 0, то добавленный при инициализации компонент blueRect не бует удаляться, то есть pop работать не будет
                // Если будет 1, то будет добавлять и удаляться greenRect
                if (stackView.depth > 1)
                {
                    console.log(stackView.pop())
                }
                else
                {
                    console.log(stackView.push(greenRect))
                }
            }
        }
    }

    Component.onCompleted: {
        stackView.push(blueRect)
    }
}

0
МП

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

HomePageForm {

    Loader {
        anchors.fill: parent
        id: loader
        active: true
        asynchronous: true
    }
    BusyIndicator {
        id: busyIndicator
        running: loader.status == Loader.Loading ? true : false
        anchors.centerIn: parent
    }
    TitleRect.onAppClicked: {
        stackView.push(loader)
        loader.source = "8/HomePage.qml"
        }
}    
0

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

0
МП

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

ApplicationWindow {   

    BusyIndicator {
        id: busyIndicator
        anchors.centerIn: parent
        running: loader.status == Loader.Loading
    }
}    

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

0

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

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

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

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

Loader {
    id: loader
    onStatusChanged: {
        if (loader.status == Loader.Loading) {
            busyIndicator.running = true
        } else {
            busyIndicator.running = false
        }
    }
}
0
МП

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

1
МП

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

header: ToolBar {
        Label {
            color: "#a0f649"
            text: stackView.currentItem.title
            ....
            }
        }

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

HomePageForm {   
    TitleRect.onAppClicked: {
        stackView.push(loader)
        loader.source = "8/HomePage.qml"
        }
}    

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

0

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

0
МП
import QtQuick 2.10
import QtQuick.Controls 2.3
import QtQuick.Layouts 1.0


Flickable {
    contentHeight: page.contentHeight+10
    property string title: page.title
    Page {
        id:page
        anchors.fill: parent
        title: qsTr("Текст")

        ColumnLayout {
            anchors.fill: parent

            MainButton{
                text: qsTr("1")
                onClicked: stackView.push(p1)
            }
            ...
0

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

header: ToolBar {
    Label {
      color: "#a0f649"
      text: stackView.currentItem.item.title
    }
}

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

CustomLoader.qml

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

Loader {
    property alias title: item.title
}

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

header: ToolBar {
    Label {
      color: "#a0f649"
      text: stackView.currentItem.title
    }
}
0
МП

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

import QtQuick 2.0

Loader{
    property alias title: item.title
}

0

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

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

    Connections {
        target: stackView   
        onCurrentItemChanged: {    
            labelTitle.text = stackView.currentItem.getTitle())
        }
    }

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

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

    function getTitle()
    {
      return root.item.title;
    }
  }

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

0
МП

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

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

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

0

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

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

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

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

HomePage.qml

Item {
    id: root
    title: "Home"

    Loader {
        anchors.fill: parent
        id: loader
        active: true
        asynchronous: true
    }

    BusyIndicator {
        id: busyIndicator
        anchors.centerIn: parent        
        running: loader.status == Loader.Loading ? true : false
    }

    Component.onCompleted: {
        loader.source = "8/HomePageHardPart.qml"
    }
}
0
МП

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

HomePageForm {   
    TitleRect.onAppClicked: {
        stackView.push(loader)
        loader.source = "8/HomePage.qml"
        }
}    

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

0

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

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

1

Comments

Only authorized users can post comments.
Please, Log in or Sign up
Last comments
Feb. 21, 2019, 12:51 p.m.
Евгений Легоцкой

Иногда CMake приходится перезапускать начисто, не обновляет кэш
R
Feb. 21, 2019, 12:29 p.m.
RandyGallup

Я указал данные строки, т.к. без них у меня вылетала следующая ошибка: By not providing "FindQt5Core.cmake" in CMAKE_MODULE_PATH this project has asked CMake to find a package configurat...
Feb. 21, 2019, 12:08 p.m.
BlinCT

Вот атк выглядит мой проектник, посмотрите его. cmake_minimum_required(VERSION 3.6)project(projecttimer)set(CMAKE_CXX_STANDARD 11)set(CMAKE_AUTOMOC ON)set(CMAKE_AUTORCC ON)find_packa...
Feb. 21, 2019, 12:04 p.m.
BlinCT

Смотрите, если вы используете глобально для проекта -DCMAKE_PREFIX_PATH= то вам не надо уже указывать вот эти строкиset(Qt5Core_DIR "C:/Qt/5.12.1/mingw73_64/lib/cmake/Qt5Core")set(Qt5Gui_DIR...
R
Feb. 21, 2019, 11:54 a.m.
RandyGallup

Даже не запускается. main.cpp у меня точно такой же, как в статье. CMakeLists.txt пришлось немного подправить (прикрепил ниже), т.к. не находились некоторые файлы. cmake_minimum_requi...
Now discuss on the forum
Feb. 21, 2019, 8:58 a.m.
Евгений Легоцкой

Ну у меня координаты передавались в зависимости от положения курсора мыши, а в вам по сути нужно будет аналогичным способом посылать даннные из полей ввода. Так что здесь скорее интерфес...
Feb. 20, 2019, 9:55 p.m.
Евгений Легоцкой

Не до конца понимаю сути вопроса, наверное, нужно увидеть программный код и попытку его применения, но к методам базового класса можно обращаться в наследованном классе через вызов по имени ба...
MU
Feb. 20, 2019, 3:06 p.m.
Maciej Urmański

Yes, ok I have solution! Thank you for directing me about annotate.:) Solution is: users_in = User.objects.filter(joined_users__goal=goal, joined_users__joined=True)
Feb. 20, 2019, 2:40 p.m.
Евгений Легоцкой

Думаю, что ещё можно переопределить mouseReleaseEvent(QMouseEvent* event) у QTableView, который содержит модель и немного поиграться с индексом. Если это индекс, который соответству...
Feb. 20, 2019, 10:34 a.m.
Евгений Легоцкой

Да, так тоже можно. Единственный момент в том, что lupdate не всегда понимает, к какому контексту это дело относится, и может запихать в левый контекст. В небольшом проекте это не критич...
Join us in social networks

For registered users on the site there is a minimum amount of advertising