Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why doesn't (Style)Application.Current.Resources["FrameBorder"] let me access a resource in my ResourceDirectory

I created a resource in the file FrameRes.xaml like this:

<ResourceDictionary xmlns="http://xamarin.com/schemas/2014/forms" 
                    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" 
                    x:Class="Japanese.Resources.FrameRes">
    <Style x:Key="FrameBorder" TargetType="Frame">
        <Setter Property="CornerRadius" Value="2" />
        <Setter Property="HasShadow" Value="false" />
        <Setter Property="Margin" Value="10,0" />
        <Setter Property="BorderColor" Value="{DynamicResource LineColor}" />
        <Setter Property="Padding" Value="0" />
        <Setter Property="VerticalOptions" Value="Start" />
    </Style>
</ResourceDictionary>

and this is contained inside:

<?xml version="1.0" encoding="utf-8"?>
<Application xmlns:converters="clr-namespace:Japanese" 
             xmlns="http://xamarin.com/schemas/2014/forms" 
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" 
             x:Class="Japanese.App">
    <Application.Resources>
        <ResourceDictionary Source="/Resources/FooterRes.xaml" />
        <ResourceDictionary Source="/Resources/FrameRes.xaml" />
    </Application.Resources>
</Application>

When I try to access this in C# like this then it fails and says there's no reference to FrameBorder:

var fb2 = (Style)Application.Current.Resources["FrameBorder"];

When I try to access this in C# like this it works:

Application.Current.Resources.TryGetValue("FrameBorder", out object frameBorder);
var fb = (Style)frameBorder;

Does anyone know why the first way doesn't work. It looks the same to me.

like image 896
Alan2 Avatar asked Nov 24 '18 11:11

Alan2


1 Answers

The way you have it set up, you have a ResourceDictionary inside another ResourceDictionary with no key/name/reference. When you call Application.Current.Resources["FrameBorder"];, you are accessing the upper-most level of the dictionary and looking for "FrameBorder", not its sub-levels. However, calling TryGetValue goes through all levels of the Dictionary.

Read through the Docs to understand how to access values from Dictionaries.


Ok, so I have played around with the ResourceDictionary tags and files, and I can see where you're going wrong.

According to the Xamarin Forms Docs on ResourceDictionary, you can add stand-alone ResourceDictionary XAML files to your App.xaml like so:

<App ...>
    <App.Resources>

            <!-- Add more resources here -->

            <ResourceDictionary Source="MyResourceDictionary.xaml" />

            <!-- Add more resources here -->

    </App.Resources>
    ...
</App>

You can replace App with ContentPage or ContentView, depending on your use.

What does this do?

Simply put, this creates a new ResourceDictionary object and merges other ResourceDictionary files into it. Let's go into a bit more detail.

Let's start by looking at the Application.Current.Resources property, which is of type ResourceDictionary and implements ICollection, IDictionary, and IResourceDictionary. Given the interfaces it implements, you can obtain the values by numeric or string ID (ex: App.Current.Resources[0] or App.Current.Resources["LabelStyle"]). This accesses the top-level contents of the dictionary, so only those that have been created in the <[App/ContentPage/ContentView].Resources> tag.

You also have a property called MergedDictionaries, which implements ICollection. This means that you can only access the list items by numeric ID (ex: MyMergedDictionary[0]).

When you add a ResourceDictionary file like:

<ResourceDictionary Source="MyResourceDictionary.xaml" />

You are actually merging this file with the current ResourceDictionary. To then access the contents of MyResourceDictionary you would call (in C#):

App.Current.Resources.MergedDictionaries[0]["InternalKeyName"]

This is probably better explained like so:

<App ...>
    <App.Resources>

            <!-- Called using App.Current.Resources["PrimaryColor"] -->
            <Color x:Key="PrimaryColor">#2196F3</Color>

            <!-- Called using App.Current.Resources.MergedDictionaries[0]["InternalKeyName"] -->
            <ResourceDictionary Source="MyResourceDictionary.xaml" />

    </App.Resources>
    ...
</App>

Your case

In your case you have two dictionaries merged into your Application Resources: FooterRes and FrameRes.

For better management, I would create an enum like:

public enum MergedResourcesEnum
{
    Footer,
    Frame
}

and use this enum when calling the resources in C# like so:

Application
    .Current
    .Resources
    .MergedDictionaries[(int)MergedResourcesEnum.Frame]["FrameBorder"];
like image 140
Tom Avatar answered Oct 27 '22 08:10

Tom