Pluggable Styles and Resources in WPF with Language Converter Tool

Abhi2434
Posted by in WPF category on for Beginner level | Views : 15563 red flag
Rating: 4.33 out of 5  
 3 vote(s)

In this article I have showed how you can build pluggable Resources for styles, Languages or any static objects etc. Therefore building a new style doesn't hampers your code and you can easily plugin any new style to the application inspite it is already in production environment.

I have added a language converter tool, which will generate multi lingual resources for you.


 Download source code for Pluggable Styles and Resources in WPF with Language Converter Tool

Introduction


As we go ahead with WPF, there are lots of problems we face which are such basic but with lots of importance. In my last application with WPF, I found that it is very essential to build a solid foundation to Styles and Themes which we can use in our application. While we build our application, we create resources. Some of them we place in resource dictionary while others in the window itself. Thus when we finally release our code, we find that changing the theme is to be a huge task altogether. In this article I will discuss, the steps how you can easily manipulate styles by placing the ResourceDictionary objects into another library and use .NET Reflection to plugin the Resource directly into your application.


A Note on Reflection


Reflection is a very important part of any windows application. We need reflection to call objects which are not directly referenced with the project. Keeping a strong reference between two assemblies often makes it tightly coupled with one another. That means the two component are totally dependent between one another and individual element cannot exists without the other one.

Reflection allows you to read a dll from any location by specifying the UNC path and allows you to create objects and call methods. System.Reflection comes with Classes like Assembly, Type, Module, MemberInfo, PropertyInfo, ConstructorInfo, FieldInfo, EventInfo, ParameterInfo, Enum etc to invoke various functionality of any .Net objects. For instance :

Assembly thisAssembly = Assembly.LoadFile(fi.FullName);
var object = thisAssembly.CreateInstance("MyType");

Thus the object will hold the instance of MyType.  Similar to that, each Type has methods which gets you all MethodInfo, FieldInfo, PropertyInfo etc which you can invoke through the object created as above and do your work.

In this article we will add few lines from Reflection to plugin styles and languages from a specific folder.
You can read more about Reflection from:
http://msdn.microsoft.com/en-us/library/system.reflection.aspx

Implementation of Attributes


As we are going to reference external dlls from our application, it is very essential to define the entry point for each external entity. To Define the external entity, I create a class library which mediates between the MainApplication and the Resources. The ResourceBase library will define few Attributes which will later be used to invoke members from the dlls.

It is to be noted that we will create resources as separate dlls. These attributes will allow us to get meta data of the Dll itself.

To make each of the Resource Compatible, I have created an Interface :
public interface IResourceAttribute
{
string Name { get; set; }
string Description { get; set; }
string ResourceClassPath { get; set; }
}

IResourceAttribute defines three properties. Name which we will use to call the Resource, the Description of the Resource and ResourceClassPath, which is very important to indentify the path of the class which makes the appropriate resource within the Assembly.


Now let us create Concrete Attribute that let us input the metadata specific to each types.
 [AttributeUsage(AttributeTargets.Assembly, AllowMultiple=true, Inherited=true)]
public class LocalizationResourceAttribute : Attribute, IResourceAttribute
{
public CultureInfo Culture { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public string ResourceClassPath { get; set; }

public LocalizationResourceAttribute(string culture)
{
this.Culture = new CultureInfo(culture);
}
}

Here you can see the LocalizationResourceAttribute introduces the CultureInfo object. This will let you define culture specific to the current culture of the application.

Similar to this,

 [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true, Inherited = true)]
public class StyleResourceAttribute : Attribute, IResourceAttribute
{
public string Name { get; set; }
public string Description { get; set; }
public string ResourceClassPath { get; set; }

/// <summary>
/// Defines the Color Base to be used
/// </summary>
public Color BaseColor { get; set; }
public StyleResourceAttribute(string name)
{
this.Name = name;
}
}

Here the BaseColor will allow your dll to expose the color base for default application.

Note that, we use AttributeTargets.Assembly, as we need the attribute to be present in the Assembly level. AllowMultiple = true allows you to create more than one Resource in the same assembly. Such as, we can have more than 1 style in the same dll. 

Implementation of Styles


Now as we are ready to go, let us try create a few styles and see how it looks on the application. To start, lets create a new Class library and take reference to PresentationCore.dll, PresentationFramework.dll and WindowsBase.dll.. as it is required explicitly for any application.

Note: You also need to add the dlls which you want to reference from your styles. Like if you need WPFToolKit, you can go ahead here to do that.



Next, you need to add up the Custom Dll that we have just produced. You can see in the image above, I have added my own custom ResourceBase.dll which we will use to mark the assembly with special attributes.

Now its time to implement a style for your application.

