mirror of
https://gitee.com/chinware/atomui.git
synced 2024-11-29 18:38:16 +08:00
重构 TabControl 溢出上下文菜单
重构 TabControl 溢出上下文菜单
This commit is contained in:
parent
c939b25884
commit
75bc4e3baf
@ -59,6 +59,76 @@
|
||||
</StackPanel>
|
||||
</showcase:ShowCaseItem>
|
||||
|
||||
<showcase:ShowCaseItem
|
||||
Title="Icon"
|
||||
Description="The Tab with Icon.">
|
||||
<StackPanel Orientation="Vertical" Spacing="20">
|
||||
<atom:TabControl>
|
||||
<atom:TabItem Header="Tab 1" Icon="{atom:IconProvider Kind=AppleOutlined}">Content of Tab Pane 1</atom:TabItem>
|
||||
<atom:TabItem Header="Tab 2" Icon="{atom:IconProvider Kind=AndroidOutlined}">Content of Tab Pane 2</atom:TabItem>
|
||||
<atom:TabItem Header="Tab 3" Icon="{atom:IconProvider Kind=WechatOutlined}">Content of Tab Pane 3</atom:TabItem>
|
||||
</atom:TabControl>
|
||||
<!-- -->
|
||||
<!-- <atom:CardTabControl> -->
|
||||
<!-- <atom:TabItem Header="Tab 1" Icon="{atom:IconProvider Kind=AppleOutlined}">Content of Tab Pane 1</atom:TabItem> -->
|
||||
<!-- <atom:TabItem Header="Tab 2" Icon="{atom:IconProvider Kind=AndroidOutlined}">Content of Tab Pane 2</atom:TabItem> -->
|
||||
<!-- <atom:TabItem Header="Tab 3" Icon="{atom:IconProvider Kind=WechatOutlined}">Content of Tab Pane 3</atom:TabItem> -->
|
||||
<!-- </atom:CardTabControl> -->
|
||||
</StackPanel>
|
||||
</showcase:ShowCaseItem>
|
||||
|
||||
<showcase:ShowCaseItem
|
||||
Title="Slide"
|
||||
Description="In order to fit in more tabs, they can slide left and right (or up and down).">
|
||||
<StackPanel Orientation="Vertical" Spacing="20">
|
||||
<atom:TabControl>
|
||||
<atom:TabItem Header="Tab 1">Content of Tab Pane 1</atom:TabItem>
|
||||
<atom:TabItem Header="Tab 2">Content of Tab Pane 2</atom:TabItem>
|
||||
<atom:TabItem Header="Tab 3">Content of Tab Pane 3</atom:TabItem>
|
||||
<atom:TabItem Header="Tab 4">Content of Tab Pane 4</atom:TabItem>
|
||||
<atom:TabItem Header="Tab 5">Content of Tab Pane 5</atom:TabItem>
|
||||
<atom:TabItem Header="Tab 6">Content of Tab Pane 6</atom:TabItem>
|
||||
<atom:TabItem Header="Tab 7">Content of Tab Pane 7</atom:TabItem>
|
||||
<atom:TabItem Header="Tab 8">Content of Tab Pane 8</atom:TabItem>
|
||||
<atom:TabItem Header="Tab 9">Content of Tab Pane 9</atom:TabItem>
|
||||
<atom:TabItem Header="Tab 10">Content of Tab Pane 10</atom:TabItem>
|
||||
<atom:TabItem Header="Tab 11">Content of Tab Pane 11</atom:TabItem>
|
||||
<atom:TabItem Header="Tab 12">Content of Tab Pane 12</atom:TabItem>
|
||||
<atom:TabItem Header="Tab 13">Content of Tab Pane 13</atom:TabItem>
|
||||
<atom:TabItem Header="Tab 14">Content of Tab Pane 14</atom:TabItem>
|
||||
<atom:TabItem Header="Tab 15">Content of Tab Pane 15</atom:TabItem>
|
||||
<atom:TabItem Header="Tab 16">Content of Tab Pane 16</atom:TabItem>
|
||||
<atom:TabItem Header="Tab 17">Content of Tab Pane 17</atom:TabItem>
|
||||
<atom:TabItem Header="Tab 18">Content of Tab Pane 18</atom:TabItem>
|
||||
<atom:TabItem Header="Tab 19">Content of Tab Pane 19</atom:TabItem>
|
||||
<atom:TabItem Header="Tab 20">Content of Tab Pane 20</atom:TabItem>
|
||||
</atom:TabControl>
|
||||
|
||||
<atom:CardTabControl>
|
||||
<atom:TabItem Header="Tab 1">Content of Tab Pane 1</atom:TabItem>
|
||||
<atom:TabItem Header="Tab 2">Content of Tab Pane 2</atom:TabItem>
|
||||
<atom:TabItem Header="Tab 3">Content of Tab Pane 3</atom:TabItem>
|
||||
<atom:TabItem Header="Tab 4">Content of Tab Pane 4</atom:TabItem>
|
||||
<atom:TabItem Header="Tab 5">Content of Tab Pane 5</atom:TabItem>
|
||||
<atom:TabItem Header="Tab 6">Content of Tab Pane 6</atom:TabItem>
|
||||
<atom:TabItem Header="Tab 7">Content of Tab Pane 7</atom:TabItem>
|
||||
<atom:TabItem Header="Tab 8">Content of Tab Pane 8</atom:TabItem>
|
||||
<atom:TabItem Header="Tab 9">Content of Tab Pane 9</atom:TabItem>
|
||||
<atom:TabItem Header="Tab 10">Content of Tab Pane 10</atom:TabItem>
|
||||
<atom:TabItem Header="Tab 11">Content of Tab Pane 11</atom:TabItem>
|
||||
<atom:TabItem Header="Tab 12">Content of Tab Pane 12</atom:TabItem>
|
||||
<atom:TabItem Header="Tab 13">Content of Tab Pane 13</atom:TabItem>
|
||||
<atom:TabItem Header="Tab 14">Content of Tab Pane 14</atom:TabItem>
|
||||
<atom:TabItem Header="Tab 15">Content of Tab Pane 15</atom:TabItem>
|
||||
<atom:TabItem Header="Tab 16">Content of Tab Pane 16</atom:TabItem>
|
||||
<atom:TabItem Header="Tab 17">Content of Tab Pane 17</atom:TabItem>
|
||||
<atom:TabItem Header="Tab 18">Content of Tab Pane 18</atom:TabItem>
|
||||
<atom:TabItem Header="Tab 19">Content of Tab Pane 19</atom:TabItem>
|
||||
<atom:TabItem Header="Tab 20">Content of Tab Pane 20</atom:TabItem>
|
||||
</atom:CardTabControl>
|
||||
</StackPanel>
|
||||
</showcase:ShowCaseItem>
|
||||
|
||||
</showcase:ShowCasePanel>
|
||||
|
||||
</TabItem>
|
||||
|
@ -6,12 +6,12 @@ using Avalonia.Threading;
|
||||
|
||||
namespace AtomUI.Controls;
|
||||
|
||||
internal class OverflowTabMenuItem : MenuItem
|
||||
internal class BaseOverflowMenuItem : MenuItem
|
||||
{
|
||||
#region 公共属性
|
||||
|
||||
public static readonly DirectProperty<OverflowTabMenuItem, bool> IsClosableProperty =
|
||||
AvaloniaProperty.RegisterDirect<OverflowTabMenuItem, bool>(nameof(IsClosable),
|
||||
public static readonly DirectProperty<BaseOverflowMenuItem, bool> IsClosableProperty =
|
||||
AvaloniaProperty.RegisterDirect<BaseOverflowMenuItem, bool>(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<CloseTabRequestEventArgs>? CloseTab
|
||||
{
|
||||
@ -40,29 +38,26 @@ internal class OverflowTabMenuItem : MenuItem
|
||||
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
|
||||
{
|
||||
base.OnApplyTemplate(e);
|
||||
_iconButton = e.NameScope.Find<IconButton>(OverflowTabMenuItemTheme.ItemCloseButtonPart);
|
||||
_iconButton = e.NameScope.Find<IconButton>(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; }
|
||||
}
|
@ -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<OverflowTabMenuItem>((item, scope) =>
|
||||
return new FuncControlTemplate<BaseOverflowMenuItem>((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);
|
||||
}
|
||||
|
22
src/AtomUI.Controls/TabControl/TabControlOverflowMenuItem.cs
Normal file
22
src/AtomUI.Controls/TabControl/TabControlOverflowMenuItem.cs
Normal file
@ -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();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
@ -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();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@ -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) {
|
||||
|
Loading…
Reference in New Issue
Block a user