Mostrando entradas con la etiqueta Windows. Mostrar todas las entradas
Mostrando entradas con la etiqueta Windows. Mostrar todas las entradas

World Enduro Rally

I'm happy to share the result of the work I've been doing in the last months: yesterday, World Enduro Rally came to light.



The game is available for XBoxOne, Steam, Android and Windows. You can get all the details at its Facebook page, or download directly here:





Hope you like it!

Using RacingWheels and other kind of devices in Windows.Gaming.Input

Now that UWP (Universal Windows Platform) is out (and apparently the way to go for video games), a decent Input API was more than needed, to be able to use controllers like racing wheels. And that API is Windows.Gaming.Input.

I already talked about this topic last month, but no matter how hard I tried to make my Logitech G27 be recognized by the API, it simply didn't work. And it was hard to do something wrong in the code, as reading the controller is as simple and straightforward as checking the contents of a collection.

I simply assumed that the API, or the Logitech Driver, or Windows, had problems with certain controllers somehow. And provided that the G27 is a bit old, and that Logitech hasn't updated the drivers since last March, that sounded realistic.

But yesterday, Forza 6 Apex was updated (and taken out of BETA), and finally announced steering wheel support, so I quickly updated it, just to check (to my surprise) that my G27 worked perfectly.

And it's a UWP too, so it was clearly not due to a bug in the API or the driver, but a problem in my application. Provided that the code is so simple, it had to be some configuration issue, and yes, it was...

I checked a thousand times the Capabilities section of the AppxManifest, but I never saw anything that seemed related. But thanks to Mark Thompson (and to the XBox Developer Forums), now I found out what should be added to the AppxManifest in order to make it work.

Simply add the following to the Capabilities section:

 <DeviceCapability Name="humaninterfacedevice">  
     <Device Id="any">  
      <Function Type="usage:0001 0004"/>  
      <Function Type="usage:0001 0005"/>  
     </Device>  
  </DeviceCapability>  

Et voilá. Now the G27 is properly recognized by the app. I still don't understand why this is needed for racing wheel, but not for gamepads.

However, you can find more info here:

Microsoft guys told us that this will be added to the docs soon, but for now, it's not there.

Hope it helps!

Inyectando TypeEditors dinámicamente

Muchas aplicaciones de edición todavía utilizan Windows Forms. Y muchas de ellas (sobre todo si son prototipos rápidos o herramientas internas) utilizan el PropertyGrid como método rápido y eficiente para editar propiedades de objetos.

Una de las funcionalidades más utiles de los PropertyGrids, es la posibilidad de definir TypeEditors para las propiedades de un objeto, de forma que el sistema escogerá automáticamente un interfaz de usuario específico para editar su valor. .Net incluye por defecto algunos de ellos, como el ColorPicker o el DateTimePicker:

Cuando los TypeEditors por defecto no son suficiente, es una gran idea desarrollar tus propios editores, para hacer tus editores lo más eficientes posible. Por ejemplo, el selector de colores por defecto de .Net no permite escoger valores para el canal Alpha (transparencia). La solución es sencilla y fácil: podemos implementar nuestro propio editor que sí lo permita.

Ahora bien, ¿qué ocurre si queremos aplicar ese TypeEditor a todas las propiedades de un tipo definido en otra DLL?

Es un caso bastante frecuente. En mi entorno, por ejemplo, tengo mi propia clase para almacenar colores, llamada Color4, pero está definida en una DLL específica para operaciones matemáticas. Es una DLL muy básica que quiero mantener con el menor número de referencias posible, para evitar dependencias todo lo que pueda. Por este motivo, es imposible definir el TypeEditor en dicha DLL, ya que eso implicaría referenciar System.Windows.Forms, System.Drawing, y unas cuantas cosas más que no tienen que estar ahí. A fin de cuentas, es mi editor visual el que debe depender de Windows Forms, y no mi DLL de operaciones matemáticas. ¿La solución?

Inyección dinámica de editores de tipo

La solución es asignar el TypeEditor dinámicamente, programaticamente, o como queráis decirlo. En lugar de incluirlo en tiempo de compilación, con clásico código…

 [EditorAttribute("Color4Editor", typeof(System.Drawing.Design.UITypeEditor))]

…lo añadiremos en tiempo de ejecución, algo posible gracias a la clase TypeDescriptor y su método AddAttributes.

Basta con invocar algo como lo siguiente el el inicio del programa:

TypeDescriptor.AddAttributes(typeof(Color4), new EditorAttribute(typeof(Color4Editor), typeof(UITypeEditor)));

