Author avatar

Murat Aykanat

Globalization with Attached Properties in WPF

Murat Aykanat

  • Jan 10, 2019
  • 13 Min read
  • 6,293 Views
  • Jan 10, 2019
  • 13 Min read
  • 6,293 Views
Microsoft.NET

Introduction

Recently, as a part of my phD thesis project, I needed an application that works on Windows. I decided to go for a Windows Presentation Foundation (WPF) application, which I ended up developing. But there was a problem.

My thesis was supposed to be in English so the application was supposed to be English. Yet the application was going to be used locally, and had to be in Turkish as well. So I had to globalize my application to be both in English and Turkish.

There are many ways to perform globalization, such as setting up string resources, having custom static classes that hold the original and translated text or using some globalization libraries. But I wanted something simple and easily done with Extensible Application Markup Language (XAML). The code would look like:

1<TextBlock Text=”English Text” English=”English Text” Turkish=”Türkçe Yazı” />
xml

After a bit of research I came upon a concept called “attached properties” which can be used to set custom properties on XAML.

What are attached properties?

According to an MSDN article; “An attached property is a concept defined by Extensible Application Markup Language (XAML). An attached property is intended to be used as a type of global property that is settable on any object. In Windows Presentation Foundation (WPF), attached properties are typically defined as a specialized form of dependency property that does not have the conventional property wrapper”. So basically they are the existing properties that we use in everyday WPF development such as:

1<DockPanel>
2   <CheckBox DockPanel.Dock="Top">Hello</CheckBox>
3</DockPanel>
xml

Going over the example in the MSDN article; Dockpanel.Dock is an attached property that is predefined for us.

Using attached properties, you can create new custom properties that suit your needs.

Attached Properties in Globalization

After finding out about attached properties, I aimed to create attached properties that would automatically select the Text, Header, or Tooltip that matches the current language of the application.

As an example I will show how you can use attached properties with TextBlocks. Attached properties work like extension methods, here is the code to add translated text to a TextBlock :

1namespace SomeNamespace.Extensions
2{
3	public class UiExtensions
4	{
5		public static readonly DependencyProperty TurkishTextProperty = DependencyProperty.RegisterAttached("TurkishText",
6                                                                                                        typeof(string),
7                                                                                                        typeof(UiExtensions),
8                                                                                                        new PropertyMetadata(default(string)));
9        public static readonly DependencyProperty EnglishTextProperty = DependencyProperty.RegisterAttached("EnglishText",
10                                                                                                        typeof(string),
11                                                                                                        typeof(UiExtensions),
12                                                                                                        new PropertyMetadata(default(string)));
13
14        public static void SetTurkishText(UIElement element, string value)
15        {
16            element.SetValue(TurkishTextProperty, value);
17        }
18
19        public static string GetTurkishText(UIElement element)
20        {
21            return (string) element.GetValue(TurkishTextProperty);
22        }
23
24        public static void SetEnglishText(UIElement element, string value)
25        {
26            element.SetValue(EnglishTextProperty, value);
27        }
28
29        public static string GetEnglishText(UIElement element)
30        {
31            return (string) element.GetValue(EnglishTextProperty);
32        }
33	}
34}
csharp

The code above is fairly simple. First, we create 2 separate properties for both languages, which are named TurkishText and EnglishText. These properties will be of type string . Then we define the getters and setters for these properties.

Now that we defined the properties, we can add them to our XAML code.

1<Window
2        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
3        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
4        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
5		xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
6		xmlns:uiExtensions="clr-namespace:SomeNamespace.Extensions"
7		mc:Ignorable="d" x:Class="SomeNamespace.MainWindow"
8        Title="MainWindow">
9</Window>
xml

So now we can use our newly created extension like this:

1<TextBlock Text="Example English Text"
2			uiExtensions:UiExtensions.TurkishText="Örnek Türkçe Yazı"
3			uiExtensions:UiExtensions.EnglishText="Example English Text"/>
xml

However, running the application, we see that the text does not actually change. We still need to tell the application that we want to change the language somehow.

We can simply write a LanguageChanger class and a few helper classes to do this job for us.

1namespace SomeNamespace.Globalization
2{
3    public static class AppLanguages
4    {
5        public static string English = "English";
6        public static string Turkish = "Türkçe";
7    }
8}
csharp
1namespace SomeNamespace.Utilities
2{
3    public static class UiUtilities
4    {
5        public static IEnumerable<T> GetChildren<T>(DependencyObject depObj) where T : DependencyObject
6        {
7            if (depObj != null)
8            {
9                for (int i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++)
10                {
11                    DependencyObject child = VisualTreeHelper.GetChild(depObj, i);
12                    if (child != null && child is T)
13                    {
14                        yield return (T)child;
15                    }
16
17                    foreach (T childOfChild in GetChildren<T>(child))
18                    {
19                        yield return childOfChild;
20                    }
21                }
22            }
23        }
24    }
25}
csharp
1namespace SomeNamespace.Globalization
2{
3    public class LanguageChanger
4    {
5        private readonly UIElement _element;
6        private readonly string _language;
7
8        public LanguageChanger(UIElement element, string language)
9        {
10            _element = element;
11            _language = language;
12        }
13
14        public void Change()
15        {
16            var textBlocks = UiUtilities.GetChildren<TextBlock>(_element);
17
18            if (_language == AppLanguages.English)
19            {
20                foreach (var textblock in textBlocks)
21                {
22                    textblock.Text = UiExtensions.GetEnglishText(textblock);
23                }
24            }
25            else if (_language == AppLanguages.Turkish)
26            {
27                foreach (var textblock in textBlocks)
28                {
29					textblock.Text = UiExtensions.GetTurkishText(textblock);
30                }
31            }
32        }
33    }
34}
csharp