<ResourceDictionary x:Class="GoldenBlack.GoldResource"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:Custom="http://schemas.microsoft.com/wpf/2008/toolkit">

<Color x:Key="ShadeHover">#F9E4B7</Color>
<Color x:Key="ShadeBack">#F48519</Color>
<Color x:Key="ShadeHighlight">#F9BE58</Color>


<!-- Your code goes here -->
<Style TargetType="{x:Type TextBlock}"
x:Key="tbBasic">
<Setter Property="FontFamily"
Value="Calibri" />
<Setter Property="FontSize"
Value="18"></Setter>
<Setter Property="ScrollViewer.CanContentScroll"
Value="true" />
<Setter Property="ScrollViewer.VerticalScrollBarVisibility"
Value="Auto" />
<Setter Property="ScrollViewer.HorizontalScrollBarVisibility"
Value="Auto" />
<Setter Property="Foreground"
Value="{StaticResource ShadeHighlightBrush}"></Setter>
</Style>

</ResourceDictionary>


After you are done with creating your custom Resource, you need to create a class. Just from the solution explorer add a new Class file and make it public. In your Resource file, you need to add x:Class="YourNamespace.Yourclass". Thus you need to add the x:Class as the exact logical path of the class. In my case it is x:Class="GoldenBlack.GoldResource".  So the class will look like :

namespace GoldenBlack
{
public partial class GoldResource : ResourceDictionary
{
public GoldResource()
{
InitializeComponent();
}
}
}

Actually while adding up any resource, .NET implicitly creates a class for it and then adds it. As we need to do it manually using reflection, you need to add the custom class and add InitializeComponent in its Constructor. So  in other words you need to create a custom class inherited from ResourceDicrtionary and use InitializeComponent in its default constructor.

So finally, its time to compile and produce an assembly which you could use for your main application.

Before you do, you need to add a few lines in AssemblyInfo file that you can find inside Properties folder.
[assembly: StyleResourceAttribute("GoldenBlack", Description = "Theme with Black and Gold", ResourceClassPath = "GoldenBlack.GoldResource")]
This will add a special attribute as meta data of the Assembly to ensure that the assembly is actually a Style. We will parse this attribute later on from our application and produce our actual assembly.



ResourceClassPath plays a vital role. It made us understand where the actual Resource exists. So it is very important to specify the exact classPath for the Resource in the library.

Note : I have use AllowMultiple=true, which will enable you to add more than one Style into the same assembly.

Creating the Main Application


Now its time to go to the main application and see how to apply styles dynamically. For simplicity, I have added a new class called ResourceUtil and used app.config to load the Style dynamically when the program loads.

 public static class ResourceUtil
{
public static Dictionary<IResourceAttribute, Assembly> AvailableStyles = new Dictionary<IResourceAttribute, Assembly>();


public static Color BaseColor { get; set; }
public static ResourceDictionary GetAppropriateDictionary()
{
//Get Styles Folder path
string path = ConfigurationSettings.AppSettings["stylefolderpath"];
string currentTheme = ConfigurationSettings.AppSettings["CurrentTheme"];
ResourceUtil.LoadAssemblies(AvailableStyles, path);
IResourceAttribute currentResource = AvailableStyles.Keys.FirstOrDefault<IResourceAttribute>(item => item.Name.Equals(currentTheme));



StyleResourrceAttribute sra= currentResource as StyleResourceAttribute;
if(sra != null)


BaseColor = sra.BaseColor;
// We can do this as we are fetching from AvailableStyles.
if (currentResource != null)
{
Assembly currentAssembly = AvailableStyles[currentResource];
Type resourceType = currentAssembly.GetType(currentResource.ResourceClassPath);
ConstructorInfo cinfo = resourceType.GetConstructor(Type.EmptyTypes);
ResourceDictionary dictionary = cinfo.Invoke(new object[] { }) as ResourceDictionary;
return dictionary;
}
return null;
}
private static void LoadAssemblies(Dictionary<IResourceAttribute, Assembly> resource, string path)
{
DirectoryInfo di = new DirectoryInfo(Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), path));
foreach (FileInfo fi in di.GetFiles())
{
try
{
Assembly thisAssembly = Assembly.LoadFile(fi.FullName);
var attributes = thisAssembly.GetCustomAttributes(true);
IEnumerable<object> resourceAttributes = attributes.Where(item => item is IResourceAttribute);
foreach (IResourceAttribute raatr in resourceAttributes)
AvailableStyles.Add(raatr, thisAssembly);
}
catch { }
}
}
}

Here in the code above you can see, the Application LoadAssemblies actually loads an assembly from the provided folder path. Thus in our case we load all the assemblies from the folder specified explicitely for Styles. 
So ResourceUtil.LoadAssemblies will load all the assemblies within the folder specified as path to AvailableStyles.

