question

JesseKnott-2239 avatar image
0 Votes"
JesseKnott-2239 asked JesseKnott-2239 commented

Nesting variables from ResourceDictionaries

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!

dotnet-xamarin
· 1
5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

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

It's unnecessary to create multi resource dictionaryies. For the dictionary which has single attribute, you could just use a property and set dynatic binding for the control. Then you could change the value of the dictionary in code.

Check the code:

<Application.Resources>
    <x:Double x:Key="TheFontSize">25</x:Double>
    <Color x:Key="TheBackgroundColor">Red</Color>
</Application.Resources>

<Label Text="Testing for ResourceDictionary" FontSize="{DynamicResource TheFontSize}"/>
<Label Style="Testing for ResourceDictionary" BackgroundColor="{DynamicResource TheBackgroundColor}"/>

Change the value of the dictionary in code.

App.Current.Resources["TheFontSize"] = 18; //pass the selected value from the picker
App.Current.Resources["TheBackgroundColor"] = Xamarin.Forms.Color.LightBlue ;
0 Votes 0 ·
JarvanZhang-MSFT avatar image
0 Votes"
JarvanZhang-MSFT answered JesseKnott-2239 commented

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
5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

Ahh that was my next thought, that it must be a problem with their api.
Thanks for the help!

Cheers!

0 Votes 0 ·
JesseKnott-2239 avatar image
0 Votes"
JesseKnott-2239 answered

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)..

5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.