Dynamically Adding Controls to a Windows Form

For a simple and effective solution to writing an application that can be used by several different clients, look into Skousen's article here on defining attributes needed to create a form dynamically. He reduces the need to compile and to take extra Tylenol.

Contributed by
Rating: 4 stars4 stars4 stars4 stars4 stars / 57
May 26, 2004
Rate this Article:
MEH MEH++


SEARCH ASP FREE
TOOLS YOU CAN USE

advertisement


Support files for this article can be found here.

In my development of various C# applications, I came upon a common issue that most developers find daunting. I’m writing an application that will potentially be used by several similar, but different, clients. I knew each of them would want to have a series of custom items on a page, each one appropriate for their company and business rules. As most developers know, this becomes a maintenance nightmare. A number of potential solutions present themselves:

  1. Make a different application for each company. Ouch! Not only is that a lot of work, but trying to keep everyone current is a huge bother. I suppose you could alleviate the pain somewhat by storing common functionality in a common code area, but breaking it out of company-specific code is not only is costly in development time, but calculating which pieces should be called for whom lends itself to additional CPU cycles when the application is run.
  2. Make a different form for each company. Okay. Sure, you can use a factory pattern to pull up the appropriate forms, but do you really want to have every potential company's form in the application? It makes for a monster program, again sacrificing speed and maintenance. A whiz with building scripts could make it so it would only include the appropriate forms for that company, but this adds an additional level of complexity that results in potential breakage and maintenance.
  3. Define in an external file the attributes needed to create a form dynamically. When you deploy, you only install the file appropriate to that company. The application takes care of the rest, reading in the file and creating the form on the fly. As an added bonus, if a company adds a new item, or needs to take on off, the change is as simple as modifying the attribute file. When the application is re-run, the new attributes are instantly included. No need to compile anything! 

I liked the last idea best, especially since it would potentially allow me to train the customer to make their own enhancements. Hey, any simplistic development I can push off my plate so I can focus on the more difficult stuff is a good idea in my book!

Due to the structure and flexibility of .NET, I found it was a snap to implement my idea.

The test application was a simple one: I wanted a form that helped me keep track of all the medication I take while working on a last minute, midnight hour application. (Hey, just kidding here, folks, especially if this is my wife or mother reading this). I wanted to define all the medications and their dosages in an external file, creating the form on the fly.

In addition, I wanted to add the ability to click on a medication with my mouse. If I clicked with the left button, I wanted the count of the number of times to go up. In case I had over clicked with the left button, if I clicked with the right, I wanted the count to go down. This handling of mouse events would allow me to test for the handling of events in my dynamically created form.

Lastly, I wanted the form to automatically size itself to fit all of the items I entered.

Here is how I did it. (See the support files link above.)

Defining the Attribute File

I started with the simplest part of my application: defining the external attribute file I would read when creating my form. The idea had XML written all over it. An XML file would allow me to define all the elements and attributes I would need, and allow for potentially validating the information against a DTD or XSD. Although I didn’t get to the validating part (an exercise for the student), the file was very straightforward. It looks like this:

<?xml version="1.0" encoding="utf-8" ?>
<medications>
 <medication name="Aspirin" dosage="50mg"/>
 <medication name="Tylenol" dosage="100mg"/>
 <medication name="Motrin" dosage="200mg"/>
 <medication name="Aleve" dosage="75mg"/>
 <medication name="Pepsid" dosage="25mg"/>
 <medication name="Excedrin" dosage="300mg"/>
 <medication name="Tums" dosage="500mg"/>
</medications>

Easy enough. All my common medications were there. Should one of my co-workers want to use the application for their own nefarious purposes, the name and dosage information in the XML file was easily available for them to change. I saved it in the “bin/Debug” directory so it would be available to the executable without any directory padding.

Designing the Form

I knew that I wanted my form to have three items on each row. The first would be a CheckBox control, with the text of the checkbox being the name of the medication. Second, I wanted a basic TextBox control. And lastly, I wanted a NumericUpDown control that indicated how many times I had taken the medication. I wanted something that looked like this:

skoussen
 
Differences Between Static and Dynamic Forms

One of the more obvious things I would have to deal with when creating my form would be the simple fact that, when I add an item to a form, none of the private data members Visual Studio kindly created for me would be available. You’ve seen them, I’m sure. If I place a CheckBox on my form in the Design window, when I viewed the code, I would expect to see a line such as the following placed in my code source file:

  private System.Windows.Forms.CheckBox checkBox1;

Likewise, in the InitializeComponent method (located in the hidden “Windows Form Designer” region), Visual Studio places the proper creation code for the checkbox, specifying and setting the various properties I had set in the Design window.

Alas, all of that simplicity and ease of use would be gone. The only items I “statically” placed on my form were the three labels at the top. Everything else would be added dynamically.