De esta forma, mantenemos las DLLs limpias de referencias innecesarias, y solo dependeremos de Windows Forms y similares donde realmente se necesita: en el editor.

Listo ! Smile

DirectX Control Panel and D3D Debug Output in D3D 9.x/10.x/11.x for Windows 7, 8 and 8.1

Debugging D3D applications can be a pain, but it´s completely necessary sometimes if you want to know what´s going on in your D3D application (error codes don´t give much information without the debug output).
However, things have changed quite a bit recently in the latest versions of Windows (8.1), Visual Studio (2013) and DirectX (11.2). The following video explains some of the changes related to D3D Debugging, the DirectX Control Panel, and how all the new infrastructure works:

You can also access the content in the form of slides.
Keep in mind that some of the DirectX features are no longer distributed with the DirectX SDK, but with the Windows SDK. So, we will try to cover all the possible cases you could face when trying to activate the Debug Output in D3D, no matter if you work in Windows 7 with the old version of DirectX SDK (June 2010), if you are in Windows 7 or Windows 8 and use the new Windows SDK, or if you are in the latest Windows 8.1 with its own Windows SDK.

The New DirectX Control Panel

We will need to deal with it to enable D3D debug and to manage other stuff, so first thing is to learn to differentiate between the old one (June 2010 DirectX SDK) and the new ones (Windows SDK). It´s easy: the new ones only include one tab (Direct3D 10.x/11.x):
Old Control Panel (DirectX SDK June 2010)
New DX Control Panel (Windows SDK)
image image
Location:
C:\Program Files (x86)\Microsoft DirectX SDK (June 2010)\Utilities\bin\x64 (or x86)
Location:
C:\Windows\System32

So, if you are developing for D3D 10.x or 11.x, use the new one as the old one won´t have any effect. If you are still using D3D9 and the old DX SDK 2010, grab the one on your left.
Note: See the above video to learn about new features in the panel like the “Feature level limit”.

Windows 7

D3D 9.x

If you are still developing with D3D9, honestly you should seriously consider moving forward. But if you can´t, and you need to enable debug in your app, you just need to use the OLD Control Panel described above, and navigate to the Direct3D 9 tab to make sure you select “Use Debug Version of Direct3D 9”, and turn the Debug Output Level to “More”, just like depicted in the following image:
image
That should force your DirectX applications to use the Debug version of the DirectX libraries, so you should immediately start to see debug output in Visual Studio.

Managed D3D9 applications (SlimDX, SharpDX and similar wrappers)

If you are developing in C#, keep in mind that you will also need to activate the flag “Enable native code debugging” under the Debug tab of your main project properties in Visual Studio. If not, the native debug output cannot get through to the output window.
image

D3D 10.x / 11.x

Important None: The necessary components for debugging D3D 10.x and 11.x are no longer installed with the old DirectX SDK (June 2010). In order to have them you need to install the Windows 8 SDK (even if you are in Win7). If you don´t have the necessary components, the creation of the device with the "debug" flag will fail (see below for more info). One easy way to check if you have the components is to check the existance of the NEW DX Control Panel, in C:\Windows\System32.

Activating the debug output in D3D 10.x / 11.x is a bit different, as settings are handled per application (you need to add your exe to a list in the control panel, and set an specific configuration for it in there). To do so, please follow these steps:
  1. 1.- Open the NEW DirectX Control Panel and navigate to the Direct3D 10.x / 11 tab
  2. 2.- Click on “Edit List” to add your exe to the list of applications controlled by the DX panel
  3. 3.- In the window that will pop up (below), click on the dots “…” and navigate to your exe file. Then click “Ok”.
image
  1. 4.- Back in the main tab, choose the configuration you want (probably want to set “Force On” to force debug output), and mute all the message types you don´t want to see (if any)
Once your exe is on the list of apps the Control Panel manages, next step is to make sure your D3D device connects to the Debug Layer of DirectX.
You can find more info here, but basically what you need to do is create your Device with Creation Flags including the D3D11_CREATE_DEVICE_DEBUG flag.

Managed D3D 10.x /11.x applications (SlimDX, SharpDX and similar wrappers)

Just like with D3D 9, when developing in C# you should remember to activate the flag “Enable native code debugging” under the Debug tab of your main project properties in Visual Studio. If not, the native debug output cannot get through to the output window (see above in this post for more info).

Windows 8.x + Windows SDK

This part covers the case when working in Windows 8.x with the newer versions of the Windows SDK.

D3D 9.x

