Click here to Skip to main content
15,868,030 members
Articles / Desktop Programming / WPF

Pluggable Styles and Resources in WPF with Language Converter Tool

Rate me:
Please Sign up or sign in to vote.
4.96/5 (17 votes)
31 Oct 2010CPOL9 min read 40.8K   748   31   8
In this article, I have shown how you can build pluggable Resources for styles, Languages or any static objects, etc. Therefore building a new style doesn't hamper your code and you can easily plugin any new style to the application even though it is already in the production environment.

Introduction

As we go ahead with WPF, there are lots of problems we face which are very 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 we place 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 components are totally dependent between one another and an individual element cannot exist 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:

C#
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 MSDN Reflection.

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.

plugin1.JPG

To make each of the Resources compatible, I have created an Interface:

C#
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 identify the path of the class which makes the appropriate resource within the Assembly.

Now let us create a Concrete Attribute that lets us input the metadata specific to each type.

C#
[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:

C#
[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. As such, 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, let's 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.

plugin2.JPG

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

Now it's time to implement a style for your application.

XML
<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> 

plugin3.JPG

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:

C#
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, it's 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.

C#
[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.

plugin4.JPG

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 it's 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.

C#
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 explicitly for Styles. So ResourceUtil.LoadAssemblies will load all the assemblies within the folder specified as path to AvailableStyles.

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

C#
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, let's create a handler for Application.Startup and place few lines to load the Dictionary.

C#
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:

XML
<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 that 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.

plugin5.JPG

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:

C#
((App)Application.Current).Resources.MergedDictionaries.Add(dictionary); 

Thus, you can easily make the application 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 don't remember, you can try it from:

So let's extend this with plugin based language application.

Working almost in 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:

XML
<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:

XML
<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 in the same way, while the Values are modified. Now it's time to compile the assembly. Before doing that, let's add the custom attributes to AssemblyInfo.cs file of the project.

C#
[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, let's modify the main XAML code with DynamicResources for the strings:

XML
<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:

C#
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:

plugin8.JPG

So the application looks like:

plugin9.JPG

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 Tool

Creating Language resources 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 ResourceConverter For Language using Bing translator, or read my blog post Resource Generator Tool for WPF.

plugin6.JPG

After you run the application, you will find the UI something like what is shown 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:

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

plugin7.JPG

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

XML
<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 Plug-gable resources is what every application needs. We build applications long before we need styles and Resources. Functionality is the primary thing for any application. But following these basic guidelines will make you add plug-gable themes very easily.

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

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
President
India India
Did you like his post?

Oh, lets go a bit further to know him better.
Visit his Website : www.abhisheksur.com to know more about Abhishek.

Abhishek also authored a book on .NET 4.5 Features and recommends you to read it, you will learn a lot from it.
http://bit.ly/EXPERTCookBook

Basically he is from India, who loves to explore the .NET world. He loves to code and in his leisure you always find him talking about technical stuffs.

Working as a VP product of APPSeCONNECT, an integration platform of future, he does all sort of innovation around the product.

Have any problem? Write to him in his Forum.

You can also mail him directly to abhi2434@yahoo.com

Want a Coder like him for your project?
Drop him a mail to contact@abhisheksur.com

Visit His Blog

Dotnet Tricks and Tips



Dont forget to vote or share your comments about his Writing

Comments and Discussions

 
GeneralMy vote of 5 Pin
Claude He4-Nov-13 19:15
Claude He4-Nov-13 19:15 
Generalnice Pin
shaheen_mix7-Jan-12 1:23
shaheen_mix7-Jan-12 1:23 
GeneralMy vote of 5 Pin
Weidong Shen21-Mar-11 8:55
Weidong Shen21-Mar-11 8:55 
GeneralMy vote of 5 Pin
Abhijit Jana5-Dec-10 8:00
professionalAbhijit Jana5-Dec-10 8:00 
Nice Abhishek !
GeneralNice job my friend, nice job indeed Pin
Sacha Barber5-Dec-10 5:17
Sacha Barber5-Dec-10 5:17 
GeneralRe: Nice job my friend, nice job indeed Pin
Abhishek Sur5-Dec-10 7:54
professionalAbhishek Sur5-Dec-10 7:54 
GeneralNice Article! Pin
Your Display Name Here1-Nov-10 7:06
Your Display Name Here1-Nov-10 7:06 
GeneralRe: Nice Article! Pin
Abhishek Sur2-Nov-10 4:54
professionalAbhishek Sur2-Nov-10 4:54 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.