What Do I Need to Store?

It was okay that I didn’t have any private variables (beyond the three labels) since I didn’t need to access or manipulate any of the checkbox or textbox controls after they were created. But I realized that if I wanted to handle the mouse click events, changing the values of the numeric controls, I would need to keep around some references to the numeric up down controls. So I created a private Hashtable to store them in. I called it nudControls. Why wouldn’t I also need to store the check box controls? Well, the Mouse Up event handler looks like this:

private void cbName_MouseUp(object sender, System.Windows.Forms.MouseEventArgs e)

The given sender object is the object that sent the event, in this case the checkbox control itself. More on this later.

Upon further analysis, I observed that I would need to store the vertical position of the last control I placed in the form. This would allow me to re-size the form once all of the controls were dynamically added. So I created a private int called vertPos to store that information.

Loading from XML

Next I create a private method to read the XML. Since I was only going to read the file, I decided a standard SAX parser would be sufficient. The .NET XmlTextReader class implements the SAX parser, so I was ready to go. Here is what the method looks like:

private void LoadFromXML () {
 XmlTextReader xmlReader = null;
 try  {
  //Create an instance of the XMLTextReader.
  // Since we will only be reading the file, a SAX parser will do fine
  xmlReader = new XmlTextReader("Medications.xml");

  while (xmlReader.Read() ) {

   if (xmlReader.NodeType == XmlNodeType.Element) {
    // Look for "medication" tags, ignoring all others
    if (xmlReader.Name == "medication") {

     // Get out the "name" and "dosage attributes
     string name = xmlReader.GetAttribute("name");
     string dosage = xmlReader.GetAttribute("dosage");

     // Create a row of dynamic controls
     vertPos = CreateRow(name, dosage);

    } // test for "medication" name
   } // test for element node type

  } // end loop through reading
 } catch (XmlException ex){
  System.Console.Write("An XML exception occurred: n" + ex.ToString());
 } catch (Exception ex){
  System.Console.Write("A general exception occurred: n" +ex.ToString());
 }       
 finally {
  if (xmlReader != null) {
   xmlReader.Close();
  }
 }
}

For those familiar with reading  XML files, the code above is familiar. After creating our xmlReader, we loop through looking for element nodes. If the element Name is equal to “medication,” we extract the “name” and “dosage” attributes, and create a row. The loop continues until all elements have been read.

Creating the Controls

You will note that the above method calls a method named CreateRow. This is where the actual dynamic creation takes place. Here is what the method looks like:

  private int CreateRow(string name, string dosage) {

   // Calculate the vertical position of the controls.
   // Do this by multiplying the number of items already added
   // (and stored in nudControls)
   // by the height of each item, plus 32 for the spacing at the top
   int yPos = (nudControls.Count * 24) + 32;

   // Calculate the tab order.
   // Do this by multiplying the number of items already added
   // (and stored in nudControls)
   // by the number of controls we are adding on each row
   int tabIndex = (nudControls.Count * 3);

   // Create three form controls, a check box (cbName),
   // a text box (txtDosage), and a numeric up down (nudNum).

   // cbName
   CheckBox cbName = new CheckBox();
   cbName.Location = new System.Drawing.Point(8, yPos);
   cbName.Name = "cbName" + name;
   cbName.Size = new System.Drawing.Size(144, 22);
   cbName.TabIndex = tabIndex++;
   // Note: Here we not only set the text of the item by setting it
   //  to the given name, storing this here will allow us to
   // retrieve it on a mouse up event.
   cbName.Text = name;
   cbName.MouseUp += new
    System.Windows.Forms.MouseEventHandler(this.cbName_MouseUp);

   // txtDosage
   TextBox txtDosage = new TextBox();
   txtDosage.Location = new System.Drawing.Point(160, yPos);
   txtDosage.Name = "txtDosage" + name;
   txtDosage.Size = new System.Drawing.Size(48, 22);
   txtDosage.TabIndex = tabIndex++;
   txtDosage.Text = dosage;

   // nudNum
   NumericUpDown nudNum = new NumericUpDown();
   nudNum.Location = new System.Drawing.Point(224, yPos);
   nudNum.Name = "nudNum" + name;
   nudNum.Size = new System.Drawing.Size(40, 22);
   nudNum.TabIndex = tabIndex++;

   // Add the new controls to this form
   this.Controls.Add(cbName);
   this.Controls.Add(nudNum);
   this.Controls.Add(txtDosage);

   // Add the numeric up down to our internal list so we
   // can later retrieve it. The key is the name of the row.
   nudControls.Add(name, nudNum);

   return yPos;
  }

Again, the code is pretty self-explanatory.
 
A few notes: The controls are added to the form using the this.Controls.Add method (this is the same call used for all controls in the form, and can be found in the InitializeComponents method Visual Studio creates for you). 

