Nesting variables from ResourceDictionaries

Jesse Knott 686 Reputation points
2021-05-10T00:39:46.733+00:00

Hello, I've got what may be an overly complex system for themes in my app.

I have 3 different ResourceDictionaries that I apply in my apps startup.
In order of loading they are

FontSizeDictionary ->Dimension specific
ColorThemeDictionary -> User chosen color scheme.
and
AppStylesDictionary -> Application wide styles for controls.

The app checks the screen size, then picks the appropriate fonts dictionary. Since this is being done based on screen size I didn't think using <OnIdiom> would be an appropriate solution since resolutions can be different on different device and screen sizes.

After it's picked the font sizes, the app selects the color theme the user has chosen. This dictionary is merged, and finally the styles dictionary is merged into the app.

When loading the AppStylesDictionary, I am getting an exception thrown about an invalid cast for one of my settings

Here is the code for how the Themes are applied

App.xaml.cs

....
        /// <summary>
        /// Switch the theme from light to dark or vice versa.
        /// </summary>
        public void ChangeTheme()
        {
            try
            {
                // check if the theme key exists, if not default it to the light theme.
                if (!AppSettingsFunctions.CheckKey("App_Theme"))
                {
                    AppSettingsFunctions.SaveSettingData("App_Theme", 0);
                }

                var theme = AppSettingsFunctions.GetSetting("App_Theme");

                if (string.IsNullOrEmpty(theme.ToString()))
                {
                    theme = Theme.Light;
                }

                var themeChoice = Convert.ToInt32(theme, CultureInfo.InvariantCulture);

                if (IsASmallDevice())
                {
                    dictionary.MergedDictionaries.Add(Themes.SmallFonts.SharedInstance);
                }
                else
                {
                    dictionary.MergedDictionaries.Add(Themes.MediumFonts.SharedInstance);
                }

                switch (themeChoice)
                {
                    case (int)Theme.Dark:

                        dictionary.MergedDictionaries.Add(Themes.DarkThemeDictionary.SharedInstance);
                        break;

                    case (int)Theme.Light:
                        dictionary.MergedDictionaries.Add(Themes.LightThemeDictionary.SharedInstance);
                        break;

                    case (int)Theme.Colorful:
                        dictionary.MergedDictionaries.Add(Themes.ColorfulThemeDictionary.SharedInstance);
                        break;

                    default:
                        dictionary.MergedDictionaries.Add(Themes.LightThemeDictionary.SharedInstance);
                        break;
                }

                //Finally apply the styles for the application
                this.Resources.MergedDictionaries.Add(Themes.AppStyles.SharedInstance);
            }
            catch (Exception ex)
            {
                DebugTools.LogException(ex);
            }
        }

In my FontsDictionaries I have the variables specified as Int16 values

<ResourceDictionary
    x:Class="BoomStick.Themes.MediumFonts"
    xmlns="http://xamarin.com/schemas/2014/forms"
    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml">

    <x:Int32 x:Key="SmallFont">11</x:Int32>
    <x:Int32 x:Key="MediumFont">13</x:Int32>
    <x:Int32 x:Key="LargeFont">15</x:Int32>
    <x:Int32 x:Key="BorderFont">17</x:Int32>
    <x:Int32 x:Key="LabelFont">15</x:Int32>
    <x:Int32 x:Key="DataFont">14</x:Int32>
</ResourceDictionary>

The odd part is, when I use the font sizes in the AppStyles dictionary it works for one type of style definition but not another.

Here is an excerpt from the AppStylesDictionary that demonstrates my issue

    <!--Defines error label style This gets applied to <Label> objects-->
    <Style x:Key="Notation" TargetType="Label">
        <Setter Property="TextColor" Value="{StaticResource Key=cAltTextColor}" />
        <Setter Property="FontAttributes" Value="Bold" />
        <Setter Property="FontSize" Value="{StaticResource Key=MediumFont}" />
        <Setter Property="Margin" Value="-5,5,0,0" />
        <Setter Property="FontFamily" Value="{StaticResource Key=Baloo2-SemiBold}" />
        <Setter Property="LineBreakMode" Value="WordWrap" />
    </Style>

    <Style
        ApplyToDerivedTypes="True"
        CanCascade="True"
        TargetType="textinputlayout:SfTextInputLayout">
        <Setter Property="UnfocusedColor" Value="{StaticResource Key=cHilightColor}" />
        <Setter Property="FocusedColor" Value="{StaticResource Key=cTextColor}" />
        <Setter Property="ContainerType" Value="None" />
        <Setter Property="HintLabelStyle">
            <Setter.Value>
                <textinputlayout:LabelStyle
                    FontAttributes="Bold"
                    FontFamily="{StaticResource Key=Baloo2-SemiBold}"
                    FontSize="{StaticResource Key=MediumFont}" />
            </Setter.Value>
        </Setter>
        <Setter Property="ErrorLabelStyle">
            <Setter.Value>
                <textinputlayout:LabelStyle
                    FontAttributes="Italic"
                    FontFamily="{StaticResource Key=Baloo2-SemiBold}"
                    FontSize="{StaticResource Key=SmallFont}" />
            </Setter.Value>
        </Setter>
    </Style>