Debugging D3D 9 applications in Windows 8 should work exactly the same as we did in Windows 7. Of course, the new Windows SDK doesn’t include tools to configure D3D9, so you should install the June 2010 DX SDK to get access to the OLD control panel. I couldn’t make sure this works as all my machines are updated to Windows 8.1, so any feedback here will be really welcome.
What I can tell you is that, unfortunately, D3D9 debugging seems to be disabled in Windows 8.1. If you open the OLD DX Control Panel, you will see that all the debug parts of the D3D 9 tab are grayed out. I tried by all means to bring it back with no luck, so if you manage to enable it, please let me know.

D3D 10.x / 11.x

Enabling debug output for D3D 10.x and 11.x is pretty much the same as in the case of Windows 7, unless this time you will need to use the NEW version of the DX Control Panel, located in C:\Windows\System32 instead of the usual DXSDK folders.
Also, remember to create your devices specifying the D3D11_CREATE_DEVICE_DEBUG creation flag (as described above), and in the case of developing in C#, remember to activate the “Enable native code debugging” option in your main project.

Troubleshooting

  • The application works but I get no debug output: If you are in D3D9, make sure you activated the Debug libraries in the old DX Control Panel. Also, if you work in C#, ensure to activate the “Enable native code debugging” option. If you work in D3D 10/11, make sure you created the device with the D3D11_CREATE_DEVICE_DEBUG flag, and don´t forget to add your app to the list of programs managed by the DX Control Panel. In all cases, always use the appropriate DX Control Panel (see above to learn about this).
  • In D3D 10.x / 11.x, the application fails while trying to create the device with the DEBUG creation flag: This usually happens if you don´t have the correct SDK installed. If you are in Windows 7 or in Windows 8, make sure you install the Windows 8 SDK. If you are in the latest Windows 8.1 you should install its own Windows 8.1 SDK, as it´s not compatible with the 8.0 SDK version. One easy way to check if you have the components is to check the existance of the NEW DX Control Panel, in C:\Windows\System32.

Por qué el Nokia Lumia 9XX es el mejor móvil del mercado

Soy un usuario de telefonía bastante básico, lo reconozco. A estas alturas, le pido más bien pocas cosas a un móvil… Básicamente: hacer llamadas y SMS, Whatsapp, Line, Facebook, Twitter, Skype, poder navegar con fluidez, accesibilidad básica (Wifi, compartir conexión de internet, etc), una cámara de fotos decente, un buen navegador GPS, seguridad, tener mapas y guías offline, ser resistente, una experiencia de usuario rápida y sin retardos, un interfaz de usuario fácil e intuitivo, una buena integración con mi PC y una buena duración de batería. Bueno, visto así, quizá no sean tan pocas cosas… Sonrisa

Pues resulta que hoy me he dado cuenta de que en muchos de esos apartados, los Nokia Lumia 9XX son los mejores. Con mucha diferencia.

Ya se ha hablado largo y tendido de Windows Phone 8, de su interfaz, etc, por lo que no me centraré aquí en los aspectos propios del Sistema Operativo, sino en aquellos que hacen que los Lumia 9XX destaquen, incluso por encima de otros terminales WP8.

En mi caso, tengo un Lumia 920, así que vamos allá… Aspectos realmente increíbles de este teléfono:

Cámara de Fotos / Video

La cámara del Lumia es, simplemente, alucinante. Ya se ha hablado largo y tendido de ella, pero casi siempre la gente se limita a hablar de su capacidad para hacer fotos con poca luz (que la verdad, es increíble). Pero tiene otras muchas virtudes:

Modo macro

Es simplemente alucinante que una cámara de móvil pueda hacer una foto así, y encima sin tener que pegarte con ajustes de ningún tipo: solo tienes que dejarla en modo auto y acercarla mucho a algo. Donde cualquier otro móvil sacaría un borrón, el Lumia 920 saca esto (fotos tomada esta misma mañana):

183

172

Otros ejemplos de fotos:

Todas estas fotos han sido sacadas con mi propio Lumia 920, esta semana:

186176

170137

Video:

El estabilizador de imagen del Nokia Lumia 920 es alucinante. Realmente permite hacer grabaciones increíbles, incluso cuando vas caminando. Aquí tenéis un ejemplo (no grabado por mi), aunque por mi experiencia personal debo decir que el resultado todavía es más impresionante que lo que se aprecia en el:

Mapas / Navegación

Ahora, la versión WP8 de Here Maps y Here City Lens, de Nokia, permiten “tirar” de os mapas offline descargados para el navegador. Esto es algo que siempre he echado mucho de menos en todos los móviles que he tenido, ya que… ¿Cuando utilizas con más frecuencia los mapas del movil y las herramientas de guía? Cuando viajas. Y si estás en el extranjero, y no tienes roaming, no te sirven de nada.

