ButtonGroup
Overview
Section titled “Overview”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>Properties
Section titled “Properties”| 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.
Methods
Section titled “Methods”| 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. |
UIA mechanism
Section titled “UIA mechanism”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).
Web deltas
Section titled “Web deltas”NaviusButtonGroupItemderives fromButtonBasedirectly instead of theNaviusButtonprimitive, 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.