In the above code, the StaticResource used for the <Label> style is fine, but later when it's used in the styling for the textinputlayout control, it throws an exception saying that the specified cast is invalid. If I hard code the values it works fine. Also the FontFamily flag is being populated just fine. I have tried specifying the font sizes using <x:Int16(32,64)> and also tried them as <x:String>

For the time being I've resigned myself to manually coding the sizes, but it would be nice to be able to get this working.

Cheers!

Xamarin
Xamarin
A Microsoft open-source app platform for building Android and iOS apps with .NET and C#.
5,252 questions
{count} votes

Accepted answer
  1. JarvanZhang 23,931 Reputation points
    2021-05-14T02:42:22.397+00:00

    Hello,​

    Welcome to our Microsoft Q&A platform!

    The only thing I can think of is that it must be something to do with the fact that I am making the style using the <Setter.Value> open and close tag.

    I created a basic demo to test logic, the result is as you imagined. I defined a x:Double type resource in the App.xaml and let the 'inputLayout:LabelStyle.FontSize' property reference the value, but the value cannot be accesssed. If I changed the code to FontSize="25", it works fine. It seems that the properties of 'inputLayout:LabelStyle' cannot access the resource correctly which may be a potential issue with the Syncfusion api. You could report the problem to the product team in its portal.

       <Application ...  
           xmlns:inputLayout="clr-namespace:Syncfusion.XForms.TextInputLayout;assembly=Syncfusion.Core.XForms">  
           <Application.Resources>  
               <x:Double x:Key="TheFontSize">25</x:Double>  
               <Style x:Key="inputStyle" TargetType="inputLayout:SfTextInputLayout">  
                   <Setter Property="HintLabelStyle">  
                       <Setter.Value>  
                           <inputLayout:LabelStyle  
                               FontAttributes="None"  
                               FontSize="{DynamicResource TheFontSize}" />  
                       </Setter.Value>  
                   </Setter>  
               </Style>  
           </Application.Resources>  
       </Application>  
    

    Best Regards,

    Jarvan Zhang


    If the response is helpful, please click "Accept Answer" and upvote it.

    Note: Please follow the steps in our documentation to enable e-mail notifications if you want to receive the related email notification for this thread.


1 additional answer

Sort by: Most helpful
  1. Jesse Knott 686 Reputation points
    2021-05-13T17:20:37.09+00:00

    I've tried changing the font size control, to using your suggestion. It does make the code a bit easier to manage since I have one less resource dictionary to merge, but I still cannot get the code to work as expected.

    With the recommended changes I now define the font sizes like this.

    app.xaml

     <Application.Resources>
            <ResourceDictionary x:Name="dictionary">
                <x:String x:Key="SmallFont">11</x:String>
                <x:String x:Key="MediumFont">13</x:String>
                <x:String x:Key="LargeFont">15</x:String>
                <x:String x:Key="BorderFont">17</x:String>
                <x:String x:Key="LabelFont">15</x:String>
                <x:String x:Key="DataFont">14</x:String>
                <x:String x:Key="BorderFontLarge">10</x:String>
            </ResourceDictionary>
        </Application.Resources>
    

    then in my code where I merge the color theme the user has selected,

    ......
     if (smallDevice)
                {
                    App.Current.Resources["SmallFont"] = 11;
                    App.Current.Resources["MediumFont"] = 26;
                    App.Current.Resources["LargeFont"] = 15;
                    App.Current.Resources["BorderFont"] = 17;
                    App.Current.Resources["LabelFont"] = 15;
                    App.Current.Resources["DataFont"] = 14;
                    App.Current.Resources["BorderFontLarge"] = 19;
                }
    ....
    

    However I still cannot get the code to perform as expected. For instance, this code throws the "Invalid cast exception"

    <Style
        ApplyToDerivedTypes="True"
        CanCascade="True"
        TargetType="{x:Type textinputlayout:SfTextInputLayout}">
        <Setter Property="HintLabelStyle">
            <Setter.Value>
                <textinputlayout:LabelStyle
                    FontAttributes="None"
                    FontFamily="{StaticResource Key=Baloo2-SemiBold}"
                    FontSize="{StaticResource Key=MediumFont}" />  <!-- this line throws the exception -->
            </Setter.Value>
        </Setter>
    </Style>
    

    And if I change the offending line to use {DynamicResource Key=MediumFont} it passes without error, but the value from MediumFont never populates?

    The only thing I can think of is that it must be something to do with the fact that I am making the style using the <Setter.Value> open and close tag. Which may perhaps be creating a snapshot of the code at that moment, so the dynamic tags will not populate since they get modified after that snapshot?
    (Seriously wild guess, but it's all I can come up with)..

    0 comments No comments