Fill TabView from Pages' titles of corresponding SwipeView - qt

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
}
}

Related

QML: Separators between list delegates

Has anyone found a good way to add separators between QML list delegates?
There is a very similar question here already but my problem is a bit more complex: How to set custom separator between items of ListView
Most of the time I use something similar as in an answer there:
ListView {
delegate: ItemDelegate {
...
Separator {
anchors { left: parent.left; bottom: parent.bottom; right: parent.right }
visible: index < ListView.View.count
}
}
}
However, depending on the design and backend data, I don't always have a ListView/Repeater at hand and need to add manual items in a ColumnLayout instead or a mix of some items from a repeater and some manual ones:
ColumnLayout {
ItemDelegate {
...
}
Separator {
Layout.fillWidth: true
}
ItemDelegate {
...
}
}
Now, both of those work but it's extremely annoying to always remember and type that separator. After lots of trying I still haven't been able to figure out a component that would take care of it.
The closest I've come to a custom Layout component like this (e.g ItemGroup.qml):
Item {
default property alias content: layout.data
ColumnLayout {
id: layout
}
Repeater {
model: layout.children
delegate: Separator {
parent: layout.children[index]
anchors { left: parent.left; bottom: parent.bottom; right: parent.right }
visible: index < layout.children.length
}
}
}
Now this works fine for manually adding items to such a group, but again it will not work in many corner cases. For instance putting a Repeater into such an ItemGroup will create a separator for the Repeater too (given it inherits Item and thus is included in children) which results in a visual glitch with one seemingly floating separator too much...
Anyone came up with a more clever solution for this?
I'd try this approach:
Make a custom component based on ColumnLayout.
Use default property ... syntax to capture children added to it into a separate list property.
Create a binding for the children property of ColumnLayout that interleaves each real child in your default property list with one of your Separators (using a Component to declare it and createObject() to create each one).
Here's a working example:
Separator.qml
import QtQuick 2.0
import QtQuick.Layouts 1.12
Rectangle {
Layout.fillWidth: true
height: 1
color: "red"
}
SeparatedColumnLayout.qml
import QtQuick 2.15
import QtQuick.Layouts 1.12
ColumnLayout {
id: layout
default property list<Item> actualChildren
property Component separatorComponent: Qt.createComponent("Separator.qml")
children: {
var result = [];
for(var i = 0;i < actualChildren.length;++i) {
result.push(actualChildren[i]);
if (i < actualChildren.length - 1) {
result.push(separatorComponent.createObject(layout));
}
}
return result;
}
}
main.qml:
import QtQuick 2.11
import QtQuick.Window 2.11
import QtQuick.Controls 2.4
import QtQuick.Layouts 1.12
ApplicationWindow {
id: root
visible: true
width: 640
height: 480
title: qsTr("Hello World")
SeparatedColumnLayout {
anchors.fill: parent
Text {
Layout.alignment: Qt.AlignHCenter
text: "1"
}
Text {
Layout.alignment: Qt.AlignHCenter
text: "2"
}
Text {
Layout.alignment: Qt.AlignHCenter
text: "3"
}
}
}
The result:

Load SwipeView pages dynamically

