'How do I set the ComboBox width to fit the largest item

I would like that my ComboBox has to adapt its width to the longest String Item of my list.

Code Example:

ComboBox {
    model: [ "Banana", "Apple", "ThisIsTheLongestWordThatIHave,"Coconut" ]
}

Any idea of how to do it?



Solution 1:[1]

There is no built-in mechanism for this in Quick-Controls-2 combobox (at the time of writing, Qt 5.9), so you have to do it yourself. Something like this...

main.qml

MyComboBox {
    id: comboBox1
    sizeToContents: false
    model: [ "Banana", "Apple", "ThisIsTheLongestWordThatIHave", "Coconut" ]
}

MyComboBox {
    id: comboBox2
    anchors.top: comboBox1.bottom
    sizeToContents: true
    model: [ "Banana", "Apple", "ThisIsTheLongestWordThatIHave", "Coconut" ]
}

MyComboBox.qml

ComboBox {
    id: control

    property bool sizeToContents
    property int modelWidth

    width: (sizeToContents) ? modelWidth + 2*leftPadding + 2*rightPadding : implicitWidth

    delegate: ItemDelegate {
        width: control.width
        text: control.textRole ? (Array.isArray(control.model) ? modelData[control.textRole] : model[control.textRole]) : modelData
        font.weight: control.currentIndex === index ? Font.DemiBold : Font.Normal
        font.family: control.font.family
        font.pointSize: control.font.pointSize
        highlighted: control.highlightedIndex === index
        hoverEnabled: control.hoverEnabled
    }

    TextMetrics {
        id: textMetrics
    }

    onModelChanged: {
        textMetrics.font = control.font
        for(var i = 0; i < model.length; i++){
            textMetrics.text = model[i]
            modelWidth = Math.max(textMetrics.width, modelWidth)
        }
    }
}

Note that if you change the model type from a QML List to a different type, such as C++ QStringList, QList<QObject*> or QAbstractListModel, then you migth need to modify this line textMetrics.text = model[i] to retrieve the text from the model items in a slightly different way.

Solution 2:[2]

As of Qt 6, this is now possible by setting the ComboBox's implicitContentWidthPolicy to either ComboBox.WidestText , which will update whenever the model changes, or ComboBox.WidestTextWhenCompleted, which will check just once, when the ComboBox is loaded. (Keep in mind that the latter might not work as expected if the model isn't already available at the instant the ComboBox is loaded.)

Solution 3:[3]

@Mark Ch - MyComboBox doesn't work with Controls 2; the width of the indicator is not taken into account so it is too narrow if the indicator has any width.

It worked for me by replacing the assignment for width: with the following:

width: sizeToContents
             ? (modelWidth + leftPadding + contentItem.leftPadding
                           + rightPadding + contentItem.rightPadding)
             : implicitWidth

Solution 4:[4]

Here's a different approach which is less dependent on internals, works with any kind of model, and with alternate ComboBox styles e.g. "material":

The idea is to just set currentItem to each possible value and let the ComboBox internals do their thing; then observe the resulting widths. ComboBox.contentItem is a TextField, and TextField.contentWidth has what we want. We don't have to know how to iterate the model or emulate what a delegate might do to change formatting. The desired ComboBox width is the max of those contentWidths, plus padding and indicator width.

The calculation can not be directly bound to width because a binding loop would occur. Instead, width is calculated and set statically when the onCompleted signal occurs.

Note: The following code doesn't yet handle dynamically updated models. I may update this post later...

USAGE:

import QtQuick 2.9
import QtQuick.Controls 2.2
import "ComboBoxHacks.js" as CBH
...
ComboBox {
  id: myCB
  Component.onCompleted: width = CBH.calcComboBoxImplicitWidth(myCB)
  ...
}

And here is the javascript code:

/* ComboBoxHacks.js */
function calcComboBoxImplicitWidth(cb) {
  var widest = 0
  if (cb.count===0) return cb.width
  var originalCI = cb.currentIndex
  if (originalCI < 0) return cb.width // currentIndex ?  deleted item
  do {
    widest = Math.max(widest, cb.contentItem.contentWidth)
    cb.currentIndex = (cb.currentIndex + 1) % cb.count
  } while(cb.currentIndex !== originalCI)

  return widest + cb.contentItem.leftPadding + cb.contentItem.rightPadding
                + cb.indicator.width
}

Solution 5:[5]

You just need to update the minimumWidth when the model changes.

import QtQml 2.12
import QtQuick 2.12
import QtQuick.Controls 2.5
import QtQuick.Layouts 1.12

ComboBox {
    id: root

    onModelChanged: {
        var _maxWidth = 0
        for(var i = 0; i < model.length; i++){
            // TextMetrics does not work with Material Style
            _maxWidth = Math.max((model[i].length+1)*Qt.application.font.pixelSize, _maxWidth)
        }
        Layout.minimumWidth = _maxWidth + implicitIndicatorWidth + leftPadding + rightPadding
    }
}

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2 CrazyChucky
Solution 3 jimav
Solution 4
Solution 5 Patrick José Pereira