Also, observe that we store the NumericUpDown control for later retrieval in our hash table, nudControls. The key for the control value in the hash table is the name of the medication. Since all of my medications can be uniquely identified by the name, this makes sense. But since the hash table complains with an exception if you try to add an item with an identical key, if you have a situation where we tried to add medications with the same name (and perhaps different dosages), we would not be successful. You may need to modify the code and XML to have a unique ID used to store the values.

Regardless, note that I also set the checkbox Text property to the name of the medication. This comes in handy later when we handle the Mouse Up event. Note in the checkbox creation that adding an event handle is as simple as calling the following, passing in the method that will be called when the event takes place:

 cbName.MouseUp += new
  System.Windows.Forms.MouseEventHandler(this.cbName_MouseUp);

Lastly, I return the calculated yPos variable so I can store it (in LoadFromXML) for a final resizing of the form.

Handling the Mouse Up Event

The Mouse Up event handler does not contain many surprises. Here is how it looks:

private void cbName_MouseUp(object sender, System.Windows.Forms.MouseEventArgs e) {

 // Cast the sender object to the check box
 // that received the mouse up event.
 CheckBox cbName = (CheckBox) sender;

 // Locate the associated numeric up down from our hash table of controls.
 // The key is the text of the check box (stored previously)
 NumericUpDown nudName = (NumericUpDown) nudControls[cbName.Text];
 // Get out the current value
 int val = (int) nudName.Value;

 // Figure out which button they clicked.
 if (e.Button == MouseButtons.Left) {
  val++;
 } else if (e.Button == MouseButtons.Right) {
  val--;
  if (val < 0) {
   val = 0;
  }
 }
 // Set the value of the numeric up down
 nudName.Value = val;

 // Check to see if we should have the checkbox checked
 if (val == 0) {
  cbName.Checked = false;
 } else {
  cbName.Checked = true;
 }
}

Again, the idea here is that we want to increase the value of the associated numeric up down if the left mouse was clicked, and decrease the value if the right button was clicked.
 
First, we know what specific checkbox we are dealing with because the method passes in the sender object, in this case the desired check box. We immediately cast it to a CheckBox object so we can access and modify some of its parameters.

Next, the trick is getting the appropriate numeric up down control. Recall that when we created the control, we added it to an internal hash table called nudControls, using the name of the medication as a key. This also happens to be the Text property of the checkbox. So we can easily locate the numeric up down control that we need by asking for it by the value of the checkbox Text parameter.

We then perform the appropriate logic, and our method is complete!

Resizing the Form

Last, I thought it would be nice to include a method that would change the size (height) of the form. Since I would not know how many controls I would be adding at design time, I knew that if I wanted all of the controls to be visible to the end-user I would have to handle this dynamically. Here is the method:

 private void SizeToFit() {
   // If called after the dynamic controls were created,
   // vertPos is now the last vertical position of the
   // last item added. Resize the form, adding some bottom spacing.
   Size size = new Size(this.Size.Width, vertPos + 27);
   this.ClientSize = size;
  }

No surprises, right? I call this method from the constructor, immediately after loading the dynamic controls and calling InitializeComponent.

Summary

And there you have it! Not only did I find that this saved me a great deal of work, but deployment became simple. Likewise, future maintenance and modification was a snap. All I had to do was change an XML file, and the next time the form was loaded, all of the items would be placed in their appropriate location, with the appropriately sized dialog. Simple and effective, my favorite kind of software.

blog comments powered by Disqus
.NET ARTICLES

- .Net 4.5 Brings Changes
- Understanding Events in VB.NET
- Objects, Properties, Events and Methods in V...
- Install Visual Web Developer Express 2010
- Microsoft Gadgeteer an Open Source Alternati...
- Best DotNetNuke Modules
- Facebook Image Viewer in Visual Basic
- Murach`s ADO.NET 4 Database Programming with...
- 5 Must Have Visual Studio 2010 Extensions
- Dynamic Web Applications with ASP.NET Mono u...
- PDFSharp: HTML to PDF in ASP.NET 3.5 using V...
- Using the PDFSharp Library in ASP.NET 3.5 wi...
- Sending Email in ASP.NET 3.5 using VB.NET wi...
- ASP.NET 3.5 Role Based Security and User Aut...
- Creating ASP.NET Login Web Pages and Basic C...

ASP Web Hosting ASP.Net Web Hosting Windows Web Hosting
ASP Free Forums 
 RSS  Tutorials RSS
 RSS  Forums RSS
 RSS  All Feeds
Site Map 
Request Media Kit
Write For Us Get Paid 
Weekly Newsletter
 
Developer Updates  
Free Website Content 
Privacy Policy 
Support 


© 2003-2012 by Developer Shed. All rights reserved. DS Cluster 10 - Follow our Sitemap
Most Popular Topics
All ASP.Net Tutorials