Friday, September 6, 2013

Accessing C++ model's roles from SelectionDialog

Consider you have a Qt C++ model with several roles (as an example, a model Customer with roles "id", "name" and "address"), and you wanted to have a SelectionDialog to select one of the items in the model with access to the data on its roles.

SelectionDialog only gives access to the selected index, and C++ model's roles are not directly accessible from QML.

Here I present a somewhat hacky way in pure QML around this restriction without modifying the C++ model. Basically we create a new SelectionDialog, called ItemSelectionDialog, that is similar to SelectionDialog. The difference is that instead of emitting selectedIndexChanged signals, it emits itemSelected signals that have as parameter the model's item. Then you can in QML access the selected item's different roles. It works by creating a proxy ListView for the model. Since ListView has functions to access the model's items, it can be used in the selection dialog to access the item.

ItemSelectionDialog:

import QtQuick 1.1
import com.nokia.meego 1.0

Item {
    id: root
    property alias model: dDialog.model
    property alias titleText: dDialog.titleText
    property alias selectedIndex: dDialog.selectedIndex
    property variant selectedItem

    signal itemSelected(variant item)
    signal accepted

    function open() {
        dDialog.open()
    }

    // This is a hack to access from SelectionDialog the roles of the model.
    ListView {
        id: lModel
        model: root.model
        delegate: Item {
            property variant myModel: model
        }
    }

    SelectionDialog {
        id: dDialog
        onSelectedIndexChanged: {
            if (selectedIndex > -1) {
                // This is a hack to access from SelectionDialog the roles of the model.
                // Ugly but works.
                lModel.currentIndex = selectedIndex
                root.selectedItem = lModel.currentItem.myModel
                itemSelected(lModel.currentItem.myModel)
            } else {
                root.selectedItem = null
            }
        }
        onAccepted: {
            root.accepted()
        }
    }
}

Using it is similar to using SelectionDialog, but instead of watching for onSelectedIndexChanged signals, you're watching for onItemSelected signals.

For example using the example given at the beginning:

ItemSelectionDialog {
    model: customers
    onItemSelected: {
        console.log("id: ", item.id)
        console.log("name: ", item.name)
        console.log("address: ", item.address)
    }
}