DomUI

ComboFixed2

ComboFixed2<T> is a drop-down over a list of values you state yourself, each with the label to show for it.

ComboFixed2<String> medium = new ComboFixed2<>(List.of(
    new ValueLabelPair<>("cd", "Compact disc"),
    new ValueLabelPair<>("lp", "Vinyl LP"),
    new ValueLabelPair<>("dl", "Download")
));

FormBuilder fb = new FormBuilder(cp);
fb.label("Medium").control(medium);

Value and label

The list is a List<ValueLabelPair<T>>: the pair carries the value the control hands back and the text the user reads. That is the whole difference with ComboLookup2, which takes the values themselves and works out their labels from metadata.

ComboFixed2<T> therefore has two types inside it - it extends ComboComponentBase2<ValueLabelPair<T>, T> - but from the outside it is an IControl<T>: getValue() hands back a T.

Building one from an enum

ComboFixed2<Medium> medium = ComboFixed2.createEnumCombo(Medium.class);
Method What you get
createEnumCombo(Class, exceptions...) every constant except the exceptions, sorted by label
createEnumCombo(Class, boolean sorted, exceptions...) the same, in declaration order when sorted is false
createEnumCombo(List<T>) / createEnumCombo(T...) exactly these constants, in that order
createCombo(T... items) any objects at all, labelled by their domain label or toString()
createCombo(List<T>, QField<T,?> labelField) any objects, labelled by one of their properties
createComboFor(PropertyMetaModel, editable) a combo for a property, from its domain values and metadata

The enum labels come from the .properties file next to the enum (Vinyl.label=Vinyl LP). A combo built from a property can do better: it uses that property's own bundle first, so the same enum value can read differently in two places.

The empty choice

A combo that is not mandatory renders an extra, empty option at the top - that is how it can hand back null. What that option says is up to you:

combo.setEmptyText("- pick a medium -");

A mandatory combo renders the empty option only while it has no valid value at the moment it is built: build it with a value and the user cannot un-choose. Picking a value in the browser does not take the empty choice away - the option list is only rewritten when the control is rebuilt. getValue() on an empty mandatory combo reports Mandatory field and throws.

The rest of the control

Method What it does
setValue(T) / getValue() the value; it must be one of the list
setData(List<ValueLabelPair<T>>) replace the whole list (this rebuilds the control)
setMandatory(boolean) see above; it rebuilds, because the empty option changes
setReadOnly(boolean) renders as the label of its value, no select element at all
setDisabled(boolean) / setDisabledBecause(String) a greyed-out select
setOnValueChanged(...) called when another option is picked
immediate() post the change even without a handler
addExtraButton(icon, title, click) a small button right of the combo
setRenderer(IRenderInto<ValueLabelPair<T>>) draw the options yourself instead of showing the label

A read-only combo is worth calling out: it is not a disabled select but plain text, so a read-only form reads as a form rather than as a wall of grey boxes.

From a property

fb.property(shipment, Shipment_.state()).control() gives a ComboFixed2 for a boolean or an enum property with more than five domain values, and a RadioGroup for one with five or fewer. Asking for control(ComboFixed2.class) overrides the count. A boolean property gets Yes/No from DomUI's own bundle, and a primitive boolean one is made mandatory as well - it has no third state to offer.

Last modified: 2026/09/02