¿Por qué obtengo un "La conversión especificada no es válida" cuando uso una vista personalizada con un enlace pero no cuando lo configuro manualmente?

Aug 17 2020

Estoy construyendo una vista personalizada usando una ObservableCollection como BindableProperty (mi objetivo era actualizar el formato de la vista según su contenido, en este caso una lista de colores). Creo que tengo la propiedad configurada correctamente en la vista personalizada. Si configuro la propiedad enlazable en C #, funciona. Si trato de usar un {Binding varname} dentro de XAML, arroja un error "La conversión especificada no es válida". Estoy tratando de averiguar dónde está mi problema.

A continuación, en la MainPage () pública, puede ver la línea comentada donde configuro manualmente la propiedad. Si elimino el comentario de esto y saco "Color = {Binding Test}" de MainPage.xaml, todo funciona. Si ejecuto esto como está, obtengo la excepción.

Aquí está todo mi código:

MainPage.xaml.cs

public partial class MainPage : ContentPage
{
    public ObservableCollection<Color> Test { get; set; }
    public MainPage()
    {
        Test = new ObservableCollection<Color>();

        Test.Add(Color.Blue);
        Test.Add(Color.Green);
        Test.Add(Color.Red);
        Test.Add(Color.Purple);
        
        this.BindingContext = this;
        InitializeComponent();

        //myView.Colors = Test;
    }

    

    private void OnClicked(object sender, EventArgs e)
    {
        myView.Colors.Add(Color.Yellow);
    }
}

MainPage.xaml

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
         xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
         xmlns:local="clr-namespace:ColorBoxes"
         x:Class="ColorBoxes.MainPage">

    <StackLayout HorizontalOptions="CenterAndExpand">
        <local:ColorBoxView  x:Name="myView" Colors="{Binding Test}" WidthRequest="400" HeightRequest="600"/>
        <Button Text="Add Color" Clicked="OnClicked"/>
    </StackLayout>

</ContentPage>

ColorBoxView.xaml.cs

public partial class ColorBoxView : ContentView
{
    public ColorBoxView()
    {
       InitializeComponent();
       
    }
    
    public static BindableProperty ColorList = BindableProperty.Create(
        propertyName: "Colors",
        returnType: typeof(ObservableCollection<Color>),
        declaringType: typeof(ColorBoxView),
        defaultValue: new ObservableCollection<Color>(),
        defaultBindingMode: BindingMode.OneWay,
        propertyChanged: HandleColorListPropertyChanged
    );

    public ObservableCollection<Color> Colors
    {
        get { return (ObservableCollection<Color>) base.GetValue(ColorList); }
        set
        {
            if (value != this.Colors)
            {
                base.SetValue(ColorList, value);
                Colors.CollectionChanged += Items_CollectionChanged;
            }
        }
    }
   
    
    void Items_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
    {
        buildView(Colors);
    }

    private void buildView(ObservableCollection<Color> newColors)
    {
        /* This code is never reached */
    }
    private static void HandleColorListPropertyChanged(BindableObject bindable, object oldValue, object newValue)
    {
        ObservableCollection<Color> newColors = (ObservableCollection<Color>) newValue;
        ColorBoxView thisView = (ColorBoxView) bindable;

        thisView.buildView(newColors);
        
    }

ColorBoxView.xaml

<?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="ColorBoxes.ColorBoxView" >

    <Grid  x:Name="BoxGrid"  HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand" 
           RowSpacing="0" ColumnSpacing="0" Padding="0">
    </Grid>

</ContentView>

Seguimiento de pila:

System.InvalidCastException: Specified cast is not valid. 
   at ColorBoxes.MainPage.InitializeComponent () [0x00023] in /Users/docmani/RiderProjects/ColorBoxes/ColorBoxes/ColorBoxes/obj/Debug/netstandard2.0/MainPage.xaml.g.cs:26
   at ColorBoxes.MainPage..ctor () [0x00060] in /Users/docmani/RiderProjects/ColorBoxes/ColorBoxes/ColorBoxes/MainPage.xaml.cs:25
   at ColorBoxes.App..ctor () [0x0000f] in /Users/docmani/RiderProjects/ColorBoxes/ColorBoxes/ColorBoxes/App.xaml.cs:15
   at ColorBoxes.iOS.AppDelegate.FinishedLaunching (UIKit.UIApplication app, Foundation.NSDictionary options) [0x00007] in /Users/docmani/RiderProjects/ColorBoxes/ColorBoxes/ColorBoxes.iOS/AppDelegate.cs:25
   at at (wrapper managed-to-native) UIKit.UIApplication.UIApplicationMain(int,string[],intptr,intptr)
   at UIKit.UIApplication.Main (System.String[] args, System.IntPtr principal, System.IntPtr delegate) [0x00005] in /Library/Frameworks/Xamarin.iOS.framework/Versions/13.18.2.1/src/Xamarin.iOS/UIKit/UIApplication.cs:86
   at UIKit.UIApplication.Main (System.String[] args, System.String principalClassName, System.String delegateClassName) [0x0000e] in /Library/Frameworks/Xamarin.iOS.framework/Versions/13.18.2.1/src/Xamarin.iOS/UIKit/UIApplication.cs:65
   at ColorBoxes.iOS.Application.Main (System.String[] args) [0x00001] in /Users/docmani/RiderProjects/ColorBoxes/ColorBoxes/ColorBoxes.iOS/Main.cs:16

Información adicional: intenté actualizar Xamarin.Forms en NuGet ya que había pasado un tiempo. Ahora ni siquiera se compilará con el enlace. Me da:

