Search code examples
c#xamarin.formsxamarin.androidconditional-formatting.net-standard-2.0

Xamarin Forms: Trying to build conditional formatting in for Android, Error: System.InvalidCastException Message=Object must implement IConvertible


I have a page set up with a grid:

<Grid Padding="10,0,10,0"  RowSpacing="20" ColumnSpacing="10" VerticalOptions="CenterAndExpand" >
<Grid.ColumnDefinitions>
    <ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
    <RowDefinition Height="*" />
</Grid.RowDefinitions>
<Label Grid.Row="1" Grid.Column="0" Text="Last Updated:" Style="{DynamicResource ListItemTextStyle}"  />
<Label Grid.Row="1" Grid.Column="1" Text="{Binding LastUpdated}" Style="{DynamicResource ListItemDetailTextStyle}" HorizontalTextAlignment="End" />
<Label Grid.Row="2" Grid.Column="0" Text="Next Payday:" Style="{DynamicResource ListItemTextStyle}"  />
<Label Grid.Row="2" Grid.Column="1" Text="{Binding NextPayday}" Style="{DynamicResource ListItemDetailTextStyle}"  HorizontalTextAlignment="End"/>
<Label Grid.Row="3" Grid.Column="0" Text="Available in account:" Style="{DynamicResource ListItemTextStyle}"  />
<Label Grid.Row="3" Grid.Column="1" Text="{Binding AvailableInAccount, StringFormat='({0:+0.0;-0.0})'}" TextColor="{Binding AvailableInAccount, Converter={StaticResource SignToBrushConverter}}" HorizontalTextAlignment="End"/>
<Label Grid.Row="4" Grid.Column="0" Text="Overdrawn/Excess:" Style="{DynamicResource ListItemTextStyle}" />
<Label Grid.Row="4" Grid.Column="1" Text="{Binding OverdrawnExcess, StringFormat='({0:+0.0;-0.0})'}" TextColor="{Binding OverdrawnExcess, Converter={StaticResource SignToBrushConverter}}" HorizontalTextAlignment="End"/>
<Label Grid.Row="5" Grid.Column="0" Text="Budgeted vs Actual:" Style="{DynamicResource ListItemTextStyle}" />
<Label Grid.Row="5" Grid.Column="1" Text="{Binding BudgetedVsActual, StringFormat='({0:+0.0;-0.0})'}" TextColor="{Binding BudgetedVsActual, Converter={StaticResource SignToBrushConverter}}" HorizontalTextAlignment="End"/>
<Label Grid.Row="6" Grid.Column="0" Text="Budgeted vs Suggested:" Style="{DynamicResource ListItemTextStyle}" />
<Label Grid.Row="6" Grid.Column="1" Text="{Binding BudgetedVsSuggested, StringFormat='({0:+0.0;-0.0})'}" TextColor="{Binding BudgetedVsSuggested, Converter={StaticResource SignToBrushConverter}}" HorizontalTextAlignment="End"/>
<Label Grid.Row="7" Grid.Column="0" Text="Budget/Bank Difference:" Style="{DynamicResource ListItemTextStyle}" />
<Label Grid.Row="7" Grid.Column="1" Text="{Binding BankBalanceVariation, StringFormat='({0:+0.0;-0.0})'}" TextColor="{Binding BankBalanceVariation, Converter={StaticResource SignToBrushConverter}}" HorizontalTextAlignment="End"/>

Have included the converter resource:

<ContentPage.Resources>
    <ResourceDictionary>
        <common:SignToBrushConverter x:Key="SignToBrushConverter" />
    </ResourceDictionary>
</ContentPage.Resources>

And is bound to fields of type "double". It runs through this which I found here: String format positive and negative values and conditional color formatting XAML.

I changed some things to get it to compile for Xamarin forms:

  • DependencyProperty throws "The name 'DependencyProperty' does not exist in the current context"
  • DefaultNegativeBrush.Freeze() throws "'Brush' does not contain a definition for 'Freeze'"

So I removed them, and I also changed the 'Colors.Red' etc to 'Color.Red' as Colors doesn't exist. I believe this should return the brush type?:

public Brush NegativeBrush { get; set; }
public Brush PositiveBrush { get; set; }
public Brush ZeroBrush { get; set; }

public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
    if (!IsSupportedType(value))
    {
        return ZeroBrush ?? DefaultZeroBrush;
    }

    double doubleValue = System.Convert.ToDouble(value);

    if (doubleValue < 0d) return NegativeBrush ?? DefaultNegativeBrush;
    if (doubleValue > 0d) return PositiveBrush ?? DefaultPositiveBrush;

    return ZeroBrush ?? DefaultZeroBrush;
}

but then it throws an error:

System.InvalidCastException Message=Object must implement IConvertible.

But I'm not sure what is getting cast here, isn't this just returning a formatting value to the Xamarin form?

I suspect it's because I didn't add all the [ValueConversion(typeof(double), typeof(Brush))]

Because I got this error: The type or namespace name 'ValueConversionAttribute' could not be found (are you missing a using directive or an assembly reference?)

and I couldn't find a reference that works for Xamarin forms


Solution

  • In Xamarin.Android, it's different with the WPF. You could not use the same code in some way.

    The code work in WPF. But it does not work in Xamarin. That's why you get the error below.

      DependencyProperty throws "The name 'DependencyProperty' does not exist in the current context"
      DefaultNegativeBrush.Freeze() throws "'Brush' does not contain a definition for 'Freeze'"
    

    According to the code and the link you provided, you could use the Color to set the TextColor of the label. It is easy to get.

        public class SignToBrushConverter : IValueConverter
    {
        //public Brush NegativeBrush { get; set; }
        //public Brush PositiveBrush { get; set; }
        //public Brush ZeroBrush { get; set; }
        private static readonly Color DefaultZeroBrush = Color.Green;
        private static readonly Color DefaultNegativeBrush = Color.Red;
        private static readonly Color DefaultPositiveBrush = Color.Green;
    
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
    
            if (!IsSupportedType(value))
            {
                return DefaultZeroBrush;
            }
    
            double doubleValue = System.Convert.ToDouble(value);
    
            if (doubleValue < 0d)
            {
                return DefaultNegativeBrush;
            }
            else if (doubleValue > 0d)
            {
                return DefaultPositiveBrush;
            }
    
            return DefaultZeroBrush;
        }
        private static bool IsSupportedType(object value)
        {
            return value is int || value is double || value is byte || value is long ||
                   value is float || value is uint || value is short || value is sbyte ||
                   value is ushort || value is ulong || value is decimal;
        }
        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }
    

    Code behind:

       public string LastUpdated { get; set; }
        public string NextPayday { get; set; }
        public int AvailableInAccount { get; set; }
        public int OverdrawnExcess { get; set; }
        public int BudgetedVsActual { get; set; }
        public int BudgetedVsSuggested { get; set; }
        public int BankBalanceVariation { get; set; }
        public Page3()
        {
            InitializeComponent();
            LastUpdated = "a";
            NextPayday = "a";
            AvailableInAccount = 1;
            OverdrawnExcess = -2;
            BudgetedVsActual = 111;
            BudgetedVsSuggested = -222;
            BankBalanceVariation = 12;
    
            BindingContext = this;
        }