I'm trying to customize the PageIndicator
to do what I currently have three buttons do, namely:
PageIndicator
(currently I have 3 pages, one button per page with onClicked
set to change the SwipeView
to a predefined index — no PageIndicator
)PageIndicator
)My SwipeView
has the following structure:
SwipeView {
id: detailsView
Layout.fillWidth: true
Layout.preferredHeight: layoutTopLevel.height * .9
Page {
id: captureViewPage
header: Text {
text: "Capture view"
horizontalAlignment: Text.AlignHCenter
font.pixelSize: 20
}
}
Page {
id: helpViewPage
header: Text {
text: "Help view"
horizontalAlignment: Text.AlignHCenter
font.pixelSize: 20
}
footer: TabBar {
id: helpViewSubCategories
currentIndex: 0
TabButton {
text: qsTr("Gestures")
}
TabButton {
text: qsTr("General")
}
}
}
Page {
id: settingsViewPage
header: Text {
text: "Settings view"
horizontalAlignment: Text.AlignHCenter
font.pixelSize: 20
}
footer: TabBar {
id: settingsViewSubCategories
currentIndex: 0
TabButton {
text: qsTr("Language")
}
TabButton {
text: qsTr("Device")
}
}
}
}
Both the SwipeView
and the PageIndicator
(see below) are part of a ColumnLayout
and are sublings:
ColumnLayout {
id: layoutDetailsAndMenu
spacing: 0
SwipeView { ... }
PageIndicator { ... }
}
First of all, I have researched how the delegate
property of the PageIndicator
works. For example, the following QML code:
PageIndicator {
id: detailsViewIndicator
count: detailsView.count
currentIndex: detailsView.currentIndex
interactive: true
anchors.bottom: detailsView.bottom
anchors.bottomMargin: -40
anchors.horizontalCenter: parent.horizontalCenter
delegate: Rectangle {
implicitWidth: 15
implicitHeight: 15
radius: width
color: "#21be2b"
opacity: index === detailsView.currentIndex ? 0.95 : pressed ? 0.7 : 0.45
Behavior on opacity {
OpacityAnimator {
duration: 100
}
}
}
}
produces the following greenish result:
Make it possible to change pages by clicking on the respective item inside the PageIndicator
For some reason, the inactive
doesn't work at all. I will quote the documentation what this property is supposed to do:
interactive : bool
This property holds whether the control is interactive. An interactive page indicator reacts to presses and automatically changes the current index appropriately. The default value is false.
I don't know if this is a bug or I'm missing something but even without the customization via delegate
clicking on a page indicator item does NOT change the current page (the number of these items does equal the number of pages so clicking on an index that doesn't have a page assigned to it is out of question).
So in order to make my first wish come true, I added a MouseArea
:
PageIndicator {
// ...
delegate: Rectangle {
MouseArea {
anchors.fill: parent
onClicked: {
if(index !== detailsView.currentIndex) {
detailsView.setCurrentIndex(index);
}
}
}
}
}
As you can see, I'm using the onClicked
event handler (or whatever these things are called) and check whether the current index of the PageIndicator
equals the page in my SwipeView
. If that's not the case, I use setCurrentIndex(index)
to set my SwipeView
to the selected index.
After I wrote this, I was pretty satisfied that it worked as I envisioned it (though the interactive
thing is still bothering me). Next thing was to add the images...
Display custom image for each page indicator item
First let me show you how I'd like things to look (this is actually the final result but more on that — later down the line):
NOTE: I've used the Qt
logo, which I don't own. It's for demonstration purposes.
From left to right, the QRC
URLs are:
The source for this is as follows:
delegate: Image {
source: detailsViewIndicator.indicatorIcons[detailsView.currentIndex]
height: 30
width: 30
opacity: index === detailsView.currentIndex ? 0.95 : pressed ? 0.7 : 0.45
MouseArea {
anchors.fill: parent
onClicked: {
if(index !== detailsView.currentIndex) {
detailsView.setCurrentIndex(index);
source = detailsViewIndicator.indicatorIcons[detailsView.currentIndex];
}
}
}
}
where indicatorIcons
is a variant
property of my PageIndicator
:
property variant indicatorIcons: [
"qrc:/icons/qtlogo.png",
"qrc:/icons/qtlogo1.png",
"qrc:/icons/qtlogo2.png"
]
I've used an array of string
objects for the QRC
URLs since it seems impossible to do
delegate: detailsViewIndicator.indicatorImages[detailsView.currentIndex]
with indicatorImages
being:
property list<Image> indicatorImages: [
Image { source: "..." },
Image { source: "..." },
Image { source: "..." }
]
The issues I'm having are with the actual loading of the images, and I have the feeling that it has something to do with the list
problem I've described above. With the code:
delegate: Image {
source: detailsViewIndicator.indicatorIcons[detailsView.currentIndex]
// ...
}
first time I run my application, I get:
This is due to the fact that the initially selected index is 0
so an Image
with source: "qrc:/icons/qtlogo.png"
is generated and all page indicator items are populated with it. If I select one of the other two as the initially selected page, I will get qrc:/icons/qtlogo1.png
and qrc:/icons/qtlogo2.png
respectively.
Swiping in the SwipeView
(not clicking on the PageIndicator
) leads to
and
This only in one direction (index-wise from left to right). If I go backwards I get the same results but in the opposite order.
Clicking makes things more interesting. In the screenshot below, I've clicked on the second page indicator item (with qrc:/icons/qtlogo1.png
as source for the Image
) after the initialization:
Next, I clicked on the third page indicator item:
After some more clicking, I got:
Obviously, this is not how things are supposed to work. I'd like to have the final result all the time from start to end and no matter which page has been swiped away or page indicator item clicked.
Has anyone done anything like this? Is this even possible?
For some reason the inactive doesn't work at all
I'm assuming this is a typo, as using the interactive
property works for me (Qt 5.7):
import QtQuick 2.7
import QtQuick.Controls 2.0
ApplicationWindow {
visible: true
PageIndicator {
count: 3
interactive: true
onCurrentIndexChanged: print(currentIndex)
}
}
As does a simplified version of your first example:
import QtQuick 2.7
import QtQuick.Controls 2.0
ApplicationWindow {
visible: true
PageIndicator {
id: detailsViewIndicator
count: 3
currentIndex: 0
interactive: true
delegate: Rectangle {
implicitWidth: 15
implicitHeight: 15
radius: width
color: "#21be2b"
opacity: index === detailsViewIndicator.currentIndex ? 0.95 : pressed ? 0.7 : 0.45
Behavior on opacity {
OpacityAnimator {
duration: 100
}
}
}
}
}
So, if the SwipeView
isn't changing pages, the problem is likely there, and we'd need to see that code.
I'd like to have the final result all the time from start to end and no matter which page has been swiped away or page indicator item clicked.
I think the problem is with the index you're using for the icon array:
source: detailsViewIndicator.indicatorIcons[detailsView.currentIndex]
That's using the currently selected index, whereas it seems like you want to use the index of that delegate:
source: detailsViewIndicator.indicatorIcons[index]
By the way, a simpler way of specifying the icon source would be to use string concatenation (and giving your files the appropriate naming to match):
source: "qrc:/icons/qtlogo" + index + ".png"
In the code you added, the SwipeView
doesn't set its currentIndex
to the currentIndex
of the PageIndicator
. You can do so like this:
currentIndex: detailsViewIndicator.currentIndex
The full example:
import QtQuick 2.5
import QtQuick.Layouts 1.1
import QtQuick.Controls 2.0
ApplicationWindow {
visible: true
width: 640
height: 480
title: qsTr("Hello World")
ColumnLayout {
anchors.fill: parent
SwipeView {
id: detailsView
currentIndex: detailsViewIndicator.currentIndex
Layout.fillWidth: true
Layout.fillHeight: true
Page {
id: captureViewPage
header: Text {
text: "Capture view"
horizontalAlignment: Text.AlignHCenter
font.pixelSize: 20
}
}
Page {
id: helpViewPage
header: Text {
text: "Help view"
horizontalAlignment: Text.AlignHCenter
font.pixelSize: 20
}
footer: TabBar {
id: helpViewSubCategories
currentIndex: 0
TabButton {
text: qsTr("Gestures")
}
TabButton {
text: qsTr("General")
}
}
}
Page {
id: settingsViewPage
header: Text {
text: "Settings view"
horizontalAlignment: Text.AlignHCenter
font.pixelSize: 20
}
footer: TabBar {
id: settingsViewSubCategories
currentIndex: 0
TabButton {
text: qsTr("Language")
}
TabButton {
text: qsTr("Device")
}
}
}
}
PageIndicator {
id: detailsViewIndicator
count: detailsView.count
currentIndex: detailsView.currentIndex
interactive: true
anchors.horizontalCenter: parent.horizontalCenter
}
}
}
Another note: using vertical anchors (anchors.bottom
, anchors.top
) in a ColumnLayout
won't work. Only horizontal anchors can be used in the immediate child of a vertical layout, and vice versa.