0

Is this a bug in TabView implementation? Or what could I be doing wrong?

Summary:

Binding seems to get out of sync when adding and removing tabs from tab TabView.

Minimal Working Example that Demonstrates the Issue:

Code for minimal working example that demonstrates the issue here:

https://github.com/elbrandt/WinUI3_TabView_Binding_Issue

Description

In a desktop WinUI3 app, using .NET8, I have a TabView that has its TabItemsSource property bound to an ObservableCollection of TabViewModel instances in the page's ViewModel class.

Each time a new tab button is clicked, a ViewModel command method is executed, which instantiates a TabViewModel class and adds it to the ObservableCollection. The TabViewModel class has a single integer property, MyNumber that is initialized to a unique, monotonically increasing integer.

The MyNumber property is bound to both the TabViewItem's Header property, as well as to a TextBlock in a custom control that is the content of the TabViewItem

Behavior (Reproducing)

The Header and and TextBlock should always be in sync, since they are bound to the same member of the object that is the DataContext for the TabViewItem.

However, if you 1. Add two tabs, 2. Close the two tabs, 3. Add another tab, then you will see the two UI elements are no longer in sync.

What is going on? Am I doing something wrong? Or is this a bug in the TabView code?

Thanks in advance!

(I have cross posted this to https://learn.microsoft.com/en-us/answers/questions/edit/2145997 as well, in hopes of getting an answer somewhere, quickly).

enter image description here

2 Answers 2

0

This behavior is expected since TabView will reuse items.

I'd recommend using a DependencyProperty:

MyControl.xaml.cs

public sealed partial class MyControl : UserControl
{
    public MyControl()
    {
        this.InitializeComponent();
    }

    public int MyControlNumber
    {
        get => (int)GetValue(MyControlNumberProperty);
        set => SetValue(MyControlNumberProperty, value);
    }

    public static readonly DependencyProperty MyControlNumberProperty =
        DependencyProperty.Register(
            nameof(MyControlNumber),
            typeof(int),
            typeof(MyControl),
            new PropertyMetadata(default));

MyControl.xaml

<TextBlock Text="{x:Bind MyControlNumber, Mode=OneWay}"/>

then:

<DataTemplate x:DataType="local:TabViewModel">
    <TabViewItem Header="{x:Bind MyNumber, Mode=OneWay}">
        <local:MyControl MyControlNumber="{x:Bind MyNumber, Mode=OneWay}" />
    </TabViewItem>
</DataTemplate>
Sign up to request clarification or add additional context in comments.

2 Comments

Thanks for the reply and the proposed workaround. I found what seems to be a more general solution, posted above. Let me know if you see any downsides to what I propose in my answer. Thanks again!
I still recommend the approach in my answer as at least it's easier to understand the code. If you don't want to create a DP for each property, you can just create a DP for a class.
0

After posting the question, I carefully read the 'Data Binding In Depth' page here and decided to try updating the TabItem.Content's bindings manually. This seems to fix the issue, if performed at a strategic time (on tab activation, for example).

My fix involves adding this to the MainWindow.xaml.cs:

       private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
       {
           // this is being done from a button as proof that it works.
           // there are cleaner ways and places to do this in real code.
           foreach (var t in tabView.TabItems)
           {
               var d = this.tabView.ContainerFromItem(t);
               ((MyControl)((TabViewItem)d).Content).UpdateBindings();
           }
       }

and in MyControl.xaml.cs:

        public void UpdateBindings()
        {
            Bindings.Update();
        }

This has the advantage over Andrew's proposed solution by not requiring explicit dependency properties for every data-bound element of MyControl, which seems more flexible. Maybe my solution will have some other downside that I've yet to uncover, but for now this seems the most flexible.

I've updated my MWE with a branch containing the workaround: https://github.com/elbrandt/WinUI3_TabView_Binding_Issue/tree/workaround

As a general comment:

The 'reuse' of the control and mismatch of bound data context may indeed be 'as designed', and probably helps with performance if there are many many tabs (which is unlikely to be the case...), so I fail to see how this design is 'expected behavior' for anyone using the class. Even if the TabView has no implementation bugs per this design, I'd consider this a 'design bug' because it is certainly not the expected behavior as a consumer of the class.

1 Comment

It is appreciated to share your answer here. Please don't forget to accept your answer since the it works for you.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.