Skip to content

[EditableComboBox] Add grouping support#591

Open
Xavier Fortin (xfortin-devolutions) wants to merge 7 commits into
masterfrom
EditableComboBox-grouping-support
Open

[EditableComboBox] Add grouping support#591
Xavier Fortin (xfortin-devolutions) wants to merge 7 commits into
masterfrom
EditableComboBox-grouping-support

Conversation

@xfortin-devolutions

@xfortin-devolutions Xavier Fortin (xfortin-devolutions) commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds optional item grouping to EditableComboBox. Drop-down items can be grouped under headers, with a customizable group order and an empty group key that renders headerless (its items appear at the top with no header).

The feature reuses the proven GroupedComboBox design (interleave non-selectable header pseudo-items into the drop-down; empty group key renders headerless) rather than inventing a new one. Grouping is inert unless a group selector is configured, so existing EditableComboBox usage is unaffected.

What changed

Shared grouping core (combo-box-scoped names, since only the combo box controls use them)

  • Controls/ComboBoxGroupHeader.cs (new) — the header pseudo-item, extracted from GroupedComboBox so both controls share it.
  • Helpers/ComboBoxGroupedItemsBuilder.cs (new) — the group → order → interleave logic, extracted from GroupedComboBox.RebuildDisplayItems. GroupedComboBox now delegates to it (behavior-preserving).

EditableComboBox

  • New properties mirroring GroupedComboBox: GroupBinding / GroupSelector, GroupOrderAlphabetical, GroupOrderSelector / GroupOrderBinding, HeaderForeground, HeaderMargin, HeaderTemplate, EmptyGroupName.
  • Headers are built into the private drop-down list (filteredItems) — the public ItemsSource is never mutated.
  • Filter mode re-groups the filtered subset, so groups with no matches drop out along with their headers.
  • Header-safety: headers can't be selected, matched by text, or committed as a value; Down-arrow-on-open skips a leading header; a pointer click on a header is a no-op that keeps the drop-down open (matches GroupedComboBox).
  • InnerComboBox renders the reused GroupedComboBoxHeaderItem container for header rows (no new per-theme styling).

Sample

  • EditableComboBoxDemo gains a Grouping section mirroring the GroupedComboBox demo set (named groups, empty group with/without EmptyGroupName, scrolling/virtualized, HeaderTemplate, inner slots) plus a Live-tests panel. Page wrapped in a ScrollViewer so it scrolls.

Notes / intentional differences from GroupedComboBox

  • No inline "Pure XAML" header formEditableComboBox grouping is data-driven (GroupBinding/GroupSelector), so headers aren't declared inline as items.
  • Items use EditableComboBoxDataTemplate with SelectedItemValue so the editable textbox shows a clean value.
  • Not yet included: live re-grouping on per-item PropertyChanged (collection add/remove is reactive; editing an item's group key updates on next open). Can be added if needed.

Testing

  • Full solution builds clean (0 errors, no new warnings).
  • PageCatalogTests + MainWindowNavigationTests pass (demo page + view model instantiate in all themes).
  • Manually verified in the SampleApp (MacClassic): grouped headers render and are non-selectable, keyboard skips them, headerless empty group sits at the top, filter drops empty groups, header clicks keep the drop-down open, selection still works.

⚠️ The EditableComboBoxDemo visual-regression baselines need regenerating — the page content changed, so it affects every theme it's tested in (MacClassic, LiquidGlass, DevExpress, Linux). Baselines are stored per-OS: the macOS set can be regenerated locally (UPDATE_BASELINES=true ./devtest --filter EditableCombo, one run covers all those themes); the Windows and Linux baseline sets are regenerated on their respective CI runners.

@xfortin-devolutions Xavier Fortin (xfortin-devolutions) changed the title [EditableComboBox] Add grouping support (DEVEX-402) [EditableComboBox] Add grouping support Jul 16, 2026
Rename GroupHeader -> ComboBoxGroupHeader and GroupedItemsBuilder ->
ComboBoxGroupedItemsBuilder to scope these shared, combo-box-only helpers
and avoid implying a general-purpose grouping utility.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds optional grouping support to EditableComboBox using shared grouping infrastructure from GroupedComboBox.

Changes:

  • Extracts shared group-header and item-building logic.
  • Adds grouping, ordering, templating, filtering, and selection safeguards.
  • Expands the SampleApp grouping demonstrations.

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
src/Devolutions.AvaloniaControls/README.md Documents grouping support.
src/Devolutions.AvaloniaControls/Helpers/ComboBoxGroupedItemsBuilder.cs Builds grouped display lists.
src/Devolutions.AvaloniaControls/Controls/GroupedComboBox/GroupedComboBox.axaml.cs Uses shared grouping infrastructure.
src/Devolutions.AvaloniaControls/Controls/EditableComboBox/InnerComboBox.cs Creates and styles header containers.
src/Devolutions.AvaloniaControls/Controls/EditableComboBox/EditableComboBox.axaml.cs Implements grouping behavior and APIs.
src/Devolutions.AvaloniaControls/Controls/ComboBoxGroupHeader.cs Defines shared header pseudo-items.
samples/SampleApp/ViewModels/EditableComboBoxViewModel.cs Supplies grouping demo data and commands.
samples/SampleApp/DemoPages/EditableComboBoxDemo.axaml Adds grouping demonstrations and live tests.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread samples/SampleApp/DemoPages/EditableComboBoxDemo.axaml

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 8 out of 8 changed files in this pull request and generated 1 comment.

GroupOrderBinding's getter was built against the item type but invoked
with the group-key string, so a path binding like {Binding Group.SortOrder}
threw InvalidCastException; it also formatted the value to a string, sorting
numeric orders lexicographically (10 before 2).

The shared builder now evaluates the order selector against a representative
item of each group and keeps the raw value (BuildRawGetter), so it works for
any IComparable and sorts numerically. The key-based GroupOrderSelector
delegate is adapted onto the item, so its behavior is unchanged. Applied to
both EditableComboBox and GroupedComboBox, and documented on the
GroupOrderBinding property.
Model the sample food data with a shared FoodGroup(Name, SortOrder) that
items reference, and add an "Order via binding" case (GroupOrderBinding
bound to Group.SortOrder) to both the EditableComboBox and GroupedComboBox
grouping demos.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 11 out of 11 changed files in this pull request and generated 2 comments.

Comment on lines +496 to +500
<EditableComboBox.ItemTemplate>
<EditableComboBoxDataTemplate x:DataType="viewModels:FoodItem" SelectedItemValue="{Binding Name}">
<TextBlock Text="{Binding Name}" />
</EditableComboBoxDataTemplate>
</EditableComboBox.ItemTemplate>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's good to be able to supply template (and kinda required for any ItemsControl), but we should also support ItemsControl's DisplayMemberBinding property, that would be used as a fallback when there's no template (both for the item in the dropdown and the displayed value; if there's a template but no SelectedItemValue binding then it would use that too).

In fact, wouldn't that render SelectedItemValue redundant / unneeded?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But since that's already like this, that would be a separate task.

else if (this.innerComboBox.SelectedIndex == -1)
{
this.innerComboBox.SelectedIndex = 0;
// Skip a leading group header so the first arrow-down lands on a selectable item.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't that be just when using grouping?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

3 participants