Home > Software design >  Use Binding Context on Application Resources
Use Binding Context on Application Resources

Time:12-17

I have a Xamarin app which defines certain attributes and styles for the controls centrally in the App.xaml

<?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="AppMsMovil.App">
<Application.Resources>
    <ResourceDictionary>
        <Color x:Key="MainColorBlue">#003E62</Color>
        <Color x:Key="MainTextColorBlue">White</Color>
        <Color x:Key="SecondaryColorBlue">#6f95ab</Color>
        <Color x:Key="MainColorRed">#EC1B30</Color>
        <Color x:Key="MainTextColorRed">White</Color>
        <Color x:Key="SecondaryColorRed">#edb2b8</Color>
        <Color x:Key="MainColorGray">#e6e4df</Color>
        <Color x:Key="SelectedItemColor">#f7d6a1</Color>
        <Color x:Key="UnselectedItemColor">White</Color>

        <Color x:Key="CanceledColor">#ffa099</Color>
        <Color x:Key="ClosedColor">#fded98</Color>
        <Color x:Key="SentColor">#a9efbb</Color>

        <Style TargetType="Entry">
            <Setter Property="BackgroundColor" Value="White" />
            <Setter Property="PlaceholderColor" Value="Gray" />
            <Setter Property="TextColor" Value="Black" />
        </Style>
        <Style TargetType="Button">
            <Setter Property="BackgroundColor" Value="{StaticResource MainColorBlue}" />
            <Setter Property="TextColor" Value="White" />
            <Setter Property="CornerRadius" Value="20" />
        </Style>
        <Style TargetType="Label">
            <Setter Property="TextColor" Value="{StaticResource MainColorBlue}" />
        </Style>

        <Style x:Key="DynamicOnDeviceLabelStyle" TargetType="Label" >
            <Setter Property="FontSize" Value="{Binding LcModel.DynamicOnDeviceLabelFontSize}" />
        </Style>

        <Style TargetType="ContentPage" ApplyToDerivedTypes="True">
            <Setter Property="BackgroundColor" Value="{StaticResource MainColorGray}" />
        </Style>

        <Style TargetType="ContentView" ApplyToDerivedTypes="True">
            <Setter Property="BackgroundColor" Value="{StaticResource MainColorGray}" />
        </Style>

        <Style TargetType="Picker">
            <Setter Property="TextColor" Value="Black"></Setter>
            <Setter Property="TitleColor" Value="#7e8185"></Setter>
        </Style>

        <Style TargetType="SearchBar">
            <Setter Property="TextColor" Value="Black"></Setter>
            <Setter Property="BackgroundColor" Value="DarkGray"></Setter>
        </Style>

    </ResourceDictionary>
</Application.Resources>

I'm trying to create a Style To apply the FontSize Property to Labels controles inside the application. However, i want the value of FontSize within the Style be determined deppending on the applicatrion execution environment. (If Tablet, i want a size, if cellphone i want another) To do this, i create the Model and ViewModel classes for the App class as follows.

Class: AppModel.cs

using System.ComponentModel;
using System.Runtime.CompilerServices;

namespace AppMsMovil.Models {
    public class AppModel : INotifyPropertyChanged {

    private double LcDblDynamicOnDeviceLabelFontSize;
        public double DynamicOnDeviceLabelFontSize {
            get => LcDblDynamicOnDeviceLabelFontSize;
            set {
                if (LcDblDynamicOnDeviceLabelFontSize != value) {
                    LcDblDynamicOnDeviceLabelFontSize = value;
                    OnPropertyChanged();
                }
            }
        }

        /// Para actualizar los controles enlazados
        public event PropertyChangedEventHandler PropertyChanged;
        public virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

Class: AppViewModel.cs

using AppMsMovil.Models;
using Xamarin.Forms;

namespace AppMsMovil.ViewModels {
    public class AppViewModel {

        public AppModel LcModel { get; set; }

        public AppViewModel() {
            LcModel = new AppModel();
            SbSetDynamicOnDeviceProperties();
        }

        private void SbSetDynamicOnDeviceProperties() {
            string StrOnDeviceLabelFontSize = Device.Idiom == TargetIdiom.Phone ? "Small" : Device.Idiom == TargetIdiom.Tablet ? "Medium" : "Medium";
            LcModel.DynamicOnDeviceLabelFontSize = (double)new FontSizeConverter().ConvertFromInvariantString(StrOnDeviceLabelFontSize);
        }
    }
}

Finally, on the App class, i set the BindingContext with a ViewModel object.

Class: App.xaml.cs

using Xamarin.Forms;
using AppMsMovil.ViewModels;

namespace AppMsMovil {
    public partial class App : Application {

        public bool IsLoggedIn {
            get {
                return Current.Properties.ContainsKey("IsLoggedIn") && (bool)Current.Properties["IsLoggedIn"];
            }
        }

        public App() {
            InitializeComponent();
            BindingContext = new AppViewModel();
            MainPage = new AppShell();
        }

        protected override void OnStart() {
        }

        protected override void OnSleep() {
            Current.Properties.Remove("IsLoggedIn");
        }

        protected override void OnResume() {
        }            
    }
}

if i run the application, i can see that the Model and ViewModel class are created correctly, but, at i cannot see that the Get method of the DynamicOnDeviceLabelFontSize property on the AppModel.cs class is executed. so i think that the Setter's Value on my Style is not being assigned with that value. (I confirm this because on the XAML file where im using the Style, is not taking the correct value)

NOTE: If i set a static value on Setter's Value of the Style, i can see that the Label control takes the correct value, so, this is not a Style problem, but it seems that the Setter's Value is not taking the value from Binding.

Question: Is it possible ti assign a value on a Setter´s Value of the Style in Application.Resources using a Binding? or i'm wrong with this??

Any support will be welcome.

Regards!

UPDATE: Solved using ideas from comments... this is the final solution:

