Simax emergency braking practice, with and without ABS

The following video shows one of the Road Safety training exercises available in the Simax Road Safety simulator. It allows to practice emergency braking and obstacle avoidance on wet surfaces. The driver learns how to react depending on the presence of an ABS system or not.

 

Simax Driving Simulator exercise: emergency situations practice

Today, we have released a new video about emergency situations practice in Simax:

Description: This exercise allows the driver to practice reactions at emergency situations like water ponds on the road, accidents, trucks dropping their load, or even rocks falling. The simulator can even analyze reaction times, traveling direction progress, etc.

Hope you like it…

XNA/Android cross-platform and strongly-typed access to game contents


In this previous post, we already showed how to use T4 templates for several use cases, with a special focus on Resource Files (ResX). Now we are going to complete that post with another one, focused specifically on Content management.
What we want to achieve is an elegant, cross-platform, and strongly-typed way of accessing contents in our projects.

The XNA approach to contents

XNA identifies contents with Asset Names, in the form of strings, but it doesn’t offer any form of strong-typed access, what is very bug-prone, because if you misspell the name of an asset, you won’t notice until runtime, or you won’t notice ever…

The Android approach to contents

Android already offers strongly-typed access to contents that are placed below the “Resources” special folder. Unfortunately, there are a lot of limitations for the contents inside that folder. One of the most evident (and stupid), is that contents cannot be re-arranged into subfolders, what makes it almost un-usable for medium-big projects. Besides that, the kind of access Android gives to that folder is through INT identifiers, what conflicts with the XNA way of doing this (which uses Asset names).
One of the possible solutions is to move our contents to the “Assets” folder, where things can be arranged arbitrarily, and where assets are identified with a string, very much like in XNA. Too bad that Android doesn’t offer strongly-typed access to that folder…

What we want to achieve

1.- We want to be able to arrange our contents in sub-folders, so in Android, we will have to go to the Assets approach, instead of the Resources one.
2.- That solves also the unification of types when identifying assets. In both cases (XNA and Android), we will be using Asset Names as strings.
3.- In both sides we will need to provide a strongly-typed way of accessing contents.
4.- We want the exact same interface that is finally published outwards, so that every piece of code that uses our strongly-typed classes, write the exact same code no matter which platform we are coding on.

An implementation using again T4 templates

Again, we will write down two different T4 templates, one for XNA and one for Android. Both of them will have to do merely the same, but with some minor differences. Let’s see them:

Example: XNA T4 Template to give strongly-typed access to contents

This template will be placed wherever we want to use it. It can be in the main XNA Game project, or in a library project shared all around. Basically, it will search inside the Visual Studio solution for the Game’s Content Project. Once found, it will iterate recursively through the file and folder structure of the project, generating classes that give strong-typed access to each Asset.
Imagine we have the following structure in our contents project:
image
We want to get an output like the following:
image
As you can see, we will use namespaces to represent the tree-structure of folders in the content project. Once we find a folder with one or more content files, we will create a class named “Keys” that will hold properties to access asset names. We will also create an enumeration with all the assets found at that level. This way, we also allow to navigate through the contents tree, if needed.

The code

The XNA template that generates that, is the following:
<#
//  --------------------------------------------------------------------------------------------------------------
//  Template: Generates C# code to give strongly-typed access to Asset files
//  Author: Inaki Ayucar
//  Website: www.graphicdna.net
//  Based on the work of: http://blog.baltrinic.com
//  -----------------------------------------------------------------------------------------------------------
#>
<#@ template debug="true" hostspecific="true" #>
<#@ assembly name="System.Core" #>
<#@ assembly name="System.Xml" #>
<#@ assembly name="Microsoft.VisualStudio.Shell.Interop.8.0" #>
<#@ assembly name="EnvDTE" #>
<#@ assembly name="EnvDTE80" #>
<#@ assembly name="VSLangProj" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ import namespace="System.IO" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Text" #>
<#@ import namespace="System.Text.RegularExpressions" #>
<#@ import namespace="System.Xml" #>
<#@ import namespace="Microsoft.VisualStudio.Shell.Interop" #>
<#@ import namespace="EnvDTE" #>
<#@ import namespace="EnvDTE80" #>
<#@ import namespace="Microsoft.VisualStudio.TextTemplating" #>
<#  // ------------------------------------------------------------------------------------------------------
    // Get global variables
    // ------------------------------------------------------------------------------------------------------
    var serviceProvider = Host as IServiceProvider;
    if (serviceProvider != null)
        Dte = serviceProvider.GetService(typeof(SDTE)) as DTE;
 
 
    // Fail if we couldn't get the DTE. This can happen when trying to run in TextTransform.exe
    if (Dte == null)
        throw new Exception("T4MVC can only execute through the Visual Studio host");
 
 
    Project = GetXNAContentsProject(Dte);
    if (Project == null)
    {
        Error("Could not find XNA Content Project.");
        return"XX";
    }   
    Project prjT4 = GetProjectContainingT4File(Dte);
    if (prjT4 == null)
    {
        Error("Could not find Template's project");
        return"XX";
    }   
 
     AppRoot = Path.GetDirectoryName(Project.FullName) + '\\';
     RootNamespace = prjT4.Properties.Item("RootNamespace").Value.ToString();
    // --------------------------------------------------------------------------------------------------
