4

UPDATE: Added code for SwipeView

I'm trying to customize the PageIndicator to do what I currently have three buttons do namely

  • Make it possible to change pages by clicking on the respective item inside the PageIndicator (currently I have 3 pages, one button per page with onClicked set to change the SwipeView to a predefined index - no PageIndicator)
  • Display custom image for each page indicator item (currently I have 3 pages, one button per page with a custom image - no 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:

enter image description here


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

enter image description here

From left to right the QRC URLs are:

  • qrc:/icons/qtlogo.png

    enter image description here

  • qrc:/icons/qtlogo1.png

    enter image description here

  • qrc:/icons/qtlogo2.png

    enter image description here

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'm 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:

enter image description here

This is due to the fact that the initially selected index is 0 so an Image with source: "qrc:/icons/qtlogo.png" is generated an 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

enter image description here

and

enter image description here

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:

enter image description here

Next I clicked on the third page indicator item:

enter image description here

After some more clicking I got:

enter image description here

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?

rbaleksandar
  • 8,713
  • 7
  • 76
  • 161

1 Answers1

3

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.

Mitch
  • 23,716
  • 9
  • 83
  • 122
  • Will update my question. As for the fact that it's working in your case - does the `PageIndicator` has to be a child of the `SwipeView` or can it be a subling? In my case it's actually a subling so perhaps that's the reason... – rbaleksandar Jul 22 '16 at 12:42
  • And yes, `inactive` is a typo. :D – rbaleksandar Jul 22 '16 at 12:50
  • You got one upvote from me for the `source: "qrc:/icons/qtlogo" + index + ".png"` which is a really great idea. The images are displayed properly now! The clicking is still a problem. :3 – rbaleksandar Jul 22 '16 at 12:57
  • Btw the `onCurrentIndexChanged` is not triggered at all (if I remove my `MouseArea` modification). I have the feeling that I'm doing some really stupid mistake here. – rbaleksandar Jul 22 '16 at 13:00
  • Kudos for the help! I guess it's not that out of the box and one does indeed have to bind both to one another in order to make things work. Now I can finally remove my buttons and also the `MouseArea`. :'D – rbaleksandar Jul 25 '16 at 06:58
  • Btw bottom margin **does** have effect. If I don't use it the `PageIndicator` covers the content of the current `Page`. – rbaleksandar Jul 25 '16 at 07:09
  • Using the example above? You must be using different code then, because the last example works for me. – Mitch Jul 25 '16 at 07:14
  • Your code does work. And it's basically what I have. However I also have defined the `preferredHeight` of `detailsView` to be 90% of the top-level layout's height as you can see in the added code for the `SwipeView`. So I guess that's the reason why bottom margin has quite a visible effect in my case. – rbaleksandar Jul 25 '16 at 07:17
  • It might work, but it's not intentional, and you shouldn't rely on it. It could also be that we're talking about different things, as I don't have your full code... but e.g. the following is not supported: `ColumnLayout { Item { anchors.bottom: parent.bottom } } ` – Mitch Jul 25 '16 at 09:03
  • Will investigate further. Again thank you so much for the help! – rbaleksandar Jul 25 '16 at 10:56