Now its time to invoke the Resource and get an object of ResourceDictionary. As the actual Dictionary object is not present with us now as we didnt have strong reference to the loaded assembly, we use Reflection for this purpose.

IResourceAttribute currentResource = AvailableStyles.Keys.FirstOrDefault<IResourceAttribute>(item => item.Name.Equals(currentTheme));

The above line filters out all the assemblies loaded in AvailableStyles and gives only the Resource object for which the currentTheme specified within the app.config matches.

As the attribute also has a BaseColor, we need to add that functionality too. So we place the color to the BaseColor object.

So finally, lets create a handler for Application.Starup and place few lines to load the Dictionary.
 public partial class App : Application
{
private void Application_Startup(object sender, StartupEventArgs e)
{
ResourceDictionary dictionary = ResourceUtil.GetAppropriateDictionary();
if (dictionary != null)
this.Resources.MergedDictionaries.Add(dictionary);
}
}

So this would add the new ResourceDictionary to the Application. Hence the styles are applied.

Wait wait, this is not the end. You also need to make a few adjustments to your application. Means you can reference the styles only by using DynamicResource rather than StaticResource. StaticResource tries to find the resources during compile time, and thus in our case it will not find it there. So our sample code will look like :


<Window x:Class="MainWpfApplication.MyMainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MyMainWindow"
Background="{DynamicResource ShadeFadedBackgroundBrush}">
<Grid>
<DockPanel HorizontalAlignment="Center" VerticalAlignment="Center">
<TextBlock DockPanel.Dock="Top" MinWidth="400" Style="{DynamicResource tbBasic}" Text="This is my custom Text"/>
<Button Content="Accept" Click="Button_Click" DockPanel.Dock="Top" Style="{DynamicResource btnBasic}"/>
</DockPanel>
</Grid>
</Window>


You can see, I have replaced all the StaticResource elements to DynamicResource and hence we open the ability to change the styles at runtime.

Now, place the dlls to the application directory as specified in app.config and run the application.



Hence you can see the Style is changed when you change the CurrentTheme key of your app.config to SilverRed to GoldenBlack. Voila, we are done with it.

You can load the Resources dynamically if you want. To do so you need to hold the current Resource which is added to the application, and then remove the current theme and add the ResourceDictionary using the following code :
((App)Application.Current).Resources.MergedDictionaries.Add(dictionary);

Thus you can easily make the application to dynamically load the resources based on users interaction.

Working with Other Resources


This is not the end of this. Few days before I have introduced a way to take resources as a technique of multilingual application. If you dont remember, you can try it from here :
Working with Multilingual WPF Application

So lets extend this with plugin based language application.

Working almost the similar way we add a new method to ResourceUtil to return appropriate ResourceDictionary for Current Language settings from the user.

So that ResourceDictionary will look like :
<ResourceDictionary x:Class="LanguageResource.EnglishResource"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:system="clr-namespace:System;assembly=mscorlib">
<system:String x:Key="rKeychangetheme" >Change Theme</system:String>
<system:String x:Key="rKeycustomtext" >This is my custom Text</system:String>
<system:String x:Key="rKeyaccept" >Accept</system:String>
</ResourceDictionary>
Similar to that, we add a French Dictionary. To show you how you can use multiple resources in the same library, I have added the FrenchResourceDictionary in the same folder :

<ResourceDictionary x:Class="LanguageResource.FrenchResource"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:system="clr-namespace:System;assembly=mscorlib">
<system:String x:Key="rKeychangetheme">Changer de thème</system:String>
<system:String x:Key="rKeycustomtext">C'est mon texte personnalisé</system:String>
<system:String x:Key="rKeyaccept">Accepter</system:String>
</ResourceDictionary>

You can notice that the keys are all maintained the same way, while the Values are modified. Now its time to compile the assembly. Before doing that lets add the custom attributes to AssemblyInfo.cs file of the project.

[assembly: LocalizationResource("en-US", Name = "English dictionary", Description = "For English Dictionary", ResourceClassPath = "LanguageResource.EnglishResource")]
[assembly: LocalizationResource("fr-Fr", Name = "French dictionary", Description = "For French Dictionary", ResourceClassPath = "LanguageResource.FrenchResource")]

I have added both the resources to the same dll, so you have to add both of them to the AssemblyInfo. We will load each of them to the main application later.

Now similar to this, lets modify the main XAML code with DynamicResources for the strings :

