Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Arithmetic Operations inside XAML Resource Dictionary

What I want to do

I've been exploring XAML Resource Dictionaries recently. They are very powerful, but in order to cut down (even further) on the changes that would need to be made to accommodate any modifications, I'd like to use some basic arithmetic operations to change the HeightRequest property of an Entry.

I'm already making good use of OnPlatform and OnIdiom for different aspects, like FontSize.

For the iOS Platform, I'd like to make the HeightRequest of an entry 20+(FontSize). The FontSize is already set using OnIdiom (it's slightly increased for tablets).

In a perfect world, the core thing which I'm trying to do might look something like
<Setter Property="HeightRequest" Value="{DynamicResource StandardFontSize}+10">

What "works"

I have a working solution if I use a combination of OnIdiom and OnPlatform.

<?xml version="1.0" encoding="utf-8" ?>
<Application xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="XamarinDesigner.App"
             xmlns:local="clr-namespace:XamarinDesigner"
             >
    <Application.Resources>
        <ResourceDictionary>
            <OnIdiom x:Key="StandardFontSize" x:TypeArguments="x:Double"  Tablet="22" Phone="18"/>
            <Style x:Key="MyEntry" TargetType="Entry">
                <Setter Property="FontSize" Value="{DynamicResource StandardFontSize}"/>
                <Setter Property="HeightRequest">
                    <Setter.Value>
                        <OnIdiom x:TypeArguments="x:Double">
                            <OnIdiom.Phone>
                                <OnPlatform x:TypeArguments="x:Double" iOS="30"/>
                            </OnIdiom.Phone>
                            <OnIdiom.Tablet>
                                <OnPlatform x:TypeArguments="x:Double" iOS="40"/>
                            </OnIdiom.Tablet>
                        </OnIdiom>
                    </Setter.Value>
                </Setter>
                <Setter Property="VerticalOptions" Value="Center"/>
            </Style>
        </ResourceDictionary>
    </Application.Resources>
</Application>

With this 'solution' - I need to set the value explicitly and do the calculations myself. While this works, I'd like to be able to perform a basic arithmetic operation to find the value of FontSize, and add some number to it.

What I've tried

In another attempt I've made, I've found a converter and tried to adapt it to my use case. While there is no intellisense or build/compile errors, the app crashes immediately after opening. The .cs file for ArithmeticConverter can be found in the link above.

<?xml version="1.0" encoding="utf-8" ?>
<Application xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="XamarinDesigner.App"
             xmlns:local="clr-namespace:XamarinDesigner"
             >
    <Application.Resources>
        <local:ArithmeticConverter x:Key="AScript"/>

        <ResourceDictionary>
            <OnIdiom x:Key="StandardFontSize" x:TypeArguments="x:Double"  Tablet="22" Phone="18"/>

            <Style x:Key="MyEntry" TargetType="Entry">
                <Setter Property="FontSize" Value="{DynamicResource StandardFontSize}"/>
                <Setter Property="HeightRequest" Value="{Binding Converter={StaticResource AScript},ConverterParameter=Int32.Parse(20+{DynamicResource StandardFontSize}}"/>
                <Setter Property="VerticalOptions" Value="Center"/>
            </Style>

        </ResourceDictionary>
    </Application.Resources>
</Application>

I don't fully understand the use of converters, and {Binding} inside of a value in App.xaml is also something that is new to me. Looking at the example provided with the converter, I think I'm close to being correct, and may just need a push in the right direction?


Is it possible to do this basic sort of arithmetic function in the App.xaml alone(or with the use of a converter)? I'm hoping to contain as much as I can to this file.

Other solutions I've found in my search have mentioned the use of a viewmodel, but this is a 'global' change I want to apply to every entry per platform/idiom, so I can't see how that adaption might work.

Thanks for your time!

like image 890
Bejasc Avatar asked Oct 03 '18 02:10

Bejasc


1 Answers

One of the reason your app is crashing is because Converter is outside the ResourceDictionary.

Solution 1

Binding should be used only when there is a BindingContext assigned, hence you need to assign it in cs file.

App.cs:

public App()
{
    InitializeComponent();
    BindingContext = new { EntryHeightRequest = 10 };
    MainPage = ...
}

App.xaml:

<ResourceDictionary>
    <local:ArithmeticConverter x:Key="AScript"/>
    <OnIdiom x:Key="StandardFontSize" x:TypeArguments="x:Double"  Tablet="22" Phone="18"/>
    <Style x:Key="MyEntry" TargetType="Entry">
        <Setter Property="FontSize" Value="{DynamicResource StandardFontSize}" /> 
        <Setter Property="HeightRequest" Value="{Binding EntryHeightRequest, Converter={StaticResource AScript},ConverterParameter="{StaticResource StandardFontSize}"/>
        <Setter Property="VerticalOptions" Value="Center"/>
    </Style>
</ResourceDictionary>

ArithmeticConverter.cs:

public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
    if(value is int constant && parameter is OnIdiom<double> dynamicSize)
        return constant + dynamicSize.GetValue();
    return -1;
}

OnIdiomExtension:

    public static T GetValue<T>(this OnIdiom<T> idiom)
    {
        switch(Device.Idiom)
        {
            case TargetIdiom.Phone:
                return idiom.Phone;

            case TargetIdiom.Desktop:
                return idiom.Desktop;

            case TargetIdiom.Tablet:
                return idiom.Tablet;

            case TargetIdiom.TV:
                return idiom.TV;

            case TargetIdiom.Watch:
                return idiom.Watch;

            default:
                throw new NotSupportedException();
        }
    }

Beware: When I tried, BindingContext is passed to ResourceDictionary(but this post contradicts it, may be they changed?)

Solution 2

Similar to Solution 1 but instead of setting BindingContext you can use OnIdiom on HeightRequest with default value.

<Setter Property="HeightRequest" Value="{OnIdiom Default=10, Converter={StaticResource AScript}, ConverterParameter={StaticResource StandardFontSize}}" />
like image 79
shanranm Avatar answered Oct 24 '22 06:10

shanranm