xamarin / Xamarin.Forms

Xamarin.Forms is no longer supported. Migrate your apps to .NET MAUI.
https://aka.ms/xamarin-upgrade
Other
5.62k stars 1.87k forks source link

[Bug] Bindings don't work properly for custom views inside a CarouselView #13669

Open Tommigun1980 opened 3 years ago

Tommigun1980 commented 3 years ago

Description

Bindings for a custom view inside a CarouselView don't work properly:

Screenshot 2021-02-05 at 10 28 34

The screenshot contains of a CarouselView with a data template consisting of the following: 1) A label, where its text is bound to the current item in the ItemsSource ("Item 1"). This works. 2) A custom view (blue border), where its binding is fed with a hard-coded value. This works. 3) A custom view (blue border), where its binding is fed with the current item in the ItemsSource ("Item 1"). This doesn't work and the binding comes out as blank.

This is problematic as the CarouselView is not able to use any components/visualizations I have created, ones I am using everywhere else.

So to reiterate, the following does not work inside a CarouselView:

<views:MyView MyName="{Binding .}" />

(where 'Binding .' is the CarouselView's ItemsSource instance)

While this does work inside a CarouselView:

<views:MyView MyName="Some text" />

Tested only on iOS, don't know if it also happens on Android. There are some issues that sound quite similar, such as https://github.com/xamarin/Xamarin.Forms/issues/13376.

Steps to Reproduce

1) Run the attached repro project. 2) Inspect the ItemsSource bindings not working for custom views inside the CarouselView.

Expected Behavior

Custom views with bindings should work inside CarouselViews.

Actual Behavior

Custom views with bindings don't work inside CarouselViews.

Basic Information

Environment

Show/Hide Visual Studio info ``` === Visual Studio Community 2019 for Mac (Preview) === Version 8.9 Preview (8.9 build 1451) Installation UUID: 72636114-810a-4854-b886-e8b8d8efe7d3 GTK+ 2.24.23 (Raleigh theme) Xamarin.Mac 6.18.0.23 (d16-6 / 088c73638) Package version: 612000113 === Mono Framework MDK === Runtime: Mono 6.12.0.113 (2020-02/4fdfb5b1fd5) (64-bit) Package version: 612000113 === Xamarin Designer === Version: 16.9.0.266 Hash: c4842c761 Branch: remotes/origin/c4842c761b9b6a95407f72278ca7fb42f8f7fdf0 Build date: 2021-01-07 06:17:08 UTC === Roslyn (Language Service) === 3.9.0-3.20619.14+df59a33fd9beff9790e01a2a1ab21e4a1e6921b3 === NuGet === Version: 5.8.0.6860 === .NET Core SDK === SDK: /usr/local/share/dotnet/sdk/5.0.102/Sdks SDK Versions: 5.0.102 5.0.101 5.0.100 3.1.405 3.1.404 3.1.403 MSBuild SDKs: /Applications/Visual Studio.app/Contents/Resources/lib/monodevelop/bin/MSBuild/Current/bin/Sdks === .NET Core Runtime === Runtime: /usr/local/share/dotnet/dotnet Runtime Versions: 5.0.2 5.0.1 5.0.0 3.1.11 3.1.10 3.1.9 === .NET Core 3.1 SDK === SDK: 3.1.405 === Xamarin.Profiler === Version: 1.6.15.68 Location: /Applications/Xamarin Profiler.app/Contents/MacOS/Xamarin Profiler === Updater === Version: 11 === Apple Developer Tools === Xcode 12.4 (17801) Build 12D4e === Xamarin.Mac === Xamarin.Mac not installed. Can't find /Library/Frameworks/Xamarin.Mac.framework/Versions/Current/Version. === Xamarin.iOS === Version: 14.9.0.27 (Visual Studio Community) Hash: f4c9327fa Branch: main Build date: 2020-11-19 10:57:31-0500 === Xamarin.Android === Version: 11.2.0.0 (Visual Studio Community) Commit: xamarin-android/d16-9/f908d16 Android SDK: /Users/tommikiviniemi/Library/Developer/Xamarin/android-sdk-macosx Supported Android versions: None installed SDK Tools Version: 26.1.1 SDK Platform Tools Version: 30.0.4 SDK Build Tools Version: 30.0.2 Build Information: Mono: 5e9cb6d Java.Interop: xamarin/java.interop/d16-9@1d382be ProGuard: Guardsquare/proguard/v7.0.1@912d149 SQLite: xamarin/sqlite/3.32.2@cfe06e0 Xamarin.Android Tools: xamarin/xamarin-android-tools/main@ad80a42 === Microsoft OpenJDK for Mobile === Java SDK: /Users/tommikiviniemi/Library/Developer/Xamarin/jdk/microsoft_dist_openjdk_1.8.0.25 1.8.0-25 Android Designer EPL code available here: https://github.com/xamarin/AndroidDesigner.EPL === Android SDK Manager === Version: 16.9.0.21 Hash: 57e40ba Branch: remotes/origin/main Build date: 2021-01-08 01:57:14 UTC === Android Device Manager === Version: 16.9.0.14 Hash: 0fdccda Branch: remotes/origin/main Build date: 2021-01-08 01:57:36 UTC === Build Information === Release ID: 809001451 Git revision: cfd15313a6388ef8dada0182e22a058131c46f9d Build date: 2021-01-15 08:42:21-05 Build branch: release-8.9 Xamarin extensions: cfd15313a6388ef8dada0182e22a058131c46f9d === Operating System === Mac OS X 10.16.0 Darwin 20.3.0 Darwin Kernel Version 20.3.0 Thu Jan 21 00:07:06 PST 2021 root:xnu-7195.81.3~1/RELEASE_X86_64 x86_64 ```