<Grid>
<DockPanel HorizontalAlignment="Center" VerticalAlignment="Center">
<TextBlock DockPanel.Dock="Top" FontSize="20" Style="{DynamicResource tbBasic}" x:Name="tbCurrentTheme" />
<TextBlock DockPanel.Dock="Top" MinWidth="400" Style="{DynamicResource tbBasic}" Text="{DynamicResource rKeycustomtext}"/>
<Button Content="{DynamicResource rKeyaccept}" Click="Button_Click" DockPanel.Dock="Top" Style="{DynamicResource btnBasic}"/>
</DockPanel>
</Grid>


So the slight alteration to ResourceUtil language method looks like :

 public static ResourceDictionary GetAppropriateLanguage()
{
//Get Language Folder path
string path = ConfigurationSettings.AppSettings["languagefolderpath"];
CultureInfo currentCulture = Thread.CurrentThread.CurrentCulture;
ResourceUtil.LoadAssemblies(AvailableDictionaries, path);
IResourceAttribute currentResource = AvailableDictionaries.Keys.FirstOrDefault<IResourceAttribute>(item =>
{
LocalizationResourceAttribute la = item as LocalizationResourceAttribute;
if (la != null)
return la.Culture.Equals(currentCulture);
return false;
});

if (currentResource != null)
{
Assembly currentAssembly = AvailableDictionaries[currentResource];
Type resourceType = currentAssembly.GetType(currentResource.ResourceClassPath);
ConstructorInfo cinfo = resourceType.GetConstructor(Type.EmptyTypes);
ResourceDictionary dictionary = cinfo.Invoke(new object[] { }) as ResourceDictionary;
return dictionary;
}
return null;

}

Thus, we load the languages according to the Regional settings. You can change  the logic according to what suits you.

To change the language settings you can try :


So the application looks like :


So you can see the Text is modified according to the languages added to the application. Similar to this, the Object Resource can also be plugged in.

Language Converter


Creating Language resources is often comes to me as very boring. So I thought it would be nice to give you a tool which converts one Resource to another. So, if you have built only one string Resource and want to give support for multiple resources to your customers, try my Language converter to generate Resource files for you.

You can find the language converter tool with full source code  from  below:
ResourceConverter For Language using Bing translator
Or  read my blog post :
Resource Generator Tool for WPF



After you run the application you will find the UI something like what showed above. You need to choose the Target file, which we have to build. Let me create the English resource first, and choose the target as the same file.  The english file looks like :

<ResourceDictionary x:Class="LanguageResource.EnglishResource"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:system="clr-namespace:System;assembly=mscorlib">
<system:String x:Key="rKeychangetheme" >Change Theme</system:String>
<system:String x:Key="rKeycustomtext" >This is my custom Text</system:String>
<system:String x:Key="rKeyaccept" >Accept</system:String>
</ResourceDictionary>

In the destination, you need to specify a name which the converter will convert to, and click Convert.



The resource will be converter instantly to French, and keys will remain same.

<ResourceDictionary x:Class="LanguageResource.FrenchResource"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:system="clr-namespace:System;assembly=mscorlib">
<system:String x:Key="rKeychangetheme">Changer de thème</system:String>
<system:String x:Key="rKeycustomtext">C'est mon texte personnalisé</system:String>
<system:String x:Key="rKeyaccept">Accepter</system:String>
</ResourceDictionary>

So this is what we needed for the application. I have added all the supported languages from Bing translator to this tool, so that you can change resources from any language to any other. 

To Know more about this tool Please go ahead and Read :

Resource Generator Tool for WPF

Conclusion


I think Pluggable resources is what every application needs. We build application long before we need styles and Resources. Functionality is the primary thing for any application. But following these basic guidelines will make you to add pluggable themes very easily.

I hope this article will help you. Thank you for reading.

Page copy protected against web site content infringement by Copyscape

About the Author

Abhi2434
Full Name: Abhishek Sur
Member Level: Silver
Member Status: Member,Microsoft_MVP,MVP
Member Since: 12/2/2009 4:19:08 AM
Country: India
www.abhisheksur.com
http://www.abhisheksur.com
Working for last 2 and 1/2 years in .NET environment with profound knowledge on basics of most of the topics on it.

Login to vote for this post.

Comments or Responses

Posted by: Hitesh.dil on: 6/10/2010
Hi Abhishek,

Nice Post.
But could you tell me in which VS version & dot net framework it supports?
Is it created in 2008/2010 ?

Thanks
Hitesh
Posted by: Abhi2434 on: 6/10/2010
Well, you can try it.

Actually I have built this using VS 2008, but it would surely work with VS2010 as well.

For compatibility I have used VS2008 for the sample application as I know most of the people still works with VS2008.

Thanks Hitesh for your comment.
Keep in touch.

Abhishek Sur
www.abhisheksur.com

Login to post response

Comment using Facebook(Author doesn't get notification)