diff --git a/samples/AtomUI.Demo.Desktop/ShowCase/TabControlShowCase.axaml b/samples/AtomUI.Demo.Desktop/ShowCase/TabControlShowCase.axaml
index 3236865a7..67f084a7d 100644
--- a/samples/AtomUI.Demo.Desktop/ShowCase/TabControlShowCase.axaml
+++ b/samples/AtomUI.Demo.Desktop/ShowCase/TabControlShowCase.axaml
@@ -59,6 +59,76 @@
+
+
+
+ Content of Tab Pane 1
+ Content of Tab Pane 2
+ Content of Tab Pane 3
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Content of Tab Pane 1
+ Content of Tab Pane 2
+ Content of Tab Pane 3
+ Content of Tab Pane 4
+ Content of Tab Pane 5
+ Content of Tab Pane 6
+ Content of Tab Pane 7
+ Content of Tab Pane 8
+ Content of Tab Pane 9
+ Content of Tab Pane 10
+ Content of Tab Pane 11
+ Content of Tab Pane 12
+ Content of Tab Pane 13
+ Content of Tab Pane 14
+ Content of Tab Pane 15
+ Content of Tab Pane 16
+ Content of Tab Pane 17
+ Content of Tab Pane 18
+ Content of Tab Pane 19
+ Content of Tab Pane 20
+
+
+
+ Content of Tab Pane 1
+ Content of Tab Pane 2
+ Content of Tab Pane 3
+ Content of Tab Pane 4
+ Content of Tab Pane 5
+ Content of Tab Pane 6
+ Content of Tab Pane 7
+ Content of Tab Pane 8
+ Content of Tab Pane 9
+ Content of Tab Pane 10
+ Content of Tab Pane 11
+ Content of Tab Pane 12
+ Content of Tab Pane 13
+ Content of Tab Pane 14
+ Content of Tab Pane 15
+ Content of Tab Pane 16
+ Content of Tab Pane 17
+ Content of Tab Pane 18
+ Content of Tab Pane 19
+ Content of Tab Pane 20
+
+
+
+
diff --git a/src/AtomUI.Controls/TabControl/OverflowTabMenuItem.cs b/src/AtomUI.Controls/TabControl/BaseOverflowMenuItem.cs
similarity index 60%
rename from src/AtomUI.Controls/TabControl/OverflowTabMenuItem.cs
rename to src/AtomUI.Controls/TabControl/BaseOverflowMenuItem.cs
index 0754eedc7..33329ef3c 100644
--- a/src/AtomUI.Controls/TabControl/OverflowTabMenuItem.cs
+++ b/src/AtomUI.Controls/TabControl/BaseOverflowMenuItem.cs
@@ -6,12 +6,12 @@ using Avalonia.Threading;
namespace AtomUI.Controls;
-internal class OverflowTabMenuItem : MenuItem
+internal class BaseOverflowMenuItem : MenuItem
{
#region 公共属性
- public static readonly DirectProperty IsClosableProperty =
- AvaloniaProperty.RegisterDirect(nameof(IsClosable),
+ public static readonly DirectProperty IsClosableProperty =
+ AvaloniaProperty.RegisterDirect(nameof(IsClosable),
o => o.IsClosable,
(o, v) => o.IsClosable = v);
@@ -24,8 +24,6 @@ internal class OverflowTabMenuItem : MenuItem
get => _isClosable;
set => SetAndRaise(IsClosableProperty, ref _isClosable, value);
}
-
- public TabStripItem? TabStripItem { get; set; }
public event EventHandler? CloseTab
{
@@ -40,29 +38,26 @@ internal class OverflowTabMenuItem : MenuItem
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
base.OnApplyTemplate(e);
- _iconButton = e.NameScope.Find(OverflowTabMenuItemTheme.ItemCloseButtonPart);
+ _iconButton = e.NameScope.Find(BaseOverflowMenuItemTheme.ItemCloseButtonPart);
if (_iconButton is not null) {
_iconButton.Click += (sender, args) =>
{
- if (Parent is MenuBase menu) {
- var eventArgs = new CloseTabRequestEventArgs(CloseTabEvent, TabStripItem!);
- RaiseEvent(eventArgs);
- Dispatcher.UIThread.Post(() =>
- {
- menu.Close();
- });
- }
+ NotifyCloseRequest();
};
}
}
+
+ protected virtual void NotifyCloseRequest()
+ {
+ }
}
internal class CloseTabRequestEventArgs : RoutedEventArgs
{
- public CloseTabRequestEventArgs(RoutedEvent routedEvent, TabStripItem stripItem)
+ public CloseTabRequestEventArgs(RoutedEvent routedEvent, object tabItem)
: base(routedEvent)
{
- TabStripItem = stripItem;
+ TabItem = tabItem;
}
- public TabStripItem TabStripItem { get; }
+ public object TabItem { get; }
}
\ No newline at end of file
diff --git a/src/AtomUI.Controls/TabControl/OverflowTabMenuItemTheme.cs b/src/AtomUI.Controls/TabControl/BaseOverflowMenuItemTheme.cs
similarity index 87%
rename from src/AtomUI.Controls/TabControl/OverflowTabMenuItemTheme.cs
rename to src/AtomUI.Controls/TabControl/BaseOverflowMenuItemTheme.cs
index 76392a35a..5c08ae91e 100644
--- a/src/AtomUI.Controls/TabControl/OverflowTabMenuItemTheme.cs
+++ b/src/AtomUI.Controls/TabControl/BaseOverflowMenuItemTheme.cs
@@ -13,21 +13,21 @@ using Avalonia.Styling;
namespace AtomUI.Controls;
[ControlThemeProvider]
-internal class OverflowTabMenuItemTheme : BaseControlTheme
+internal class BaseOverflowMenuItemTheme : BaseControlTheme
{
public const string ItemDecoratorPart = "PART_ItemDecorator";
public const string MainContainerPart = "PART_MainContainer";
public const string ItemTextPresenterPart = "PART_ItemTextPresenter";
public const string ItemCloseButtonPart = "PART_ItemCloseIcon";
- public OverflowTabMenuItemTheme()
- : base(typeof(OverflowTabMenuItem))
+ public BaseOverflowMenuItemTheme()
+ : base(typeof(BaseOverflowMenuItem))
{
}
protected override IControlTemplate BuildControlTemplate()
{
- return new FuncControlTemplate((item, scope) =>
+ return new FuncControlTemplate((item, scope) =>
{
var container = new Border()
{
@@ -61,8 +61,8 @@ internal class OverflowTabMenuItemTheme : BaseControlTheme
Grid.SetColumn(itemTextPresenter, 0);
TokenResourceBinder.CreateTokenBinding(itemTextPresenter, ContentPresenter.MarginProperty, MenuResourceKey.ItemMargin);
- CreateTemplateParentBinding(itemTextPresenter, ContentPresenter.ContentProperty, OverflowTabMenuItem.HeaderProperty);
- CreateTemplateParentBinding(itemTextPresenter, ContentPresenter.ContentTemplateProperty, OverflowTabMenuItem.HeaderTemplateProperty);
+ CreateTemplateParentBinding(itemTextPresenter, ContentPresenter.ContentProperty, BaseOverflowMenuItem.HeaderProperty);
+ CreateTemplateParentBinding(itemTextPresenter, ContentPresenter.ContentTemplateProperty, BaseOverflowMenuItem.HeaderTemplateProperty);
itemTextPresenter.RegisterInNameScope(scope);
@@ -82,7 +82,7 @@ internal class OverflowTabMenuItemTheme : BaseControlTheme
};
- CreateTemplateParentBinding(closeButton, IconButton.IsVisibleProperty, OverflowTabMenuItem.IsClosableProperty);
+ CreateTemplateParentBinding(closeButton, IconButton.IsVisibleProperty, BaseOverflowMenuItem.IsClosableProperty);
TokenResourceBinder.CreateGlobalTokenBinding(menuCloseIcon, PathIcon.NormalFilledBrushProperty, GlobalResourceKey.ColorIcon);
TokenResourceBinder.CreateGlobalTokenBinding(menuCloseIcon, PathIcon.ActiveFilledBrushProperty, GlobalResourceKey.ColorIconHover);
@@ -110,7 +110,7 @@ internal class OverflowTabMenuItemTheme : BaseControlTheme
private void BuildCommonStyle(Style commonStyle)
{
- commonStyle.Add(OverflowTabMenuItem.ForegroundProperty, MenuResourceKey.ItemColor);
+ commonStyle.Add(BaseOverflowMenuItem.ForegroundProperty, MenuResourceKey.ItemColor);
{
var borderStyle = new Style(selector => selector.Nesting().Template().Name(ItemDecoratorPart));
borderStyle.Add(Border.MinHeightProperty, MenuResourceKey.ItemHeight);
@@ -122,7 +122,7 @@ internal class OverflowTabMenuItemTheme : BaseControlTheme
// Hover 状态
var hoverStyle = new Style(selector => selector.Nesting().Class(StdPseudoClass.PointerOver));
- hoverStyle.Add(OverflowTabMenuItem.ForegroundProperty, MenuResourceKey.ItemHoverColor);
+ hoverStyle.Add(BaseOverflowMenuItem.ForegroundProperty, MenuResourceKey.ItemHoverColor);
{
var borderStyle = new Style(selector => selector.Nesting().Template().Name(ItemDecoratorPart));
borderStyle.Add(Border.BackgroundProperty, MenuResourceKey.ItemHoverBg);
@@ -134,7 +134,7 @@ internal class OverflowTabMenuItemTheme : BaseControlTheme
private void BuildDisabledStyle()
{
var disabledStyle = new Style(selector => selector.Nesting().Class(StdPseudoClass.Disabled));
- disabledStyle.Add(OverflowTabMenuItem.ForegroundProperty, MenuResourceKey.ItemDisabledColor);
+ disabledStyle.Add(BaseOverflowMenuItem.ForegroundProperty, MenuResourceKey.ItemDisabledColor);
Add(disabledStyle);
}
diff --git a/src/AtomUI.Controls/TabControl/TabControlOverflowMenuItem.cs b/src/AtomUI.Controls/TabControl/TabControlOverflowMenuItem.cs
new file mode 100644
index 000000000..17c51607e
--- /dev/null
+++ b/src/AtomUI.Controls/TabControl/TabControlOverflowMenuItem.cs
@@ -0,0 +1,22 @@
+using Avalonia.Controls;
+using Avalonia.Threading;
+
+namespace AtomUI.Controls;
+
+internal class TabControlOverflowMenuItem : BaseOverflowMenuItem
+{
+ protected override Type StyleKeyOverride => typeof(BaseOverflowMenuItem);
+ public TabItem? TabItem { get; set; }
+
+ protected override void NotifyCloseRequest()
+ {
+ if (Parent is MenuBase menu) {
+ var eventArgs = new CloseTabRequestEventArgs(CloseTabEvent, TabItem!);
+ RaiseEvent(eventArgs);
+ Dispatcher.UIThread.Post(() =>
+ {
+ menu.Close();
+ });
+ }
+ }
+}
diff --git a/src/AtomUI.Controls/TabControl/TabControlScrollViewer.cs b/src/AtomUI.Controls/TabControl/TabControlScrollViewer.cs
index 88455b34a..c23643cc7 100644
--- a/src/AtomUI.Controls/TabControl/TabControlScrollViewer.cs
+++ b/src/AtomUI.Controls/TabControl/TabControlScrollViewer.cs
@@ -1,4 +1,9 @@
-namespace AtomUI.Controls;
+using Avalonia.Controls;
+using Avalonia.Controls.Primitives;
+using Avalonia.Interactivity;
+using Avalonia.Threading;
+
+namespace AtomUI.Controls;
internal class TabControlScrollViewer : BaseTabScrollViewer
{
@@ -9,4 +14,93 @@ internal class TabControlScrollViewer : BaseTabScrollViewer
#endregion
protected override Type StyleKeyOverride => typeof(BaseTabScrollViewer);
+
+ protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
+ {
+ base.OnApplyTemplate(e);
+ if (_menuIndicator is not null) {
+ _menuIndicator.Click += HandleMenuIndicator;
+ }
+ }
+
+ private void HandleMenuIndicator(object? sender, RoutedEventArgs args)
+ {
+ if (_menuFlyout is null) {
+ _menuFlyout = new MenuFlyout();
+ }
+
+ if (TabStripPlacement == Dock.Top || TabStripPlacement == Dock.Bottom) {
+ _menuFlyout.Placement = PlacementMode.Bottom;
+ } else {
+ _menuFlyout.Placement = PlacementMode.Right;
+ }
+
+ // 收集没有完全显示的 Tab 列表
+ _menuFlyout.Items.Clear();
+ if (TabControl is not null) {
+ for (int i = 0; i < TabControl.ItemCount; i++) {
+ var itemContainer = TabControl.ContainerFromIndex(i)!;
+ if (itemContainer is TabItem tabItem) {
+ var itemBounds = itemContainer.Bounds;
+ var left = Math.Floor(itemBounds.Left - Offset.X);
+ var right = Math.Floor(itemBounds.Right - Offset.X);
+ if (TabStripPlacement == Dock.Top || TabStripPlacement == Dock.Bottom) {
+ if (left < 0 || right > Viewport.Width) {
+ var menuItem = new TabControlOverflowMenuItem
+ {
+ Header = tabItem.Header,
+ TabItem = tabItem,
+ IsClosable = tabItem.IsClosable
+ };
+ menuItem.Click += HandleMenuItemClicked;
+ menuItem.CloseTab += HandleCloseTabRequest;
+ _menuFlyout.Items.Add(menuItem);
+ }
+ }
+ }
+ }
+
+ if (_menuFlyout.Items.Count > 0) {
+ _menuFlyout.ShowAt(_menuIndicator!);
+ }
+ }
+ }
+
+ private void HandleMenuItemClicked(object? sender, RoutedEventArgs args)
+ {
+ if (TabControl is not null) {
+ Dispatcher.UIThread.Post(sender =>
+ {
+ if (sender is TabControlOverflowMenuItem tabControlMenuItem) {
+ var tabItem = tabControlMenuItem.TabItem;
+ if (tabItem is not null) {
+ tabItem.BringIntoView();
+ TabControl.SelectedItem = tabItem;
+ }
+ }
+ }, sender);
+ }
+ }
+
+ private void HandleCloseTabRequest(object? sender, RoutedEventArgs args)
+ {
+ if (sender is TabControlOverflowMenuItem tabControlMenuItem) {
+ if (TabControl is not null) {
+ if (TabControl.SelectedItem is TabItem selectedItem) {
+ if (selectedItem == tabControlMenuItem.TabItem) {
+ var selectedIndex = TabControl.SelectedIndex;
+ object? newSelectedItem = null;
+ if (selectedIndex != 0) {
+ newSelectedItem = TabControl.Items[--selectedIndex];
+ }
+ TabControl.Items.Remove(tabControlMenuItem.TabItem);
+ TabControl.SelectedItem = newSelectedItem;
+ } else {
+ TabControl.Items.Remove(tabControlMenuItem.TabItem);
+ }
+ }
+ }
+
+ }
+ }
}
\ No newline at end of file
diff --git a/src/AtomUI.Controls/TabControl/TabStrip/TabStripOverflowMenuItem.cs b/src/AtomUI.Controls/TabControl/TabStrip/TabStripOverflowMenuItem.cs
new file mode 100644
index 000000000..b473e9c27
--- /dev/null
+++ b/src/AtomUI.Controls/TabControl/TabStrip/TabStripOverflowMenuItem.cs
@@ -0,0 +1,22 @@
+using Avalonia.Controls;
+using Avalonia.Threading;
+
+namespace AtomUI.Controls;
+
+internal class TabStripOverflowMenuItem : BaseOverflowMenuItem
+{
+ protected override Type StyleKeyOverride => typeof(BaseOverflowMenuItem);
+ public TabStripItem? TabStripItem { get; set; }
+
+ protected override void NotifyCloseRequest()
+ {
+ if (Parent is MenuBase menu) {
+ var eventArgs = new CloseTabRequestEventArgs(CloseTabEvent, TabStripItem!);
+ RaiseEvent(eventArgs);
+ Dispatcher.UIThread.Post(() =>
+ {
+ menu.Close();
+ });
+ }
+ }
+}
diff --git a/src/AtomUI.Controls/TabControl/TabStrip/TabStripScrollViewer.cs b/src/AtomUI.Controls/TabControl/TabStrip/TabStripScrollViewer.cs
index 4a1c07b21..514e9a648 100644
--- a/src/AtomUI.Controls/TabControl/TabStrip/TabStripScrollViewer.cs
+++ b/src/AtomUI.Controls/TabControl/TabStrip/TabStripScrollViewer.cs
@@ -46,7 +46,7 @@ internal class TabStripScrollViewer : BaseTabScrollViewer
var right = Math.Floor(itemBounds.Right - Offset.X);
if (TabStripPlacement == Dock.Top || TabStripPlacement == Dock.Bottom) {
if (left < 0 || right > Viewport.Width) {
- var menuItem = new OverflowTabMenuItem()
+ var menuItem = new TabStripOverflowMenuItem()
{
Header = tabStripItem.Content,
TabStripItem = tabStripItem,
@@ -71,7 +71,7 @@ internal class TabStripScrollViewer : BaseTabScrollViewer
if (TabStrip is not null) {
Dispatcher.UIThread.Post(sender =>
{
- if (sender is OverflowTabMenuItem tabStripMenuItem) {
+ if (sender is TabStripOverflowMenuItem tabStripMenuItem) {
var tabStripItem = tabStripMenuItem.TabStripItem;
if (tabStripItem is not null) {
tabStripItem.BringIntoView();
@@ -84,7 +84,7 @@ internal class TabStripScrollViewer : BaseTabScrollViewer
private void HandleCloseTabRequest(object? sender, RoutedEventArgs args)
{
- if (sender is OverflowTabMenuItem tabStripMenuItem) {
+ if (sender is TabStripOverflowMenuItem tabStripMenuItem) {
if (TabStrip is not null) {
if (TabStrip.SelectedItem is TabStripItem selectedItem) {
if (selectedItem == tabStripMenuItem.TabStripItem) {