Forwarding keys to a Listview delegate inside a Popup



  • I'm trying to implement an autocomplete box similar to this https://material.angularjs.org/latest/demo/autocomplete.
    I'm using a TextField and a Popup with a listview to show the suggestions, but I'm having trouble forwarding keys from TextField to the listview delegate.

    What I'm doing now is opening the popup on the textfield onTextChanged event and then query the database for suggestions and populate the suggestions model. I'm using Keys.forwardTo to forward key events from the textfield to the listview in the popup, and it works fine, for navigation. But I can't figure out select an element from the list using 'return' key.

    This is main.qml

    TextField {
        id: searchBar
        placeholderText: "Search..."            
        focus: true
        property string searched_text: searchBar.text
        onTextChanged: {
          qmlComm.autoCompleteSearch(searchBar.text)
          autocomplete.open()
        }
        Keys.forwardTo: [autocomplete.autocompleteListViewAlias]            
    }
    
    AutoCompletePopup{
            id: autocomplete
            x:searchBar.x
            y:searchBar.y - 10
            height: mainAppPage.height / 2
            width: searchBar.width
        }
    

    This is popup.qml:

    Popup {
        id: autoCompletePopup
        property alias autocompleteListViewAlias: autoCompleteListView
        x: 200
        y: 200
        width: 200
        height: 400
        modal: false
        focus: false
        clip: true
        closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
    
        Component {
            id: highlightBar
            Rectangle {
                width: autoCompleteListView.width
                height: 60
                color: Material.color(Material.Pink)
                opacity: 0.7
                y: autoCompleteListView.currentItem.y            
            }
        }
    
        Rectangle {
            id: autoCompleteRect        
            color: "transparent"
            height: autoCompletePopup.height
            width: autoCompletePopup.width
    
            ListView {
                id: autoCompleteListView
                anchors.fill: parent
                model: autoCompleteModel            
                delegate: Item {
                    id: autoCompleteDelegate
                    height: 60
                    Rectangle {
                        id: autoCompleteDelegateBackground
                        width: autoCompleteListView.width
                        height: 60
                        focus: true
                        color: "transparent"
                      
                        Text {
                            id: autoCompleteItemName
                            text: name
                            font.capitalization: Font.Capitalize
                            anchors.left: autoCompleteDelegateBackground.left
                            anchors.leftMargin: 10
                            anchors.verticalCenter: autoCompleteDelegateBackground.verticalCenter
                        }
                        
    
                        MouseArea {
                            id: autoCompleteMouseArea
                            anchors.fill: autoCompleteDelegateBackground
                            onClicked: {
                                console.log("You clicked " + tableName + " " + name + " " + id)  //tableName,name and id are model role names
                                autoCompleteListView.currentIndex = index
                            }
                        }
                        // this is the problem, this event does not trigger
                        Keys.onReturnPressed: {               
                            console.log("Pressed Enter on " + name)
                        }
    
                        
    
                    }
                }
                // successfully triggered when keys are forwarded
                Keys.onPressed: {
                   if (event.key === Qt.Key_Down) {
                       console.log("AUTOCOMPLETE : Pressed down")
                   } else if (event.key === Qt.Key_Up) {
                       console.log("AUTOCOMPLETE : Pressed up")                    
                   } else if (event.key === Qt.Key_Return) {
                       console.log("AUTOCOMPLETE : Pressed enter")
                   }
               }
                highlight: highlightBar
                highlightFollowsCurrentItem: false
                ScrollBar.vertical: ScrollBar {
                }
                focus: false
                clip: true
            }
        }
    }
    

    I managed to get the navigation and selection behavior I want if I force the focus on the delegate on a mouse click like so : (in the mousearea on click event) autoCompleteListView.forceActiveFocus(); But then the textbox looses focus and the user can no longer type.
    I'm looking for a way to react to a Keys.onReturnPressed event inside the delegate, or some other solution that would help me select an item from a listview while maintaining the focus on the textedit.

    Thank You!

    P.S, I'm new to QML and Qt, so I'm also open to any suggestions that would help me build an autocomplete box that use completely different approach from what I have taken.



  • Does this do more or less what you want (besides that it's using a dummy model instead of doing any actual filtering)?

    import QtQuick 2.6
    import QtQuick.Controls 2.0
    
    ApplicationWindow {
        width: 640
        height: 480
        visible: true
    
        TextField {
            id: textField
            focus: true
            anchors.centerIn: parent
            onTextChanged: popup.open()
            Keys.forwardTo: [listView.currentItem, listView]
    
            Popup {
                id: popup
                padding: 1
                y: parent.height
                width: parent.width
                contentHeight: listView.contentHeight
    
                ListView {
                    id: listView
                    currentIndex: 0
                    anchors.fill: parent
                    model: ["foo", "bar", "baz"]
                    delegate: ItemDelegate {
                        text: modelData
                        width: parent.width
                        highlighted: index === listView.currentIndex
                        Keys.onReturnPressed: {
                            textField.text += text
                            popup.close()
                        }
                    }
                }
            }
        }
    }


  • Yes! Adding forwarding to listview.currentItem does exactly what I want. Thank You!