The LanguageChanger code should allow our WPF to find all the TextBlocks and change their language to whichever language setting we decide.

However, there is a big problem here. Our application will try and change ALL TextBlocks in our XAML. So if it cannot find any EnglishText or TurkishText, the application will default to empty strings which are not very desirable because we do not want ALL our TextBlocks to change, just the blocks that are EnglishText.

To solve this issue we need a flag for the LanguageChanger class to decide whether or not the application should translate.

For this we need yet another attached property:

1namespace SomeNamespace.Extensions
2{
3	public class UiExtensions
4	{
5		public static readonly DependencyProperty TurkishTextProperty = DependencyProperty.RegisterAttached("TurkishText",
6                                                                                                        typeof(string),
7                                                                                                        typeof(UiExtensions),
8                                                                                                        new PropertyMetadata(default(string)));
9        public static readonly DependencyProperty EnglishTextProperty = DependencyProperty.RegisterAttached("EnglishText",
10                                                                                                        typeof(string),
11                                                                                                        typeof(UiExtensions),
12                                                                                                        new PropertyMetadata(default(string)));
13		public static readonly DependencyProperty ChangeLanguageProperty = DependencyProperty.RegisterAttached("ChangeLanguage",
14																												typeof(bool),
15																												typeof(UiExtensions),
16																												new PropertyMetadata(default(bool)));
17
18        public static void SetTurkishText(UIElement element, string value)
19        {
20            element.SetValue(TurkishTextProperty, value);
21        }
22
23        public static string GetTurkishText(UIElement element)
24        {
25            return (string) element.GetValue(TurkishTextProperty);
26        }
27
28        public static void SetEnglishText(UIElement element, string value)
29        {
30            element.SetValue(EnglishTextProperty, value);
31        }
32
33        public static string GetEnglishText(UIElement element)
34        {
35            return (string) element.GetValue(EnglishTextProperty);
36        }
37
38        public static void SetChangeLanguage(UIElement element, bool value)
39        {
40            element.SetValue(ChangeLanguageProperty, value);
41        }
42
43        public static bool GetChangeLanguage(UIElement element)
44        {
45            return (bool) element.GetValue(ChangeLanguageProperty);
46        }
47	}
48}
csharp

The property we added is of type bool and it will help LanguageChanger whether to skip this TextBlock or not.

We also need to change our XAML code accordingly and add our newly created property.

1<TextBlock Text="Example English Text"
2			uiExtensions:UiExtensions.ChangeLanguage="True"
3			uiExtensions:UiExtensions.TurkishText="Örnek Türkçe Yazı"
4			uiExtensions:UiExtensions.EnglishText="Example English Text"/>
xml

When ChangeLanguage is true then it will process that TextBlock, if false, then it will skip it.

Finally we need to edit our LanguageChanger :

1namespace SomeNamespace.Globalization
2{
3    public class LanguageChanger
4    {
5        private readonly UIElement _element;
6        private readonly string _language;
7
8        public LanguageChanger(UIElement element, string language)
9        {
10            _element = element;
11            _language = language;
12        }
13
14        public void Change()
15        {
16            var textBlocks = UiUtilities.GetChildren<TextBlock>(_element);
17
18
19            if (_language == AppLanguages.English)
20            {
21                foreach (var textblock in textBlocks)
22                {
23                    if (UiExtensions.GetChangeLanguage(textblock))
24                    {
25                        textblock.Text = UiExtensions.GetEnglishText(textblock);
26                    }
27                }
28            }
29            else if (_language == AppLanguages.Turkish)
30            {
31                foreach (var textblock in textBlocks)
32                {
33                    if (UiExtensions.GetChangeLanguage(textblock))
34                    {
35                        textblock.Text = UiExtensions.GetTurkishText(textblock);
36                    }
37                }
38            }
39        }
40    }
41}
csharp

You can now use your newly-created custom properties to create translations for every TextBlock in your application purely in XAML.

Do we stop at translating only TextBlocks? Of course not!

We can use attached properties on every type of string we want to translate. For example, headers, context menus, contents etc. Simply create some attached properties, add them to your LanguageChanger, or whatever class you are using for globalization, and edit your XAML code as we did with TextBlocks.

Conclusion

Attached properties are a very useful part of WPF, and their use can be extensive on extending or creating new properties for your controls and objects. I found them especially useful when I was attempting globalization. This technique will hopefully help you in your own projects as well.