How do I set the ComboBox width to fit the largest item
Asked Answered
G

5

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?

Geographical answered 11/7, 2017 at 9:2 Comment(2)
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?Split
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.Geographical
T
6

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

Trochophore answered 7/3, 2022 at 22:36 Comment(0)
E
5

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.

Eburnation answered 12/7, 2017 at 6:28 Comment(3)
Thanks, I need something like that. I am currently using a C++ model but I might find a solution thanks to your helpGeographical
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 changedEburnation
If your ComboBox is set into a layout, do not forget to add Layout.preferredWidth: width to the combo box properties.Prunella
S
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
Selfcentered answered 2/2, 2019 at 22:3 Comment(0)
S
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
}
Selfcentered answered 2/2, 2019 at 22:41 Comment(0)
C
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
    }
}
Cuirbouilli answered 20/9, 2019 at 20:14 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.