Pues bien, Nokia ha solucionado este problema de forma brillante, y con los 32 GB de almacenamiento del Lumia 920 tenemos espacio más que de sobra para descargar mapas.

Navegador GPS:

Es, a falta de otra palabra mejor… brillante. Solo diré que, desde que adquirí mi primer Nokia Lumia, nunca más he tocado mi navegador TomTom:

  • Las indicaciones por voz son las mejores que he visto nunca en un navegador, casi no hace falta ni mirar a la pantalla (simplemente haz lo que te va diciendo). El volumen es más que de sobra para escuchar las indicaciones, incluso cuando llevas música en el coche, o las ventanillas abiertas (de hecho, siempre llevo el volumen a menos de la mitad, ya que si no resulta excesivo).
  • Dado que utiliza el sensor GPS en combinación con la triangulación de los repetidores de telefonía para localizarte, no le cuesta ni 3 segundos encontrarte en el mapa. La precisión es siempre muy buena, y gracias a los repetidores de telefonía, no se queda completamente sin señal en los túneles. La precisión baja mucho, pero al menos tu coche sigue moviéndose en el mapa, algo importante en ciudades con mucha circulación subterránea.
  • Los mapas (disponibles para todo el mundo), son gratis… ¿Algo más que decir?
  • En cuanto a la fiabilidad, solo puedo decir que he utilizado intensamente el sistema de Nokia en España, Francia, Inglaterra y Estados Unidos, sin ningún tipo de problema.

Skype

Utilizo bastante Skype, y la implementación para WP8, en combinación con la calidad de la cámara del Lumia 920 es alucinante. Además, tengo la suerte de contar con una red 4G, así que va como un auténtico tiro. La calidad de imagen es notablemente mejor que la de la cámara webcam de mi portátil. Flipante !

Resistencia

Todos hemos visto multitud de vídeos haciendo todo tipo de perrerías con las pantallas Gorilla Glass de los Lumia. Tengo Lumias desde hace un par de años, y la verdad es que todas las pantallas están como el primer día. Ni la más mínima raya, sin haber tenido ningún tipo de cuidado con ellas, ni llevar protector. Mis móviles han convivido en mis bolsillos con las llaves de casa, prácticamente siempre, y ni una muesca. Solo hay que ver este video…:

Cosas a mejorar

Sinceramente, la duración de la batería sigue siendo muy justita cuando le das un uso intenso (al igual que en todos los móviles de alta gama). En días muy intensivos, deberás pelear para llegar al final del día sin ver el simbolito de “batería a punto de fallecer”. El día que consigan mejorar ese aspecto, realmente habrá mucha gente que no necesitará ni tablets, ni portátiles…

Otro aspecto en el que todavía está por detrás es el cliente de Whatsapp. Evidentemente, esto es culpa de la propia whatsapp, y no de Nokia ni de Microsoft, pero es una pena. El cliente sigue siendo realmente cutre. Vamos Whatsapp !!!!

Al final, quizá tenga que lanzar Microsoft su propio cliente, como ha hecho con el lector de PDF, para remediar el desastroso trabajo que hizo Adobe con su cliente PDF para Windows Phone… Era realmente lamentable, se colgaba, y no era capaz de abrir prácticamente ningún PDF de tamaño medio. El lector de PDF de Microsoft funciona francamente bien.

Resumen

Nokia Lumia 920: Su cámara es la mejor del mercado (con mucha diferencia). Para el usuario medio, permite sustituir a una cámara de fotos compacta en el 90% de los casos. Como cámara de video, es realmente brillante también (incluso mejor que muchas cámaras de video específicas, que no incluyen ni grabación 1080p ni estabilización de imagen).

Como navegador GPS, no tiene rival. Sus mapas son muy muy buenos también, su funcionalidad Skype es increíble y el resto de funcionalidades del móvil, están cuando menos a la altura de los mejores.

¿Se puede pedir algo más?

Bueno, sí… Que ciertos redactores de páginas como Gizmodo.es dejen de lado su aparente aversión a todo lo que huela a Microsoft, y valoren los productos objetivamente. Quizá algún día salga a la luz que, en realidad, están a nómina de otras empresas, porque si no no se entiende…

Saludos !!!

New XNA 4 book by Kurt Jaegers [Packt Publishing]

Kurt Jaegers has a new book on XNA 4 Game Development. I´ll review it in a few days, by now, I paste here some word from the author itself:

“This book follows the same style as my previous books on 2D game development with XNA, bringing three different 3D games to life. I cover items such as:
- The basic concepts behind 3D graphics and game design
- Generating geometry with triangles
- Converting height map images into terrain
- An introduction to HLSL, including writing shaders that handle lighting and multi-texturing
- Building a 2D button-based interface to overlay on your 3D action
- Implementing skyboxes for full 3D backgrounds”

More info here and here.

Los límites de la memoria

Este artículo trata de servir como introducción a la gestión de memoria en .Net, los límites que el Runtime y la plataforma establecen para cada proceso, así como algunos Tips para lidiar con los problemas a los que nos enfrentamos al acercarnos a esos límites.

Memoria disponible por proceso

Como muchos de vosotros sabéis, por mucha memoria RAM que tenga instalada un ordenador, existen varias barreras impuestas a la cantidad de memoria usable en nuestras aplicaciones.

Por ejemplo, en un sistema de 32 bits no se pueden instalar más de 4GB de memoria física, evidentemente, porque 2^32 (dos elevado a 32) nos proporciona un espacio de direcciones con 4.294.967.296 entradas distintas (4GB). Pero incluso cuando el sistema cuente con 4GB de memoria física, nuestras aplicaciones se encontrarán con una barrera de 2GB impuesta por el sistema.

En estos entornos de 32 bits, cada proceso puede acceder a un espacio de direcciones de 2GB como máximo, porque el sistema se reserva los otros 2 para las aplicaciones que corren en modo Kernel (aplicaciones del sistema). Este comportamiento por defecto puede cambiarse mediante el uso del flag “/3gb” en el boot.ini del sistema, haciendo que Windows reserve 3GB para las aplicaciones que corren en Modo Usuario y 1GB de memoria para el Kernel.

Aún así, el límite por proceso permanecerá en 2GB, a no ser que explícitamente activemos un flag determinado (IMAGE_FILE_LARGE_ADDRESS_AWARE) en la cabecera de la aplicación. A esta combinación de flags en sistemas x86 se le denomina comúnmente: 4GT (4 GigaByte Tuning).

En sistemas de 64 bits sucede algo parecido. Aunque no tienen la misma limitación en cuanto a memoria física disponible, ni la impuesta por la reserva de direcciones para el kernel (y por lo tanto el flag /3gb no aplica en estos casos), el sistema también establece un límite por defecto de 2 GB para cada proceso, a no ser que se active el mismo flag en la cabecera de la aplicación (IMAGE_FILE_LARGE_ADDRESS_AWARE).

Activando el flag: IMAGE_FILE_LARGE_ADDRESS_AWARE
  • En el caso de aplicaciones nativas (C++), establecer dicho flag es fácil, ya que basta con añadir el parámetro /LARGEADDRESSAWARE a los parámetros del Linker dentro de Visual Studio.
  • En el caso de aplicaciones .Net:
    1. Si están compiladas para 64bits, este flag estará activado por defecto, por lo que podrán acceder a un espacio de direcciones de 8 TB (dependiendo del S.O.)
    2. Si están compiladas para 32bits, el entorno de Visual Studio no nos ofrece ninguna opción para activar dicho flag, por lo que tendremos que hacerlo con la utilidad EditBin.exe, distribuida con Visual Studio, la cual modificará el ejecutable de nuestra aplicación (activándole dicho flag).

La siguiente tabla, obtenida de esta página, muestra de forma resumida los límites en el espacio de direcciones de la memoria virtual, en función de la plataforma y del tipo de aplicación que estemos desarrollando:

image

Esta página tiene mucha más información sobre los límites de memoria según las versiones del S.O.

Los límites del sistema, más cerca de lo que crees

Hoy día, la memoria es barata, pero como ya se ha explicado en el apartado anterior, hay un buen número de casos en los que, por mucha memoria que instalemos en el PC, nuestro proceso solo podrá acceder a 2GB de la misma.

Además de esto, si vuestra aplicación está desarrollada en .Net, os encontraréis con que el propio Runtime introduce un overhead importante en cuestiones de memoria (suele decirse que está en torno a los 600-800 MB), por lo que en una aplicación corriente, es usual empezar a encontrar OutOfMemoryExceptions alrededor de los 1.3 GB de memoria usados. En este blog se discute el tema.

Por lo tanto, si no estamos en uno de esos casos en los que podemos direccionar más de 2GB, y además desarrollamos en .Net, independientemente de la memoria física instalada en el sistema nuestro límite real estará en torno a 1.3 GB de memoria RAM.

Para el 99% de las aplicaciones diarias, es más que suficiente, pero otras que requieren cálculos masivos, o que se relacionan con bases de datos, muy frecuentemente superarán ese límite.

