I had this issue last week. I wanted to use a generic form but the designer (very useful in Windows Forms) was always reporting a strange error even if the compilation was not reporting any issues.
If you ever tried, you surely had the issue.
If you never tried, read on, this is a great feature and the workaround to make the designer is really easy!
Downloadable demo code
The downloadable demo code this month is provided once again in both VB and C#. The solution was created using Visual Studio 2015 and the .Net Framework 4.6.1 but Generics are around since the .Net Framework 2.0.
Why generics?
As you already know, generics let you use typed members without really knowing the exact type until runtime. It lets you save a lot of code. And it prevents you from using other mechanism such as Reflection.
If you never looked at generics, better start by looking at https://msdn.microsoft.com/en-us/library/ms172192(v=vs.110).aspx and https://msdn.microsoft.com/en-us/library/ms379564(v=vs.80).aspx.
I have been using generic classes for a long time but never had to deal with generic forms which in my mind would have been just the same but the designer decided otherwise.
Issue with generic forms inheritance
The .Net Framework itself has no issues at all with you inheriting a generic form because for the framework, a form is just another class.
The problem lies in Visual Studio and more specifically, the forms’ designer.
If you try to open the designer of a form inheriting a generic form, chances are that you will see either the error shown in figure 1 or 2 (depending on the language you are using). The error is reported differently depending on the language but the result is the same, you won’t see your form!
Figure 1: The Windows Forms designer hardly refusing to show a form inheriting a generic one
Figure 2: Same issue but from a C# project
Chances are that if you test it by yourself, you will first create a generic form and then create a second form to inherit from the base one. You will probably double-click to open the child form from the designer and you might see it.
At this point you will surely think that I am a liar. But play a bit with your forms, the base one and the child one. Sooner or later, you will eventually get the error.
Let’s recreate a project and show how to fix it.
Creating the demo project
I don’t want something too complex. Just enough to be able to demonstrate the error and how to fix it.
I will create a project with a menu form (2 buttons to show a Client or an Employee form), a base generic form with a button using the Generic type to show the type and a value from the generic class (MyGenericForm) and 2 children inheriting from it creating the specific instance of a class and passing if to the form (fClient and fEmployee). And might seems complex but it is not.
Figure 3: The demo application in action
Creating the classes
We will just create 3 little classes. The first one is an abstract one, exposing a single read-only property and is named cBaseEntity and looks like this:
Namespace Classes
Public MustInherit Class cBaseEntity
public MustOverride ReadOnly Property Name() As String
End Class
End NameSpace
The next 2 classes are inheriting from cBaseEntity and are specialized for a client and for an employee. For my demo, I simply want to return a value to prove the class we are using.
This is the code for the cClient class:
Namespace Classes
Public Class cClient
Inherits cBaseEntity
Public Overrides ReadOnly Property Name As String
Get
Return "Client"
End Get
End Property
End Class
End Namespace
This is the code for the cEmployee class:
Namespace Classes
Public Class cEmployee
Inherits cBaseEntity
Public Overrides ReadOnly Property Name As String
Get
Return "Employee"
End Get
End Property
End Class
End Namespace
Nothing is related to generics so far. Let’s move on.
Creating the base generic form
The first form to be created will be the one becoming the generic one. Create a regular form (I named mine MyGenericForm). Add a button to the form and handle the Click event of the button (check the code below to see what we want to show). Finally, add the generic clause to your form definition (the part after the form name showing “(of T As {cBaseEntity} )”). Your code should look like this:
Imports DemoGenericWinFormsVB.Classes
Public partial Class MyGenericForm(of T As {cBaseEntity} )
Protected ClassInstance as T
Private Sub btnGenericForm_Click(sender As Object, e As EventArgs) Handles btnGenericForm.Click
MessageBox.Show("Generic form showing type " + GetType(T).ToString() + " - Name=" + ClassInstance.Name)
End Sub
End Class
For C# developers, the class declaration should read like this:
public partial class MyGenericForm<T> : Form where T : cBaseEntity
If you stop there and you check your error list window, you will find errors. This is because you have changed the signature of the class and you need to keep the other signature found in the .designer file on par. Click on your form name in the code editor and hit F12. That should open the code editor for the .designer file. Add the same generic clause after the form name. It should look like this:
Partial Class MyGenericForm(of T As {cBaseEntity} )
inherits System.Windows.Forms.Form
C# developers also need to add the generic part to the .designer file but don’t have to repeat the restriction to the cBaseEntity type.
partial class MyGenericForm <T>
Now, all the errors should be removed.
Creating children forms
You probably already have worked with inherited forms. The first part is exactly the same. First we need to add a new form to our project (fClient for example). Once added, we need to add the Inherits clause. So I always add it to the form’s code so it looks like this:
Imports DemoGenericWinFormsVB.Classes
Public Class fClient
Inherits MyGenericForm(Of cClient)
End Class
Because the .designer.vb file instructs the class to inherits from System.Windows.Forms.Form and .Net can only inherits from a single class, the error list will complain that base classes are different. Click on your class name in the code editor and hit F12 to open the .designer.vb file. Delete the Inherits statement from there and all compilation errors should go away.
For this part, C# developers don’t have anything special to do. The first code Window contains the inheritance and no modification to the .designer.cs file is required. That means the form’s code needs to be changed from:
public partial class fClient : Form
To:
public partial class fClient : MyGenericForm<cClient>
At this point, you might to open the child form designer and chances are good that you will see it correctly as explained before. But it won’t take long before the error shows up!
Fixing the issue
There is a solution to the designer issue. The solution is somewhat easy. All you need is a little class definition (that you will surely keep empty) just to fool the Visual Studio designer.
The only thing that this little class is doing is to sit between the generic base class and the child instance. You define one like this in VB:
Public class MyGenericFormOfClientBase
Inherits MyGenericForm(of cClient)
End Class
Or like this in C#:
public class MyGenericFormOfClientBase : MyGenericForm<cClient> { }
I always add these little class at the bottom of my generic class (but you can add them almost anywhere in your project (except at the top of a form that requires a designer).
You now have to change the fClient to inherits from this little class:
Public Class fClient
Inherits MyGenericForm(Of cClient)
End Class
Starting from now, the designer shouldn’t cause you problems.
Conclusion
This is an example of what can happens when the tooling improves over the time. Some part of it might have issues trying to follow. The designer exists since day one and the generic feature only appeared at v 2.0. at least, we have a workaround that is not too demanding!