I have created the following MWE (Qt 5.13.0):
import QtQuick 2.0
import QtQuick.Window 2.2
import QtQuick.Controls 2.3
ApplicationWindow
{
property int itemsNo: 3;
id: window
visible: true
width: 480
height: 480
SwipeView
{
anchors.fill: parent;
id: theSwipeView;
Loader
{
sourceComponent: theSingleComp;
Component
{
id: theSingleComp;
Page
{
Text
{
text: "The single one";
}
}
}
}
Repeater
{
model: itemsNo;
Loader
{
sourceComponent: theMultiComp;
Component
{
id: theMultiComp;
Page
{
Text
{
text: "The multi one " +
(theSwipeView.currentIndex - 1);
}
}
}
}
}
}
}
In my program, I have an unique component (theSingleComp) and multiple components behind him (theMultiComp). As for now, I need to implement the following functionality:
In case the model used for theMultiComp has only 1 item, display only this item and not the theSingleComp. In case the are more theMultiComp items, display it like now. It seems to me that there is no possibility for this to work if I keep the items defined statically. But on the other hand, I don't know how to do this dynamically, since there is a case in which one of the components should not be displayed at all. I tried an approach like this:
sourceComponent: (itemsNo > 1) ? theSingleComp : null;
But then the page for this null component is still created.
Your problem is that Loader is an Item and SwipeView creates a page for it even if it doesn't have a source component.
To solve this problem you can use Repeater instead with a model of 1 (or 0 to disable it). Repeater is also an Item but it has some special code under the hood to be ignored by containers.
import QtQuick 2.0
import QtQuick.Window 2.2
import QtQuick.Controls 2.3
ApplicationWindow
{
id: window
property int itemsNo: 0
visible: true
width: 480
height: 480
SwipeView {
id: theSwipeView
anchors.fill: parent
Repeater {
model: window.itemsNo > 1 ? 1 : 0
Page {
Text {
text: "The single one"
}
}
}
Repeater {
model: window.itemsNo
Page {
Text {
text: "The multi one " + model.index
}
}
}
}
}
(I've simplified your code to remove the explicit Components and the Loaders)
I have come up with the following solution but I am not happy with it. It's very hacky and the user can see how the page index changes.
import QtQuick 2.0
import QtQuick.Window 2.2
import QtQuick.Controls 2.3
ApplicationWindow
{
property int itemsNo: 2;
id: window
visible: true
width: 480
height: 480
SwipeView
{
anchors.fill: parent;
id: theSwipeView;
Component.onCompleted:
{
if (itemsNo > 1)
insertItem(0, theSingleComp);
set0IndexTimer.start();
}
Timer
{
id: set0IndexTimer;
interval: 1;
running: false;
repeat: false;
onTriggered: theSwipeView.setCurrentIndex(0);
}
onCurrentIndexChanged: console.log("page: ", currentIndex);
Repeater
{
model: itemsNo;
Loader
{
sourceComponent: theMultiComp;
Component
{
id: theMultiComp;
Page
{
Text
{
text: "The multi one " + theSwipeView.currentIndex;
}
}
}
}
}
}
Item
{
id: theSingleComp;
Page
{
Text
{
text: "The single one";
}
}
}
}
I am still seeking some other examples.

Adding TabButton dynamically to TabBar in QML

I am trying to add a tabButton to TabBar dynamically on pressing a button but i have spent a lot of time searching but i am not getting how to add, below is the code which i am working on :
MyTabButton.qml
import QtQuick 2.4
import QtQuick.Controls 2.2
Item
{
property int BtnWidth:0
property int BtnHeight:0
property string BtnText: ""
property bool isChecked : false
TabButton
{
id:tabBtn
text:BtnText
width:BtnWidth
height:BtnHeight
}
}
MainForm.qml
import QtQuick 2.4
import QtQuick.Controls 2.2
Rectangle
{
Button
{
id:button
width:100
height:100
anchors.top:parent.top
text:qStr("Add")
onClicked{
//How to add logic here to add tab in below tabBar.
}
}
TabBar
{
id:tabBar
anchors.top:button.bottom
width:500
height:500
}
}
Example:
import QtQuick 2.7
import QtQuick.Controls 2.0
ApplicationWindow {
id: window
width: 360
height: 630
visible: true
header: TabBar {
id: tabBar
}
Component {
id: tabButton
TabButton { }
}
Button {
text: "Add"
anchors.centerIn: parent
onClicked: {
var tab = tabButton.createObject(tabBar, {text: "Tab " + tabBar.count})
tabBar.addItem(tab)
}
}
}
You need to have something like a Component that is a TabButton. Your file MyTabButton.qml won't result in a TabButton, but instead an Item containing a TabButton, but with this, your TabBar does not know what to do.
So your file will need to have TabButton as root element
//MyTabButton.qml
import QtQuick 2.4
import QtQuick.Controls 2.2
TabButton
{
id: tabBtn
// customize as you like
}
Then you create a Component of this in your file where you want to use it. (e.g. main.qml)
import QtQuick 2.4
import QtQuick.Controls 2.0
ApplicationWindow {
width: 800
height: 600
visible: true
TabBar {
id: tabBar
width: 800
height: 50
}
// The component is like a factory for MyTabButtons now.
// Use myTabButton.createObject(parent, jsobject-with-property-assignments) to create instances.
Component {
id: myTabButton
MyTabButton {
/// EDIT ACCORDING TO YOUR COMMENTS ***
Connections {
target: tabBar
onCurrentIndexChanged: doSomething()
}
/// EDIT OVER
}
}
Button {
anchors.centerIn: parent
// Create a object out of the component, and add it to the container
onClicked: tabBar.addItem(myTabButton.createObject(tabBar /*, { object to set properties }*/))
}
}
TabBar inherits Container, which has addItem().
Try it in Window
Row {
anchors.fill: parent
TabBar {
id: tabBar
currentIndex: 0
width: parent.width - addButton.width
TabButton { text: "TabButton" }
}
Component {
id: tabButton
TabButton { text: "TabButton" }
}
Button {
id: addButton
text: "+"
flat: true
onClicked: {
tabBar.addItem(tabButton.createObject(tabBar))
console.log("added:", tabBar.itemAt(tabBar.count - 1))
}
}
}

QObject::findChild() returns None without obvious reason

I'm new to Qt Quck and Qt5/PyQt, and now I've faced a strange problem. I'm trying to find an object with objectName "test" in the below QML definition like this:
self.rootObject().findChild(QObject, "test")
But the call returns None. However, if I move the objectName: "test" property to the parent Tab element, then it's found successfully. It's only not found whem inside the child Item. Similarly, addChannel, modifyChannel and removeChannel objects are also not found by findChild().
import QtQuick 2.0
import QtQuick.Layouts 1.1
import QtQuick.Controls 1.1
import "TouchStyles"
Item {
ListModel { }
TouchButtonFlatStyle { id: touchButtonFlat }
TouchTabViewStyle { id: touchTabView }
Rectangle {
width: 480
height: 230
TabView {
currentIndex: 0
tabPosition: 1
anchors.fill: parent
style: touchTabView
Tab {
title: "Play"
Item {
anchors.fill: parent
PianoKeyboard { anchors.centerIn: parent }
}
}
Tab {
title: "Channels"
Item {
objectName: "test"
ListView {
anchors.fill: parent
model: listModel
delegate: Channel {}
}
BorderImage {
border.bottom: 8
anchors.bottom: parent.bottom
source: "images/toolbar.png"
width: parent.width
height: 50
RowLayout {
anchors.verticalCenter: parent.verticalCenter
anchors.horizontalCenter: parent.horizontalCenter
Button { text: "Add"; objectName: "addChannel" }
Button { text: "Modify"; objectName: "modifyChannel" }
Button { text: "Remove"; objectName: "removeChannel" }
}
}
}
}
}
}
}
What am I doing wrong? The Qt documentation says that the search is performed recursively. Why doesn't it traverse the entire object tree?
The problem is related to the fact that tabs are "instantiated" only on demand. The first tab is always instantiated, so if you put the objectName there it will be found.
It will be found in the second tab only if you instantiate the second tab (select it). Similarly, using findChild on the TabView probably instantiates each tab (since it looking for them), so after that a findChild works even if second tab was not selected.
Conclusion: instantiate all tabs first (doing a findChild on the TabView is one way but may be a hack), then do the findChild for the item.

Add elements dynamically to SplitView in QML

I am working with QML and I want to add elements to SplitView dynamically eg. onMouseClick, but so far I didn't find the answer.
What I've found out so far is that the SplitView has it's default property set to it's first child's data property. So I guess I should try and add new dynamically created components with the parent set to that child (splitView1.children[0]). Unfortunately that doesn't work either. What is more the number of children of that first child is zero after the component has finished loading (seems like the SplitLayout's Component.onCompleted event calls a function that moves those children somewhere else). Thus the added children do not render (and do not respond to any of the Layout attached properties).
Please see the following code snippet:
import QtQuick 2.1
import QtQuick.Controls 1.0
import QtQuick.Layouts 1.0
ApplicationWindow {
width: 600
height: 400
SplitView {
anchors.fill: parent
Rectangle {
id: column
width: 200
Layout.minimumWidth: 100
Layout.maximumWidth: 300
color: "lightsteelblue"
}
SplitView {
id: splitView1
orientation: Qt.Vertical
Layout.fillWidth: true
Rectangle {
id: row1
height: 200
color: "lightblue"
Layout.minimumHeight: 1
}
// Rectangle { //I want to add Rectangle to splitView1 like this one, but dynamicly eg.onMouseClick
// color: "blue"
// }
}
}
MouseArea {
id: clickArea
anchors.fill: parent
onClicked: {
console.debug("clicked!")
console.debug("len: " + splitView1.__contents.length); // __contents is the SplitView's default property - an alias to the first child's data property
var newObject = Qt.createQmlObject('import QtQuick 2.1; Rectangle {color: "blue"}',
splitView1, "dynamicSnippet1"); //no effect
// var newObject = Qt.createQmlObject('import QtQuick 2.1; import QtQuick.Layouts 1.0; Rectangle {color: "blue"; width: 50; height: 50}',
// splitView1, "dynamicSnippet1"); //rectangle visible, but not in layout(?) - not resizeable
}
}
}
Is there any way I can make the dynamically created components render properly in the SplitView as the statically added ones?
It appears that the API does not provide support for dynamic insertion of new elements. Even if you do get it to work it would be a hack and might break with future releases. You may need to roll your own control to mimic the behavior you want. Ideally it should be backed by some sort of model.
As of QtQuick Controls 1.3, SplitView has an addItem(item) method.
you have to use the Loader for load dinamicaly objects. in onClicked handle you have to declare sourceComponent property to change the source of the Loader, something like this:
ApplicationWindow {
width: 600
height: 400
SplitView {
anchors.fill: parent
Rectangle {
id: column
width: 200
Layout.minimumWidth: 100
Layout.maximumWidth: 300
color: "lightsteelblue"
}
SplitView {
id: splitView1
orientation: Qt.Vertical
Layout.fillWidth: true
Rectangle {
id: row1
height: 200
color: "lightblue"
Layout.minimumHeight: 1
}
Loader {
id:rect
}
}
}
MouseArea {
id: clickArea
anchors.fill: parent
onClicked: {
console.debug("clicked!")
console.debug("len: " + splitView1.__contents.length) // __contents is the SplitView's default property - an alias to the first child's data property
rect.sourceComponent = algo
}
}
Component {
id:algo
Rectangle {
anchors.fill: parent
color: "blue"
}
}
}
I saw the source code of SplitView, it calculate each split region when Component.onCompleted signal. So I think that is a key point. No matter how you do (insert, dynamic create). The region won't be reset after you insert a new region for split.

Resources