Skip to content

ButtonGroup

ButtonGroup is a segmented button row: an ItemsControl whose children are NaviusButtonGroupItem instances placed directly by the consumer, a plain UIElement collection like a StackPanel’s Children, not a data-bound ItemsSource. Every item renders as a plain square segment; the single rounded silhouette comes from a Grid.Clip on the group’s own container (a RoundedClipConverter multi-binding reading the container’s live size and its CornerRadius token), not from per-item corner math. Each item still needs to know whether it is the last one in its Orientation, so its own trailing edge grows a second hairline border (every other item’s single border already becomes the shared divider with its next sibling).

xmlns:buttonGroup="clr-namespace:Navius.Wpf.Ui.ButtonGroup;assembly=Navius.Wpf.Ui"
<buttonGroup:NaviusButtonGroup>
<buttonGroup:NaviusButtonGroupItem Content="Cut" />
<buttonGroup:NaviusButtonGroupItem Content="Copy" />
<buttonGroup:NaviusButtonGroupItem Content="Paste" />
</buttonGroup:NaviusButtonGroup>
Property Type Default From Description
Orientation System.Windows.Controls.Orientation Horizontal NaviusButtonGroup Direction of the segment row. Registered via RegisterAttached with Inherits (plus GetOrientation/SetOrientation accessors, alongside the instance CLR property), so the border-mask trigger reads the same value through property value inheritance from any item — Register-applied Inherits never propagates beyond the owner type.
Items ItemCollection empty ItemsControl (NaviusButtonGroup) Populated directly with NaviusButtonGroupItem instances.
Content object - ContentControl (NaviusButtonGroupItem, via ButtonBase) The segment’s label.

An attached, read-only IsLastItem boolean (NaviusButtonGroup.GetIsLastItem) is stamped onto each item by the group itself, not set by the consumer; it drives the border-thickness triggers in Themes/ButtonGroup.xaml that add the trailing hairline only on the last segment of each orientation.

The attached registration of Orientation is a 1.0.0-preview.6 fix (the same gap fixed for NaviusSidebar.IsCollapsed): the property was previously Register-applied, whose Inherits metadata only resolves on the owner type, so items always read the Horizontal default and the vertical divider trigger (non-last items switching BorderThickness from 1,1,0,1 to 1,1,1,0, moving the open divider edge from right to bottom) never fired — vertical groups rendered with the horizontal border pattern, doubling the hairlines between stacked segments. Covered by NaviusButtonGroup_Orientation_InheritsToRealizedItems and NaviusButtonGroup_VerticalOrientation_MovesNonLastItemDividerToBottomEdge in UiButtonGroupTests.cs.

Method Description
NaviusButtonGroup.IsLast(index, count) Pure position computation (index == count - 1, false when count <= 0), split out for direct unit testing without a container generator.
NaviusButtonGroup.GetIsLastItem(DependencyObject) Reads the attached, read-only IsLastItem value stamped on a realized item container.

NaviusButtonGroup does not override OnCreateAutomationPeer and falls back to ItemsControl’s own peer, ItemsControlAutomationPeer, reporting AutomationControlType.List. NaviusButtonGroupItem derives directly from ButtonBase rather than Button (so it can carry its own template without the NaviusButton primitive’s hardcoded corner radius), and ButtonBase itself does not supply an automation peer; only concrete subclasses such as Button do, so the item ships its own NaviusButtonGroupItemAutomationPeer (a FrameworkElementAutomationPeer implementing IInvokeProvider), reporting AutomationControlType.Button. A disabled item throws ElementNotEnabledException from Invoke; otherwise Invoke queues activation onto the item’s dispatcher (DispatcherPriority.Input), matching the UIA Invoke contract’s requirement to return immediately, and runs it through ButtonBase.OnClick rather than a bare Click raise, so a bound Command executes and not just the Click event. The unit suite (UiButtonGroupTests.cs) exercises the Button control type, the Invoke pattern’s presence, the disabled-throws behavior, and that an enabled Invoke raises Click and executes a bound Command exactly once the dispatcher queue is pumped; it also covers the position math (ButtonGroup_IsLast_*) and container stamping (ButtonGroup_PrepareContainer_StampsIsLastItem in tests/Navius.Wpf.Tests/UiCompositeTests.cs). The M6 audit found the RoundedClipConverter multi-binding clean (inherently one-way, no template-part lookup, no animation).

  • NaviusButtonGroupItem derives from ButtonBase directly instead of the NaviusButton primitive, because it needs its own template rather than the primitive’s hardcoded full-radius corner treatment; the group’s outer clip supplies the rounding instead.