Code re-use

This article was originally published in VSJ, which is now part of Developer Fusion.
Virtually every application that we write to go further than our own PC ought to have an “about” box. Usually there are two important pieces of information displayed here – the version of the application, and the owner of the copyright. It only takes two minutes (if that) to knock up an about box – just create a form, type in some text and add a means of invoking it.

Doing this creates one of the longest-standing enemies of the software engineer – duplicated information. The version number in particular is likely to change on a regular basis, and it’s necessary to put the correct version number in the assembly metadata, not least because this ensures it is compiled as a Win32 VS_VERSIONINFO resource (which can be viewed in the properties window in Explorer). What are the odds that in the rush to release a bug-fixed version of the software on a Friday afternoon you forget to update it in the about box as well?

This article describes an about form written in C#, which can simply be copied into any project, together with a means of invoking it. It will automatically pick up the application name and version information from the assembly metadata using reflection, and will adopt the icon of its parent window. This means that you have to do virtually nothing specific to the project.

The first step in creating this reusable about form is to create the form itself – see Figure 1.

Figure 1
Figure 1

All that has been placed on this form is a PictureBox (highlighted), a label and an OK button. The corresponding objects are defined by Visual Studio as follows:

private System.Windows.Forms.
					PictureBox IconBox;
private System.Windows.Forms.Label
					TextArea;
private System.Windows.Forms.Button
					OKButton;
All of the properties for these items are as set by Visual Studio initially with the exception of:
this.TextArea.TextAlign =
	ContentAlignment.MiddleLeft;
this.IconBox.SizeMode =
	PictureBoxSizeMode.CenterImage;
Having established the Form and its contents, the next step is to add code to populate the form to the Form_Load handler. The first thing to do is to get the Icon from the parent window and use it for both the about box window icon, and to fill the PictureBox control:
Icon i = Owner.Icon;
this.Icon = i;
IconBox.Image = i.ToBitmap();
Note the use of Icon.ToBitmap to convert the icon to a GDI+ bitmap that the PictureBox is capable of displaying. Next we need to populate the information in the text area. To do this we need to find out the application name, version and copyright. This is where the System.Reflection namespace comes in handy, more specifically the Assembly class and the AssemblyName class which stores the name and version information.
Assembly ThisAssembly =
	Assembly.GetExecutingAssembly();

AssemblyName ThisAssemblyName =
	ThisAssembly.GetName();
The version number of a Win32 file or .NET assembly is in four bytes, specified as <major version>.<minor version>.<build number>.<revision>. The last two bytes were seldom used, so .NET offers the ability to set these to default values, which will be different for each build. The major and minor numbers are the interesting bit, so this line reformats it to a string in standard form:
string FriendlyVersion =
	string.Format(
	“{0:D}-{1:D2}” ,
	ThisAssemblyName.Version.Major,
	ThisAssemblyName.Version.Minor );
Having extracted the version number, we now want to get hold of the assembly title and copyright strings. We could just use the assembly name from the AssemblyName class, but this is the filename for the assembly and thus is likely to be less descriptive than the Title. These can be obtained by searching the assembly attributes for the specific attributes we’re looking for:
Array Attributes = 
	ThisAssembly.GetCustomAttributes(
		false );

string Title = “Unknown Application”;
string Copyright =
	“Unknown Copyright”;

foreach ( object o in Attributes )
{
	if ( o is AssemblyTitleAttribute )
	{
		Title = ((
	AssemblyTitleAttribute)o).Title;
	}
	else if ( o is
		AssemblyCopyrightAttribute )
	{
	Copyright =
(AssemblyCopyrightAttribute)o).
							Copyright;
	}
}
The content of the form is completed by formatting this information (along with the full version number) as a string for display.
this.Text = “About “ + Title;
StringBuilder sb =
	new StringBuilder(“”);
sb.Append( Title );
sb.Append( “ Version “ );
sb.Append( FriendlyVersion );
sb.Append( “ (“ );
sb.Append(
ThisAssemblyName.Version.ToString());
sb.Append( “)\n\n” );
sb.Append( Copyright );
TextArea.Text = sb.ToString();
It’s not very important when adding a few strings together, but the use of a StringBuilder above is much more efficient than just adding the individual strings together (which creates a new object for each addition operation!).

