9

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?

Ed Nio
  • 593
  • 2
  • 11
  • 27
  • Is the model rather static or dynamic? (e.g. will the entries appear and disappear over the lifetime of your `ComboBox`?) How frequently? If the longest word is removed, do you expect the `ComboBox` to shrink again, or only grow if a longer word is appended? – derM - not here for BOT dreams Jul 11 '17 at 09:22
  • The model is defined by a XML file set by the user. User will have to restart the software if he wants to change it. So there is no dynamic add/delete. – Ed Nio Jul 11 '17 at 09:56

5 Answers5

4

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.

Mark Ch
  • 2,840
  • 1
  • 19
  • 31
  • Thanks, I need something like that. I am currently using a C++ model but I might find a solution thanks to your help – Ed Nio Jul 12 '17 at 14:05
  • delegates sometimes use a different font to the rest of the control, so i've added a redefinition of the delegate to make the fonts the same, and also to set the TextMetrics font, just in case the Combobox font has been changed – Mark Ch Jul 13 '17 at 06:05
  • If your `ComboBox` is set into a layout, do not forget to add `Layout.preferredWidth: width` to the combo box properties. – air-dex Jan 22 '19 at 01:11
4

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.)

CrazyChucky
  • 3,263
  • 4
  • 11
  • 25
1

@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
jimav
  • 671
  • 6
  • 16
1

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
}
jimav
  • 671
  • 6
  • 16
1

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
    }
}
Patrick José Pereira
  • 1,613
  • 2
  • 11
  • 12