HandyOrg / HandyOrg/HandyControl

TabControl Overflow Button & Context Menu Exceptions

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

Nobody has claimed this yet.

Dominant language
C#
Stars
7.2k
Forks
1.2k
PR merge metrics
No merged PRs in 30d

Description

I am getting the following exceptions thrown in my application. I am creating hc:TabControl TabItems programmatically because I want to be able to keep them in sync with my ListBox selection. I have a NavButton custom control defined for items in my ListBox. I am attempting to keep track of hc:TabItems in private HashSet<string> openTabIdentifiers = new HashSet<string>(); The Context Menu exception occurs after I have adjusted the index of an hc:TabItem in my NewWindow_Closed method.

I have attached videos to demonstrate the problems.
Thank you for any assistance.

System.NullReferenceException
HResult=0x80004003
Message=Object reference not set to an instance of an object.
Source=HandyControl
StackTrace:
at HandyControl.Controls.TabItem.<.ctor>b__49_3(Object s, CanExecuteRoutedEventArgs e)

System.InvalidOperationException
Logical tree depth exceeded while traversing the tree. This could indicate a cycle in the tree.

MainWindow.xaml

<ListBox Grid.Row="1" Grid.ColumnSpan="2" x:Name="sidebar" Margin="0,0,5.5,0" SelectionMode="Single" 
 SelectionChanged="CreateFrame_Click" HorizontalAlignment="Stretch" BorderThickness="0"
 ItemsSource="{Binding NavigationButtons}">
    <ListBox.ItemTemplate>
        <DataTemplate>
            <local:NavButton NavLink="{Binding NavLink}" NavType="{Binding NavType}" Content="{Binding Content}"/>
        </DataTemplate>
    </ListBox.ItemTemplate>
</ListBox>

<Grid x:Name="contentGrid" Grid.Row="1" Grid.Column="2">
    <hc:TabControl x:Name="tabControl" IsAnimationEnabled="True" ShowOverflowButton="True" IsTabFillEnabled="False"
                   ShowContextMenu="True" ShowCloseButton="True" IsDraggable="True" CanBeClosedByMiddleButton="False"/>
</Grid>

MainWindow.xaml.cs

