unoplatform / uno

Open-source platform for building cross-platform native Mobile, Web, Desktop and Embedded apps quickly. Create rich, C#/XAML, single-codebase apps from any IDE. Hot Reload included! 90m+ NuGet Downloads!!
https://platform.uno
Apache License 2.0
8.93k stars 725 forks source link

Callback for `RegisterPropertyChangedCallback` is fired when a child is added and it inherits a property whereas it's not in WinUI #15437

Open Youssef1313 opened 8 months ago

Youssef1313 commented 8 months ago

Current behavior

            var sp = new StackPanel()
            {
                Background = new SolidColorBrush(Colors.Red),
                XYFocusKeyboardNavigation = Microsoft.UI.Xaml.Input.XYFocusKeyboardNavigationMode.Disabled,
                FlowDirection = FlowDirection.RightToLeft,
            };

            var tb = new TextBlock();
            tb.RegisterPropertyChangedCallback(UIElement.XYFocusKeyboardNavigationProperty, (_, _) =>
            {
                sp.Children.Clear();
                sp.Children.Add(new Border());
            });

            tb.RegisterPropertyChangedCallback(FrameworkElement.FlowDirectionProperty, (_, _) =>
            {
                sp.Children.Clear();
                sp.Children.Add(new Border());
            });

            sp.Children.Add(tb);

This test code fires both callbacks. On WinUI, none is fired.

Expected behavior

No response

How to reproduce it (as minimally and precisely as possible)

No response

Workaround

No response

Works on UWP/WinUI

None

Environment

No response

NuGet package version(s)

No response

Affected platforms

No response

IDE

No response

IDE version

No response

Relevant plugins

No response

Anything else we need to know?

No response

Youssef1313 commented 8 months ago

Fixing this isn't as easy as it may look. The binding engine relies on RegisterPropertyChangedCallback, and fixing this issue will break the binding engine.

Youssef1313 commented 7 months ago

NOTE: In WinUI, x:Bind uses RegisterPropertyChangedCallback. So, for the following code:

    <StackPanel>
        <Button Content="Click me" Click="Button_Click" />
        <TextBlock x:Name="tb">
            <Run Text="Hello1" x:Name="hello1" />
            <Run Text="Hello2" Foreground="Red" />
            <Run Text="Hello2" Foreground="{x:Bind hello1.Foreground, Mode=OneWay}" />
        </TextBlock>
    </StackPanel>
        private void Button_Click(object sender, RoutedEventArgs e)
        {
            tb.Foreground = new SolidColorBrush(Colors.Green);
        }

It initially renders as follows:

image

Then, once the button is clicked, it becomes:

image

Youssef1313 commented 7 months ago

Another scenario not aligning with my hypothesis:

    <StackPanel x:Name="sp">
        <Button Content="Click me" Click="Button_Click" />
        <TextBlock x:Name="tb1" Text="Inherited normally" />
        <TextBlock x:Name="tb2" Text="Bound using x:Bind" FlowDirection="{x:Bind tb1.FlowDirection, Mode=OneWay}" />
        <TextBlock x:Name="tb3" Text="Bound using Binding" FlowDirection="{Binding FlowDirection, Mode=OneWay}" />
    </StackPanel>
        public MainWindow()
        {
            this.InitializeComponent();
            tb3.DataContext = tb1;
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            sp.FlowDirection = FlowDirection.RightToLeft;
        }

Before click:

image

After click:

image

Callstack when x:Bind was notified of the inherited property change:

image

**The key difference is that in the first scenario, the children are Runs (not UIElements). It seems that inheritance is only propagated to Children UIElements. We need to validate whether Uno behaves the same or not (I have not yet)