Adding dynamically to qml tabbar and stacklayout - qt

I'm trying to create a tabbed pages in qml. I used TabBar associated with StackLayout:
TabBar {
id: bar
width: parent.width
TabButton {
text: qsTr("Home")
}
TabButton {
text: qsTr("Discover")
}
TabButton {
text: qsTr("Activity")
}
}
StackLayout {
width: parent.width
currentIndex: bar.currentIndex
Item {
id: homeTab
}
Item {
id: discoverTab
}
Item {
id: activityTab
}
}
A new tabButton can be easily added by this code dynamically:
var tab = tabButton.createObject(TTabButton, {text: tabName});
bar.addItem(tab);
which TTabButton is a separate file consisting TabButton item. But I can't find any ways to add a new page to StackLayout. It seems it is supposed to be static. So my question is how to have dynamic tab-paged in qml?

You can add to the children of StackLayout:
var item = stackItem.createObject(null, {id: "tabName"})
layout.children.push(item)
Where stackItem is the Component of those items you add to your StackLayout layout.

You can manipulate the children (or data) property of the StackLayout directly as suggested, but potentially a better idea is to use a Repeater in conjunction with an ObjectModel:
StackLayout
{
id: stackLayout
currentIndex: bar.currentIndex
Repeater
{
model: ObjectModel
{
id: container
}
}
}
This way you get the much richer and easier API of ObjectModel:
let tabPage = tabPageComponent.createObject(stackLayout);
container.append(tabPage);
let tabPage2 = tabPageComponent.createObject(stackLayout);
container.insert(0, tabPage2);
container.move(1, 0);
// etc.
One important detail here is that when removing a tab, it's necessary to manually destroy() it, since the Repeater/ObjectModel only manages the hierarchy, not lifetime.
let tabPage = container.get(0);
container.remove(0);
tabPage.destroy();

Related

How to reuse a nested listview's delegate?

I have a nested listview structure where the delegate listview will contain another listview. I would like the nested listview's delegate to refer to itself because the nested listview will contain the same type of item as itself, but this doesn't appear to work.
Component {
id: subSequenceComponent
ItemDelegate {
id: subSequenceItemDelegate
property var id: edit.id
ColumnLayout {
Text{
text: edit.name
}
ListView {
width: 180; height: 200
model: items.subModelFromId(subSequenceItemDelegate.id)
delegate: subSequenceComponent
}
}
}
}
This works:
Component {
id: subSequenceComponent
ItemDelegate {
id: subSequenceItemDelegate
property var id: edit.id
ColumnLayout {
Text{
text: edit.name
}
ListView {
width: 180; height: 200
model: items.subModelFromId(subSequenceItemDelegate.id)
delegate: Text{
text: edit.name
}
}
}
}
}
Is there a way to reuse the same delegate you are a part of?
It may be connected to this bug. Basically, QML has some checks that are supposed to prevent accidental infinite recursions, but they are not particularly well implemented, and trigger false positives even for scenario where nesting is intended and there is no danger of infinite recursion.
If that is the case, then you can trick that check by using an additional Loader that will load the component from a string, which will not catch the nesting recursion.

QML fill ColumnLayout with createObject in a Component

