Using Custom Resource Managers in Windows Forms


The ResourceManagerProvider is all fine and dandy, but Windows Forms developers have an additional hurdle to overcome if it is to be used in a Windows form. The problem lies in the very first line of the form's InitializeComponent method when Form.Localizable is TRue:

 // Visual Studio 2003 System.Resources.ResourceManager resources =     new System.Resources.ResourceManager(typeof(Form1)); // Visual Studio 2005 System.ComponentModel.ComponentResourceManager resources =     new System.ComponentModel.ComponentResourceManager(     typeof(Form1)); 


Clearly, the Visual Studio designer doesn't respect our new ResourceManager Provider class and blindly uses good old ResourceManager or Component ResourceManager. This really doesn't help us much if we want to get our resources from, say, a database. What we need is for Visual Studio to generate code that uses our ResourceManagerProvider instead of ResourceManager/Component ResourceManager. There are two possible solutions to this problem. The first is that we can write a Visual Studio add-in that modifies or replaces the code generator for the Windows Forms designer. I decided against this approach because the second solution is much easier. The second solution is that we can resign ourselves to the fact that Visual Studio is going to write code that we don't want it to, but we can try to correct the problem before the resource manager gets used. This is the solution that we implement in this chapter.

It is not very well known that it is possible to inject completely new code into the InitializeComponent method. The goal of our solution, therefore, is to inject the following new line of code into the InitializeComponent method:

 resources = Internationalization.Resources.     ResourceManagerProvider.GetResourceManager(typeof(Form1)); 


("Form1" is the name of the form class.) This code assigns a new value to the local resources variable. It is true that the InitializeComponent method will first create a redundant ResourceManager or ComponentResourceManager that will not be used and will be dereferenced when we subsequently assign a new resource manager from our ResourceManagerProvider.GetResourceManager method, and this is wasteful. But it is also true that it solves our problem.

The secret to injecting this new code into InitializeComponent lies in creating a component that has custom serialization code. You can achieve this with the DesignerSerializer attribute:

 [DesignerSerializer(typeof(ResourceManagerSetterSerializer), typeof(CodeDomSerializer))] public class ResourceManagerSetter : System.ComponentModel.Component { } 


Our new ResourceManagerSetter class inherits from Component and, therefore, sits in the nonvisual area of the form designer. It has no properties, so there is nothing to serialize. Its only presence so far is that, like any component, the form class has a private field:

 private Internationalization.Resources.ResourceManagerSetter     resourceManagerSetter1; 


And the field is initialized in InitializeComponent:

 this.resourceManagerSetter1 =     new Internationalization.Resources.ResourceManagerSetter(); 


The DesignerSerializer attribute tells the form designer to serialize this component using the ResourceManagerSetterSerializer class, and that this class is a kind of CodeDomSerializer. CodeDom ("code document object model") is a .NET technology that enables you to create classes from which code can be generated. It is a great technology that is used throughout Visual Studio, and although it can require a little thought to get started, it has enormous potential and is well worth mastering. The ResourceManagerSetter class is now complete.

All of the action occurs in the ResourceManagerSetterSerializer class:

 public class ResourceManagerSetterSerializer : CodeDomSerializer {     public override object Deserialize(         IDesignerSerializationManager manager, object codeDomObject)     {         CodeDomSerializer baseSerializer = (CodeDomSerializer)             manager.GetSerializer(typeof(ResourceManagerSetter).             BaseType, typeof(CodeDomSerializer));         return             baseSerializer.Deserialize(manager, codeDomObject);     }     public override object Serialize(         IDesignerSerializationManager manager, object value)     {         CodeDomSerializer baseSerializer = (CodeDomSerializer)             manager.GetSerializer(typeof(ResourceManagerSetter).             BaseType, typeof(CodeDomSerializer));         object codeObject = baseSerializer.Serialize(manager, value);         if (codeObject is CodeStatementCollection)         {             CodeStatementCollection statements =                (CodeStatementCollection) codeObject;             CodeExpression leftCodeExpression =                 new CodeVariableReferenceExpression("resources");             CodeTypeDeclaration classTypeDeclaration =                 (CodeTypeDeclaration) manager.GetService(                 typeof(CodeTypeDeclaration));             CodeExpression typeofExpression =                 new CodeTypeOfExpression(classTypeDeclaration.Name);             CodeExpression rightCodeExpression =                 new CodeMethodInvokeExpression(                 new CodeTypeReferenceExpression(                 "Internationalization.Resources."+                 "ResourceManagerProvider"),                 "GetResourceManager",                 new CodeExpression[] {typeofExpression});             statements.Insert(0, new CodeAssignStatement(                 leftCodeExpression, rightCodeExpression));         }         return codeObject;     } } 