Y lo que es peor…

Para complicar todavía más el asunto, una cosa es tener memoria disponible, y otra muy distinta es tener bloques de memoria contiguos disponibles.

Como todos sabéis, fruto de la gestión que el Sistema Operativo hace de la memoria, de técnicas como la Paginación, y de la creación y destrucción de objetos, la memoria poco a poco va quedando fragmentada. Esto quiere decir que, aunque tengamos suficiente memoria disponible, esta puede estar dividida en muchos bloques pequeños, en lugar de un único hueco con todo el tamaño disponible.

Los Sistemas Operativos modernos, y la propia plataforma .Net, tratan de evitar esto con técnicas de Compactación, y aunque reducen notablemente el problema, no lo eliminan por completo. Este completo artículo describe en detalle la gestión de memoria del Garbage Collector de .Net, y la labor de compactación que realiza.

¿En qué afecta la fragmentación? En mucho, ya que si vuestra aplicación necesita reservar un Array contiguo de 10 MB, y aunque todavía haya 1GB de memoria disponible, si la memoria está muy fragmentada y el sistema no es capaz de encontrar un bloque contiguo de ese tamaño, obtendremos un OutOfMemoryException.

En .Net, la fragmentación y compactación de objetos en memoria guarda una estrecha relación con el tamaño de éstos. Por eso, el siguiente apartado hablará un poco sobre este tema.

Grandes objetos en memoria

A la hora de reservar memoria para un único objeto, la plataforma .Net establece ciertos límites. Por ejemplo, en las versiones de .Net 1.0, 2.0, 3.0, 3.5 y 4.0, ese límite es de 2GB. Tanto para plataformas x86 como x64, ningún objeto único puede ser mayor de ese tamaño. Es así de simple. Únicamente a partir de .Net 4.5 este límite puede ser excedido (en procesos x64 exclusivamente). Aunque sinceramente, salvo rarísimas excepciones, si necesitas reservar más de 2GB de memoria para un único objeto, quizá deberías replantearte el diseño de tu aplicación.

En el mundo .Net, el Garbage Collector clasifica a los objetos en dos tipos: objetos grandes y objetos pequeños. Es una división bastante gruesa, la verdad, pero es así. ¿Qué considera .Net como un objeto pequeño? Todo aquel que ocupe menos de 85000 bytes.

Cuando el CLR de .Net es cargado, se reservan dos porciones de memoria diferentes: un Heap para los objetos pequeños (también llamado SOH, o Small Objects Heap), y otra para los objetos grandes (también llamado LOH, o Large Object Heap), y cada tipo de objeto se almacena en su Heap correspondiente.

¿En qué afecta todo esto al tema que estamos tratando? Sencillo, compactar objetos grandes es costoso, y a día de hoy, simplemente no se hace. Los objetos considerados “Grandes”, y que se introducen en el LOH, no se compactan (aunque el equipo de desarrollo advierte que pueden hacerlo algún día). Como mucho, cuando dos objetos grandes adyacentes son liberados, se fusionan en un único espacio de memoria disponible, pero ningún objeto es “movido” para realizar tareas de compactación.

Este fantástico artículo contiene muchísima más información acerca del LOH y su funcionamiento.

Arrays C# en los límites de la memoria

En C#, los Arrays Simples (de una dimensión) son una de las formas más comunes de consumir memoria, y debes saber que el CLR los reserva siempre como bloques continuos de memoria. Es decir, cuando instanciamos un objeto de tipo byte[1024], estamos solicitando al sistema un único bloque continuo de 1KB, y se generará un OutOfMemoryException si no encuentra ningún hueco contiguo de ese tamaño.

Cuando es necesario utilizar un Array de más de una dimensión, C# nos ofrece distintas opciones:

Arrays anidados, o arrays de arrays

Declarados como byte[][], suponen el método clásico de implementar arrays multi-dimensionales. De hecho, en lenguages como C++, es el único tipo de array multi-dimensional soportado de forma nativa.

En lo relativo a memoria, se comportan como un array simple (un único bloque de memoria), en el que cada elemento es otro array simple (esta vez del tipo declarado, y que también es un bloque único en memoria, pero distinto a los demás). Por lo tanto, en lo que a bloques de memoria se refiere, un array de tipo byte[1024][1024], utilizará 1024 bloques de memoria distintos (cada uno de 1024 bytes).

Arrays Multi-Dimensionales

C# introduce un nuevo tipo de Arrays, soportado de forma nativa: los arrays multi-dimensionales. En el caso de 2 dimensiones, se declaran como byte[,].