    <Style x:Key="DynamicOnDeviceLabelStyle" TargetType="Label" >
        <Setter Property="FontSize" Value="{OnIdiom Phone=Small, Tablet=Medium}" />
    </Style>

Thanks a lot!

CodePudding user response:

Finally back at home, I want to show you some cool trick you can do with OnIdiom

1st: Using OnIdiom in XAML. You can use OnIdiom on any property of a Element in a view stack. This is useful to be able to manage different values depending on a device.

{OnIdiom Phone=Value, Tablet=Value}

2nd: There is also OnPlatform this is enables you to do different thing with different platforms.

OnPlatform{OnPlatform Android=Value, iOS=Value}

Further on you can combine these 2 to mix them together and this is how it looks combined

{OnIdiom Phone={OnPlatform Android=Value, iOS=Value}, Tablet={OnPlatform Android=Value, iOS=Value}}

You can go even a step further with styles and implement something along the line of this. You'll be able to determine if device is General Size or Small Size.

Inside App.xaml make sure to add x:Name="dictionary to your main Application.Resources otherwise you wont be able to load the custom dictionaries in the code behind.

<Application.Resources>
    <ResourceDictionary x:Name="dictionary"> <----- IMPORTANT
    </ResourceDictionary>        
</Application.Resources>

Inside App.xaml.cs add this to the class. You want to determine what the screen size is, so we set couple of const of sizes. Create a method IsASmallDevice that will return true if device is small otherwise false. We then want to load the styles with LoadStyles method by using DeviceInfo class and checking which platform we're working on to load correct styles. Once we have things understood we want to merge our custom ResourceDictionary with our AppResource dictionary by doing MergedDictionaries.Add(our instance of ResourceDictionary).

public partial class App : Application
{
    const int smallWightResolution = 768;
    const int smallHeightResolution = 1280;

    public App()
    {
        InitializeComponent();
        LoadStyles();

        MainPage = new AppShell();
    }

    void LoadStyles()
    {
        var isSmall = IsASmallDevice();

        if (DeviceInfo.Platform == DevicePlatform.Android)
        {
            if (isSmall)
                dictionary.MergedDictionaries.Add(AndroidSmallDeviceStyles.SharedInstance);
            else
                dictionary.MergedDictionaries.Add(AndroidDeviceStyles.SharedInstance);
        }else if (DeviceInfo.Platform == DevicePlatform.iOS)
        {
            if (isSmall)
                dictionary.MergedDictionaries.Add(iOSSmallDeviceStyles.SharedInstance);
            else
                dictionary.MergedDictionaries.Add(iOSDeviceStyles.SharedInstance);
        }
    }

    public static bool IsASmallDevice()
    {
        var mainDisplayInfo = DeviceDisplay.MainDisplayInfo;

        var width = mainDisplayInfo.Width;

        var height = mainDisplayInfo.Height;
        return (width <= smallWightResolution && height <= smallHeightResolution);
    }
}

Next you want to create Content Page and change it up like this. Make the Content Page into a ResourceDictionary allowing you to do the same as the App ResourceDictionary but seperated.

<?xml version="1.0" encoding="UTF-8"?>
<ResourceDictionary xmlns="http://xamarin.com/schemas/2014/forms"
                    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
                    x:Class="uCue_Game.Styles.AndroidDeviceStyles">

</ResourceDictionary>

Inside that Content Page.xaml.cs add this to enable it as ResourceDictionry and have a static property to make a new instance of that class allowing you to call it in the App depending on the device size that was loaded.

public partial class AndroidDeviceStyles : ResourceDictionary
{
    public static AndroidDeviceStyles SharedInstance { get; } = new AndroidDeviceStyles();
    public AndroidDeviceStyles()
    {
        InitializeComponent();
    }
}

Inside that new ResourceDictionary this is how you want to set new styles.

<?xml version="1.0" encoding="UTF-8"?>
<ResourceDictionary xmlns="http://xamarin.com/schemas/2014/forms"
                    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
                    x:Class="uCue_Game.Styles.AndroidDeviceStyles">
    
    <Style x:Key="Public Key To Access Through Static Resource"
           TargetType="Your Targer Type e.g Grid">
        <Setter Property="WidthRequest"
                Value="100" />
    </Style>
    
</ResourceDictionary>

This is how I usually do mine one for each platform. I've found that it is almost impossible to get it right with a single style between each platform so yes I know it might be a lot of work to begin with but once it is done you'll be very happy with yourself having this kid of separation and control over each style in your application.
enter image description here

If you got any more questions about this please let me know in the comments I will be more than happy to show you more and explain things I might of forgot to mention in this answer. I will be reading through this again tomorrow and see what I missed to update it to make it much more richer for not only you but for others.

CodePudding user response:

Finally found a solution...

The model and viewmodel classes are not necessary, i only need to change this line.

Solved using ideas from comments... this is the final solution:

<Style x:Key="DynamicOnDeviceLabelStyle" TargetType="Label" >
    <Setter Property="FontSize" Value="{OnIdiom Phone=Small, Tablet=Medium}" />
</Style>

Thanks a lot!

  • Related