Important: Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

Custom Combo Box Auto hide Drop Down when lost focus



  • Hi Everyone !

    I know, this is such noob Question but I don't have time for research

    What I want is create a Custom Combo Box.

    • Drop down Item must be same style on Windows & Mac
      (Original Combo Box show a tick mark on MAC)
    • It can Show / Hide and Enable / Disable some items in model when needed
      (I tried custom ComboBoxStyle but it doesn't work. Invisible Item in Drop Down List still take an empty space)

    So, I implement my ComboBox like this

    FocusScope {
        id: root
        width: 160
        height: 24
        property var model
        ....
        Rectangle {
            id: recButton
            anchors.fill: parent
            border.width: 1
            border.color: MDStyle.borderColor
            radius: 2
            clip: true
    
            Label {
                id : lblDisplayText
                anchors.left: parent.left
                anchors.leftMargin: 2
                anchors.right: parent.right
                anchors.rightMargin: recButton.width + 2
                anchors.verticalCenter: parent.verticalCenter
                text: "Selected Item"
            }
    
            Rectangle {
                id: recArrowContainer
                height: parent.height
                width: height
                anchors.right: parent.right
                color: "transparent"
                Image {
                    id: imgArrow
                    anchors.centerIn: parent
                    width: sourceSize.width
                    height: sourceSize.height
                    source: "qrc:/Images/GUI/metrixa-icon-down-arrow.png"
                }
            }
    
            MouseArea {
                anchors.fill: parent
                hoverEnabled: true
                onClicked: {
                    root.focus = true
                    root.state = root.state==="dropDown"?"":"dropDown"
                }
            }
        }
    
        Rectangle {
            id: recDropDown
            width:root.width;
            height:0;
            clip:true;
            radius:2;
            anchors.top: recButton.bottom;
            anchors.margins: 1;
            border.width: 1
            border.color: MDStyle.borderColor
        }
    
        states: State {
            name: "dropDown";
    
            PropertyChanges { target: recDropDown; height:24 * (root.model ? root.model.length : 4) }
        }
    }
    

    The problem is "How can I detect when my control is lost focus to hide the Drop Down.
    Included focus on next form item by tab key, mouse click on other form item, or click on form back ground

    I also look in original ComboBox qml file and notice about FocusScope, Menu, Popup but doesn't know how it work.

    Please help me ! ASAP !!!



  • @Dong
    Maybe this can help:

    Whether or not an Item has active focus can be queried through the property Item::activeFocus property
    


  • @medyakovvit

    I'm already tried focus & activeFocus. but doesn't work.

    I set focus for popup Item (Rectangle) when clicked on MouseArea
    And print out focus & activeFocus value (it both : true)

    But when I click outside (Click on Window's background)
    Nothing happen.
    It only change to :false: when I switch to another Windows.



  • Finally, I made it !!!

    1. To implement "Drop Down List" just like Normal Combo

    • "Drop Down List" need to be on top of all other control
    • "Drop Down List" must be disappear when click outside it

    To do that, I need to set the "Root Item" (ApplicationWindow or other Component) as parent of maskMouseArea & recDropDown
    (There is a little tricky here and I write a javascript function to return the "Root Item" & "Referred Coordinate" to the Combo - You need this to display the popup by set x, y position)

    function getRootComponent(component) {
        var result = {Component : component, refX : 0, refY : 0 }
        while (result.Component.parent) {
            result.refX = result.refX + result.Component.x
            result.refY = result.refY + result.Component.y
            result.Component = result.Component.parent
        }
        return result;
    }
    

    When user Click on maskMouseArea it'll close the recDropDown

    Bellow is source code of MyComboBox.qml

    (Note CommonScript.jsBinding is my function to support binding with Dynamic Property, you can use normal binding)

    import QtQuick 2.2
    import QtQuick.Controls 1.2
    import QtQuick.Controls.Private 1.0
    import QtQuick.Controls.Styles 1.2
    
    import "../"
    import "../commonScripts.js" as CommonScript
    
    Rectangle {
        id: root
        width: 160
        height: MDStyle.comboBoxHeight
        border.width: 1
        border.color: MDStyle.borderColor
        color: MDStyle.backGroundColor
        radius: 2
        clip: true
    
        signal activated(int index)
    
        property int currentIndex: -1
        property var selectedValue
        property var selectedItem
        property string displayMember: "Text"
        property string valueMember: "Value"
        property string imageSourceMember: "ImageSource"
        property bool showIcon: false
        property int dropDownMinWidth: root.width
        property int dropDownMaxWidth: 2 * dropDownMinWidth
        property int maxRowsCount: 6
        property var model
    
        property bool popupVisible : false
        property int rowHeight : 28
    
        property string displayText: ""
    
        Item {
            id: privateProperties
            property var rootAncestor: CommonScript.getRootComponent(root)
            property int popupWidth : root.dropDownMinWidth
            property bool popupWidthAutoAdjusted: false
        }
    
        property Component displayItem:
            Label {
            id : lblDisplayText
            anchors.left: parent.left
            anchors.leftMargin: MDStyle.textFontSize / 2
            anchors.right: parent.right
            anchors.rightMargin: recArrowContainer.width + 2
            anchors.verticalCenter: parent.verticalCenter
            font.family: MDStyle.fontFamily
            font.pixelSize: MDStyle.textFontSize
            color: MDStyle.fontColor
            clip: true
    
            text: displayText != "" ? displayText :
                listItems.selectedIndex >= 0 ? listItems.model[listItems.selectedIndex].binding(displayMember) : ""
        }
    
        Rectangle {
            id: recControlHover
            anchors.fill: parent
            anchors.margins: 2
            border.width: 0
            color: "#113399FF"
            visible: clickableArea.containsMouse
        }
    
        //MouseArea to show popup when clicked
        MouseArea {
            id: clickableArea
            anchors.fill: parent
            hoverEnabled: true
            onClicked: {
                root.popupVisible = true
                return true;
            }
        }
    
        //Loader for Display Item
        Loader {
            id : displayItemLoader
            anchors.left: parent.left
            anchors.right: parent.right
            anchors.verticalCenter: parent.verticalCenter
    
            sourceComponent: displayItem
        }
    
        //Arrow Image
        Rectangle {
            id: recArrowContainer
            height: parent.height - 2 * anchors.margins
            width: height - 2 * anchors.margins
            anchors.top: parent.top
            anchors.right: parent.right
            anchors.margins: 1
            radius: 2
            color: "transparent"
            Image {
                id: imgArrow
                anchors.centerIn: parent
                width: sourceSize.width
                height: sourceSize.height
                source: "qrc:/Images/GUI/metrixa-icon-down-arrow.png"
            }
        }
    
        //Mask Mouse Area (used to hide Drop Down List when click outside Drop Down List)
        MouseArea {
            id: maskMouseArea
            parent: privateProperties.rootAncestor.Component
            anchors.fill: parent
            visible: root.popupVisible
            onClicked: {
                root.popupVisible = false
                return false
            }
            z: 9998
        }
    
        //Drop Down List
        Rectangle {
            id: recDropDown
            parent: privateProperties.rootAncestor.Component
            width: {
                return CommonScript.max(root.width,
                    privateProperties.popupWidth
                    + listItems.anchors.leftMargin + listItems.anchors.rightMargin
                    + ddlScrollView.anchors.leftMargin + ddlScrollView.anchors.rightMargin)
            }
            height: root.rowHeight * CommonScript.min(root.maxRowsCount, (root.model ? root.model.length : 0))
                    + ddlScrollView.anchors.topMargin + ddlScrollView.anchors.bottomMargin
                    + listItems.anchors.topMargin + listItems.anchors.bottomMargin
            clip: true
            radius:2
            x: privateProperties.rootAncestor.refX
            y: privateProperties.rootAncestor.refY + root.height + 1
            border.width: 1
            border.color: MDStyle.borderColor
            color: "#FFFFFF"
            visible: root.popupVisible
            z: 9999
    
            ScrollView {
                id: ddlScrollView
                anchors.fill: parent
                anchors.margins: 1
                clip: true
    
                ListView {
                    id: listItems
                    anchors.left : parent.left
                    anchors.right : parent.right
                    anchors.top : parent.top
                    orientation: Qt.Vertical
                    anchors.margins: 2
                    height: {
                        //Only count displayed items
                        var visibleItemsCount = 0
                        for (var i = 0; i < listItems.model.length; i++) {
                            var isVisible = true;
                            if (listItems.model[i].binding("Visible") !== undefined)
                                visible = listItems.model[i].binding("Visible");
    
                            if (isVisible) visibleItemsCount = visibleItemsCount + 1
                        }
    
                        return CommonScript.min(maxRowsCount, visibleItemsCount) * root.rowHeight
                    }
                    property int selectedIndex: -1
                    model: root.model
    
                    delegate: Rectangle {
                        id: recItemContainer
                        width: parent.width
                        height: bVisible ? root.rowHeight : 0
                        clip: true
    
                        property var itemModel: model
                        property bool bVisible: CommonScript.jsBinding(recItemContainer, "bVisible", model ? model.modelData : undefined, "Visible", true)
                        property bool bEnable: CommonScript.jsBinding(recItemContainer, "bEnable", model ? model.modelData : undefined, "Enable", true)
                        property bool isHovered: false
    
                        Rectangle {
                            id: recHilight
                            color: "#663399FF"
                            anchors.fill: parent
                            anchors.margins: 0
                            radius: 2
                            visible: index === listItems.selectedIndex
                        }
    
                        Rectangle {
                            id: recDisabled
                            color: "#666666"
                            anchors.fill: parent
                            anchors.margins: 0
                            radius: 2
                            visible: false //!recItemContainer.bEnable
                        }
    
                        Rectangle {
                            id: recItemHover
                            color: "#333399FF"
                            anchors.fill: parent
                            anchors.margins: 1
                            radius: 2
                            visible: itemMouseArea.containsMouse
                        }
    
                        Loader {
                            id: listItemLoader
                            sourceComponent: listItem
                            //anchors.fill: parent
                            property var model: recItemContainer.itemModel
    
                            onLoaded: {
                                adjustPopupWidth();
                            }
    
                            function adjustPopupWidth()
                                {
                                var pWidth = CommonScript.max(listItemLoader.item.width, root.dropDownMinWidth)
                                if (pWidth > privateProperties.popupWidth) privateProperties.popupWidthAutoAdjusted = true
                                pWidth = CommonScript.max(pWidth, privateProperties.popupWidth)
                                pWidth = CommonScript.min(pWidth, root.dropDownMaxWidth)
    
                                root.rowHeight = CommonScript.max(listItemLoader.item.height, root.rowHeight)
                                privateProperties.popupWidth = pWidth
                                if (listItemLoader.width == 0) listItemLoader.width = pWidth
                            }
                        }
    
                        MouseArea {
                            id: itemMouseArea
                            anchors.fill: parent
                            enabled: recItemContainer.bEnable
                            hoverEnabled: true
                            onClicked: {
                                if (recItemContainer.bEnable) {
                                    if (listItems.selectedIndex != index) {
                                        listItems.selectedIndex = index
                                        listItems.updateSelection()
                                        activated(index)
                                    }
                                    root.popupVisible = false
                                }
                                return true
                            }
                        }
                    }
    
                    onCurrentIndexChanged: {
                        updateSelection()
                    }
    
                    function updateSelection ()
                    {
                        displayText = ""
                        root.currentIndex = listItems.selectedIndex
                        root.selectedValue = listItems.model[listItems.selectedIndex].binding(root.valueMember)
                        root.selectedItem = listItems.model[listItems.selectedIndex]
                    }
                }
            }
        }
    
        property Component listItem:
            Rectangle {
            width: recImage.width + lblItemText.contentWidth + 2 * lblItemText.anchors.leftMargin
            height: MDStyle.textBoxHeight
    
            Rectangle {
                id: recImage
                width: parent.height
                height: parent.height
                anchors.top: parent.top
                anchors.left: parent.left
                color: "transparent"
                visible: root.showIcon
    
                Image {
                    id: imgItemIcon
                    width: sourceSize.width
                    height: sourceSize.height
                    anchors.centerIn: parent
                    source: CommonScript.jsBinding(imgItemIcon, "source", model ? model.modelData : undefined, imageSourceMember, "")
                }
            }
    
            Label {
                id: lblItemText
                anchors.left: root.showIcon ? recImage.right : parent.left
                anchors.right: parent.right
                anchors.verticalCenter: parent.verticalCenter
                anchors.leftMargin: font.pixelSize / 2
                font.family: MDStyle.fontFamily
                font.pixelSize: MDStyle.textFontSize
                property bool bEnable: CommonScript.jsBinding(lblItemText, "bEnable", model ? model.modelData : undefined, "Enable", true)
                color: bEnable ? MDStyle.fontColor : MDStyle.fontColorDisable
                clip: true
                text: CommonScript.jsBinding(lblItemText, "text", model ? model.modelData : undefined, displayMember, "")
            }
        }
    
        onCurrentIndexChanged: {
            if (listItems.selectedIndex != currentIndex) listItems.selectedIndex = currentIndex
        }
    
        onSelectedValueChanged: {
            updateCurrentIndex()
        }
    
        onModelChanged: {
            updateCurrentIndex()
        }
    
        function updateCurrentIndex() {
            if (!selectedValue) {
                displayText = CommonScript.VariesText
            }
            else if (selectedValue === undefined) {
                console.log("Selected Value is undefined")
            }
            else {
                console.log("Selected Value: " + selectedValue)
            }
    
            displayText = ""
    
            if (root.currentIndex >= 0) {
                if (model[root.currentIndex].binding(valueMember) === selectedValue) return;
            }
    
            for (var i = 0; i < model.length; i++) {
                if (model[i].binding(valueMember) === selectedValue) {
                    root.currentIndex = i;
                    return;
                }
            }
        }
    }