Почему я получаю сообщение «Указанная трансляция не действительна» при использовании настраиваемого представления с привязкой, но не при установке вручную?

Aug 17 2020

Я создаю пользовательское представление, используя ObservableCollection в качестве BindableProperty (моей целью было обновить формат представления в зависимости от его содержимого, в данном случае списка цветов). Я думаю, что свойство настроено правильно в пользовательском представлении. Если я установил привязываемое свойство в C #, он заработает. Если я попытаюсь использовать {Binding varname} внутри XAML, будет выдана ошибка «Указанное приведение недействительно». Я пытаюсь понять, в чем моя проблема.

Ниже в общедоступной MainPage () вы можете увидеть закомментированную строку, в которой я вручную установил свойство. Если я раскомментирую это и возьму "Color = {Binding Test}" из MainPage.xaml, все заработает. Если я запущу это как есть, я получу исключение.

Вот весь мой код:

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>

Трассировки стека:

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

Дополнительная информация. Я пробовал обновить Xamarin.Forms в NuGet, так как это было давно. Теперь он даже не компилируется с привязкой. Это дает мне:

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

Но опять же, если я вручную установлю значение в C #, оно все равно будет работать. Мне определенно что-то не хватает в моем XAML, но я не знаю, что это такое.

Ответы

LeonLu-MSFT Aug 17 2020 at 10:16

Вы можете изменить BindablePropertyследующее ColorListProperty, а затем провести тест в 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"/>

Если вы хотите, чтобы CollectionChangedсобытие выполнялось, когда вы нажимаете кнопку, вы можете добавить конструктор 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();
        }

Ваш setметод будет выполнен, когда вы установите новый. ObservableCollectionЕсли вы просто добавите элемент в существующий ObservableCollection, используйте CollectionChangedего, ColorBoxViewвозможно, лучше.

BTGraham Aug 17 2020 at 20:33

Итак, после многих поисков я нашел несколько примеров, в которых тип BindingProperty был установлен на «IEnumerable». После переделки кода я получил следующее, что работает:

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

    }

}

Мне было бы очень интересно узнать официальную причину, по которой это работает. Комментарии приветствуются.