The form has only one event handler, to deal with when the user clicks the “OK” button. This just closes the form:

private void OKButton_Click(object
	sender, System.EventArgs e)
{
	Close();
}
Finally, we want to make it easy to remember how to use this class. The simplest way to do this is to make the default constructor private, thus preventing the creation of an AboutForm object from outside the class. We must then implement an internal static method to create and display an AboutForm on behalf of the application – if there is only a single static method available to the application, and no constructor, it makes it difficult to use the class incorrectly. Making it internal ensures that this class is placed in the same assembly as the main application form – important since we call the Assembly.GetExecutingAssembly function.
internal static void ShowAboutForm(
	IWin32Window Owner )
{
	System.Diagnostics.Debug.Assert(
		( Owner != null ) ||
	!( Owner is IWin32Window ) ,
	“AboutForm.AboutForm.ShowAboutForm 
	MUST be supplied with a valid
	parent window” );
	AboutForm form = new AboutForm();
	form.ShowDialog( Owner );
}
Just to be on the safe side, we use the Assert function of the Debug class to make sure that a valid owner parameter is passed.

The complete code for the AboutForm class can be downloaded and is shown below:

using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Reflection;
using System.Text;

namespace AboutForm
{
	/// <summary>
	/// Summary description for AboutForm.
	/// </summary>
	public class AboutForm : System.Windows.Forms.Form
	{
		private System.Windows.Forms.PictureBox IconBox;
		private System.Windows.Forms.Label TextArea;
		private System.Windows.Forms.Button OKButton;
		/// <summary>
		/// Required designer variable.
		/// </summary>
		private System.ComponentModel.Container components = null;

		private AboutForm()
		{
			//
			// Required for Windows Form Designer support
			//
			InitializeComponent();

			//
			// TODO: Add any constructor code after
			// InitializeComponent call
			//
		}

		/// <summary>
		/// Clean up any resources being used.
		/// </summary>
		protected override void Dispose( bool disposing )
		{
			if( disposing )
			{
				if(components != null)
				{
					components.Dispose();
				}
			}
			base.Dispose( disposing );
		}

		#region Windows Form Designer generated code
		/// <summary>
		/// Required method for Designer support – do not modify
		/// the contents of this method with the code editor.
		/// </summary>
		private void InitializeComponent()
		{
			this.IconBox = new System.Windows.Forms.PictureBox();
			this.TextArea = new System.Windows.Forms.Label();
			this.OKButton = new System.Windows.Forms.Button();
			this.SuspendLayout();
			//
			// IconBox
			//
			this.IconBox.Location = new System.Drawing.Point(24, 24);
			this.IconBox.Name = “IconBox”;
			this.IconBox.Size = new System.Drawing.Size(56, 56);
			this.IconBox.SizeMode = PictureBoxSizeMode.CenterImage;
			this.IconBox.TabIndex = 0;
			this.IconBox.TabStop = false;
			//
			// TextArea
			//
			this.TextArea.Location = new System.Drawing.Point(96, 24);
			this.TextArea.Name = “TextArea”;
			this.TextArea.Size = new System.Drawing.Size(208, 56);
			this.TextArea.TabIndex = 1;
			this.TextArea.Text = “label1”;
			this.TextArea.TextAlign = ContentAlignment.MiddleLeft;
			//
			// OKButton
			//
			this.OKButton.Location = new System.Drawing.Point(120, 96);
			this.OKButton.Name = “OKButton”;
			this.OKButton.TabIndex = 2;
			this.OKButton.Text = “OK”;
			this.OKButton.Click += new System.EventHandler(this.OKButton_Click);
			//
			// AboutForm
			//
			this.AcceptButton = this.OKButton;
			this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
			this.ClientSize = new System.Drawing.Size(320, 128);
			this.Controls.AddRange(new System.Windows.Forms.Control[] {
				this.OKButton,
				this.TextArea,
				this.IconBox});
			this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog;
			this.MaximizeBox = false;
			this.MaximumSize = new System.Drawing.Size(326, 160);
			this.MinimizeBox = false;
			this.MinimumSize = new System.Drawing.Size(326, 160);
			this.Name = “AboutForm”;
			this.SizeGripStyle = System.Windows.Forms.SizeGripStyle.Hide;
			this.Text = “About: “;
			this.Load += new System.EventHandler(this.AboutForm_Load);
			this.ResumeLayout(false);

		}
		#endregion

