Search code examples
qmlqt5qtquickcontrolsqtquickcontrols2

Why does my custom TabBar lose it's content children in this example?


I am trying to create a TabBar has preview images of the connected layout's children. However after adding several tabs (the exact number depends on the number of elements within the tabs) QML throws an error and the PreviewTabBar loses all its content children.

The following is a minimal working example:

My main.qml:

import QtQuick 2.8
import QtQuick.Controls 2.1
import QtQuick.Layouts 1.3

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

    StackLayout {
        id: swipeView
        anchors.fill: parent
        currentIndex: tabBar.currentIndex
    }

    Timer {
        interval: 50; running: true; repeat: true
        onTriggered: addTab()
    }

    function addTab() {
        console.log("add Tab")
        var component = Qt.createComponent("qrc:/TabContent.qml")
        if(component.status !== Component.Ready)
            console.log("component not ready")
        var item = component.createObject(swipeView)
        tabBar.addTab(item)
        tabBar.currentIndex = tabBar.contentChildren.length - 1
        console.log("current index " + tabBar.currentIndex)
    }


    header: PreviewTabBar {
        id: tabBar
        currentIndex: swipeView.currentIndex
    }
}

The PreviewTabBar.qml containing previews of the content:

import QtQuick 2.8
import QtQuick.Controls 2.1

TabBar {
    signal closeCurrentTab

    clip: true
    background: Rectangle {
        color: "white"
    }

    function addTab(imageSource) {
        var component = Qt.createComponent("PreviewTabButton.qml")
        if(component.status !== Component.Ready)
            console.log("component not ready")
        else {
            var item = component.createObject()
            item.setSource(imageSource)
            addItem(item)
        }
    }

    function closeTab() {
        console.log("closeTab")
        closeCurrentTab()
    }
}

and last but not least the PreviewButton.qml using a ShaderEffectSource to render the preview:

import QtQuick 2.8
import QtQuick.Controls 2.1

TabButton {
    height: 80
    width: 140

    function setSource(source) {
        preview.sourceItem = source
    }

    contentItem: ShaderEffectSource {
        id: preview
    }
}

This example gets to about 80 tabs on my machine, after that the PreviewTabBar loses all its children (not so the StackLayout). However in the real life example with more complicated tab contents I only get up to around 8 tabs. What could I be doing wrong?

Here is the relevant part of the application output:

qml: current index 99
qml: add Tab
file:///usr/lib/qt/qml/QtQuick/Controls.2/TabButton.qml:65: TypeError:     Cannot read property of null
qml: current index 100
qml: add Tab
qml: current index 1

I tried finishing the dynamic component creation in a callback as described here:

http://doc.qt.io/qt-5/qtqml-javascript-dynamicobjectcreation.html#creating-a-component-dynamically

however that brough no improvement.

Here is a link to the example project:

https://www.file-upload.net/download-12341284/tabtestshader.zip.html


Solution

  • The most probable cause is line 17 in PreviewTabBar.qml which reads:

    var item = component.createObject()
    

    As you have no parent set in the createObject()-function the GarbageCollector tends to run wild, and delete your object, even if it is still referenced.
    Though not documented that way, you should always pass a parent object, to make sure it survives the GC.

    A more stable way would be to generate the Tabs from a model, and add the according model entries in the addTab-functions.

    As a little remark on the side: You create a new component everytime you call one of your addTab-functions. Why don't you declare it once like

     Component {
         id: myComp1
         ...
     }
    

    and create the objects from that?