Why I can't bind the background color in different platform?

Viewed 564

Here is 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:d="http://xamarin.com/schemas/2014/forms/design"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             mc:Ignorable="d"
             x:Class="App1.MainPage">

    <BoxView>
        <BoxView.BackgroundColor>
            <OnPlatform x:TypeArguments="Color">
                <On Platform="Android" Value="{Binding First}"></On>
                <On Platform="iOS" Value="{Binding Second}"></On>
            </OnPlatform>
        </BoxView.BackgroundColor>
    </BoxView>
</ContentPage>

Here is code-behind:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Xamarin.Forms;

namespace App1
{
    // Learn more about making custom code visible in the Xamarin.Forms previewer
    // by visiting https://aka.ms/xamarinforms-previewer
    [DesignTimeVisible(false)]
    public partial class MainPage : ContentPage
    {
        public MainPage()
        {
            InitializeComponent();
            this.BindingContext = this;
        }
        public Color First {
            get {
                return Color.FromHex("#575757");
            }
        }
        public Color Second
        {
            get
            {
                return Color.FromHex("#ffffff");
            }
        }
    }
}

After I ran the program, it reports an error: Specified cast is not valid.

enter image description here

What's the problem with it?

2 Answers

I had to dug a bit into the docs to find it. OnPlatform can't take bindable values for the Color property.

From OnPlatform markup extension docs:

The XAML parser expects that values of the correct type will be provided to properties consuming the OnPlatform markup extension. If type conversion is necessary, the OnPlatform markup extension will attempt to perform it using the default converters provided by Xamarin.Forms. However, there are some type conversions that can't be performed by the default converters and in these cases the Converter property should be set to an IValueConverter implementation.

When you open the tag <BoxView.BackgroundColor>, the xaml analyzer will expect a value of type Color, like you have defined it in the next row. After that, you are passing a value that extends BindingBase class and is returning a value of this type. There is a cast conflict with the bindable value and the Color object that the parser expects out of the box.

What is happening behind the scenes is (look at the comments):

<BoxView>
    <BoxView.BackgroundColor> <!-- You are saying that we want to modify a property of type Color -->
        <OnPlatform x:TypeArguments="Color"> <!-- Here the analyzer expects type Color, nothing else will work | checked build time -->
            <On Platform="Android" Value="{Binding First}"></On> <!-- The parser expects type Color, but is receiving type BindingBase | checked run time -->
            <On Platform="iOS" Value="{Binding Second}"></On> <!-- The parser expects type Color, but is receiving type BindingBase | checked run time -->
        </OnPlatform>
     </BoxView.BackgroundColor>
</BoxView>

According to the documentation, you can try to define the cast in a converter, which is not the best approach, since you will be hiding this part of the converting behind the scenes. An alternative (and better) approach is to have the BackgroundColor's binding to one property and define the logic there like so:

<BoxView BackgroundColor="{Binding BgColor}" />

and in the code-behind:

public Color BgColor => Device.RuntimePlatform == Device.Android ? Color.FromHex("#575757") : Color.FromHex("#ffffff");

NB: The last code assumes that you are developing only for 2 platforms - iOS & Android. If you are/plan to develop to other platforms, you need to extend the RuntimePlatform check.

The Xaml you pasted suppose that the OnPlatform object is the target of the Binding to string, and then the OnPlatform returns the right string depending on the Platform.

Unfortunately, that doesn't work because OnPlatform is not a BindableObject and can't be the target of a Binding.

It's well explained in this answer : https://stackoverflow.com/a/41829242/8187800 .

There are two ways to fix the problem .

  1. Hard code the color string in xaml .

    <BoxView>
      <BoxView.BackgroundColor>
        <OnPlatform x:TypeArguments="Color">
            <On Platform="Android" Value="#575757"></On>
            <On Platform="iOS" Value="#ffffff"></On>
        </OnPlatform>
       </BoxView.BackgroundColor>
    </BoxView>
    

    Refer https://forums.xamarin.com/discussion/33117/setting-platform-specific-background-color-in-xaml .

  2. Change Color to BindingBase .

       <OnPlatform x:TypeArguments="BindingBase">
            <On Platform="Android" Value="{Binding First}"></On>
            <On Platform="iOS" Value="{Binding Second}"></On>
        </OnPlatform>
    

    in this way there is a warning but we could ignore it enter image description here

Related