Aunque son muy cómodos de utilizar (disponen entre otras cosas de métodos como GetLength, para saber el tamaño de una dimensión), y su instanciación es más sencilla, su representación en memoria es diferente a la de los arrays anidados. Éstos se almacenan como un único bloque de memoria, del tamaño total del array.

En el siguiente apartado estableceremos una comparativa entre ambos tipos:

Comparativa: [,] vs [][]

El array 2D [,] (se almacena en un solo bloque):

Ventajas:

  • Utiliza menos memoria total (no tiene que almacenar las referencias a los n arrays simples)
  • Su creación es más rápida: reservar un bloque grande de memoria para para un solo objeto es más rápido que reservar bloques más pequeños para muchos objetos.
  • Su instanciación es más sencilla: una sola línea basta (new byte[128,128]).
  • Proporciona métodos útiles, como GetLength, y su uso es más claro y limpio.

Inconvenientes:

  • Encontrar un solo bloque de memoria continuo para el array puede ser un problema, si éste es muy grande o nos encontramos cerca del limite de RAM.
  • El acceso a los elementos del array es más lento que en arrays anidados (ver abajo)

El array anidado [][] (que se almacena en N bloques):

Ventajas:

  • Es más fácil encontrar memoria disponible para el array, ya que requiere de n bloques de tamaño más pequeño, lo cual debido a la fragmentación, suele ser más probable que encontrar un único bloque más grande.
  • El acceso a los elementos del array es más rápido que en los arrays 2D, gracias a las optimizaciones del compilador para manejar arrays simples (en definitiva, un array de arrays se compone de muchos arrays 1D).

Inconvenientes:

  • Utiliza más memoria total (tiene que almacenar las referencias a los n arrays simples)
  • Su creación es más lenta, ya que hay que reservar N bloques de memoria, en lugar de uno solo.
  • Su instanciación es un poco más molesta, ya que hay que recorrer el array instanciando cada uno de sus elementos (ver Tip más abajo).
  • No proporciona los métodos disponibles en los arrays 2D, y su uso puede ser un poco más confuso.

Este blog explica muy bien esta comparativa.

Conclusión

Cada usuario debe escoger el tipo de array que más le convenga en función de su experiencia y el contexto concreto en el que esté. No obstante, un desarrollador que habitualmente utilice gran cantidad de memoria, y preocupado por el rendimiento, tenderá a escoger siempre arrays anidados (o arrays de arrays [][]).

Tip: código generico para instanciar arrays anidados

Dado que instanciar un array de arrays es un poco molesto y repetitivo (y ya dijimos aqui que no conviene duplicar código), el siguiente método genérico se encargará de esa tarea por vosotros:

        public static T[][] Allocate2DArray<T>(int pWidth, int pHeight)            
        {
            T[][] ret = new T[pWidth][];
            for (int i = 0; i < pHeight; i++)
                ret[i] = new T[pHeight];

            return ret;
        }

Espero que os Sirva !!!

Properly calculating the diffuse contribution of lights in HLSL Shaders

It’s been many years since Vertex and Pixel Shaders came out, and several years too since the Fixed Pipeline is deprecated, but there are still many questions in the forums out there asking about how to properly calculate the diffuse contribution of Lights. This paper has a great tutorial about the issue, and includes a whole Shader that mimics the Fixed Pipeline behavior. However, we will see here how to perform just the basic calculations, just in case you don’t need to emulate the full pipeline.
First thing is to write some D3D9 code that allows you to switch from the old Fixed Pipeline and your own Shaders, using the same parameters. Doing so, you will easily find any behavior differences in light calculations. You can read more about how D3D9 Fixed Pipeline calculates lighting in this page.
When writing shaders, people tend to calculate the diffuse contribution like:
Out.Color = (materialAmbient * lightAmbient) + (materialDiffuse * lightDiffuse * dot(Normal, L));
Where L is the vector from the vertex position (in world coordinates) to the light.
Apart from not doing any specular or emissive calculations (which could not be necessary in many cases, depending on your scenario), there are several mistakes in that approach:
1.- You don’t want the dot to return negative values, because it will black out colors wrongly. So, you need to clamp it to the 0..1 range, using the saturate operator: saturate(dot(Normal, L))
2.- In order to get the same results as the Fixed Pipeline, you should include Attenuation calculations, because they modify the intensity of light with the distance between the point being lit and the light source. Attenuation (as opposed to what its name suggests), not only attenuates light, but also can increase intensity in some circumstances. (See below how to properly calculate attenuation factors)
3.- Once you are calculating attenuation, you should remove the materialDiffuse factor from the previous equation, as you don’t want it to be attenuated too. You will apply it later, when the entire lighting contribution is properly calculated and attenuated.
Keeping those 3 things in mind, the final calculation in a vertex shader would be:
    float4 LightContrib = (0.f, 0.f, 0.f, 0.f);
    float fAtten = 1.f;

    // 1.- First, we store the total ambient light in the scene (multiplication of material_ambient, light_ambient, and any other global ambient component)
    Out.Color = mMaterialAmbient * mLightAmbient;

    // 2.- Calculate vector from point to Light (both normalized and not-normalized versions, as we might need to calculate its length later)
    float pointToLightDif = mLightPos - P;
    float3 pointToLightNormalized = normalize(pointToLightDif);
    
    // 3.- Calculate dot product between world_normal and pointToLightNormalized
    float NDotL = dot(Nw, pointToLightNormalized);        
    if(NDotL > 0)
    {
        LightContrib = mLightDiffuse * NDotL * mLightDivider;     
            
        float LD = length(pointToLightDif);        
        if(LD > mLightRange)
            fAtten = 0.f;
        else
            fAtten = 1.f/(mLightAtt0 + mLightAtt1*LD + mLightAtt2*LD*LD);
        
        LightContrib *= fAtten;
    }
    Out.Color += LightContrib * mMaterialColor;
    Out.Color = saturate(Out.Color);

 