 MainPage.xaml(9, 46): [XFC0009] No property, BindableProperty, or event found for "Colors", or mismatching type between value and property.

Pero de nuevo, si configuro manualmente el valor en C #, todavía funciona. Definitivamente me falta algo en mi XAML pero no sé qué es.

Respuestas

LeonLu-MSFT Aug 17 2020 at 10:16

Puede cambiar lo BindablePropertysiguiente ColorListProperty, luego hacer una prueba en el xaml.

public partial class ColorBoxView : ContentView
    {
        public ColorBoxView ()
        {
            InitializeComponent ();
        }
 
        public static readonly BindableProperty ColorListProperty = BindableProperty.Create(
            nameof(ColorList), 
            typeof(ObservableCollection<object>), 
            typeof(ColorBoxView), 
            new ObservableCollection<object>(), 
            BindingMode.OneWay, 
            propertyChanged: ContractListPropertyChangedDelegate);
        public ObservableCollection<object> ColorList
        {
            get =>
            (ObservableCollection<object>)GetValue(ColorListProperty);
            set => SetValue(ColorListProperty, value);
        }
        private static void ContractListPropertyChangedDelegate(BindableObject bindable, object oldValue, object newValue)
        {
            // throw new NotImplementedException();
         //   ColorBoxView colorBoxView= bindable as ColorBoxView;


        }
 <local:ColorBoxView  x:Name="myView" ColorList="{Binding Test}"  WidthRequest="400" HeightRequest="600"/>

Si desea que CollectionChangedse ejecute el evento, cuando haga clic en el botón, puede agregar el constructor de ColorList.CollectionChanged += ColorList_CollectionChangedin ColorBoxView.

public ColorBoxView ()
        {
            InitializeComponent ();
            ColorList.CollectionChanged += ColorList_CollectionChanged;

        }

        private void ColorList_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
        {
           // throw new NotImplementedException();
        }

Su setmétodo se ejecutará, cuando se establece una nueva ObservableCollection, si usted acaba de añadir el artículo a existido ObservableCollection, su uso CollectionChangeden el ColorBoxViewquizás mejor.

BTGraham Aug 17 2020 at 20:33

Entonces, después de muchas búsquedas, encontré varios ejemplos en los que el tipo de BindingProperty se estableció en 'IEnumerable'. Después de rehacer mi código, se me ocurrió lo siguiente que funciona:

public partial class ColorBoxView : ContentView
{
    public ColorBoxView()
    {
       InitializeComponent();
    }
    
    public static readonly BindableProperty ColorsProperty =
        BindableProperty.Create (nameof (Colors), typeof (IEnumerable), typeof (ColorBoxView), null,
            BindingMode.Default, null, HandleColorListPropertyChanged);

    public IEnumerable Colors
    {
        get { return (IEnumerable) GetValue (ColorsProperty); }
        set
        {
            SetValue (ColorsProperty, value);
            var oc = (ObservableCollection<Color>) (this.Colors);
            oc.CollectionChanged += Items_CollectionChanged;
        }
    }

    private void Items_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
    {
        buildView( (ObservableCollection<Color>)Colors);
    }

    private void buildView(ObservableCollection<Color> newColors)
    {
        /* Update code */
    }

    private static void HandleColorListPropertyChanged(BindableObject bindable, object oldValue, object newValue)
    {
        ObservableCollection<Color> newColors = (ObservableCollection<Color>) newValue;
        ColorBoxView thisView = (ColorBoxView) bindable;

        thisView.Colors = newColors;
        thisView.buildView(newColors);

    }

}

Me interesaría mucho conocer la razón oficial por la que esto funciona. Se agradecen los comentarios.