Perché ricevo un messaggio "Cast specificato non valido" quando utilizzo una visualizzazione personalizzata con un'associazione ma non quando imposto manualmente?

Aug 17 2020

Sto creando una visualizzazione personalizzata utilizzando una ObservableCollection come BindableProperty (il mio obiettivo era aggiornare il formato della visualizzazione in base al suo contenuto, in questo caso un elenco di colori). Penso di aver impostato correttamente la proprietà nella visualizzazione personalizzata. Se imposto la proprietà associabile in C #, funziona. Se provo a utilizzare un {Binding varname} all'interno di XAML, viene visualizzato un errore "Cast specificato non valido". Sto cercando di capire dov'è il mio problema.

Di seguito nella MainPage () pubblica puoi vedere la riga commentata in cui ho impostato manualmente la proprietà. Se rimuovo questo commento e prendo "Color = {Binding Test}" da MainPage.xaml, tutto funziona. Se lo eseguo così com'è, ottengo l'eccezione.

Ecco tutto il mio codice:

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>

Stack Trace:

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

Informazioni aggiuntive: ho provato ad aggiornare Xamarin.Forms in NuGet da un po 'di tempo. Ora non verrà nemmeno compilato con l'associazione. Mi dà:

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

Ma ancora una volta, se imposto manualmente il valore in C # funziona ancora. Sicuramente mi manca qualcosa nel mio XAML ma non so cosa sia.

Risposte

LeonLu-MSFT Aug 17 2020 at 10:16

Puoi modificare BindablePropertyquanto segue ColorListProperty, quindi fare un test nel file 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"/>

Se si desidera CollectionChangedevento da eseguire, quando si fa clic sul pulsante, è possibile aggiungere l' ColorList.CollectionChanged += ColorList_CollectionChangednel ColorBoxViewcostruttore di 's.

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

        }

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

Il vostro setmetodo sarà eseguito, quando si imposta una nuova ObservableCollection, se si aggiunge solo la voce da esistito ObservableCollection, l'uso CollectionChangedin ColorBoxViewforse meglio.

BTGraham Aug 17 2020 at 20:33

Quindi, dopo molte ricerche, ho trovato diversi esempi in cui il tipo di BindingProperty era impostato su "IEnumerable". Dopo aver rifatto il mio codice, ho trovato quanto segue che funziona:

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);

    }

}

Sarei molto interessato a conoscere il motivo ufficiale per cui funziona. I commenti sono apprezzati.