microsoft / microsoft-ui-xaml

Windows UI Library: the latest Windows 10 native controls and Fluent styles for your applications
MIT License
6.15k stars 665 forks source link

TabViewItem instances reused incorrectly #9745

Open Mangepange opened 1 week ago

Mangepange commented 1 week ago

Describe the bug

I noticed that when I used Behaviors on content in TabViewItems, and on their headers, they behave irratically when removing tabs, and adding them back again.

It seems that the TabView recognizes the viewmodel that I add back again, and reuses an old TabViewItem where the Behaviors has been detached. The behavior is re-attached for the TabViewItems content, but the behavior in its header is never reattached.

Also, it seems that when removing the currently selected tab, the next selected TabViewItem does not attach its behaviors correctly.

I can probably manage to get around parts of the issue if I skip detaching my behaviors, but I'm a bit surprised that the TabView reuses old TabViewItems.

Steps to reproduce the bug

I wrote a small example that illustrates the issue. A behavior sets the text foreground color to green or red, depending on if it is attached or detached.

Initial state image

Removed the viewmodel B, and added it back again. Now the behavior for the 'C' content is detached, and also for the 'B' header. image

Selecting the newly added item, the contents behavior is re-attached, but not the one for the header. image

Usercontrol:

<UserControl
    x:Class="App4.MyUserControl"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:App4"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:i="using:Microsoft.Xaml.Interactivity"
    mc:Ignorable="d">
    <UserControl.Resources>
    </UserControl.Resources>

    <StackPanel Orientation="Vertical">

        <Button Content="Remove current item"
                Click="{x:Bind ViewModel.RemoveCurrent}"/>

        <Button Content="Add again"
                Click="{x:Bind ViewModel.AddLastRemoved}"/>

        <TabView TabItemsSource="{x:Bind ViewModel.Items}"
                 SelectedItem="{x:Bind ViewModel.CurrentItem, Mode=TwoWay}"
                 VerticalAlignment="Stretch">
            <TabView.TabItemTemplate>
                <DataTemplate x:DataType="local:ItemViewModel">
                    <TabViewItem>
                        <TabViewItem.Header>
                            <TextBlock Text="{x:Bind Name}">
                                <i:Interaction.Behaviors>
                                    <local:TextBlockColorBehavior/>
                                </i:Interaction.Behaviors>
                            </TextBlock>
                        </TabViewItem.Header>

                        <TextBlock Text="{x:Bind Name}">
                            <i:Interaction.Behaviors>
                                <local:TextBlockColorBehavior/>
                            </i:Interaction.Behaviors>
                        </TextBlock>
                    </TabViewItem>
                </DataTemplate>
            </TabView.TabItemTemplate>
        </TabView>

    </StackPanel>

</UserControl>

Corresponding ViewModels:

public class ItemViewModel
{
    public required string Name { get; init; }
}

public class MyViewModel
{
    private ItemViewModel? _lastRemoved;

    public MyViewModel()
    {
        Items.Add(new ItemViewModel { Name = "ViewModel A" });
        Items.Add(new ItemViewModel { Name = "ViewModel B" });
        Items.Add(new ItemViewModel { Name = "ViewModel C" });
    }

    public ObservableCollection<ItemViewModel> Items { get; } = [];

    public ItemViewModel? CurrentItem { get; set; }

    public void RemoveCurrent()
    {
        _lastRemoved = CurrentItem;
        Items.Remove(_lastRemoved);
    }

    public void AddLastRemoved()
    {
        if (_lastRemoved != null)
        {
            Items.Add(_lastRemoved);
        }
    }
}

Behavior for changing color depending on attached/detached:

public class TextBlockColorBehavior : Behavior<TextBlock>
{
    protected override void OnAttached()
    {
        base.OnAttached();
        AssociatedObject.Foreground = new SolidColorBrush(Colors.Green);
    }

    protected override void OnDetaching()
    {
        base.OnDetaching();
        AssociatedObject.Foreground = new SolidColorBrush(Colors.Red);
    }
}

Expected behavior

TabViewItem instances should not be reused when adding back a ViewModel instance that has been used previously. If it's part of some smart caching, I at least expect that behaviors should be reattached properly.

Screenshots

No response

NuGet package version

WinUI 3 - Windows App SDK 1.5.4: 1.5.240607001

Windows version

Windows 11 (22H2): Build 22621

Additional context

No response

github-actions[bot] commented 1 week ago

Hi I'm an AI powered bot that finds similar issues based off the issue title.

Please view the issues below to see if they solve your problem, and if the issue describes your problem please consider closing this one. Thank you!

Open similar issues:

Closed similar issues:

Note: You can give me feedback by thumbs upping or thumbs downing this comment.