HandyControl icon indicating copy to clipboard operation
HandyControl copied to clipboard

TabControl Overflow Button & Context Menu Exceptions

Open NicholasRicher opened this issue 10 months ago • 0 comments

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

NicholasRicher avatar Apr 08 '24 15:04 NicholasRicher