diff --git a/samples/ControlCatalog/Pages/CommandBar/CommandBarDynamicOverflowPage.xaml b/samples/ControlCatalog/Pages/CommandBar/CommandBarDynamicOverflowPage.xaml
index 6d66c19c96..f3098dee0f 100644
--- a/samples/ControlCatalog/Pages/CommandBar/CommandBarDynamicOverflowPage.xaml
+++ b/samples/ControlCatalog/Pages/CommandBar/CommandBarDynamicOverflowPage.xaml
@@ -8,6 +8,7 @@
M15.6,10.79C17.04,10.07 18,8.64 18,7C18,4.79 16.21,3 14,3H7V21H14.73C16.78,21 18.5,19.37 18.5,17.32C18.5,15.82 17.72,14.53 16.5,13.77C16.2,13.59 15.9,13.44 15.6,13.32V10.79M10,6.5H13C13.83,6.5 14.5,7.17 14.5,8C14.5,8.83 13.83,9.5 13,9.5H10V6.5M13.5,17.5H10V14H13.5C14.33,14 15,14.67 15,15.5C15,16.33 14.33,17.5 13.5,17.5Z
M10,4V7H12.21L8.79,15H6V18H14V15H11.79L15.21,7H18V4H10Z
M19,4H15.5L14.5,3H9.5L8.5,4H5V6H19M6,19A2,2 0 0,0 8,21H16A2,2 0 0,0 18,19V7H6V19Z
+ M14,2H6A2,2 0 0,0 4,4V20A2,2 0 0,0 6,22H18A2,2 0 0,0 20,20V8L14,2M18,20H6V4H13V9H18V20M16,11V18.1L13.9,16L11.1,18.8L8.3,16L11.1,13.2L9,11.1L16,11Z
@@ -33,6 +34,18 @@
IsChecked="True"
IsCheckedChanged="OnDynamicOverflowChanged" />
+
+
+
+
+
+
@@ -47,11 +60,11 @@
-
-
+
+
+
+
+
+
+
diff --git a/samples/ControlCatalog/Pages/CommandBar/CommandBarDynamicOverflowPage.xaml.cs b/samples/ControlCatalog/Pages/CommandBar/CommandBarDynamicOverflowPage.xaml.cs
index 45a1c95527..19a451b55c 100644
--- a/samples/ControlCatalog/Pages/CommandBar/CommandBarDynamicOverflowPage.xaml.cs
+++ b/samples/ControlCatalog/Pages/CommandBar/CommandBarDynamicOverflowPage.xaml.cs
@@ -9,7 +9,13 @@ namespace ControlCatalog.Pages
public CommandBarDynamicOverflowPage()
{
InitializeComponent();
+ if (SecondaryVisibleCheck.IsChecked != true)
+ {
+ DemoBar.SecondaryCommands?.Remove(DemoSecondaryCommand);
+ }
+
((INotifyCollectionChanged)DemoBar.OverflowItems).CollectionChanged += OnOverflowChanged;
+ ((INotifyCollectionChanged)DemoBar.VisiblePrimaryCommands).CollectionChanged += OnOverflowChanged;
UpdateStatus();
}
@@ -29,6 +35,26 @@ namespace ControlCatalog.Pages
DemoBar.IsDynamicOverflowEnabled = DynamicOverflowCheck.IsChecked == true;
}
+ private void OnSecondaryVisibilityChanged(object? sender, RoutedEventArgs e)
+ {
+ if (DemoBar?.SecondaryCommands == null || DemoSecondaryCommand == null)
+ return;
+
+ bool shouldInclude = SecondaryVisibleCheck.IsChecked == true;
+ bool isIncluded = DemoBar.SecondaryCommands.Contains(DemoSecondaryCommand);
+
+ if (shouldInclude && !isIncluded)
+ {
+ DemoBar.SecondaryCommands.Add(DemoSecondaryCommand);
+ }
+ else if (!shouldInclude && isIncluded)
+ {
+ DemoBar.SecondaryCommands.Remove(DemoSecondaryCommand);
+ }
+
+ UpdateStatus();
+ }
+
private void OnOverflowChanged(object? sender, NotifyCollectionChangedEventArgs e)
{
UpdateStatus();
@@ -36,10 +62,40 @@ namespace ControlCatalog.Pages
private void UpdateStatus()
{
- var total = DemoBar.PrimaryCommands.Count;
- var overflow = DemoBar.OverflowItems.Count;
- var visible = total - overflow;
- StatusText.Text = $"Showing {visible} of {total} commands, {overflow} in overflow";
+ int visiblePrimaryCommandCount = 0;
+ int visiblePrimarySeparatorCount = 0;
+ foreach (var item in DemoBar.VisiblePrimaryCommands)
+ {
+ if (item is CommandBarSeparator)
+ visiblePrimarySeparatorCount++;
+ else
+ visiblePrimaryCommandCount++;
+ }
+
+ int overflowCommandCount = 0;
+ int overflowSeparatorCount = 0;
+ bool hasSyntheticOverflowDivider = false;
+ foreach (var item in DemoBar.OverflowItems)
+ {
+ if (item is CommandBarSeparator separator)
+ {
+ overflowSeparatorCount++;
+ if (!DemoBar.PrimaryCommands.Contains(separator) &&
+ !DemoBar.SecondaryCommands.Contains(separator))
+ {
+ hasSyntheticOverflowDivider = true;
+ }
+ }
+ else
+ {
+ overflowCommandCount++;
+ }
+ }
+
+ StatusText.Text =
+ $"Visible primary: {visiblePrimaryCommandCount} commands, {visiblePrimarySeparatorCount} separators\n" +
+ $"Overflow items: {overflowCommandCount} commands, {overflowSeparatorCount} separators\n" +
+ $"Synthetic overflow divider: {(hasSyntheticOverflowDivider ? "present" : "absent")}";
}
}
}
diff --git a/src/Avalonia.Controls/CommandBar/CommandBar.cs b/src/Avalonia.Controls/CommandBar/CommandBar.cs
index d8047f9d9e..6b92a935ad 100644
--- a/src/Avalonia.Controls/CommandBar/CommandBar.cs
+++ b/src/Avalonia.Controls/CommandBar/CommandBar.cs
@@ -7,6 +7,7 @@ using Avalonia.Controls.Metadata;
using Avalonia.Controls.Primitives;
using Avalonia.Interactivity;
using Avalonia.Metadata;
+using Avalonia.Reactive;
using Avalonia.Threading;
namespace Avalonia.Controls
@@ -136,6 +137,8 @@ namespace Avalonia.Controls
private readonly ObservableCollection _visiblePrimaryCommands = new();
private readonly ObservableCollection _overflowItems = new();
+ private readonly CommandBarSeparator _overflowPrimarySecondarySeparator = new();
+ private readonly CompositeDisposable _secondaryCommandVisibilitySubscriptions = new();
private bool _isDynamicUpdateInProgress;
private double _constraintWidth = double.PositiveInfinity;
private bool _openedViaKeyboard;
@@ -151,6 +154,7 @@ namespace Avalonia.Controls
var secondaryCommands = new ObservableCollection();
SetCurrentValue(SecondaryCommandsProperty, (IList)secondaryCommands);
+ RebuildSecondaryCommandVisibilitySubscriptions();
SizeChanged += CommandBar_SizeChanged;
}
@@ -386,6 +390,18 @@ namespace Avalonia.Controls
UpdateDynamicOverflow();
}
+ protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
+ {
+ base.OnAttachedToVisualTree(e);
+ RebuildSecondaryCommandVisibilitySubscriptions();
+ }
+
+ protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
+ {
+ _secondaryCommandVisibilitySubscriptions.Clear();
+ base.OnDetachedFromVisualTree(e);
+ }
+
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
base.OnPropertyChanged(change);
@@ -439,6 +455,7 @@ namespace Avalonia.Controls
oldSecondary.CollectionChanged -= OnSecondaryCommandsChanged;
if (change.NewValue is INotifyCollectionChanged newSecondary)
newSecondary.CollectionChanged += OnSecondaryCommandsChanged;
+ RebuildSecondaryCommandVisibilitySubscriptions();
UpdateDynamicOverflow();
}
}
@@ -583,6 +600,7 @@ namespace Avalonia.Controls
private void OnSecondaryCommandsChanged(object? sender, NotifyCollectionChangedEventArgs e)
{
+ RebuildSecondaryCommandVisibilitySubscriptions();
UpdateDynamicOverflow();
}
@@ -598,12 +616,9 @@ namespace Avalonia.Controls
{
_visiblePrimaryCommands.Clear();
_overflowItems.Clear();
+ SetOverflowMode(_overflowPrimarySecondarySeparator, false);
- foreach (var item in SecondaryCommands)
- {
- SetOverflowMode(item, true);
- _overflowItems.Add(item);
- }
+ var overflowedPrimaryCommands = new List();
var availableWidth = double.IsFinite(_constraintWidth) ? _constraintWidth : Bounds.Width;
@@ -669,8 +684,6 @@ namespace Avalonia.Controls
? a.Order.CompareTo(b.Order)
: a.Index.CompareTo(b.Index));
- // Separators stay in the primary bar but are not counted toward maxItems.
- // If no non-separator buttons fit, separators are moved to overflow too.
var visibleIndices = new HashSet();
int nonSeparatorCount = 0;
for (var i = 0; i < prioritized.Count; i++)
@@ -688,6 +701,8 @@ namespace Avalonia.Controls
if (nonSeparatorCount == 0)
visibleIndices.Clear();
+ TrimOrphanedSeparatorsFromVisibleCommands(PrimaryCommands, visibleIndices);
+
for (var i = 0; i < PrimaryCommands.Count; i++)
{
if (visibleIndices.Contains(i))
@@ -695,15 +710,20 @@ namespace Avalonia.Controls
SetOverflowMode(PrimaryCommands[i], false);
_visiblePrimaryCommands.Add(PrimaryCommands[i]);
}
+ else if (PrimaryCommands[i] is CommandBarSeparator)
+ {
+ SetOverflowMode(PrimaryCommands[i], false);
+ }
else
{
SetOverflowMode(PrimaryCommands[i], true);
- _overflowItems.Add(PrimaryCommands[i]);
+ overflowedPrimaryCommands.Add(PrimaryCommands[i]);
}
}
}
}
+ AddOverflowItems(overflowedPrimaryCommands);
HasSecondaryCommands = _overflowItems.Count > 0;
UpdateOverflowButtonVisibility();
}
@@ -731,6 +751,8 @@ namespace Avalonia.Controls
if (SecondaryCommands != null)
foreach (var cmd in SecondaryCommands)
ApplyLabelPositionToElement(cmd);
+
+ ApplyLabelPositionToElement(_overflowPrimarySecondarySeparator);
}
private void ApplyLabelPositionToElement(ICommandBarElement element)
@@ -758,5 +780,121 @@ namespace Avalonia.Controls
_ => HasSecondaryCommands // Auto
};
}
+
+ private void AddOverflowItems(IReadOnlyList overflowedPrimaryCommands)
+ {
+ for (var i = 0; i < overflowedPrimaryCommands.Count; i++)
+ _overflowItems.Add(overflowedPrimaryCommands[i]);
+
+ if (overflowedPrimaryCommands.Count > 0 && HasVisibleElements(SecondaryCommands))
+ {
+ SetOverflowMode(_overflowPrimarySecondarySeparator, true);
+ _overflowItems.Add(_overflowPrimarySecondarySeparator);
+ }
+
+ foreach (var item in SecondaryCommands)
+ {
+ SetOverflowMode(item, true);
+ _overflowItems.Add(item);
+ }
+ }
+
+ private void RebuildSecondaryCommandVisibilitySubscriptions()
+ {
+ _secondaryCommandVisibilitySubscriptions.Clear();
+
+ if (SecondaryCommands is null)
+ return;
+
+ for (var i = 0; i < SecondaryCommands.Count; i++)
+ {
+ if (SecondaryCommands[i] is Avalonia.Visual visual)
+ {
+ bool isInitialValue = true;
+ visual.GetObservable(Avalonia.Visual.IsVisibleProperty)
+ .Subscribe(_ =>
+ {
+ if (isInitialValue)
+ {
+ isInitialValue = false;
+ return;
+ }
+
+ UpdateDynamicOverflow();
+ })
+ .DisposeWith(_secondaryCommandVisibilitySubscriptions);
+ }
+ }
+ }
+
+ private static bool HasVisibleElements(IList commands)
+ {
+ for (var i = 0; i < commands.Count; i++)
+ {
+ if (commands[i] is Avalonia.Visual visual && visual.IsVisible)
+ return true;
+ }
+
+ return false;
+ }
+
+ private static void TrimOrphanedSeparatorsFromVisibleCommands(
+ IList commands, HashSet visibleIndices)
+ {
+ var toRemove = new List();
+ for (var i = 0; i < commands.Count; i++)
+ {
+ if (!visibleIndices.Contains(i) || commands[i] is not CommandBarSeparator)
+ continue;
+
+ bool hasNonSeparatorBefore = FindNonSeparatorInVisibleCommands(
+ commands, visibleIndices, forward: false, startIndex: i - 1, out _);
+ bool hasNonSeparatorAfter = FindNonSeparatorInVisibleCommands(
+ commands, visibleIndices, forward: true, startIndex: i + 1, out _);
+
+ if (!hasNonSeparatorBefore || !hasNonSeparatorAfter)
+ toRemove.Add(i);
+ }
+
+ foreach (var idx in toRemove)
+ visibleIndices.Remove(idx);
+
+ bool previousWasSeparator = false;
+ for (var i = 0; i < commands.Count; i++)
+ {
+ if (!visibleIndices.Contains(i))
+ continue;
+
+ if (commands[i] is CommandBarSeparator)
+ {
+ if (previousWasSeparator)
+ visibleIndices.Remove(i);
+ else
+ previousWasSeparator = true;
+ }
+ else
+ {
+ previousWasSeparator = false;
+ }
+ }
+ }
+
+ private static bool FindNonSeparatorInVisibleCommands(
+ IList commands, HashSet visibleIndices,
+ bool forward, int startIndex, out int foundIndex)
+ {
+ foundIndex = -1;
+ var i = startIndex;
+ while (forward ? i < commands.Count : i >= 0)
+ {
+ if (visibleIndices.Contains(i) && commands[i] is not CommandBarSeparator)
+ {
+ foundIndex = i;
+ return true;
+ }
+ i += forward ? 1 : -1;
+ }
+ return false;
+ }
}
}
diff --git a/tests/Avalonia.Controls.UnitTests/CommandBarTests.cs b/tests/Avalonia.Controls.UnitTests/CommandBarTests.cs
index 1950cff16c..371cbe4d6d 100644
--- a/tests/Avalonia.Controls.UnitTests/CommandBarTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/CommandBarTests.cs
@@ -874,13 +874,17 @@ public class CommandBarItemWidthTests : ScopedTestBase
public void ItemWidthBottom_Controls_HowManyButtonsFit()
{
var cb = CreateWithWidth(300);
- cb.SecondaryCommands!.Add(new CommandBarButton()); // forces overflow button
+ var secondary = new CommandBarButton();
+ cb.SecondaryCommands!.Add(secondary); // forces overflow button
for (int i = 0; i < 4; i++)
cb.PrimaryCommands!.Add(new CommandBarButton());
cb.IsDynamicOverflowEnabled = true;
Assert.Equal(3, cb.VisiblePrimaryCommands.Count);
- Assert.Equal(1, cb.OverflowItems.Count - 1); // -1 for the secondary command
+ Assert.Equal(3, cb.OverflowItems.Count);
+ Assert.IsType(cb.OverflowItems[0]);
+ Assert.IsType(cb.OverflowItems[1]);
+ Assert.Same(secondary, cb.OverflowItems[2]);
}
[Fact]
@@ -1196,6 +1200,309 @@ public class CommandBarOverflowKeyboardTests : ScopedTestBase
}
}
+public class CommandBarSeparatorOverflowTests : ScopedTestBase
+{
+ private static CommandBar CreateWithWidth(double width)
+ {
+ var cb = new CommandBar();
+ cb.Measure(new Size(width, double.PositiveInfinity));
+ return cb;
+ }
+
+ [Fact]
+ public void TrailingSeparator_IsNotLastVisibleItem()
+ {
+ // [Btn, Btn, Sep, Btn] with room for 2 buttons: Sep should NOT trail.
+ var cb = CreateWithWidth(300);
+ cb.SecondaryCommands!.Add(new CommandBarButton());
+ cb.PrimaryCommands!.Add(new CommandBarButton());
+ cb.PrimaryCommands.Add(new CommandBarButton());
+ cb.PrimaryCommands.Add(new CommandBarSeparator());
+ cb.PrimaryCommands.Add(new CommandBarButton());
+ cb.IsDynamicOverflowEnabled = true;
+
+ Assert.IsNotType(cb.VisiblePrimaryCommands[^1]);
+ }
+
+ [Fact]
+ public void TrailingSeparator_MovedToOverflow()
+ {
+ // [Btn, Sep, Btn, Btn] with room for 1 button: Sep after the single visible button should overflow.
+ var cb = CreateWithWidth(300);
+ cb.ItemWidthBottom = 260;
+ cb.SecondaryCommands!.Add(new CommandBarButton());
+ cb.PrimaryCommands!.Add(new CommandBarButton());
+ cb.PrimaryCommands.Add(new CommandBarSeparator());
+ cb.PrimaryCommands.Add(new CommandBarButton());
+ cb.PrimaryCommands.Add(new CommandBarButton());
+ cb.IsDynamicOverflowEnabled = true;
+
+ Assert.Equal(1, cb.VisiblePrimaryCommands.Count);
+ Assert.IsType(cb.VisiblePrimaryCommands[0]);
+ }
+
+ [Fact]
+ public void MultipleSeparators_AllTrailingOnesStripped()
+ {
+ // [Btn, Sep, Sep, Btn] with room for 1: both trailing separators should be stripped.
+ var cb = CreateWithWidth(300);
+ cb.ItemWidthBottom = 260;
+ cb.SecondaryCommands!.Add(new CommandBarButton());
+ cb.PrimaryCommands!.Add(new CommandBarButton());
+ cb.PrimaryCommands.Add(new CommandBarSeparator());
+ cb.PrimaryCommands.Add(new CommandBarSeparator());
+ cb.PrimaryCommands.Add(new CommandBarButton());
+ cb.IsDynamicOverflowEnabled = true;
+
+ Assert.Equal(1, cb.VisiblePrimaryCommands.Count);
+ Assert.IsType(cb.VisiblePrimaryCommands[0]);
+ }
+
+ [Fact]
+ public void MidSeparator_StaysVisible_WhenButtonsOnBothSides()
+ {
+ // [Btn, Sep, Btn] with room for all: separator stays.
+ var cb = CreateWithWidth(300);
+ cb.PrimaryCommands!.Add(new CommandBarButton());
+ cb.PrimaryCommands.Add(new CommandBarSeparator());
+ cb.PrimaryCommands.Add(new CommandBarButton());
+ cb.IsDynamicOverflowEnabled = true;
+
+ Assert.Equal(3, cb.VisiblePrimaryCommands.Count);
+ Assert.IsType(cb.VisiblePrimaryCommands[1]);
+ }
+
+ [Fact]
+ public void AllButtonsOverflow_SeparatorsAlsoOverflow()
+ {
+ // [Sep, Btn, Btn] with room for 0: everything overflows.
+ var cb = CreateWithWidth(50);
+ cb.SecondaryCommands!.Add(new CommandBarButton());
+ cb.PrimaryCommands!.Add(new CommandBarSeparator());
+ cb.PrimaryCommands.Add(new CommandBarButton());
+ cb.PrimaryCommands.Add(new CommandBarButton());
+ cb.IsDynamicOverflowEnabled = true;
+
+ Assert.Empty(cb.VisiblePrimaryCommands);
+ }
+
+ [Fact]
+ public void LeadingSeparator_IsStrippedFromVisible()
+ {
+ // [Sep, Btn, Btn, Btn] with room for 2: leading Sep should be stripped.
+ var cb = CreateWithWidth(300);
+ cb.SecondaryCommands!.Add(new CommandBarButton());
+ cb.PrimaryCommands!.Add(new CommandBarSeparator());
+ cb.PrimaryCommands.Add(new CommandBarButton());
+ cb.PrimaryCommands.Add(new CommandBarButton());
+ cb.PrimaryCommands.Add(new CommandBarButton());
+ cb.IsDynamicOverflowEnabled = true;
+
+ Assert.IsNotType(cb.VisiblePrimaryCommands[0]);
+ }
+
+ [Fact]
+ public void ConsecutiveSeparators_CollapsedToOne()
+ {
+ // [Btn, Sep, Sep, Btn] all fit: only one separator should remain.
+ var cb = CreateWithWidth(300);
+ cb.PrimaryCommands!.Add(new CommandBarButton());
+ cb.PrimaryCommands.Add(new CommandBarSeparator());
+ cb.PrimaryCommands.Add(new CommandBarSeparator());
+ cb.PrimaryCommands.Add(new CommandBarButton());
+ cb.IsDynamicOverflowEnabled = true;
+
+ int sepCount = CountSeparators(cb.VisiblePrimaryCommands);
+ Assert.Equal(1, sepCount);
+ }
+
+ [Fact]
+ public void OrphanedMidSeparator_RemovedWhenNeighborOverflows()
+ {
+ // [Btn1, Sep, Btn2, Sep, Btn3] with room for 2: Btn3 overflows,
+ // second Sep becomes trailing and is removed. First Sep stays.
+ var cb = CreateWithWidth(300);
+ cb.ItemWidthBottom = 100;
+ cb.SecondaryCommands!.Add(new CommandBarButton());
+ cb.PrimaryCommands!.Add(new CommandBarButton());
+ cb.PrimaryCommands.Add(new CommandBarSeparator());
+ cb.PrimaryCommands.Add(new CommandBarButton());
+ cb.PrimaryCommands.Add(new CommandBarSeparator());
+ cb.PrimaryCommands.Add(new CommandBarButton());
+ cb.IsDynamicOverflowEnabled = true;
+
+ Assert.IsNotType(cb.VisiblePrimaryCommands[^1]);
+ Assert.Equal(1, CountSeparators(cb.VisiblePrimaryCommands));
+ }
+
+ [Fact]
+ public void SeparatorBetweenOverflowedButtons_IsRemoved()
+ {
+ // [Btn1, Btn2, Sep, Btn3, Btn4] with room for 2: Btn3 and Btn4 overflow,
+ // Sep has no non-separator after it in visible set, so it is removed.
+ var cb = CreateWithWidth(300);
+ cb.ItemWidthBottom = 100;
+ cb.SecondaryCommands!.Add(new CommandBarButton());
+ cb.PrimaryCommands!.Add(new CommandBarButton());
+ cb.PrimaryCommands.Add(new CommandBarButton());
+ cb.PrimaryCommands.Add(new CommandBarSeparator());
+ cb.PrimaryCommands.Add(new CommandBarButton());
+ cb.PrimaryCommands.Add(new CommandBarButton());
+ cb.IsDynamicOverflowEnabled = true;
+
+ Assert.Equal(0, CountSeparators(cb.VisiblePrimaryCommands));
+ }
+
+ [Fact]
+ public void MultipleSeparatorGroups_OnlyValidOnesRemain()
+ {
+ // [Btn, Sep, Btn, Sep, Btn, Sep, Btn] with room for 3:
+ // last Btn overflows, last Sep becomes trailing, the rest stay.
+ var cb = CreateWithWidth(300);
+ cb.SecondaryCommands!.Add(new CommandBarButton());
+ cb.PrimaryCommands!.Add(new CommandBarButton());
+ cb.PrimaryCommands.Add(new CommandBarSeparator());
+ cb.PrimaryCommands.Add(new CommandBarButton());
+ cb.PrimaryCommands.Add(new CommandBarSeparator());
+ cb.PrimaryCommands.Add(new CommandBarButton());
+ cb.PrimaryCommands.Add(new CommandBarSeparator());
+ cb.PrimaryCommands.Add(new CommandBarButton());
+ cb.IsDynamicOverflowEnabled = true;
+
+ Assert.IsNotType(cb.VisiblePrimaryCommands[^1]);
+ Assert.IsNotType(cb.VisiblePrimaryCommands[0]);
+ }
+
+ [Fact]
+ public void OnlySeparators_AllOverflow()
+ {
+ // [Sep, Sep, Sep] with no buttons: all should overflow.
+ var cb = CreateWithWidth(300);
+ cb.SecondaryCommands!.Add(new CommandBarButton());
+ cb.PrimaryCommands!.Add(new CommandBarSeparator());
+ cb.PrimaryCommands.Add(new CommandBarSeparator());
+ cb.PrimaryCommands.Add(new CommandBarSeparator());
+ cb.IsDynamicOverflowEnabled = true;
+
+ Assert.Empty(cb.VisiblePrimaryCommands);
+ }
+
+ [Fact]
+ public void PrimarySeparator_IsRemovedInsteadOfBecomingFirstOverflowItem()
+ {
+ var cb = CreateWithWidth(300);
+ cb.ItemWidthBottom = 260;
+
+ var leadingSeparator = new CommandBarSeparator();
+ var firstButton = new CommandBarButton();
+ var overflowedButton = new CommandBarButton();
+
+ cb.PrimaryCommands!.Add(leadingSeparator);
+ cb.PrimaryCommands.Add(firstButton);
+ cb.PrimaryCommands.Add(overflowedButton);
+ cb.IsDynamicOverflowEnabled = true;
+
+ Assert.Single(cb.OverflowItems);
+ Assert.Same(overflowedButton, cb.OverflowItems[0]);
+ Assert.DoesNotContain(leadingSeparator, cb.OverflowItems);
+ }
+
+ [Fact]
+ public void OverflowedPrimaryCommands_PrecedeSecondaryCommands_WithSyntheticSeparator()
+ {
+ var cb = CreateWithWidth(300);
+ cb.ItemWidthBottom = 260;
+
+ var visiblePrimary = new CommandBarButton();
+ var originalPrimarySeparator = new CommandBarSeparator();
+ var overflowedPrimaryOne = new CommandBarButton();
+ var overflowedPrimaryTwo = new CommandBarButton();
+ var secondary = new CommandBarButton();
+
+ cb.PrimaryCommands!.Add(visiblePrimary);
+ cb.PrimaryCommands.Add(originalPrimarySeparator);
+ cb.PrimaryCommands.Add(overflowedPrimaryOne);
+ cb.PrimaryCommands.Add(overflowedPrimaryTwo);
+ cb.SecondaryCommands!.Add(secondary);
+ cb.IsDynamicOverflowEnabled = true;
+
+ Assert.Equal(4, cb.OverflowItems.Count);
+ Assert.Same(overflowedPrimaryOne, cb.OverflowItems[0]);
+ Assert.Same(overflowedPrimaryTwo, cb.OverflowItems[1]);
+ Assert.IsType(cb.OverflowItems[2]);
+ Assert.NotSame(originalPrimarySeparator, cb.OverflowItems[2]);
+ Assert.Same(secondary, cb.OverflowItems[3]);
+ Assert.DoesNotContain(originalPrimarySeparator, cb.OverflowItems);
+ }
+
+ [Fact]
+ public void HiddenSecondaryCommands_DoNotGetSyntheticOverflowSeparator()
+ {
+ var cb = CreateWithWidth(300);
+ cb.ItemWidthBottom = 260;
+
+ var visiblePrimary = new CommandBarButton();
+ var overflowedPrimary = new CommandBarButton();
+ var hiddenSecondary = new CommandBarButton { IsVisible = false };
+
+ cb.PrimaryCommands!.Add(visiblePrimary);
+ cb.PrimaryCommands.Add(overflowedPrimary);
+ cb.SecondaryCommands!.Add(hiddenSecondary);
+ cb.IsDynamicOverflowEnabled = true;
+
+ Assert.Equal(2, cb.OverflowItems.Count);
+ Assert.Same(overflowedPrimary, cb.OverflowItems[0]);
+ Assert.Same(hiddenSecondary, cb.OverflowItems[1]);
+ Assert.DoesNotContain(cb.OverflowItems, x => x is CommandBarSeparator);
+ }
+
+ [Fact]
+ public void TogglingSecondaryVisibility_RebuildsSyntheticOverflowSeparator()
+ {
+ var cb = CreateWithWidth(300);
+ cb.ItemWidthBottom = 260;
+
+ var visiblePrimary = new CommandBarButton();
+ var overflowedPrimary = new CommandBarButton();
+ var secondary = new CommandBarButton();
+
+ cb.PrimaryCommands!.Add(visiblePrimary);
+ cb.PrimaryCommands.Add(overflowedPrimary);
+ cb.SecondaryCommands!.Add(secondary);
+ cb.IsDynamicOverflowEnabled = true;
+
+ Assert.Equal(3, cb.OverflowItems.Count);
+ Assert.Same(overflowedPrimary, cb.OverflowItems[0]);
+ Assert.IsType(cb.OverflowItems[1]);
+ Assert.Same(secondary, cb.OverflowItems[2]);
+
+ secondary.IsVisible = false;
+
+ Assert.Equal(2, cb.OverflowItems.Count);
+ Assert.Same(overflowedPrimary, cb.OverflowItems[0]);
+ Assert.Same(secondary, cb.OverflowItems[1]);
+ Assert.DoesNotContain(cb.OverflowItems, x => x is CommandBarSeparator);
+
+ secondary.IsVisible = true;
+
+ Assert.Equal(3, cb.OverflowItems.Count);
+ Assert.Same(overflowedPrimary, cb.OverflowItems[0]);
+ Assert.IsType(cb.OverflowItems[1]);
+ Assert.Same(secondary, cb.OverflowItems[2]);
+ }
+
+ private static int CountSeparators(IReadOnlyList items)
+ {
+ int count = 0;
+ for (var i = 0; i < items.Count; i++)
+ {
+ if (items[i] is CommandBarSeparator)
+ count++;
+ }
+ return count;
+ }
+}
+
file sealed class DelegateCommand : System.Windows.Input.ICommand
{
private readonly System.Action