#>
// ------------------------------------------------------------------------------------------------------
// <auto-generated>
//     This code was generated by a tool.
//
//     Changes to this file may cause incorrect behavior and will be lost if
//     the code is regenerated.
// </auto-generated>
// ------------------------------------------------------------------------------------------------------
using System.Threading;
 
 
<#
    try
    {
        // We are storing in a List<ResourceEntry> (declared below) a list with all string entries of
        // all files found matching our search criteria
        AllEntries = new Dictionary<string, List<AssetFileInfo>>();
 
        string projectFileName = Path.GetFileName(Project.FullName);
        string projectFullPath = Project.FullName.Substring(0, Project.FullName.Length - projectFileName.Length);
 
        // Find files on our project that match our search criteria (recursively), and store every string
        // entry on those files
        FindResourceFilesRecursivly(projectFullPath, Project.ProjectItems, "");
 
 
        foreach(string path in AllEntries.Keys)
        {
            if(path == null || path == "")
                continue;
 
            List<string> enumNames = new List<string>();
 
            string aux = path;
 
            // To avoid conflict names with namespaces, the class names will always be "Keys"
            string className = "Keys";
 
            if(aux.EndsWith("\\"))
                aux = aux.Remove(aux.Length - 1, 1);
            string pathNameSpace = aux.Replace("\\", ".");
 
            // Start of namespace
            if(pathNameSpace != "")
                WriteLine(string.Format("namespace {0}.Assets.{1}", RootNamespace, pathNameSpace));
            else WriteLine(string.Format("namespace {0}.Assets", RootNamespace));
            WriteLine("{");
 
            // Start of class
            WriteLine(string.Format("\tpublic class {0}", className));
            WriteLine("\t{");
            foreach(AssetFileInfo info in AllEntries[path])
            {  
                string filenameWithoutExt= Path.GetFileNameWithoutExtension(info.File);
                WriteLine(string.Format("\t\tpublic static string {0}", filenameWithoutExt));
                WriteLine("\t\t{");
                WriteLine(string.Format("\t\t\tget  {{ return \"{0}\"; }}", info.AssetName.Replace(@"\", @"\\") ));
                WriteLine("\t\t}");
 
                enumNames.Add(filenameWithoutExt);
            }
 
            // Start of Enum
            WriteLine("\t\tpublic enum eKeys");
            WriteLine("\t\t{");
            foreach(string enumname in enumNames)
                WriteLine(string.Format("\t\t\t{0},", enumname));
            // Close enum
            WriteLine("\t\t}");
 
 
            // Close class
            WriteLine("\t}");
 
 
 
            // Close namespace
            WriteLine("}");
        }
 
    }
    catch(Exception ex)
    {
        Error(ex.ToString());
    }
#>
 
 
<#+ // --------------------------------------------------------------------------------------------------------
    // Class feature control block:
    // Remarks: Identified by the #+ mark, allows to define variables, methods, etc
    // --------------------------------------------------------------------------------------------------------
    const string Kind_PhysicalFolder = "{6BB5F8EF-4483-11D3-8BCF-00C04F8EC28C}";
    bool AlwaysKeepTemplateDirty = true;
    static DTE Dte;
    static Project Project;
    static string AppRoot;
    static string RootNamespace;
    static Dictionary<string, List<AssetFileInfo>> AllEntries;
    static List<string> SupportedExtensions = new List<string>() {".dds", ".png", ".bmp", ".tga", ".jpg"};
 
    /// <Summary>
    /// FindResourceFilesRecursivly
    /// Remarks: Searches recursively in the files of our project, for those which are in the same folder
    /// that this file, or below, and that have extensions included in the supported extension list
    /// </Summary>
    void FindResourceFilesRecursivly(string pProjectFullPath, ProjectItems items, string path)
    {
        string assetRelativePath = path.TrimStart(new char[1]{'.'});
        assetRelativePath = assetRelativePath.Replace('.', '\\');
        foreach(ProjectItem item in items)
        {       
            if(item.Kind == Kind_PhysicalFolder)
                FindResourceFilesRecursivly(pProjectFullPath, item.ProjectItems, path+"."+item.Name);
            else
            {               
                // check if extension is supported
                string extension = Path.GetExtension(item.Name).ToLowerInvariant();
                if(SupportedExtensions.Contains(extension))
                {
                    string itemFileName = item.FileNames[0];
                    if(itemFileName == null)
                        continue;
 
 
                    AssetFileInfo info = new AssetFileInfo();
 
                    info.AssetName = itemFileName.Remove(0, pProjectFullPath.Length);
 
                    // XNA require que los asset names no tengan extensión
                    info.AssetName = info.AssetName.Substring(0, info.AssetName.Length - extension.Length);
 
                    info.File = item.Name;
                    info.Path = itemFileName.Substring(0, itemFileName.Length - item.Name.Length);
 
                    if(!AllEntries.ContainsKey(assetRelativePath))
                        AllEntries.Add(assetRelativePath, new List<AssetFileInfo>());
 
                    AllEntries[assetRelativePath].Add(info);
                }
            }
        }
    }
    /// <Summary>
    /// GetXNAContentsProject
    /// Remarks: http://www.codeproject.com/KB/macros/EnvDTE.aspx
    /// </Summary>
    Project GetXNAContentsProject(DTE dte)
    {
        foreach(Project prj in dte.Solution.Projects)
        {
            // XNA Content projects define this property. Use it to identify the project
            if(!HasProperty(prj.Properties,
                 "Microsoft.Xna.GameStudio.ContentProject.ContentRootDirectoryExtender.ContentRootDirectory"))
                continue;
 
            return prj;         
        }    
        return null;
    }
     /// <Summary>
    /// GetProjectContainingT4File
    /// Remarks:
    /// </Summary>
    Project GetProjectContainingT4File(DTE dte)
    {
 
        // Find the .tt file's ProjectItem
        ProjectItem projectItem = dte.Solution.FindProjectItem(Host.TemplateFile);
 
        // If the .tt file is not opened, open it
        if (projectItem.Document == null)
            projectItem.Open(Constants.vsViewKindCode);
 
        if (AlwaysKeepTemplateDirty) {
            // Mark the .tt file as unsaved. This way it will be saved and update itself next time the
            // project is built. Basically, it keeps marking itself as unsaved to make the next build work.
            // Note: this is certainly hacky, but is the best I could come up with so far.
            projectItem.Document.Saved = false;
        }
 
        return projectItem.ContainingProject;
    }
    /// <Summary>
    /// Struct: ResourceEntry
    /// Remarks: Stores information about an entry in a resource file
    /// </Summary>
    private bool HasProperty(Properties properties, string propertyName)
    {
        if (properties != null)
        {
            foreach (Property item in properties)
            {
                //WriteLine("// " + item.Name);
                if (item != null && item.Name == propertyName)
                    return true;
            }
        }
        return false;
    }
    /// <Summary>
    /// Struct: ResourceEntry
    /// Remarks: Stores information about an entry in a resource file
    /// </Summary>
    struct AssetFileInfo
    {               
        public string AssetName {get;set;}
        public string Path { get; set; }
        public string File { get; set; }
    }  
#>

Usage

One the T4 template is included on your solution, the way of accessing Assets in XNA is like the following:
Content.Load<Texture2D>(GDNA.PencilBurst.Assets.Textures.UI.Keys.circleT);
All absolutely strong-typed, much less bug-prone.

Android version

Once you have that as a start point, developing the Android version is pretty straight-forward. You just need to change a couple of things:
  • Instead of searching for the Contents Project inside your solution, you will need to search for the main project, and look for the “Assets” subfolder.
  • Asset names do include file extensions in Android, so be sure to not remove them (like in XNA)
  • Also, asset names will need to replace ‘\’ for ‘/’, so you can correctly invoke:
stream = mContext.Assets.Open(pAssetName);
And that´s all !!!

Using T4 Templates to generate custom strongly-typed code in Visual Studio


Strongly typed code rocks. Easy as that. Reduces bugs, and makes your developments more productive and efficient. We all know that.
One example of strong-typing inside Visual Studio: resource files are parsed by default with the ResXFileCodeGenerator tool, which generates automatic properties in C# files, that give us strongly-typed access to strings.
That’s cool, by I there’s a lot of customization capabilities there missing. For instance, ResXFileCodeGenerator generates internal classes by default, and this is not always desirable. Many people struggled around this in the past, so in Visual Studio 2008 a new custom tool was introduced: PublicResXFileCodeGenerator: the same one than before, but building public classes. Cool again, but still missing many things…
So, how to customize the code generation process?

Option 1: Write your own tool

You can write a tool that mimics the behavior of ResXFileCodeGenerator, and you can install it within the Visual Studio (so you can select your ResX files to be parsed with it). It´s not too complicated, but you need to develop a separate installation project, to be able to install it within VStudio. You can find an example here.
To be honest, I don´t like the idea of having to write the extension in a different project, needing to go there for every change, recompiling, re-installing, etc. Besides that, this approach means having one single tool for every resX files you want to parse, and therefor, the tool needs to be generic enough to give support for every use case you have.
One last inconvenient, is that as far as I know, a tool like this cannot act in several files at a time. That means that it will generate a code file for each resource file. It’s impossible to generate ONE code file for SEVERAL resource files.
Seems that I´m too lazy today for all of that, so I searched for other solutions, and found one that I really like: T4 templates

Option 2: Write a T4 Text Template

A T4 Text Template is “a mixture of text blocks and control logic that generate a text file”. In other words, it’s a piece of code that will generate a text file and will include it in your Solution (below the .tt file itself). This text file, pretty well can be a source code file, so this way we can automatically generate code for the solution, with all the power to customize it.
I have been studying them for a while, and I can tell you that they are really powerful. Some relevant aspects around them:
  1. They are text files (with .tt extension), that are included INSIDE your solution, so no need to keep them in a separate project, and no need to build a setup project to install them.
  2. This .tt files are, by default, parsed by the custom tool: TextTemplatingFileGenerator
  3. They can operate on several project files at a time, not only one, generating if you want ONE code file, for SEVERAL resource files.
  4. They don´t need to be installed or distributed in any form. Simply add them to your solution
  5. Changes in the Template don’t mean to go to a different solution, rebuilding and re-installing
  6. They can be written in both C# or VisualBasic.
  7. When they are parsed, the generate a code file below the Template (see below), with the same name as the template itself:
image
  1. They are usually parsed as soon as they are modified and re-saved.
  2. Because the modification and installation process is so simple, and because you can have if you want a different T4 Template for each resX file, you can have as many versions of the templates as you wish. Each one covering different needs. And that is cool !
Any disadvantages? Visual Studio integration
By now, Visual Studio offers no integration for T4 files. That means that by default you get no syntax highlighting, no intellisense, etc.
But this can be fixed by using one of the T4 integration extensions for VStudio out there. I have tested three of them:
  • Tangible T4 Editor: Honestly, I couldn’t get it to work. I installed it, apparently with no error, but it didn’t work. And I already started this post by saying I´m too lazy today, so I tested other solutions that installed fine at first try:
  • Clarius Visual T4: It installed just fine and added syntax highlighting and intellisense to T4 files. Unfortunately, it made my Visual Studio 2010 Ultimate freeze for about 10 seconds from time to time. So I decided to try a different option.
  • Deviart T4: It installed fine, and works pretty well. The syntax highlighting gets messed from time to time, but nothing serious. Just re-opening the file fixes it. It’s fast, and I like it. It’s the clear winner. And it’s free!
image

Some basic concepts about developing T4 templates

Developing a T4 template is pretty straightforward, if you have some experience with .Net. We are not going to explain here all the coding aspects about T4 templates, as it is extremely clearly explained here and here.
However, it’s a bit meesy the first time you see one, how code blocks are mixed with plain text blocks, especially if you don´t have an extension installed that gives you syntax highlighting.
So, first thing you should understand is that T4 templates mix parts of text that will simply be copied to the generated file (Text Blocks), and others that are code blocks to control the logic of the generation (Code Blocks). In Deviart T4, you will see the following highlighting:
  1. Text blocks, copied directly to the destination file (grayed out):
image
As I mentioned, whatever you write here will be directly copied to the destination file. No matter what it is. It won´t be validated by the tool, just copied. You are responsible of writing something that makes sense, and that won’t generate compiling errors.
  1. Code blocks (surrounded by <# … #> and similar):
image
These code blocks are parsed by the tool and executed. They are validated by the compiler, just like any other piece of code you write (that means that will generate compiling errors as usually). In the previous example, the code block is writing a “}” symbol to the output file, using the WriteLine method (se next chapter for more info).

Different ways to output text to the destination file

We already seen some of them, but basically, you have three different ways of outputting text:
1.- Put a Text Block in your template (like in the previous chapter).
2.- Invoke the WriteLine method inside a Code Block. Like in the example of previous chapter, anywhere you call WriteLine(“…”) from within a code block, will write that text line to the destination file.
3.- Mixing both Code Blocks and Text Blocks, like in the following example:
image
In this example, the header Text Block (grayed out) will only be copied if insertWarningHeader == true. This means that flow control of code blocks affect the output of plain text blocks too.
Please note that you need to “end” the Code Block by using the “#>”, and therefor the text inside the braces will be identified as a Text Block. Then, re-open a code block, just to put the final brace “}” of the IF statement. Separating it into two different Code Blocks doesn’t prevent the IF from doing its job…

Other useful kinds of Code Blocks

As you can see, the <# … #> labels define the start and end of code blocks that should be parsed and evaluated. Anything outside those labels is considered text blocks. There are other kinds of code blocks, as explained here:
  • Expression code blocks (<#= … #>): They evaluate an expression, and convert the result to string. Some examples:
    1. <#= 2 + 3 #> … will output a “5”
    2. <#= numberOfEntries * 2#> … Where numberOfEntries is a valid variable on that scope, will output the result of the addition.
    3. etc.
  • Class feature code blocks (<#+ … #>): Allow to define properties or helper methods. They can be defined in separate files. The following example defines the property RootNamespace and the helper method EmitEnum, available in all the template.
image
  • Importing namespaces is also very easy, you just need to put in the top of the file statements like the following:
<#@ import namespace="System.Xml" #>
I think that there’s not too much magic in here, so I won’t bore you with more detail. Everything is really simple to follow, and is really well explained in the above links, so I guess the best way to show a real T4 Template is with an example!

Example: Custom strong-typed access to resources with a T4 template

What we need

In this example, we will used the mentioned T4 templates to give a full-featured, strong-typed access to strings in resource files. I did it to meet my own needs, but using it as a starting point, it will very easy for you to adapt it to your own.
The goal is to be able to customize the following aspects directly from the resX file:
  • Access modifier of the class: public, private, internal
  • Namespace where the class is defined
  • Generate (if wanted), an enumeration with all the keys of the entries
  • Modify the return type of the properties. Does this make any sense? Yes (read below).
  • Allow ResX files to use Conditional Compilation:
    1. It would be fantastic if we could specify different values for strings, depending on conditional compilation symbols
    2. And it would be even greater, if we could specify different return types, depending on the same conditional compilation symbols.

Does it make any sense to modify return types?
In my scenario, it does. I’ll explain it, so you can see one example. Then it’s up to you to decide if that’s useful also in other situations…
I was writing a piece of code, related to 3D graphics, that I wanted to run in both Windows Phone and Android. That code has contents (bitmaps, etc), which are identified differently in Windows Phone (XNA) projects, and Android.
In the first one, contents are identified with Asset Names, which are strings. In the second one, contents are identified with Integer IDs. In fact, Android automatically generates a class like the ones we are creating here to give strong-type access to those integers.
Well, I wanted to centralize the loading of contents, so it was obvious that I would need to unify content identification with my own IDs. I simply didn’t want to have #if #endif blocks all around my code.
Question is, that I can write two versions of methods like LoadTexture(), one for each platform, and keeping the specifics inside the Content Repository, but the problem is that Android identifies contents with a different type (ints instead of strings), and that makes my code end up with a different interface for each version. Something like this:
#if(ANDROID)
        public static void LoadTexture(int pResourceID)
        {
        }
#elif(WINDOWS_PHONE)
        public static void LoadTexture(string pAssetName)
        {
        }
#endif
I have no problem with writing two versions of the method (that’s inevitable). But having two different interfaces is bad. Really bad.
Why? Because then, every single point in my code where I use this method will need a #if #endif code block too. And I hate that. I want this contents repository to expose a single interface. How do we achieve that?
If both platforms used strings to identify contents, I could create a table to map my own resource identifiers to that ones. But Android uses ints. And what is worse, they are automatically generated. I can see what IDs Android gave to a content, but I cannot guarantee that the ID will be consistent over time, as it’s generated by an automatic tool. In addition to that, I would need to maintain that table by hand, what is horrible and very bug prone.
Mmmmmhhh…
Seems that the only solution is writing code, with methods or properties that map my own resource IDs to: string assets in the case of XNA, and resource IDs in the case of Android. Something like:
#if(ANDROID)
        public static int Button1
        {
            get
            {
                return Resource.Drawable.Button1;
            }
        }
#elif(WINDOWS_PHONE)
        public static string Button1
        {
            get
            {
                return @"Contents\Textures\UI\Button1";
            }
        }
#endif
Having a repository like this, would allow me to eliminate the #if #endif blocks when calling methods like LoadTextures, as I could use: LoadTextures ( Respository.Button1 );
If we are compiling to ANDROID, Button1 will return an int and LoadTextures() will expect an int, so no problem. If we are compiling to Windows Phone, both will give and expect a string. Everything fine again.
The problem with that is that a single project can have hundreds, or thousands of resources, an maintaining the file manually can be a nightmare. If only it could be done automatically…
That’s where the variable return type of my template kicks in. It will give us precisely that, with the particularity that when on ANDROID (being the return type an int), the template will not insert string, but a call to the Android Repository.
This way, I get rid of having to deal manually with Android int IDs, and just work with their strong-typed names.
See below for more…

The implementation

The behavior of the template we have developed, to achieve all of this is:
  • It is designed to be placed inside your projects, just by the file it will process. It has to be in the same folder and needs to have the same name. So, if you want to process the file Textures.resx, you will end up with something like this in your solution:
image
Note 1: You can easily modify it to parse all the ResX files it finds in the project at once, but this time I needed it to work this way.
Important Note 2: To avoid duplicity of generated code, and compilation errors, when you add the template to a resource file, you should disable the default parsing of that ResX file, by removing the default custom tool (ResXFileCodeGenerator) and by setting BuildAction = None.
  • It will generate strong-typed properties to access all the strings it finds in the resX file
  • It can be instructed to generate an enumerate with all the keys in the file too
  • It will automatically generate the well formatted XML comments for the properties
  • It supports some special keywords (entries starting by “#C#_”), to allow customizing the generation process:
    1. CT4_ACCESS_MODIFIERS (public, private, internal): By default, the generated class will be public, but you can include this entry to modify this behavior. You can set the following values: public, private or internal.
image
    1. CT4_OVERRIDE_NAMESPACE (namespace name): By default, the class will be in the default namespace of the project, but you can include this entry to override that behavior, setting the desired namespace in the value of the entry:
image
  1. CT4_GENERATE_ENUM (enum name): If this entry is included, the template will create an Enumeration with all the key names of the ResX file, and also an special version of the GetResourceString() method, accepting as parameter one of those enumerations. You can specify the name of the enumeration in the Value field.
image
  1. CT4_DEFAULT_RETURNTYPE (string, int, etc): Allows to specify the default return type for all properties. The default return type if string.
image
  1. CT4_CONDITIONAL_COMPILATION_SYMBOLXX (Symbol Name): Allows to use conditional compilation inside the resource files. To do so, you must first identify what conditional compilation symbols are used in your project. In this example, we will have two of them: WINDOWS_PHONE, and ANDROID. So, we will create two entries to let the generator know about them, like the following:
image
  1. CT4_CONDITIONAL_RETURNTYPE: If conditional compilation is being used, it allows to specify a different return type for each conditional symbol, with following syntax:
@COND_SYMBOL1:type_1;@COND_SYMBOL2:type_2 …
Where COND_SYMBOLXX is one of the conditional compilation symbols defined before, and type_XX is the return type desired for that symbol.
The following example a string return type for WINDOWS_PHONE, and an integer return type for ANDROID:
image

Once we have configured the generation process with the control entries, it’s time to put some data there. A normal string entry is entered as usual, with unique name, a value, and a comment if you want to. How to include conditional compilation entries?
Using conditional compilation in string entries
The name and the comment of the entry are the same as in normal ones. It’s in the Value where we put the information needed, very much like when defining specific return types for each conditional compilation. The syntax is:
@COND_SYMBOL1:value_1;@COND_SYMBOL2:value_2 …
Where COND_SYMBOLXX is one of the conditional compilation symbols defined before, and value_XX is the string value desired for that symbol.
So, the following example:
image
Will generate the following code:
   66         ///<summary>
   67         ///Button 1 image asset name or ID
   68         ///</summary>
   69         #if(WINDOWS_PHONE)
   70              public static string Button1 { get { return "Content\Textures\UI\button1"; } }
   71         #elif(ANDROID)
   72              public static int Button1 { get { return Resource.Drawable.app_Icon; } }
   73         #endif
Note that the generator also takes into account the Comment field, and that the return types and values for each version of the property are different. Also, in the case of Android, note that the get method makes a Call to the Android resource repository class, with the strongly-typed properties that access the IDs.

The template code

The template is based on this other one, but with a modified behavior to meet my own needs. The code is:
<#
//  ----------------------------------------------------------------------------------------------
//  Template: Generates C# code to give strongly-typed access to resource files
//  Author: Inaki Ayucar
//  Website: www.graphicdna.net
//  Based on the work of: http://blog.baltrinic.com
//  Links:
//          MSDN about developing T4 files: http://msdn.microsoft.com/en-us/library/bb126445.aspx
//                                          http://msdn.microsoft.com/en-us/library/dd820620.aspx
//  ----------------------------------------------------------------------------------------------
#>
<#@ template debug="true" hostspecific="true" #>
<#@ assembly name="System.Core" #>
<#@ assembly name="System.Xml" #>
<#@ assembly name="Microsoft.VisualStudio.Shell.Interop.8.0" #>
<#@ assembly name="EnvDTE" #>
<#@ assembly name="EnvDTE80" #>
<#@ assembly name="VSLangProj" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ import namespace="System.IO" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Text" #>
<#@ import namespace="System.Text.RegularExpressions" #>
<#@ import namespace="System.Xml" #>
<#@ import namespace="Microsoft.VisualStudio.Shell.Interop" #>
<#@ import namespace="EnvDTE" #>
<#@ import namespace="EnvDTE80" #>
<#@ import namespace="Microsoft.VisualStudio.TextTemplating" #>
<#  // --------------------------------------------------------------------------------------------
    // Get global variables
    // --------------------------------------------------------------------------------------------
    var serviceProvider = Host as IServiceProvider;
    if (serviceProvider != null)
        Dte = serviceProvider.GetService(typeof(SDTE)) as DTE;
 
 
    // Fail if we couldn't get the DTE. This can happen when trying to run in TextTransform.exe
    if (Dte == null)
        throw new Exception("T4MVC can only execute through the Visual Studio host");
 
    Project = GetProjectContainingT4File(Dte);
 
    if (Project == null)
    {
        Error("Could not find the VS Project containing the T4 file.");
        return"XX";
    }
 
     AppRoot = Path.GetDirectoryName(Project.FullName) + '\\';
     RootNamespace = Project.Properties.Item("RootNamespace").Value.ToString();
    // --------------------------------------------------------------------------------------------
#>
// ---------------------------------------------------------------------------------------------------
// <auto-generated>
//     This code was generated by a tool.
//
//     Changes to this file may cause incorrect behavior and will be lost if
//     the code is regenerated.
// </auto-generated>
// ---------------------------------------------------------------------------------------------------
using System.Threading;
 
 
<#
try
{
        // We are storing in a List<ResourceEntry> (declared below) a list with all string entries
        // of all files found matching our search criteria
        AllEntries = new List<ResourceEntry>();
 
        // Entries starting with "CT4_", are declared as "control" entries, defining keywords or data
        // that will modify the source code generation behavior
        ControlEntries = new List<ResourceEntry>();
 
        // Find files on our project that match our search criteria (recursively), and store every
        // string entry on those files
        FindResourceFilesRecursivlyAndRecordEntries(Project.ProjectItems, "");
        AllEntries.Sort( new Comparison<ResourceEntry>( (e1, e2) => (e1.Path + e1.File +
                                 e1.ValidIdentifierName).CompareTo(e2.Path + e2.File + e2.ValidIdentifierName)));
 
        // Parse control entries
        string overrideNameSpace = "";
        string classAccessModifier = "public";
        string generateEnumName = "";
        string defaultReturnType = "string";
        Dictionary<string, string> returnTypesForConditionalCompilation = new Dictionary<string, string>();
        List<string> conditionalCompilationSymbols = new List<string>();
        List<string> conditionalCompilationSymbolsInValues = new List<string>();
        foreach(ResourceEntry entry in ControlEntries)
        {
            if(entry.OriginalName == "CT4_OVERRIDE_NAMESPACE")
            {
                overrideNameSpace = entry.Value;
                continue;
            }
            if(entry.OriginalName == "CT4_ACCESS_MODIFIERS")
            {
                classAccessModifier = entry.Value.ToLower();
                if(classAccessModifier != "public" &&
                   classAccessModifier != "private" &&
                   classAccessModifier != "internal")
                    Error("Invalid CT4_ACCESS_MODIFIERS found: Only public, private or internal are allowed");
                continue;
 
            }
            if(entry.OriginalName == "CT4_GENERATE_ENUM")
            {
                generateEnumName = entry.Value;
                continue;
            }
            if(entry.OriginalName.StartsWith("CT4_CONDITIONAL_COMPILATION_SYMBOL"))
            {
                conditionalCompilationSymbols.Add(entry.Value);
                conditionalCompilationSymbolsInValues.Add(string.Format("@{0}:", entry.Value));
                continue;
            }      
            if(entry.OriginalName.StartsWith("CT4_DEFAULT_RETURNTYPE"))
            {
                defaultReturnType = entry.Value;
                continue;
            }
            if(entry.OriginalName.StartsWith("CT4_CONDITIONAL_RETURNTYPE"))
            {
                returnTypesForConditionalCompilation.Clear();
                bool hasCondCompilation = StringValueHasCompilationSymbols(entry.Value,
                                                               conditionalCompilationSymbolsInValues);
                if(!hasCondCompilation)
                    Error("CT4_CONDITIONAL_RETURNTYPE entry found, but no conditional symbols were found in value");
 
                Dictionary<string, string> parts = SplitStringForConditionalCompilationSymbols(entry.Value,
                                                                 conditionalCompilationSymbolsInValues);
                foreach(string symbol in parts.Keys)
                    returnTypesForConditionalCompilation.Add(symbol, parts[symbol]);
                continue;
            }      
        }
 
        // Foreach string entry found, add it's code
        string currentNamespace = "";
        string currentClass = "";
        bool thisIsFirstEntryInClass = true;
        List<string> names = new List<string>();       
        for(int i=0;i<AllEntries.Count;i++)
        {
            ResourceEntry entry = AllEntries[i];
 
            var newNamespace = overrideNameSpace == "" ? RootNamespace: overrideNameSpace;
            var newClass = entry.File;
            bool namesapceIsChanging = newNamespace != currentNamespace;
            bool classIsChanging = namesapceIsChanging || newClass != currentClass;
 
            // Close out current class if class is changing and there is a current class
            if(classIsChanging && currentClass != "")
            {
                EmitNamesInnerClass(names);
                WriteLine("\t}");
            }
 
            // Check if there is a namespace change
            if(namesapceIsChanging)
            {
                // Close out current namespace if one exists
                if( currentNamespace != "" )
                    WriteLine("}");
 
                currentNamespace = newNamespace;
 
                // Open new namespace
                WriteLine(string.Format("namespace {0}", currentNamespace));
                WriteLine("{");
 
            }
 
            // Check if there is a class Change
            if(classIsChanging)
            {
                currentClass = newClass;
                WriteLine(string.Format("\t" + classAccessModifier + " class {0}", currentClass));
                WriteLine("\t{");
                thisIsFirstEntryInClass = true;
 
                // Only if the class changed, Emit code for the ResourceManager property and
                // GetResourceString method for the current class
                #>
                private static global::System.Resources.ResourceManager resourceMan;
 
                /// <summary>
                ///   Returns the cached ResourceManager instance used by this class.
                /// </summary>
                [global::System.ComponentModel.EditorBrowsableAttribute
                                               (global::System.ComponentModel.EditorBrowsableState.Advanced)]
                private static global::System.Resources.ResourceManager ResourceManager
                {
                    get
                    {
                        if (object.ReferenceEquals(resourceMan, null))
                        {
                            global::System.Resources.ResourceManager temp = new
                                              global::System.Resources.ResourceManager("
                <#=string.Format("{0}.{1}{2}", RootNamespace, entry.Path + "." + entry.File, entry.Type) #>",
                                                        typeof(<#=entry.File#>).Assembly);
                            resourceMan = temp;
                        }
                        return resourceMan;
                    }
                }
 
                /// <summary>
                ///   Returns the formatted resource string.
                /// </summary>
                [global::System.ComponentModel.EditorBrowsableAttribute
                                                (global::System.ComponentModel.EditorBrowsableState.Advanced)]
                private static string GetResourceString(string key, params string[] tokens)
                {
                    var culture = Thread.CurrentThread.CurrentCulture;
                    var str = ResourceManager.GetString(key, culture);
 
                    for(int i = 0; i < tokens.Length; i += 2)
                        str = str.Replace(tokens[i], tokens[i+1]);
 
                    return str;
                }
 
                <#
                if(generateEnumName != "")
                {
                #>/// <summary>
                /// Returns the formatted resource string, passing the enum value as parameter
                /// </summary>
                [global::System.ComponentModel.EditorBrowsableAttribute
                                           (global::System.ComponentModel.EditorBrowsableState.Advanced)]
                private static string GetResourceString(<#= generateEnumName.ToString() #> key, params string[] tokens)
                {
                    var culture = Thread.CurrentThread.CurrentCulture;
                    var str = ResourceManager.GetString(key.ToString(), culture);
 
                    for(int i = 0; i < tokens.Length; i += 2)
                        str = str.Replace(tokens[i], tokens[i+1]);
 
                    return str;
                }
 
                <#
                }
            }         
 
 
            // Write entry comment for property
            EmitEntryComment(entry, thisIsFirstEntryInClass);
 
            // Select all tokens between braces that constitute valid identifiers
            var tokens = Regex.Matches(entry.Value, @"{(([A-Za-z]{1}\w*?)|([A-Za-z_]{1}\w+?))?}").
                                                                       Cast<Match>().Select(m => m.Value);       
            if(tokens.Any())
            {
                var inParams = tokens.Aggregate("", (list, value) => list += ", string " + value)
                    .Replace("{", "").Replace("}", "");
                if(inParams.Length > 0 ) inParams = inParams.Substring(1);
                var outParams = tokens.Aggregate("", (list, value) => list += ", \"" + value +"\", " +
                                                                value.Replace("{", "").Replace("}", "") );
 
                WriteLine(string.Format("\t\tpublic static string {0}({1}) {{ return
                          GetResourceString(\"{0}\"{2}); }}",  entry.ValidIdentifierName, inParams, outParams));
 
                names.Add(entry.ValidIdentifierName);
            }
            else
            {
                // Detect if entry has conditional compilation symbols
                string entryValue = entry.Value;
                bool hasCondCompilation = StringValueHasCompilationSymbols(entryValue,
                                                                   conditionalCompilationSymbolsInValues);
 
                if(!hasCondCompilation)
                    EmitProperty(defaultReturnType, entry.ValidIdentifierName, entryValue, "", false, false);
                else
                {
                    // If has conditional compilation, generate one versino for each symbol
                    Dictionary<string, string> valuesForCondCompilation = SplitStringForConditionalCompilationSymbols
                                                              (entryValue, conditionalCompilationSymbolsInValues);
                    int c = -1;
                    foreach(string key in valuesForCondCompilation.Keys)
                    {
                        c++;
                        string rtype = defaultReturnType;
                        if(returnTypesForConditionalCompilation.ContainsKey(key))
                            rtype = returnTypesForConditionalCompilation[key];
 
                        EmitProperty(rtype, entry.ValidIdentifierName, valuesForCondCompilation[key],
                                                          key, c == 0, c == valuesForCondCompilation.Count - 1);
                    }
                }
                names.Add(entry.ValidIdentifierName);
            }
 
            thisIsFirstEntryInClass = false;
    }
 
 
    // Close out the current class when done, writing down the names
    if(currentClass != "")
    {
        EmitNamesInnerClass(names);
 
        if(generateEnumName != "")
            EmitEnum(names, generateEnumName);
 
        names.Clear();
 
        WriteLine("\t}");
    }
}
catch(Exception ex)
{
    Error(ex.ToString());
}
#>
 
<#
    // Only close the namespace if I added one
    if(AllEntries.Count > 0)
        WriteLine("}");
#>
 
 
 
<#+ // ------------------------------------------------------------------------------
    // Class feature control block:
    // Remarks: Identified by the #+ mark, allows to define variables, methods, etc
    // ------------------------------------------------------------------------------
    const string Kind_PhysicalFolder = "{6BB5F8EF-4483-11D3-8BCF-00C04F8EC28C}";
    bool AlwaysKeepTemplateDirty = true;
    static DTE Dte;
    static Project Project;
    static string AppRoot;
    static string RootNamespace;
    static List<ResourceEntry> AllEntries;
    static List<ResourceEntry> ControlEntries;
 
    /// <Summary>
    /// FindResourceFilesRecursivlyAndRecordEntries
    /// Remarks: Searches in the files of our project, for one that is in the same folder than this
    /// template, has the same name, and has the extension ".resx". If found, takes all string entries
    /// on it and stores them in the AllEntries list.
    /// </Summary>
    void FindResourceFilesRecursivlyAndRecordEntries(ProjectItems items, string path)
    {
        // I wanna take care about file path and name, but not about extension, so take everything but the extension
        string aux = Path.GetExtension(Host.TemplateFile);
        string T4FileWithoutExtension= Host.TemplateFile.Substring(0, Host.TemplateFile.Length - aux.Length);
 
        foreach(ProjectItem item in items)
        {       
 
            if(Path.GetExtension(item.Name) == ".resx")
            {
                    string itemFileName = item.FileNames[0];
                    if(itemFileName == null)
                            continue;
                    aux = Path.GetExtension(itemFileName);       
                    itemFileName = itemFileName.Substring(0, itemFileName.Length - aux.Length);       
 
                    // If the file path and name (without extension) is not equal to the template file, continue
                    if(itemFileName.ToLowerInvariant() != T4FileWithoutExtension.ToLowerInvariant())
                        continue;
 
                    RecordEntriesInResourceFile(item, path);
 
                    // We only want to parse one file. This should never happen, but if we find 2 files, just quit
                    break;
            }
            if(item.Kind == Kind_PhysicalFolder)
                FindResourceFilesRecursivlyAndRecordEntries(item.ProjectItems, path+"."+item.Name);
        }
    }
    /// <Summary>
    /// RecordEntriesInResourceFile
    /// Remarks: For a given file, takes all its entries and stores them in the AllEntries list.
    /// </Summary>
    void RecordEntriesInResourceFile(ProjectItem item, string path)
    {
        //skip resource files except those for the default culture
        if(Regex.IsMatch(item.Name, @".*\.[a-zA-z]{2}(-[a-zA-z]{2})?\.resx"))
                return;
 
        var filePath = (string)item.Properties.Item("FullPath").Value;
        var xml = new XmlDocument();
        xml.Load(filePath);
        var entries = xml.DocumentElement.SelectNodes("//data");
 
        var parentFile = item.Name.Replace(".resx", "");
        var fileType = Path.GetExtension(parentFile);
        if(fileType != null && fileType != "")
            parentFile = parentFile.Replace(fileType, "");
 
        foreach (XmlElement entryElement in entries)
        {
            var entry = new ResourceEntry
            {           
                Path = path != "" && path != null?path.Substring(1):"",
                File = MakeIntoValidIdentifier(parentFile),
                Type = fileType,
                OriginalName = entryElement.Attributes["name"].Value,               
            };
 
            var valueElement = entryElement.SelectSingleNode("value");
            if(valueElement != null)
                entry.Value = valueElement.InnerText;
 
            var commentElement = entryElement.SelectSingleNode("comment");
            if(commentElement != null)
                entry.Comment = commentElement.InnerText;
 
            if(entry.OriginalName.StartsWith("CT4_"))
                ControlEntries.Add(entry);
            else
            {
                // Parse the name into a valid identifier
                entry.ValidIdentifierName = MakeIntoValidIdentifier(entry.OriginalName);
 
                AllEntries.Add(entry);
 
            }
        }
    }
    /// <Summary>
    /// MakeIntoValidIdentifier
    /// Remarks:
    /// </Summary>
    string MakeIntoValidIdentifier(string arbitraryString)
    {
        var validIdentifier = Regex.Replace(arbitraryString, @"[^A-Za-z0-9-._]", " ");
        validIdentifier = ConvertToPascalCase(validIdentifier);
        if (Regex.IsMatch(validIdentifier, @"^\d")) validIdentifier = "_" + validIdentifier;
        return validIdentifier;
    }
    /// <Summary>
    /// ConvertToPascalCase
    /// Remarks:
    /// </Summary>
    string ConvertToPascalCase(string phrase)
    {
        string[] splittedPhrase = phrase.Split(' ', '-', '.');
        var sb = new StringBuilder();
 
        sb = new StringBuilder();
 
        foreach (String s in splittedPhrase)
        {
            char[] splittedPhraseChars = s.ToCharArray();
            if (splittedPhraseChars.Length > 0)
            {
                splittedPhraseChars[0] = ((new String(splittedPhraseChars[0], 1)).ToUpper().ToCharArray())[0];
            }
            sb.Append(new String(splittedPhraseChars));
        }
        return sb.ToString();
    }
    /// <Summary>
    /// EmitNamesInnerClass
    /// Remarks:
    /// </Summary>
    void EmitNamesInnerClass(List<string> names)
    {
        if(names.Any())
        {
            WriteLine("\r\n\t\tpublic static class Names");
            WriteLine("\t\t{");
            foreach(var name in names)
                WriteLine(string.Format("\t\t\tpublic const string {0} = \"{0}\";", name));
            WriteLine("\t\t}");
        }
    }
    /// <Summary>
    /// EmitNamesInnerClass
    /// Remarks:
    /// </Summary>
    void EmitEnum(List<string> names, string pEnumName)
    {
        if(!names.Any())
            return;
 
        WriteLine("\r\n\t\tpublic enum " + pEnumName);
        WriteLine("\t\t{");
        foreach(var name in names)
            WriteLine(string.Format("\t\t\t{0},", name));
        WriteLine("\t\t}");
 
        names.Clear();       
    }
    /// <Summary>
    /// StringValueHasCompilationSymbols
    /// Remarks: Returns true if a conditional compilation symbol mark (@symbol:) is found in a string
    /// </Summary>
    bool StringValueHasCompilationSymbols(string pValue, List<string> pConditionalCompilationSymbolsInValues)
    {
        foreach(string symb in pConditionalCompilationSymbolsInValues)
        {
            if(pValue.Contains(symb))
                return true;
        }
        return false;
    }
    /// <Summary>
    /// SplitStringForConditionalCompilationSymbols
    /// Remarks: Splits a string (thas has been checked, and has conditional compilation symbols), and
    /// returns a dictionary where the keys are the conditional compilation symbols, and the values are
    /// the values of the string for that symbols.
    /// </Summary>
    Dictionary<string, string> SplitStringForConditionalCompilationSymbols(string entryValue,
                                                    List<string> pConditionalCompilationSymbolsInValues)
    {
        Dictionary<string, string> retValue= new Dictionary<string, string>();
        string[] parts = entryValue.Split(new char[1]{';'}, StringSplitOptions.RemoveEmptyEntries);
        foreach(string part in parts)
        {
            foreach(string symb in pConditionalCompilationSymbolsInValues)
            {
 
                if(part.StartsWith(symb))
                {
                    string origSymbol = symb.Remove(0, 1);
 
                    origSymbol = origSymbol.Remove(origSymbol.Length - 1 , 1);
 
 
                    string val = part.Remove(0, symb.Length);
                    retValue.Add(origSymbol, val);
                    break;
                }
            }
        }
        return retValue;
    }
    /// <Summary>
    /// EmitProperty
    /// Remarks: Writes down a property of the return type specified, name and value, and allowing
    /// to add a conditionalcompilationSymbol
    /// </Summary>  
    void EmitProperty(string pReturnType, string pPropertyName, string pPropertyValue,
                      string pConditionalCompilationSymbol, bool pIsFirstConditionalCompilation,
                      bool pIsLastConditionalCompilation)
    {
        bool hasCondCompilation = (pConditionalCompilationSymbol != null && pConditionalCompilationSymbol != "");
 
        // Write opening conditional compilation
        if(hasCondCompilation)
        {
            if(pIsFirstConditionalCompilation)
                WriteLine(string.Format("\t\t#if({0})", pConditionalCompilationSymbol));
            else WriteLine(string.Format("\t\t#elif({0})", pConditionalCompilationSymbol));
        }
 
        // Write property
        switch(pReturnType)
        {
            case "string":
                WriteLine(string.Format("\t\tpublic static {0} {1} {{ get {{ return \"{2}\"; }} }}",
                                        pReturnType, pPropertyName, pPropertyValue));
                break;
            default:
                WriteLine(string.Format("\t\tpublic static {0} {1} {{ get {{ return {2}; }} }}",
                                        pReturnType, pPropertyName, pPropertyValue));
                break;
        }
 
        // Close cond compilation
        if(hasCondCompilation && pIsLastConditionalCompilation)
            WriteLine("\t\t#endif");
    }
    /// <Summary>
    /// EmitEntryComment
    /// Remarks: Writes down an entry comment as a properly formatted XML documentation comment
    /// </Summary>
    void EmitEntryComment(ResourceEntry entry, bool thisIsFirstEntryInClass)
    {
            // Insert the entry comment (if any) in a proper XML documentation format
            if(entry.Comment != null)
            {
                if(!thisIsFirstEntryInClass)
                    WriteLine("");                 
                WriteLine(string.Format("\r\n\t\t///<summary>\r\n\t\t///{0}\r\n\t\t///</summary>",
                                         entry.Comment.Replace("\r\n", "\r\n\t\t///")));
            }
            else WriteLine("");
    }
    /// <Summary>
    /// GetProjectContainingT4File
    /// Remarks:
    /// </Summary>
    Project GetProjectContainingT4File(DTE dte)
    {
 
        // Find the .tt file's ProjectItem
        ProjectItem projectItem = dte.Solution.FindProjectItem(Host.TemplateFile);
 
        // If the .tt file is not opened, open it
        if (projectItem.Document == null)
            projectItem.Open(Constants.vsViewKindCode);
 
        if (AlwaysKeepTemplateDirty) {
            // Mark the .tt file as unsaved. This way it will be saved and update itself next time the
            // project is built. Basically, it keeps marking itself as unsaved to make the next build work.
            // Note: this is certainly hacky, but is the best I could come up with so far.
            projectItem.Document.Saved = false;
        }
 
        return projectItem.ContainingProject;
    }
    /// <Summary>
    /// Struct: ResourceEntry
    /// Remarks: Stores information about an entry in a resource file
    /// </Summary>
    struct ResourceEntry
    {       
        public string Path { get; set; }
        public string File { get; set; }
        public string Type { get; set; }
        public string OriginalName { get; set; }
        public string ValidIdentifierName { get; set; }
        public string Value { get; set; }
        public string Comment { get; set; }
    }  
#>

Et voilà ! An input and output example

The above template, applied to the following input:
image
Produces the following output class:
// ------------------------------------------------------------------------------------------------------
// <auto-generated>
//     This code was generated by a tool.
//
//     Changes to this file may cause incorrect behavior and will be lost if
//     the code is regenerated.
// </auto-generated>
// ------------------------------------------------------------------------------------------------------
using System.Threading;
 
 
namespace GDNA.PencilBurst
{
public class Textures
{
        private static global::System.Resources.ResourceManager resourceMan;
 
        /// <summary>
        ///   Returns the cached ResourceManager instance used by this class.
        /// </summary>
        [global::System.ComponentModel.EditorBrowsableAttribute
                              (global::System.ComponentModel.EditorBrowsableState.Advanced)]
        private static global::System.Resources.ResourceManager ResourceManager
        {
            get
            {
                if (object.ReferenceEquals(resourceMan, null))
                {
                    global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager
                                      ("GDNA.PencilBurst..Textures", typeof(Textures).Assembly);
                    resourceMan = temp;
                }
                return resourceMan;
            }
        }
 
        /// <summary>
        ///   Returns the formatted resource string.
        /// </summary>
        [global::System.ComponentModel.EditorBrowsableAttribute
                                           (global::System.ComponentModel.EditorBrowsableState.Advanced)]
        private static string GetResourceString(string key, params string[] tokens)
        {
            var culture = Thread.CurrentThread.CurrentCulture;
            var str = ResourceManager.GetString(key, culture);
 
            for(int i = 0; i < tokens.Length; i += 2)
                str = str.Replace(tokens[i], tokens[i+1]);
 
            return str;
        }
 
        /// <summary>
        /// Returns the formatted resource string, passing the enum value as parameter
        /// </summary>
        [global::System.ComponentModel.EditorBrowsableAttribute
                                              (global::System.ComponentModel.EditorBrowsableState.Advanced)]
        private static string GetResourceString(eTextureIDs key, params string[] tokens)
        {
            var culture = Thread.CurrentThread.CurrentCulture;
            var str = ResourceManager.GetString(key.ToString(), culture);
 
            for(int i = 0; i < tokens.Length; i += 2)
                str = str.Replace(tokens[i], tokens[i+1]);
 
            return str;
        }
 
 
        ///<summary>
        ///Button 1 image asset name or ID
        ///</summary>
        #if(WINDOWS_PHONE)
        public static string Button1 { get { return "Content\Textures\UI\button1"; } }
        #elif(ANDROID)
        public static int Button1 { get { return Resource.Drawable.app_Icon; } }
        #endif
 
        public static class Names
        {
            public const string Button1 = "Button1";
        }
 
        public enum eTextureIDs
        {
            Button1,
        }
    }
 
}
 
 
 
 

Other use cases

The possibilities are almost endless. You don´t need to stick to Resource Files (ResX) only. You can do this operations with almost anything. For example:
  • You can write a T4 Template for a “Contents” projects, that searches for Textures or Bitmaps in the project, and generates a Class that strong-types the names and/or paths of those textures. Creating your own Content Manager.
  • You can generate your own classes to give strong-type access to your Data-Sets, in a totally customized way.
  • Or you can generate a class that bases it’s strong type access in an enumeration, instead properties, something like the following:
internal class TexturesByEnum
    {
        private static global::System.Resources.ResourceManager resourceMan;
 
        /// <summary>
        ///   Returns the cached ResourceManager instance used by this class.
        /// </summary>
        [global::System.ComponentModel.EditorBrowsableAttribute
                                        (global::System.ComponentModel.EditorBrowsableState.Advanced)]
        private static global::System.Resources.ResourceManager ResourceManager
        {
            get
            {
                if (object.ReferenceEquals(resourceMan, null))
                {
                    global::System.Resources.ResourceManager temp =
                                                     new global::System.Resources.ResourceManager
                                                     ("GDNA.Render.Repository.Textures", typeof(Textures).Assembly);
                    resourceMan = temp;
                }
                return resourceMan;
            }
        }
 
        /// <summary>
        /// Returns the formatted resource string, passing the enum value as parameter
        /// </summary>
        [global::System.ComponentModel.EditorBrowsableAttribute
                                          (global::System.ComponentModel.EditorBrowsableState.Advanced)]
        private static string GetResourceString(eTextureIDs key, params string[] tokens)
        {
            var culture = Thread.CurrentThread.CurrentCulture;
            var str = ResourceManager.GetString(key.ToString(), culture);
 
            for (int i = 0; i < tokens.Length; i += 2)
                str = str.Replace(tokens[i], tokens[i + 1]);
 
            return str;
        }
 
        public enum eTextureIDs
        {
            Button1,
            Button2,
        }
 
        ///<summary>
        /// Indexed access to class
        ///</summary>
        public static string this[eTextureIDs id]
        {
            get
            {
                    return GetResourceString(id);
            }
        }
    }
 
This way, access to resources would be:
string aux = Textures[eTextureIDs.Button1];
 
instead of…
 
string aux = Textures.Button1;
As you can see, the customization possibilities are huge, and the examples countless.
So use your imagination !!
Cheers !