i want to add a Component dynamically to an ColumnLayout in a TabView/Tab. But i've not found a possibility to do this. The problem is that i have no correct parent reference to my ColumnLayout for the createObject call.
Because the Tab dynamically loads a Component I've encapsulated the ColumnLayout in a Component.
In the QML Debugger i can solve the following path: objForm.tabView.tabStatus.tabStatusLoader.colLayout, but i cant use this as an correct parent.
It seems not be in the scope.
TypeError: Cannot read property 'tabStatus' of undefined
ObjForm.ui.qml
Item {
id: item1
width: 400
height: 400
property QtObject object
property Component compLayout
TabView {
id: tabView
anchors.fill: parent
Tab {
id: tabStatus
title: "status"
Loader {
id: tabStatusLoader
sourceComponent: compLayout
}
}
}
}
ObjForm.qml
ObjectViewForm {
id: objForm
anchors.fill: parent
object: someObj
compLayout: Component {
id: layoutComp
ColumnLayout {
id: colLayout
spacing: 2
}
}
onObjectChanged: {
// Here i want to add the someLabel Component to the ColumnLayout
someLabel.createObject(*PARENT*)
}
Component {
id: someLabel
Row {
property string text
property string label
spacing: 5
Label {
text: parent.label
}
Label {
text: parent.text
}
}
}
Does anyone know how to solve this or can make a better suggestion?
ok I've found a solution by myself. Instead to include the ColumnLayout into the Component, i've pulled it out and made an alias property to publish it. With this it was possible to add objects to my ColumnLayout. But the ColumnLayout got the wrong parent(objForm) instead of the Component.
A Component cant be a parent because a QQuickItem* is expected instead of a QObject* and additional can't include properties except for 'id'. Therefore a dummy Item was needed.
To reparent the ColumnLayout the Item needs a Component.onCompleted function where the parent will be set.
ObjectViewForm {
id: objForm
anchors.fill: parent
object: someObj
property alias componentLayout: colLayout
compLayout: Component {
id: layoutComp
Item {
id: dummy
Component.onCompleted: {
colLayout.parent = dummy
}
}
}
ColumnLayout {
id: colLayout
spacing: 2
}

Fill TabView from Pages' titles of corresponding SwipeView

Straitforward approach to create a multiple separate visible pages with easy navigation is to use SwipeView and TabBar together:
import QtQuick 2.9
import QtQuick.Controls 2.2
import QtQuick.Layouts 1.3
Page {
id: page
header: TabBar {
id: tabBar
currentIndex: swipeView.currentIndex
TabButton {
text: qsTr("Page1")
}
TabButton {
text: qsTr("Page2")
}
}
SwipeView {
id: swipeView
width: page.width
height: page.height
currentIndex: tabBar.currentIndex
Page {
width: swipeView.width
height: swipeView.height
title: qsTr("Page1")
Pane {
anchors.fill: parent
}
}
Page {
width: swipeView.width
height: swipeView.height
title: qsTr("Page2")
Pane {
anchors.fill: parent
}
}
}
}
There is title property in the Page component. It is not visible in the above case, but it is proper place to store the tab title on my mind. Can I generate (say, using Repeater) a contents of TabBar using some properties of SwipeView pages (namely .title) to fill TabButton.text?
I looked through documentation, but can't find a property of SwipeView, which allows me to get access to its pages by means of indices (e.g. pages[index]).
Qt Quick is great! I found the answer really quickly:
Repeater {
model: swipeView.count
TabButton {
text: swipeView.contentChildren[index].title
}
}
or even simplier
Repeater {
model: swipeView.contentChildren
TabButton {
text: modelData.title
}
}

Page Navigation in QML

I'm trying to implement the following GUI in QML and having trouble understanding how to properly navigate through different pages of the application.
There are 3 buttons in the main menu. When the user clicks on the 'actor' button the UI switches to 'actor view' where the user can toggle between Thumbnail view and List View. When the user clicks on one of the actors the UI switches to Actor Detail view: A view that has a movie view 'nested in it' which lists all the actors movies.
I'm trying to implement this using StackView.
So my StackView lives in the main menu screen (main.qml) when the user clicks one of the buttons the onClicked event pushes the correct view on to the stack.
ActorsView.qml consists of an internal StackView (Most likely a bad idea) and 2 buttons that switch between Thumb and Detail view. This is done by pushing either Thumb or Detail view onto the local stack.
DetailView.qml and ThumbView.qml function exactly the same though look different. Here is where I ran into trouble. I want the main view to be notified when a click event occurs in either Detail or Thumb view. So that it could (based on the event passed information) know what view push onto the main stack. For example when the user clicks on Actor1, the main menu could push 'actor detail view for actor 1' onto the stack.
Sadly I don't know how to 'catch' events that are firing in nested components in the parent element.
I've started playing around with QML and QT just a few weeks ago, and would be happy to hear that my approach is all wrong and that there is a much better way to achieve what I want. Sadly this is the only viable option I found this far.
main.qml:
ApplicationWindow {
title: qsTr("Hello World")
width: 1280
height: 720
visible: true
id: mainWindow
Component{
id: homeScreen
Rectangle{
height: 500
width: 500
color:"blue"
anchors.centerIn: mainWindow
Text {
anchors.centerIn: parent
text: qsTr("Home")
font.pixelSize: 40
}
}
}
Component{
id: actorsView
ActorsView{
view: stack
}
}
Component{
id: moviesView
MoviesView{
view: stack
}
}
ColumnLayout{
RowLayout{
Layout.fillWidth: true
Button{
text: "Back"
onClicked: stack.pop()
}
Button{
text: "actor view"
onClicked: stack.push(actorView)
}
Button{
text: "movie view"
onClicked: stack.push(moviesView)
}
}
StackView {
id: stack
initialItem: homeScreen
Layout.fillHeight: true
Layout.fillWidth: true
}
}
}
ActorsView.qml:
Item {
property StackView view
Component {
id: actorDetailView
DetailView {
name: "actorDetailView"
text: "Actor"
}
}
Component {
id: actorThumbView
ThumbView {
name: "actorThumbView"
text: "Actor"
}
}
ColumnLayout {
RowLayout {
Text {
text: "Actor view"
Layout.fillWidth: true
horizontalAlignment: Text.AlignHCenter
}
Button {
text: "Detail"
onClicked: internalStack.push(actorDetailView)
}
Button {
text: "Thumb"
onClicked: internalStack.push(actorThumbView)
}
Button {
text: "back"
onClicked: internalStack.pop()
}
Button {
text: "depth: " + internalStack.depth
}
}
StackView {
id: internalStack
initialItem: {
console.log(internalStack.depth)
internalStack.initialItem = actorThumbView
}
Layout.fillHeight: true
Layout.fillWidth: true
}
}
}
ThumbView.qml:
Item {
property string name: "thumbView"
property string text
property int counter: 0
id:thumbView
signal thumbPressed (string pressedName)
GridLayout {
columnSpacing: 10
rowSpacing: 10
width: parent.width
Repeater {
model: 16
Rectangle {
width: 200
height: 300
color: "grey"
Text {
id: lable
text: text
anchors.centerIn: parent
}
MouseArea {
anchors.fill: parent
onClicked: {
var tag = lable.text
console.log("You have clicked " + tag)
thumbView.thumbPressed(tag)
}
}
Component.onCompleted: {
counter = counter + 1
lable.text = text + " " + counter
}
}
}
}
}
That's actually a common approach to structure a QML application, so no it's not all bad ! Nested StackViews are a powerful way to manage sub-content of a page, but surely add a level in your app structure. It's made easier by creating your own Page item, redefining the navigation and interaction as you wish.
There's different ways to handle signal in nested components. The easiest: call an identified item up in hierarchy. Local and parent elements in QML are accessible from their id directly, even if those are not in the same QML file. Which allowThis of course has the drawback of inducing coupling between your pages or components and the rest of your application.
ApplicationWindow {
id: mainWindow
function pushPage(page) {
stack.push(page)
}
function showActor(id) {
...
}
// ...
}
In your page simply...
MouseArea {
onClicked: {
mainWindow.showActor(index)
}
}
To achieve something more modular, you can rely StackView currentItem, signals, Connections and Binding elements to name a few, or implement an interface in QML and/or C++ to manage your navigation.
There's definitely a lot of possibilities depending on your goal architecture, trying & learning makes it perfect !

How to make QML component (widget) adjusts according to data

I have a Widget as QML component in Qt Quick application which is to be used in various screens to display contents.
How can I use this particular QML component to adjust according to the items in it.
If it's a generic Item you can't: you have to manually set the container's size to fit its content.
The only QML components that fits their content are the Row, Column and Grid elements.
coming in way late, but if you want to have an update-able component you can set the model for the component to any list model like:
Component{
id:comp1
model:model1
}
ListModel {
id: model1
ListElement{
name:"a"
}
ListElement{
name: "b"
}
}
Component {
id: fruitDelegate
Row {
spacing: 10
Text { text: name }
}
}
ListView {
id:listView1
anchors.fill: parent
model: fruitModel
delegate: fruitDelegate
contentWidth: Screen.width
}
then you can update the listview at will
TextInput{
id: text_input1
width:parent.width * 0.80
text:"waddup?"
}
Button {
id: button2
anchors.left: text_input1.right
text: qsTr("Send")
onClicked: {
listView1.model.append( {name: text_input1.text, colorCode:"Red" });
/*text_input1.text = ""*/
}
}

Resources