This class overrides the Deserialize method, which creates a new CodeDom Serializer and returns it. This is a standard implementation of the Deserialize method, and this implementation offers nothing new. The Serialize method returns a collection of CodeDom statements. These statements can be anything. The result is placed in the InitializeComponent method verbatim. Our implementation simply generates a single statement. The statement is an assignment statement that invokes the ResourceManagerProvider.GetResourceManager method and assigns the result to a variable called "resources". The majority of the code in this method is CodeDom code; you might want to study the documentation on CodeDom if there is anything that you don't follow. The following line, however, is worth pointing out:

 CodeTypeDeclaration classTypeDeclaration =     (CodeTypeDeclaration) manager.GetService(     typeof(CodeTypeDeclaration)); 


Recall from the line of code that we want to generate that we need to know what class we are generating the resource manager for. We need to be able to generate something like "typeof(Form1)", but we don't know what the name of the form class is. This line uses the GetService method of the IDesignerSerializationManager object passed to the Serialize method to get the CodeTypeDeclaration for the form.

The only caveat for the ResourceManagerSetter component is that it must be the first component on the form; otherwise, the assignment to the "resources" variable will occur too late and the "temporary" ResourceManager/ComponentResourceManager will not be so temporary, as it gets used to retrieve resources until our component kicks in. You can see the problem here where a Button gets created before the ResourceManagerSetter has had a chance to assign a new value to the "resources" variable:

 System.Resources.ResourceManager resources =     new System.Resources.ResourceManager(typeof(Form1)); this.button1 = new System.Windows.Forms.Button(); this.resourceManagerSetter1 =     new Internationalization.Resources.ResourceManagerSetter(); this.SuspendLayout(); // // button1 // this.button1.AccessibleDescription =     resources.GetString("button1.AccessibleDescription"); this.button1.AccessibleName =     resources.GetString("button1.AccessibleName"); etc. etc. this.button1.Text = resources.GetString("button1.Text"); this.button1.TextAlign = ((System.Drawing.ContentAlignment)     (resources.GetObject("button1.TextAlign"))); this.button1.Visible =     ((bool)(resources.GetObject("button1.Visible"))); resources = Internationalization.Resources.     ResourceManagerProvider.GetResourceManager(typeof(Form1)); 


The call to resources.GetString to assign the button's Text property will use the assembly-based resource manager.

One minor fly in the ointment with the ResourceManagerSetter approach is that Visual Studio spots that we have done something unusual and reports in the Task List (see Figure 12.6). You can just ignore this warning.

Figure 12.6. Visual Studio 2005 Objecting to ResourceManagerSetter


Finally, if you use WinRes to open forms that have a ResourceManagerSetter, remember that WinRes must be able to find all of the assemblies that are used by the form. So WinRes must be able to find the ResourceManagerSetter assembly; otherwise, it will display its unhelpful "Object reference not set to an instance of an object" error. Of course, there would be little value in using WinRes to open such forms anyway because WinRes cannot read from resources other than resx files. This limitation lends more weight to the argument to write a WinRes replacement.




.NET Internationalization(c) The Developer's Guide to Building Global Windows and Web Applications
.NET Internationalization: The Developers Guide to Building Global Windows and Web Applications
ISBN: 0321341384
EAN: 2147483647
Year: 2006
Pages: 213

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net