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 groundI also look in original ComboBox qml file and notice about FocusScope, Menu, Popup but doesn't know how it work.
Please help me ! ASAP !!!
- Drop down Item must be same style on Windows & Mac
-
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; } } } }