Screenshots

Reproduction Link

CarouselViewContentsLoseBindings.zip

Workaround

See first follow-up comment for a workaround.

Tommigun1980 commented 3 years ago

Workaround found!

Bindings for a custom view inside a CarouselView will work if, instead of doing the following in the custom view:

<ContentView
    ...
    x:Name="myRoot"
    BindingContext="{x:Reference myRoot}">
    ...
    <Label Text="{Binding SomeBinding} />

... every binding specifies a binding source instead:

    <Label Text="{Binding SomeBinding, Source={x:Reference myRoot}} />

This is is only needed if the custom view resides inside a CarouselView (and possibly a CollectionView - didn't test). Doing this also clutters the XAML a lot, where a one-time assignment is much cleaner and works everywhere else.

Very much related issue: https://github.com/xamarin/Xamarin.Forms/issues/13664

rmarinho commented 3 years ago

I think this is not a bug but how Binding and it's Source (BindingContext) works. A way to fix this could be:

On your MyView change the Bindable Property to something like:

    public static readonly BindableProperty MyNameProperty = BindableProperty.Create(
            nameof(MyName), typeof(string), typeof(MyView),
            propertyChanged: (BindableObject bindable, object oldValue, object newValue) => (bindable as MyView).lbl.Text = newValue?.ToString(););

Remove the BindingContext from the xaml on MyView, and add a x:name to the label

<?xml version="1.0" encoding="UTF-8" ?>
<ContentView
    xmlns="http://xamarin.com/schemas/2014/forms"
    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
    x:Class="CarouselViewContentsLoseBindings.Views.MyView"
    x:Name="myViewRoot">
    <ContentView.Content>
        <Frame BorderColor="Blue">
            <StackLayout>
                <Label
                    Text="My View"
                    HorizontalTextAlignment="Center" />

                <Label x:Name="lbl"
                    HorizontalTextAlignment="Center"
                    VerticalTextAlignment="Center" />
            </StackLayout>
        </Frame>
    </ContentView.Content>
</ContentView>

and then use it like

<views:MyView MyName="{Binding ., Mode=OneTime, StringFormat='Binding: \'{0}\''}" />
Tommigun1980 commented 3 years ago

I think this is not a bug but how Binding and it's Source (BindingContext) works. A way to fix this could be:

On your MyView change the Bindable Property to something like:

    public static readonly BindableProperty MyNameProperty = BindableProperty.Create(
            nameof(MyName), typeof(string), typeof(MyView),
            propertyChanged: (BindableObject bindable, object oldValue, object newValue) => (bindable as MyView).lbl.Text = newValue?.ToString(););

Remove the BindingContext from the xaml on MyView, and add a x:name to the label

<?xml version="1.0" encoding="UTF-8" ?>
<ContentView
    xmlns="http://xamarin.com/schemas/2014/forms"
    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
    x:Class="CarouselViewContentsLoseBindings.Views.MyView"
    x:Name="myViewRoot">
    <ContentView.Content>
        <Frame BorderColor="Blue">
            <StackLayout>
                <Label
                    Text="My View"
                    HorizontalTextAlignment="Center" />

                <Label x:Name="lbl"
                    HorizontalTextAlignment="Center"
                    VerticalTextAlignment="Center" />
            </StackLayout>
        </Frame>
    </ContentView.Content>
</ContentView>

and then use it like

<views:MyView MyName="{Binding ., Mode=OneTime, StringFormat='Binding: \'{0}\''}" />

Thank you. But why would it matter where the control is used? The "normal" way works as long as the control is not inside a CarouselView, which is populated from an ObservableCollection.

Having to hard-code it like that forces the control to "know" where it’s going to be used, and removes the usefulness of bindings.

rmarinho commented 3 years ago

The difference is that on a DataTemplate the BindingContext of that view is replaced by the context of the item on the collection.