rbaleksandar
rbaleksandar

Reputation: 9701

QML ListView - change all but current item

I'm following this tutorial (without the flickable content in each entry) for Qt 4.8 while using Qt 5.7 with QtQuick 2.0. The way the ListView there works is as follows:

  1. User clicks on item in list
  2. Alternative (detailed) view of item is displayed
  3. User has to click on Close button in detailed view to reset the state of entry to its default compact view.

This leads to a clutter where at some point if the user clicks on all items in which case all will be shown in their full view. Having the user click on the Close button every time he/she opens a detailed view also is (omho) not that handy.

I've altered the entry to close when the user clicks on the view. I'm also trying to prevent this clutter and achieve a more (omho) flowing behaviour:

  1. User clicks on item in list
  2. Alternative view of item is displayed
  3. User clicks on detailed view to reset state of entry to its default compact view OR
  4. User clicks on another entry and all currently in detailed view entries are reset to their compact view

Currently I'm looping through my ListView's contentItem.children[loop_index] and setting the state to "" ("Details" = show detailed view | "" = show compact view). Due to the way ListView works (loading/unloading delegates on demand) this is quite unreliable and I often get an undefined reference when I try to access the state of other delegates. The following MouseArea, which I'm using to do all that, is part of every delegate:

// state is a QML `State` that is bound to the delegate (see below for the details on it)
MouseArea {
    anchors.fill: background
    onClicked: {
        // Iterate through all other entries and close them
        for (var entry = 0; entry < listView.count; ++entry) {
            if(listView.contentItem.children[entry] !== gestureEntry) {
                console.log("Hide other element");
                listView.contentItem.children[entry].state = ""; // IT FAILS HERE (SOMETIMES)
            }
        }

        // Change view of current entry
        if(gestureEntry.state === "Details") {
            gestureEntry.state = "";
            console.log("Hiding details")
        }
        else {
            gestureEntry.state = "Details";
            console.log("Showing details");
        }
    }
}

with state being a delegate's state:

states: State {
    name: "Details"

    PropertyChanges { target: background; color: "white" }
    PropertyChanges { target: gestureImage; width: 130; height: 130 } // Make picture bigger
    PropertyChanges { target: gestureEntry; detailsOpacity: 1; x: 0; y: 0 } // Make details visible
    PropertyChanges { target: gestureEntry; height: listView.height } // Fill the entire list area with the detailed view
}

I'm thinking that the state information can be stored inside the ListModel itself making it possible to iterate through the model's contents (which are always there unlike the contents of the delegates) however I don't know how to automatically update my list (and the currently visible/invisible delegates) when an entry changes in the model. From what I've found so far it seems not possible to do that since the ListView doesn't actively monitor its ListModel.

Is this indeed the case? If yes, then is it possible to go around this problem in a different way?

Upvotes: 4

Views: 6417

Answers (2)

Florian Schmidt
Florian Schmidt

Reputation: 379

guess it is an issue because you stored a state in your delegate. You should not do this as described in the delegate-property (Link), because the delegates get reused when they get out of view.

At least you should use a when: ListView.isCurrentItem in the State and depend on a value of the ListView. So only your current delegate is maximized. Then in the MouseArea only set `ListView.view.currentIndex = index'. Don't change the state manually in the function!

I ran in the same trouble, removed the states completely and just used the attached property ListView.isCurrentItem. But binding the state to a Value from the ListView should also work, because it's not stored in the delegate.

Minimal example:

import QtQuick 2.0

Item {
    width: 800
    height: 600

    ListView {
        id: view
        anchors.fill: parent
        model: 3
        spacing: 5
        currentIndex: -1
        delegate: Rectangle {
            id: delegate
            color: ListView.isCurrentItem ? "lightblue" : "green" // directly change properties depending on isCurrentItem
            height: 100
            width: 100

            states: State {
                name: "maximized"
                when: delegate.ListView.isCurrentItem // bind to isCurrentItem to set the state
                PropertyChanges {
                    target: delegate
                    height: 200
                }
            }
            MouseArea {
                anchors.fill: parent
                //onClicked: delegate.ListView.view.currentIndex = model.index // if only selection is wanted
                onClicked: {
                    //console.debug("click");
                    if (delegate.ListView.isCurrentItem)
                    {
                        delegate.ListView.view.currentIndex = -1;

                    }
                    else
                    {
                        delegate.ListView.view.currentIndex = model.index;
                    }
                }
            }
            Text {
                anchors.centerIn: parent
                text: index
            }
        }
        Text {
            text: "CurrentIndex: " + parent.currentIndex
        }
    }
}

Upvotes: 2

Why don't you use the currentIndex property of your ListView? Just modify your delegate like this:

Item {
    id: gestureEntry
    ...
    state: ListView.isCurrentItem?"Details":""
    ...
    MouseArea {
        anchors.fill: background
        onClicked: {
            if(listView.currentIndex == index)
                listView.currentIndex = -1
            else
                listView.currentIndex = index
        }
    }
}

EDIT: The only issue with the solution above is that - upon loading - an entry in the ListView is preselected which automatically triggers the detailed view of that entry. In order to avoid that the following needs to be added to listView:

Component.onCompleted: {
    listView.currentIndex = -1;
}

This ensures that no entry will be preselected.

Upvotes: 2

Related Questions