lepoco / lepoco/wpfui

Please enhance the close button of TabItem

Open
#1,402 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

enhancement
Dominant language
C#
Stars
9.6k
Forks
1k
PR merge metrics
No merged PRs in 30d

Description

Is your feature request related to a problem? Please describe

Please implement a close button for TabItem, just like the tags on Chrome or EDGE browsers.

Describe the solution you'd like

I have implemented a close button for TabItem myself. If there is a better method, please update the functionality. This is the code I implemented myself. Firstly, add a class. Then update the style. Add Grid and place the close button in the second column.

Describe alternatives you've considered

public static class TabItemCloseBehavior
{
public static readonly DependencyProperty IsCloseButtonEnabledProperty =
DependencyProperty.RegisterAttached(
"IsCloseButtonEnabled",
typeof(bool),
typeof(TabItemCloseBehavior),
new UIPropertyMetadata(false, OnIsCloseButtonEnabledChanged));

/// <summary>Helper for getting <see cref="IsCloseButtonEnabledProperty"/> from <paramref name="obj"/>.</summary>
/// <param name="obj"><see cref="DependencyObject"/> to read <see cref="IsCloseButtonEnabledProperty"/> from.</param>
/// <returns>IsCloseButtonEnabled property value.</returns>
[AttachedPropertyBrowsableForType(typeof(DependencyObject))]
public static bool GetIsCloseButtonEnabled(DependencyObject obj)
{
    return (bool)obj.GetValue(IsCloseButtonEnabledProperty);
}

/// <summary>Helper for setting <see cref="IsCloseButtonEnabledProperty"/> on <paramref name="obj"/>.</summary>
/// <param name="obj"><see cref="DependencyObject"/> to set <see cref="IsCloseButtonEnabledProperty"/> on.</param>
/// <param name="value">IsCloseButtonEnabled property value.</param>
public static void SetIsCloseButtonEnabled(DependencyObject obj, bool value)
{
    obj.SetValue(IsCloseButtonEnabledProperty, value);
}

private static void OnIsCloseButtonEnabledChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
    if (d is TabItem tabItem && e.NewValue is bool newValue && newValue)
    {
        tabItem.Loaded += TabItem_Loaded;
    }
}

private static void TabItem_Loaded(object sender, RoutedEventArgs e)
{
    if (sender is TabItem tabItem)
    {
        // Manually add a close button
        var closeButton = new Wpf.Ui.Controls.Button
        {
            Width = 16,
            Height = 16,
            Margin = new Thickness(8, 0, 0, 0),
            Padding = new Thickness(0, 0, 0, 0),
            HorizontalAlignment = HorizontalAlignment.Center,
            VerticalAlignment = VerticalAlignment.Center,
            Background = Brushes.Transparent,
            BorderBrush = Brushes.Transparent,
            Cursor = Cursors.Hand,
            Focusable = false
        };
        var path = new Path
        {
            Data = new GeometryGroup
            {
                Children =
                [
                    new LineGeometry(new Point(0, 0), new Point(8, 8)),
                    new LineGeometry(new Point(8, 0), new Point(0, 8))
                ]
            },
            Style = (Style)Application.Current.FindResource("CloseIconStyle"), // 使用动态资源
            StrokeThickness = 1.5
        };
        closeButton.Content = path;

        closeButton.Click += CloseButton_Click;

        // Find the template root Grid for TabItem
        if (tabItem.Template?.FindName("headRoot", tabItem) is Grid grid)
        {
            // Find the second column (position of the close button)
            ColumnDefinition closeButtonColumn = grid.ColumnDefinitions[1];

            // Add the button to the TabItem template and ensure it appears in the second column
            Grid.SetColumn(closeButton, 1);
            _ = grid.Children.Add(closeButton);
        }
    }
}

private static void CloseButton_Click(object sender, RoutedEventArgs e)
{
    if (sender is Button button)
    {
        TabItem? tabItem = FindAncestor<TabItem>(button);
        if (tabItem != null)
        {
            if (ItemsControl.ItemsControlFromItemContainer(tabItem) is System.Windows.Controls.TabControl tabControl && tabItem != null)
            {
                tabControl.Items.Remove(tabItem);
            }
        }
    }
}

private static T? FindAncestor<T>(DependencyObject current)
    where T : DependencyObject
{
    do
    {
        if (current is T ancestor)
        {
            return ancestor;
        }

        current = VisualTreeHelper.GetParent(current);
    }
    while (current != null);
    return null;
}

}

Additional context

Image

Image

Image

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by locating the TabItem control and its template, especially the headRoot Grid referenced in the proposed behavior. Compare the requested close-button appearance and interaction with the supplied implementation, then verify that a TabItem can render the button and that clicking it removes the tab from its TabControl.

Written by the indexing model from the issue text.

Assessment

Tech stack
csharp
Domain
design, desktop
Issue type
Feature
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
45/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.