AvaloniaUI / Avalonia

Develop Desktop, Embedded, Mobile and WebAssembly apps with C# and XAML. The most popular .NET UI client technology
https://avaloniaui.net
MIT License
25.35k stars 2.2k forks source link

Nested TabControl raises SelectionChanged event in a wrong tab control #7866

Open DanPristupov opened 2 years ago

DanPristupov commented 2 years ago

Describe the bug Nested TabControl raises a SelectionChanged event in wrong tab control

To Reproduce Steps to reproduce the behavior:

Details.

  1. Create two tab controls:
    • Parent tab control with SelectionChanged event handler.
    • A child tab control inside the parent tab control item. The child tab control has no event handlers.
  2. Switch tab in the child tab control

Expected behavior Nothing will happen because child tab control has not event handlers

Actual behavior The parent tab control event handler gets fired.

Screenshots

IMAGE 2022-03-24 14:39:07

https://user-images.githubusercontent.com/618115/159928506-77870e54-3643-4b1a-bac9-9a03758d602b.mp4

Desktop (please complete the following information):

robloo commented 2 years ago

The problem is likely because the SelectionChanged event in SelectingItemsControl is a routed event that is bubbling up from the child into the parent. Since this event is literally the exact same event it will appear as if the parent is actually raising it when in fact it is bubbling from the child.

Can you show the code on how you are subscribing to the event just to make sure there is no confusion? It might also help to look at this in DevTools.

Otherwise, I think someone on the core team needs to reply on how to fix this. To me it looks architecture related (SelectionChanged may need to be direct instead of bubbling).

DanPristupov commented 2 years ago

I already shared link with a sample project with the core team.

Can you show the code on how you are subscribing to the event just to make sure there is no confusion?

Yes, sure.

MainWindow.xaml

<Window xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" x:Class="AvaloniaTest.MainWindow" Title="AvaloniaTest">
      <DockPanel>
        <Button Content="ShowDialog" Click="OnShowDialog"/>
        <TabControl x:Name="TabControl" />
      </DockPanel>
</Window>

MainWindow.cs

    public partial class MainWindow : Window
    {
        private TabControl _tabControl;
        public MainWindow()
        {
            InitializeComponent();

            _tabControl = this.FindControl<TabControl>("TabControl");
            _tabControl.SelectionChanged += _parentTabControl_SelectionChanged; // here is the subscription point

            var tabItem = new TabItem();
            tabItem.Header = "Item1";
            var ctrl = new MyUserControl();
            tabItem.Content = ctrl;
            _tabControl.Items = new [] { tabItem, new TabItem { Header = "Title2"} };
        }

        private async void _parentTabControl_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            var state = e.Source == sender ? "🟢" : "🔴";
            Console.WriteLine($"{DateTime.Now} {state} _parentTabControl_SelectionChanged");
        }
...
    }

MyUserControl.xaml which contains the nested tab control which doesn't have a subscription

<UserControl xmlns="https://github.com/avaloniaui"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:avaloniaTest="clr-namespace:AvaloniaTest"
             x:Class="AvaloniaTest.MyUserControl">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="*" MinHeight="110"/>
            <RowDefinition Height="1"/>
            <RowDefinition Height="*" MinHeight="110"/>
            <RowDefinition Height="*" MinHeight="110"/>
        </Grid.RowDefinitions>

      ...

        <TabControl Grid.Row="3" x:Name="TabControlWithoutEventHandler">
          <TabItem Header="TabItemWithoutEventHandler1">
            <TextBlock Text="Switching that tab must not fire any event"/>
          </TabItem>
          <TabItem Header="TabItemWithoutEventHandler1">
            <TextBlock Text="Switching that tab must not fire any event"/>
          </TabItem>
        </TabControl>
    </Grid>
</UserControl>

MyUserControl.cs

public partial class MyUserControl : UserControl
{
    public MyUserControl()
    {
        InitializeComponent();
    }

    private void InitializeComponent()
    {
        AvaloniaXamlLoader.Load(this);
    }
}