/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
    private HashSet<string> openTabIdentifiers = new HashSet<string>();

    public MainWindow()
    {
        ConfigHelper.Instance.SetLang("en");
        InitializeComponent();
        DataContext = this;
    }

    public ObservableCollection<NavButton> NavigationButtons { get; set; } = new ObservableCollection<NavButton>
    {
        new NavButton { NavLink = new Uri("/Pages/Categories.xaml", UriKind.Relative), NavType = typeof(UserControls.Categories), Content = "Categories" },
        new NavButton { NavLink = new Uri("/Pages/Customers.xaml", UriKind.Relative), NavType = typeof(UserControls.Customers), Content = "Customers" },
        new NavButton { NavLink = new Uri("/Pages/Employees.xaml", UriKind.Relative), NavType = typeof(UserControls.Employees), Content = "Employees" },
        new NavButton { NavLink = new Uri("/Pages/EmployeeTerritories.xaml", UriKind.Relative), NavType = typeof(UserControls.EmployeeTerritories), Content = "Employee Territories" },
        new NavButton { NavLink = new Uri("/Pages/OrderDetails.xaml", UriKind.Relative), NavType = typeof(UserControls.OrderDetails), Content = "Order Details" },
        new NavButton { NavLink = new Uri("/Pages/Orders.xaml", UriKind.Relative), NavType = typeof(UserControls.Orders), Content = "Orders" },
        new NavButton { NavLink = new Uri("/Pages/Products.xaml", UriKind.Relative), NavType = typeof(UserControls.Products), Content = "Products" },
        new NavButton { NavLink = new Uri("/Pages/Shippers.xaml", UriKind.Relative), NavType = typeof(UserControls.Shippers), Content = "Shippers" },
        new NavButton { NavLink = new Uri("/Pages/Suppliers.xaml", UriKind.Relative), NavType = typeof(UserControls.Suppliers),Content = "Suppliers" },
        new NavButton { NavLink = new Uri("/Pages/Territories.xaml", UriKind.Relative), NavType = typeof(UserControls.Territories), Content = "Territories" }
    };

    private void CreateFrame_Click(object sender, SelectionChangedEventArgs e)
    {
        var selected = sidebar.SelectedItem as NavButton;
        if (selected == null) return;
        if (selected.Content.ToString() != null && openTabIdentifiers.Contains(selected.Content.ToString())) return;

        HandyControl.Controls.TabItem existingTabItem = null;
        foreach (HandyControl.Controls.TabItem tabItem in tabControl.Items.OfType<HandyControl.Controls.TabItem>())
        {
            if (string.Equals(tabItem.Header.ToString(), selected.Content.ToString()))
            {
                existingTabItem = tabItem;
                break;
            }
        }

        if (existingTabItem != null)
        {
            tabControl.SelectedItem = existingTabItem;
        }
        else
        {
            Grid tabContentGrid = new Grid();
            tabContentGrid.RowDefinitions.Add(new RowDefinition { Height = new GridLength(2, GridUnitType.Star) });
            tabContentGrid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });
            tabContentGrid.RowDefinitions.Add(new RowDefinition());

            Frame pageFrame = new Frame { NavigationUIVisibility = NavigationUIVisibility.Hidden };
            pageFrame.Navigate(new Uri(selected.NavLink.ToString(), UriKind.Relative));
            Grid.SetRow(pageFrame, 0);

            GridSplitter gridSplitter = new GridSplitter
            {
                Height = 6,
                HorizontalAlignment = HorizontalAlignment.Stretch,
                VerticalAlignment = VerticalAlignment.Center,
                Background = System.Windows.Media.Brushes.DarkGray
            };
            Grid.SetRow(gridSplitter, 1);

            var userControlInstance = (UserControl)Activator.CreateInstance(selected.NavType);
            Grid.SetRow(userControlInstance, 2);

            tabContentGrid.Children.Add(pageFrame);
            tabContentGrid.Children.Add(gridSplitter);
            tabContentGrid.Children.Add(userControlInstance);

            HandyControl.Controls.TabItem newTabItem = new HandyControl.Controls.TabItem
            {
                Header = selected.Content.ToString(),
                Content = tabContentGrid,
                Tag = Guid.NewGuid().ToString()
            };
            
            newTabItem.MouseDoubleClick += TabItem_MouseDoubleClick;

            tabControl.Items.Add(newTabItem);
            tabControl.SelectedItem = newTabItem;
        }
    }

    private void TabItem_MouseDoubleClick(object sender, MouseButtonEventArgs e)
    {
        if (e.ChangedButton == MouseButton.Left)
        {
            if (sender is HandyControl.Controls.TabItem tabItem)
            {
                openTabIdentifiers.Add(tabItem.Header.ToString());

                var tabContentGrid = tabItem.Content;

                tabItem.Content = null;

                int tabIndex = tabControl.Items.IndexOf(tabItem);
                tabControl.Items.Remove(tabItem);

                Window newWindow = new Window
                {
                    Content = tabContentGrid,
                    Title = tabItem.Header.ToString(),
                    Width = 800,
                    Height = 450,
                    WindowStartupLocation = WindowStartupLocation.CenterScreen
                };

                newWindow.Tag = new Tuple<HandyControl.Controls.TabItem, int>(tabItem, tabIndex);
                newWindow.Closed += NewWindow_Closed;
                newWindow.Owner = this;
                newWindow.Show();
            }
        }
    }

    private void NewWindow_Closed(object? sender, EventArgs e)
    {
        if (sender is Window window && window.Tag is Tuple<HandyControl.Controls.TabItem, int> tuple)
        {
            int originalIndex = tuple.Item2;
            HandyControl.Controls.TabItem originalTabItem = tuple.Item1;

            if (originalIndex < 0 || originalIndex > tabControl.Items.Count)
            {
                originalIndex = tabControl.Items.Count;
            }

            tabControl.Items.Insert(originalIndex, originalTabItem);

            originalTabItem.Content = window.Content;

            openTabIdentifiers.Remove(originalTabItem.Header.ToString());

            tabControl.SelectedItem = originalTabItem;
        }
    }

NavButton.cs

public class NavButton : ListBoxItem
{
    static NavButton()
    {
        DefaultStyleKeyProperty.OverrideMetadata(typeof(NavButton), new FrameworkPropertyMetadata(typeof(NavButton)));
    }

    public Uri NavLink
    {
        get { return (Uri)GetValue(NavLinkProperty); }
        set { SetValue(NavLinkProperty, value); }
    }

    public static readonly DependencyProperty NavLinkProperty = 
        DependencyProperty.Register("NavLink", typeof(Uri), typeof(NavButton), new PropertyMetadata(null));

    public Type NavType
    {
        get { return (Type)GetValue(NavTypeProperty); }
        set { SetValue(NavTypeProperty, value); }
    }

    public static readonly DependencyProperty NavTypeProperty =
        DependencyProperty.Register("NavType", typeof(Type), typeof(NavButton), new PropertyMetadata(null));
}

Themes/Generic.xaml

<Style TargetType="{x:Type local:NavButton}">
    <Setter Property="Cursor" Value="Hand"/>
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type local:NavButton}">
                <ControlTemplate.Triggers>
                    <Trigger Property="IsMouseOver" Value="True">
                        <Setter Property="Background" Value="#d0ebff"/>
                    </Trigger>
                    <Trigger Property="IsSelected" Value="True">
                        <Setter Property="Background" Value="#d0ebff"/>
                    </Trigger>
                </ControlTemplate.Triggers>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

https://github.com/HandyOrg/HandyControl/assets/58941995/14a57e7a-4b5d-4560-96d3-fa0378543da0

https://github.com/HandyOrg/HandyControl/assets/58941995/bccc4f63-aca5-4388-829c-5ba115049af9

Contributor guide

No contributing guide indexed for this repository

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 at the HandyControl.Controls.TabItem constructor referenced in the NullReferenceException and reproduce the overflow-button and context-menu failures with the provided MainWindow.xaml.cs sequence. Trace the behavior when a tab is detached, reinserted, and selected; done means the reported exceptions no longer occur in both scenarios.

Written by the indexing model from the issue text.

Assessment

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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.