Comparison

First image is the Programmable version. You can slightly tell it by the reflections on the windows.
image
Second image is the Fixed Pipeline version (no real time reflections on windows):
image

Cómo controlar el orden de propiedades o categorías en un PropertyGrid

El control PropertyGrid es fantástico para crear herramientas de prototipado rápido, donde podamos cambiar propiedades de objetos de forma rápida y visual. Como ya sabrás, el espacio de nombres System.ComponentModel contiene multitud de atributos y herramientas para personalizar el modo en que las propiedades se agrupan y configuran dentro de un PropertyGrid.

De forma automática, las propiedades se ordenan alfabéticamente según su DisplayName, o se agrupan por categorías (y se aplica el mismo criterio alfabético dentro de éstas) si así lo selecciona el usuario. Lamentablemente, no existe una forma sencilla de poder controlar manualmente el orden de las propiedades o de las categorías.

Existen muchas formas distintas de lograrlo, pero casi todas implican escribir código. Un workaround sencillo, efectivo, y que no implica utilizar código adicional es el siguiente:

1.- Dentro del atributo DisplayName de cada propiedad, o dentro del nombre de cada categoría (atributo Category),  añadiremos por delante tantos caracteres especiales de tipo \u200B como posiciones queramos “subir” dicha propiedad o categoría hacia arriba. Dicho carácter identifica un espacio vacío de longitud 0, por lo que en la práctica no modificará el texto que se muestra en la propiedad, pero sí afectará al algoritmo de ordenación.

En el siguiente ejemplo, se muestra un objeto con dos propiedades Width y Height. De forma natural (por orden alfabético), Height aparecería antes que Width. Para modificar ese comportamiento y lograr el orden inverso, mucho más natural, solo tendremos que modificar los atributos como sigue:

        [Category("Layout")]
        [DisplayName("\u200B\u200BWidth")]
        public float Width
        {
            get { return mWidth; }
            set { mWidth = value; }
        }
        [Category("Layout")]
        [DisplayName("\u200BHeight")]
        public float Height
        {
            get { return mHeight; }
            set { mHeight = value; }
        }

Así, logramos un PropertyGrid correctamente ordenado, como el de la siguiente ilustración:

image

2.- Debemos asegurarnos de que el PropertyGrid utiliza una fuente que soporte dicho carácter, ya que no todas lo hacen. Por ejemplo, la fuente por defecto Microsoft Sans Serif 8.25 lo soporta perfectamente. No obstante, si queréis aseguraros de forma programática de que la fuente es correcta, podéis utilizar este código:

        public UIEditor()
        {
            InitializeComponent();

            this.propertyGrid1.Font = new Font("Microsoft Sans Serif", 8.25f, FontStyle.Regular);
        }

New XNA 4.0 book by Packt Publishing

Packt has released a new book on XNA 4.0 development: XNA 4.0 Game Development by Example: Beginner's Guide – Visual Basic Edition.

2403EXP_XNA%204_0%20Game%20Developement%20by%20Example

I think I will have the chance to review the book, so I’ll tell you more when I’ve read it, but it looks promising. Seems to be a must for anyone that is facing XNA 4.0 development in Visual Basic.

Cheers !

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 !