Fix embedded resources for a generic UserControl

2019-01-22 19:04发布

问题:

During a refactoring, I added a generic type parameter to MyControl, a class derived from UserControl. So my class is now MyControl<T>.

Now I get an error at runtime stating that the embedded resource file MyControl`1.resources cannot be found. A quick look with .NET Reflector shows that the resource file is actually called MyControl.resources, without the `1.

At the start of the MyControl<T>.InitializeComponent method there is this line which is probably the one causing problems:

 System.ComponentModel.ComponentResourceManager resources =
    new System.ComponentModel.ComponentResourceManager(
       typeof(MyControl<>));

How do I force the ComponentResourceManager to use the embedded resource file MyControl.resources? Other ways to resolve this issue are also welcome.

回答1:

In addition to Wim's technique, you can also declare a non-generic base control that has the same name as your generic class, and have your generic control/form derive from that non-generic base class.

This way you can trick both the designer and the compiler into using the resource file from your generic class, and you get permanent designer support once the base class is setup without having to fiddle in the .designer file everytime you rebuild :

// Empty stub class, must be in a different file (added as a new class, not UserControl 
// or Form template)
public class MyControl : UserControl
{
}

// Generic class
public class MyControl<T> : MyControl
{
     // ...
}

The only requirements are to have exactly the same name for your generic class and its base class, and that the base class must be in another class file, otherwise the designer complains about not finding one of the two classes.

PS. I tested this with forms, but it should work the same with controls.



回答2:

Turns out you can override the resource filename to load by inheriting from ComponentResourceManager like this:

   using System;
   using System.ComponentModel;

   internal class CustomComponentResourceManager : ComponentResourceManager
   {
      public CustomComponentResourceManager(Type type, string resourceName)
         : base(type)
      {
         this.BaseNameField = resourceName;
      }
   }

Now I can make sure that the resource manager loads MyControl.resources like this:

 System.ComponentModel.ComponentResourceManager resources =
    new CustomComponentResourceManager(typeof(MyControl<>), "MyControl");

This seems to work.

edit: the above line is overwritten if you use the designer, because it is in the generated code region. I avoid the designer and make use of version control tools to revert any unwanted changes, but the solution is not ideal.



回答3:

On my Visual Studio 2008 I have this error:

System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(MyControl));

Using the generic type 'WindowsFormsApplication1.UserControl1' requires '1' type arguments.

Notice that in my case code was generated without parentheses, <>, after the class name.

It is becoming interesting, see ImageList autogenerates non-compiling code in a Generic User Control.

What they said:

Posted by Microsoft on 7/6/2005 at 2:49 PM

This is an interesting bug. You've hit upon a generic scneario that we do not support in the Windows Forms designer. We will not be able to add support for this in the Whidbey (my note: Visual Studio 2008?) release. We will consider this for a future version. As a workaround, you can use the designer to create a none generic UserControl with a public Type property and then create a generic class that inherits from it and passes T into the base classes Type property.

I suppose this control cannot be designed in the Visual Studio forms designer either.



回答4:

The simplest and easiest workaround is to make a dummy class for the autogenerated typeof(). You do not need to inherit from it or even expose it to the outside:

// Non-generic name so that autogenerated resource loading code is happy
internal sealed class GridEditorForm
{
}

(In my experience, the time required getting the designer to work around generics was not worth the ideal coolness generics can provide. I won't be using generic windows forms or controls again.)