Over the years, I have done mobility applications on Windows Mobile 2003 & 5.0, and I have done globalized/localized applications in ASP, ASP.NET, and Smart Clients. Recently, I came across my first requirement to bring these things together and write a localized, Smart Client application targeted at the Windows Mobile 5.0 platform (utilizing the Microsoft Compact Framework version 2.0). Like most things new on the Compact Framework, it starts out exciting and quickly turns frustrating, as you discover all new areas of the "full" .NET Framework that were sacrificed in order to reduce the footprint of the "compact" version. This article is about the challenges I faced and the solution I used.
In the full .NET Framework, you have the
System.Resources.ResourceManager class, which provides all the built-in functionality you need to easily load resources from external assemblies based on the
ResourceManager is smart enough to find the right assembly, look for the resource and, if it doesn't find the resource, it will "auto-magically" look for your resource in the parent culture, until it either finds a match, or ends up back at the
InvariantCulture (which is basically the unknown or generic culture). Changing the resources used by your user interface is as easy as:
Thread.CurrentThread.CurrentUICulture = new System.Globalization.CultureInfo("en-US");
The first limitation with the Compact Framework is that the
CurrentThread.CurrentUICulture is not supported. This means that your application will use the culture of the OS (see this article on MSDN for more information). But what if you have a situation where all mobile devices are deployed set for US English (en-US), but some operators may want to use a particular application in Mexican Spanish (es-MX) or French Canadian (fr-CA)? You can't do it with the built-in
ResourceManager unless you use the OS' Control Panel to change the culture settings for the entire device.
Problem #1 is that we need a way to change the CultureInfo of just our application and have that be used by the ResourceManager.
Next, if you have done any globalization/localization work within Visual Studio 2005, you may have found that there is a built-in mechanism for making your forms "Localizable". I'm not going to try and get into a full dissertation on how this works. If you are interested, I would recommend a walk down "Google Lane". In short, you can use the IDE's form properties to view your form in different locales and change certain property values (i.e.,
Control.Text) on a per-locale basis. "Under the covers," the IDE creates localized resource assemblies for each locale you support, and will "auto-magically" select the right set of resources at run-time (a-la the
ResourceManager described above).
The second limitation of the Compact Framework is that the mechanism which updates the UI is all tied to the
CurrentUICulture of the current thread, which as we saw above, is never going to change (unless it is done at the device level).
Problem #2 is that we need the ability to update our UI on-the-fly whenever a change is detected in the culture settings.
In addition to solving the above problem, there were certain design goals which I had for the final solution.
- It needed to work (obviously), but in doing so, I didn't want to reinvent the wheel. It needed to use as much of the existing infrastructure as possible. In particular, this meant the logic behind finding/selecting the resource files as well as the fall-back mechanism for missing assemblies or resources.
- It needed to be as "self-contained" and easy to use as possible. I didn't want the developers to get tied up in implementing the mechanism.
- It needed to support the addition of new resource assemblies in the future without any change to the source code or binaries.
The solution that I deployed for this comes in two parts. The first is a custom
ResourceManager (implemented as a Singleton), which gives applications the ability to specify the
CultureInfo to be used when looking up resources. The second part is a
LocalizedForm (derived from
System.Windows.Forms.Form) which is designed to be used as the base class for any form that requires localization support. Please refer to the included source code and example project for full details on how this is implemented.
ResourceManager gives your application a way to specify the
CultureInfo of the resources to use. Whenever the
CultureInfo is changed, an event is raised, letting subscribers to the event know that there has been a change. The
ResourceManager also exposes some overloaded helper methods which allow the
LocalizedForm to more easily retrieve resources.
LocalizedForm provides a common base-class that can be used to provide the functionality of listening for and responding to the
ResourceManager.CultureChanged event. It will loop through all of its child controls and menus and update their appropriate properties (i.e.,
PictureBox.Image, etc.) with the appropriate, localized resource.
Another of those areas where the Compact Framework is just plain annoying is in working with menu controls. The root of the problem is that the
MenuItem doesn't actually derive from the
Control base-class and, as a result, doesn't actually have a
Name property that you can read at run-time. I played briefly with a Reflection-based solution to this problem, but having recently worked through a Reflection-based hardware abstraction layer for a similar application, I knew that I was going to run into limitations of the Compact Framework as well as performance penalties.
Instead, I placed a little of the implementation burden on the developer in order to improve performance. As a result, the
LocalizedForm base class maintains a
Dictionary<MenuItem, string> which the developer fills as part of the constructor on each form, like so:
The LocalizedForm class uses these strings in lieu of the
Control.Name property when looking for the Text value in the resource assembly.
Two Final "Gotchas"
There are two aspects of using localization that you must be careful to do correctly, or it will simply not work (and not throw any errors). If you don't locate and name your resource files correctly, it won't find them. If you don't name your resources correctly, it won't find them. In either case, the
ResourceManager will simply return its best match or (worst case) will not make any change to the UI.
Location and Name of Your Resource Files
There is one last thing that you have to be sure to do in order for the "built-in" functionality of the underlying
ResourceManager to function properly. When you add these resource files to your project, they must be located inside of a project directory named "Resources" and they must be named Resources.<culture-code>.resx (see the image at the top of this article for an example). If you do not do these two things, the
ResourceManager will not be able to locate your resource files and you will not get any localized updates.
Naming Your Resources
As you can see in the image at the top of the article, this infrastructure utilizes the name of the control, combined with the name of the parent form, to uniquely identify a resource. If you do not follow this naming convention in your resource files, a match will not be found and no change will be made to the UI (and no error thrown).
Right now, I handle the basic controls (
PictureBox). Additional controls can certainly be added by updating the
I hope that this was interesting and/or useful to you. I enjoy doing them, I appreciate feedback (both positive and constructive criticism), and I like seeing what people do with these in their own solutions. If this was useful or interesting to you, please take a moment to rate the article. If you have questions or suggestions, feel free to post them below.