		private void AboutForm_Load(object sender, System.EventArgs e)
		{
			Icon i			= Owner.Icon;
			this.Icon		= i;
			IconBox.Image	= i.ToBitmap();

			Assembly ThisAssembly = Assembly.GetExecutingAssembly();

			AssemblyName ThisAssemblyName = ThisAssembly.GetName();

			string FriendlyVersion = string.Format( “{0:D}-{1:D2}” ,
				ThisAssemblyName.Version.Major ,
				ThisAssemblyName.Version.Minor );


			Array Attributes = ThisAssembly.GetCustomAttributes( false );

			string Title	= “Unknown Application”;
			string Copyright = “Unknown Copyright”;
			foreach ( object o in Attributes )
			{
				if ( o is AssemblyTitleAttribute )
				{
					Title = ((AssemblyTitleAttribute)o).Title;
				}
				else if ( o is AssemblyCopyrightAttribute )
				{
					Copyright = ((AssemblyCopyrightAttribute)o).Copyright;
				}
			}

			this.Text = “About “ + Title;

			StringBuilder sb = new StringBuilder(“”);

			sb.Append( Title );
			sb.Append( “ Version “ );
			sb.Append( FriendlyVersion );
			sb.Append( “ (“ );
			sb.Append( ThisAssemblyName.Version.ToString()	);
			sb.Append( “)\n\n” );
			sb.Append( Copyright );

			TextArea.Text = sb.ToString();
		}

		private void OKButton_Click(object sender, System.EventArgs e)
		{
			Close();
		}

		internal static void ShowAboutForm( IWin32Window Owner )
		{
System.Diagnostics.Debug.Assert( ( Owner != null ) ||
!( Owner is IWin32Window ) ,
“AboutForm.AboutForm.ShowAboutForm MUST be supplied
with a valid parent window” );


			AboutForm form = new AboutForm();

			form.ShowDialog( Owner );
		}
	}
}
The about form can be invoked from a test application as follows:
AboutForm.AboutForm.ShowAboutForm(
	this );
To test the form, the following assembly attributes were used:
[assembly: AssemblyTitle(
	“About Form Test App”)]
[assembly: AssemblyCopyright(
	“©2004 Ian Stevenson”)]
[assembly: AssemblyVersion(“1.01.*”)]
When the test button is pressed, the about form is invoked, and the result is shown in Figure 2.

Figure 2
Figure 2

AboutForm is designed to be simply included in the main assembly of an application. This means that it can be used in projects which create a single EXE file without adding an annoying DLL that must be distributed with it. If desired, this could be improved to form part of a utility library but the Assembly.GetExecutingAssembly must be replaced with an appropriate call such as Assembly.GetEntryAssembly. It could also be modified to display additional information, such as the version number of all loaded assemblies (which can be enumerated with Assembly.GetReferencedAssemblies).

This class demonstrates some of the reflection capabilities built into the .NET framework, and can be dropped into any application to provide an instant about box, with only one line of code needed to invoke it, and without duplicating any information across multiple code files.


Ian Stevenson has been developing Windows software professionally for almost 10 years, in areas ranging from WDM device drivers through to rapid-prototyping of enterprise systems. He currently works as a consultant for The Generics Group (www.genericsgroup.com) and can be contacted at [email protected].

You might also like...

Comments

About the author

Ian Stevenson United Kingdom

Ian Stevenson has been developing Windows software professionally for 10 years, in areas ranging from WDM device drivers through to rapid-prototyping of enterprise systems. Ian currently works a...

Interested in writing for us? Find out more.

Contribute

Why not write for us? Or you could submit an event or a user group in your area. Alternatively just tell us what you think!

Our tools

We've got automatic conversion tools to convert C# to VB.NET, VB.NET to C#. Also you can compress javascript and compress css and generate sql connection strings.

“Walking on water and developing software from a specification are easy if both are frozen.” - Edward V Berard