Generic C# WinForms

September 29th, 2008

When Generics was first released in .Net 2.0, I was kind of surprised how few built in generic types there were.  A List, a dictionary, a event handler; but not much beyond that.  Nothing like what you see with C++ STL, ATL, or WTL. (turns out there are some reasons for that, C++ Templates can do things that C# Generics just can’t).  But no matter, we can invent our own.

In my current desktop app, I have a reoccurring need for an OK/Cancel form.  Plus I had a number of things I wanted preset for me. 

The first problem is working with the Generic portion of the C# language.  I want to be able to create the OK/Cancel form like this:

   1: var dlg = new OkCancelForm<MyDisplayControl>();
   2: if (dlg.ShowModal()== DialogResult.OK)
   3: {
   4:   // do something here...
   5: }

Also, I need to be able to retrieve, or load the user control that is being displayed.  I created a property called DisplayControl.  So now I can load and retrieve values from the display control.  I’ll expand my sample:

   1: var dlg = new OkCancelForm<MyDisplayControl>();
   2: dlg.DisplayControl.Data = MyData;  // dlg.DisplayControl will be of type MyDisplayControl
   3:                                    // data is a property of MyDisplayControl
   4: var result = dlg.ShowDialog();
   5: if (result == DialogResult.OK)
   6: {
   7:     var data dlg.DisplayControl.Data; 
   8:     // now do something with the data 
   9: }

OK, so that was how I wanted it to work, now onto some real code.

The Implementation

First thing to do is to just create a new form and call it OkCancelForm.  Put on an OK and cancel button just like you normally would.  For a bit of advice on this: I start with two panels, one on the bottom (containing the OK and Cancel buttons) docked to the bottom.  And the other docked to Fill in the middle.  This will host your custom control.

Now C# creates forms with 2 files, OkCancelForm.cs and OkCancelForm.designer.cs.  We first want to look at the OkCancelForm.cs, and change the class declaration to what you see below:

   1: public partial class OkCancelForm<T>: Form where T: ContainerControl, new()

If you compile after doing that, you will see errors (if you are using ReSharper, you wont even have to compile, you’ll just see errors).  Turns out we have to change the other half of the partial file implementation that makes up a WinForms form.  So open up the file named OkCancelForm.designer.cs, and change the declaration to match this:

   1: partial class OkCancelForm<T>

Even though the class names were the same, because one class was a generic class and the other was not, they did not match.

But, diving into the syntax a bit from the first sample.  After the form declaration you see this code: “Form where T: ContainerControl, new()“.  This code tells the generic (T) that T must be of type ContainerControl, and must have a default constructor that takes now parameters — because this class is going to create a new instance of T when OkCancelForm is created.  You can’t be passing in stuff through the controls constructor.  It just wont work.

Now for some more changes to OkCancelForm.cs.  Here you can see we are adding InitializeDisplayControl() to create a new copy of the display control, and we have added a property called display control of type T (which must be a ContainerControl, remember).  The other little trick we are doing there is making the control full screen. 

You will also see a reference to displayPanel on like 14.  That is one of the two panels I mentioned previously that is docked to the form in fill mode (the other is docked to the bottom of the form and holds the OK and cancel buttons).

   1: private T _displayControl;
   2: public OkCancelForm()
   3: {
   4:     InitializeComponent();
   5:     InitializeDisplayControl();
   6: }
   7:  
   8: public T DisplayControl { get { return _displayControl;  } }
   9:  
  10: private void InitializeDisplayControl()
  11: {
  12:     _displayControl = new T();
  13:     _displayControl.Dock = DockStyle.Fill;
  14:     displayPanel.Controls.Add(_displayControl);
  15: }

 

That is the basics of what you need.  Still undone in this sample is validation.  But the tricky part (knowing how to make a partial class generic) is pretty well spelled out for you here.

Enjoy.  And if you have any questions, please leave them in the comments.

  • Pingback: Dew Drop – September 30, 2008 (Evening Edition) | Alvin Ashcraft's Morning Dew

  • Pingback: Reflective Perspective - Chris Alcock » The Morning Brew #191

  • http://blog.denouter.net Frank

    Nice article. You could also use reflection to make a winform which performs some basic actions on custom objects http://blog.denouter.net/2008/08/simple-reflection-form.html

  • http://www.elegantcode.com Chris Brandsma

    @Frank (Frank is a Dutch name?):

    Your implementation is slightly different, you are generating the display objects on the fly based on a domain object. I am instantiating my display class based on the type passed in. Basically, you made a simple scaffold. How does this work with lookups? Also, I’m dealing with more complicated edit logic (plus custom edit controls).

    As far as using generics on the display class: That is correct, but that would only be beneficial if you were dealing with common controls, and then you would lose the true generic-ism of the form, that code would be tied to implementations.

    It would be better if you had your display controls inherit from a common base class, and bind the generic to that base class instead of the one I showed.

    Or, if you have a case with only one control needing custom behavior: create a controller class that creates the generic form and grabs a reference to the control, then the controller class can implement the custom behavior. At most you would only have to expose one or two new properties to make that happen.

    I guess I just don’t like using reflection unless I absolutely have to.

  • Kiran

    Will this new control work with Forms designer?

  • http://www.elegantcode.com Chris Brandsma

    @Kiran,

    Yes, it works just fine with the forms designer.

  • http://blog.denouter.net Frank

    @Chris,

    Yes it a dutch name. You are correct, if domain object gets complicated, reflection is not sufficient. I use it for very simple objects (poco).