8.3 Windows Forms Development
The Form
class in the System.Windows.Forms namespace represents a standard
window that contains Windows controls. In this section, we walk you
through the development of a Windows Forms application and introduce
you to the rich set of Windows controls that can be used on a Windows
Form.
8.3.1 Windows Forms Application
All Windows
Forms applications start out with a derived class from the
System.Windows.Forms.Form class. A simple Windows Forms application
looks like the following:public class MyForm : System.Windows.Forms.Form
{
public MyForm( )
{
Text = "Hello World";
}
public static void Main( )
{
System.Windows.Forms.Application.Run(new MyForm( ));
}
}
Basically, you define a class MyForm, which derives from the
System.Windows.Forms.Form class. In the constructor of MyForm class,
you set the Text property of the Form to Hello
World. That's all there is to it.
The static Main function is the entry point to all applications. In
the entry-point function, you call the static method
Application.Run, which starts the message loop for
the application. Because you also pass a form-derived object MyForm
to the Run method, what we have is a Windows Forms application.You can also include references to the namespaces to avoid typing the
fully qualified name of classes such as System.Windows.Forms.Form or
System.Windows.Forms.Application. To do this, include the following
line at the beginning of the source file and omit the
System.Windows.Forms prefix to your class names:using System.Windows.Forms;
To build the previously listed application, we use the command-line
C# compiler. Notice that the target type is an executable, not a DLL,
as when we compiled our web service PubsWS (type this command all on
one line):[3][3] You can also compile the simple file with
csc MyForm.cs but it's better to
know how to specify the target type and the references that your
source relies on.
csc /t:winexe
/r:system.dll
/r:System.Windows.Forms.dll
MyForm.cs
The standard Form object that is shown on the screen
doesn't do much; however, it demonstrates the
simplicity of creating a Windows Forms application. You can exit the
application by clicking on the Close button of the Control Box on the
titlebar of the form. When you do this, a quit message is injected
into the message loop, and, by default, it is processed and the
Application instance will stop.
8.3.2 Windows Controls
Windows Forms
applications can be much more involved than the application shown
earlier; however, the underlying concepts are the same. In this
section, we introduce you to the rich set of Windows controls that
you can use on your form, as well as data binding to some of these
controls. We also show how event handling works in Windows Forms
applications.
8.3.2.1 Adding controls onto the form
First of all,
we
create and add the control to the Controls
collection of the form:Button btn1 = new Button( );
btn1.Text = "Click Me";
this.Controls.Add(btn1);
Adding other types of controls follows the same convention. There are
three basic steps:Create the control.Set up the control's properties.Add the control to the Controls collection of the Form object.
8.3.2.2 Binding the event handler
This is swell, but what does
the application do when you click on the button? Nothing. We have not
yet bound the event handler to the button's event.
To do that, we first have to create the event handler. An event
handler is nothing more than a normal function, but it always has two
parameters:
object
and EventArgs. The object parameter is filled with event originator.
For example, if you clicked on a button on a form, causing the
Click event to fire, the object parameter to the
event handler will point to the button object that you actually
clicked on. The EventArgs object represents the event itself. Using
the same example, the EventArgs parameter will be the
Click event with event arguments, such as the
coordinates of the mouse, which button got clicked and so on. The
following code excerpt shows the event handler for the
Click event on a button:void btn1_onclick(Object sender, EventArgs e)
{
Text = "Sender: " + sender.ToString( ) + " - Event: " + e.ToString( );
}
That event handler changes the title of the form each time the button
is clicked. Now that we have created the event handler, we assign it
to the event Click of the button:btn1.Click += new EventHandler(btn1_onclick);
That line of code constructs an EventHandler
object from the method we passed in and passes the newly created
object to the Click event of the button. We basically register a
callback function when Click happens. (You may want to review Chapter 2 where we discuss delegates.) Here is the complete
example:using System;
using System.Windows.Forms;
public class MyForm : Form
{
void btn1_onclick(object sender, EventArgs e)
{
Text = "Sender: " + sender.ToString( ) +
" - Event: " + e.ToString( );
}
public MyForm( )
{
Text = "Hello World";
Button btn1 = new Button( );
btn1.Text = "Click Me";
this.Controls.Add(btn1);
btn1.Click += new EventHandler(btn1_onclick);
}
public static void Main( )
{
Application.Run(new MyForm( ));
}
}
When the user clicks on the button, our event handler is called
because we've already registered for the click
event. It is possible to add more than one event handler to a single
event by repeating the assignment line for other event handlers. All
handlers that are registered to handle the event are executed in the
order in which they're registered. For example, we
add the following function to the code:void btn1_onclick2(object sender, EventArgs e)
{
MessageBox.Show(String.Format("Sender: {0} - Event: {1}",
sender.ToString(), e.ToString( )));
}
and one more line to associate this function as an event handler in
the case button btn1 is clicked: btn1.Click += new EventHandler(btn1_onclick);
btn1.Click += new EventHandler(btn1_onclick2);
The result is as expected. Both event handlers get called.You can also easily remove the event handler. Replace
+= with -=:btn1.Click -= new EventHandler(btn1_onclick);
Binding event handlers to events at runtime provides the developer
with unlimited flexibility. You can programmatically bind different
event handlers to a control based on the state of the application.
For example, a button click can be bound to the
update function
when the data row exists or to the insert function
when it's a new row.As you can see, the process of binding event handlers to events is
the same in Windows Forms as in Web Forms. This consistency of
programming model is possibly due their shared substrate, the CLR in
both environments.
8.3.2.3 Data binding
There are two
kinds of data binding in Windows Forms. The first involves simple
Windows controls such as Label, TextBox, and
Button. These simple controls can be
bound to a single value only. The second involves Windows controls
that can manage lists of data such as ListBox, ComboBox, and
DataGrid. These list controls are bound to lists of values.Let's look at the first type of data binding. In the
following example, we bind text boxes to fields in a table from the
Pubs database. We extend the simple Hello, World
Windows Form application to include data access and data binding.The first thing is to obtain the data from the database.
(It's a good time to review ADO.NET in Chapter 5 if you did not read the book in the order
presented.) Let's take a look at the following
example of a C# file:using System;
using System.Windows.Forms;
using System.Data;
using System.Data.OleDb;
public class MyForm : Form
{
public static void Main( )
{
Application.Run(new MyForm( ));
}
private TextBox m_txtFirstName, m_txtLastName, m_txtPhone;
private Button m_btnPrev, m_btnNext;
private CurrencyManager m_lm;
private DataSet m_ds;
public MyForm( )
{
Text = "Simple Controls Data Binding";
// Create the first name text box.
m_txtFirstName = new TextBox( );
m_txtFirstName.Dock = DockStyle.Top;
// Create the last name text box.
m_txtLastName = new TextBox( );
m_txtLastName.Dock = DockStyle.Top;
// Create the phone text box.
m_txtPhone = new TextBox( );
m_txtPhone.Dock = DockStyle.Top;
// Add both first name and last name to the panel1.
Panel panel1 = new Panel( );
panel1.Dock = DockStyle.Left;
panel1.Controls.Add(m_txtFirstName);
panel1.Controls.Add(m_txtLastName);
panel1.Controls.Add(m_txtPhone);
// Add panel1 to the left of the form.
this.Controls.Add(panel1);
// Create the up button and bind click to event handler.
m_btnPrev = new Button( );
m_btnPrev.Text = "Up";
m_btnPrev.Dock = DockStyle.Top;
m_btnPrev.Click += new EventHandler(btnPrev_onclick);
// Create the down button and bind click to event handler.
m_btnNext = new Button( );
m_btnNext.Text = "Down";
m_btnNext.Dock = DockStyle.Top;
m_btnNext.Click += new EventHandler(btnNext_onclick);
// Add both the up and down buttons to panel2.
Panel panel2 = new Panel( );
panel2.Dock = DockStyle.Right;
panel2.Width = 50;
panel2.Controls.Add(m_btnNext);
panel2.Controls.Add(m_btnPrev);
// Add panel2 to the right of the form.
this.Controls.Add(panel2);
// Fill the dataset with the authors table from Pubs database.
m_ds = new DataSet( );
string oSQL = "select au_fname, au_lname, phone from authors";
string oConnStr =
"provider=sqloledb;server=(local);database=pubs;Integrated Security=SSPI";
OleDbDataAdapter oDA = new OleDbDataAdapter(oSQL, oConnStr);
oDA.Fill(m_ds, "tbl");
// Bind the Text property of last name text box to field au_lname.
m_txtLastName.DataBindings.Add("Text",
m_ds.Tables["tbl"],
"au_lname");
// Bind the Text property of first name text box to field au_fname.
m_txtFirstName.DataBindings.Add("Text",
m_ds.Tables["tbl"],
"au_fname");
// Bind the Text property of phone text box to field phone.
m_txtPhone.DataBindings.Add("Text",
m_ds.Tables["tbl"],
"phone");
// Obtain the list manager from the binding context.
m_lm = (CurrencyManager)this.BindingContext[m_ds.Tables["tbl"]];
}
protected void btnNext_onclick(object sender, EventArgs e)
{
// Move the position of the list manager.
m_lm.Position += 1;
}
protected void btnPrev_onclick(object sender, EventArgs e)
{
// Move the position of the list manager.
m_lm.Position -= 1;
}
}
UI controls derive from the Control class, and inherit the
DataBindings property (which is of type ControlsBindingCollection).
This DataBindings property contains a collection of Binding objects
that is used to bind any property of the control to a field in the
list data source.To bind a simple control to a record in the data source, we can add a
Binding object to the DataBindings collection for the control using
the following syntax:controlName.DataBindings.Add("Property", datasource, "columnname");
where controlName is name of the simple control
that you want to perform the data binding. The
Property item specifies the property of the simple
control you want to be bound to the data in column
columnname.The C# source file shows how to bind the Text property of the TextBox
control m_txtLastName to the
au_lname column of Authors table of the DataSet
m_ds, as well as m_txtFirstName
and m_txtPhone to columns
au_fname and phone.To traverse the list in the data source, we will use the
BindingManagerBase object. The following excerpt of code shows you
how to get to the binding manager for the data source bound to the
controls on the form. In this case, because the data is of list type,
the binding manager returned from the BindingContext is a
CurrencyManager:[4][4] If the data source returns only one
data value, the BindingManagerBase actually points to an object of
type PropertyManager. When the data source returns a list of data
value, the type is CurrencyManager.
// Obtain the list manager from the binding context.
m_lm = (CurrencyManager)this.BindingContext[m_ds.Tables["tbl"]];
To demonstrate the use of BindingManagerBase to traverse the data
source, we add two buttons onto the form, btnNext and
btnPrev. We then bind the two
buttons' click events
to btnNext_onclick and
btnPrev_onclick, respectively:protected void btnNext_onclick(object sender, EventArgs e)
{
m_lm.Position += 1;
}
protected void btnPrev_onclick(object sender, EventArgs e)
{
m_lm.Position -= 1;
}
As you use BindingManagerBase to manage the position of the
listin this case, the current record in the Authors
tablethe TextBox controls will be updated with new values.
Figure 8-3 illustrates the user interface for the
simple controls data-binding example.
Figure 8-3. Simple controls data binding

Now let's take a look at the other type of data
binding. In this example, we will bind the whole authors table to a
DataGrid:using System;
using System.Windows.Forms;
using System.Data;
using System.Data.OleDb;
public class MyForm : Form
{
public static void Main( )
{
Application.Run(new MyForm( ));
}
private Button m_btn1;
private TextBox m_txt1;
private DataGrid m_dataGrid1;
public MyForm( )
{
Text = "Hello World";
m_txt1 = new TextBox( );
m_txt1.Text = "select * from authors";
m_txt1.Dock = DockStyle.Top;
this.Controls.Add(m_txt1);
m_btn1 = new Button( );
m_btn1.Text = "Retrieve Data";
m_btn1.Dock = DockStyle.Top;
m_btn1.Click += new EventHandler(btn1_onclick);
this.Controls.Add(m_btn1);
m_dataGrid1 = new DataGrid( );
m_dataGrid1.Dock = DockStyle.Fill;
this.Controls.Add(m_dataGrid1);
this.AcceptButton = m_btn1;
}
protected void btn1_onclick(object sender, EventArgs e)
{
try {
DataSet ds = new DataSet( );
string oConnStr =
"provider=sqloledb;server=(local);database=pubs;Integrated Security=SSPI";
OleDbDataAdapter oDA =
new OleDbDataAdapter(m_txt1.Text, oConnStr);
oDA.Fill(ds, "tbl");
/* You can specify the table directly like this
*
* m_dataGrid1.DataSource = ds.Tables["tbl"];
*
* or specify the datasource and the table separately
* like this:
*/
m_dataGrid1.DataSource = ds;
m_dataGrid1.DataMember = "tbl";
} catch(Exception ex) {
MessageBox.Show("An error has occured. " + ex.ToString( ));
}
}
}
Data binding for controls of type List in Windows Forms is similar to
that of Web Forms. However, you don't have to call
the DataBind method of the control. All you have to do is set the
DataSource property of the UI control to the data source. The data
source then has to implement the IEnumerable or IListSource (or
IList, which implements IEnumerable) interfaces. As it turns out,
there are hundreds of classes that can be used as data source,
including DataTable, DataView, DataSet, and all array or collection type of
classes.The process for DataGrid data binding is also simple: just set the
DataSource property of the DataGrid object to the data source, and
you're all set. We name the table
tbl when we add it to DataSet with the data
adapter's Fill( ) method; therefore, the following
line of code just indexes into the collection of tables in the
DataSet using the table name:m_dataGrid1.DataSource = ds.Tables["tbl"];
If the data source contains more than one table, you will also have
to set the DataMember property of the DataGrid to the name of the
table you want the control to bind to:m_dataGrid1.DataSource = ds;
m_dataGrid1.DataMember = "tbl";
The results of binding the two tables to the DataGrid are shown in
Figure 8-4 and Figure 8-5.
Figure 8-4. Binding the authors table to the DataGrid

Figure 8-5. Binding the titles table to the DataGrid

8.3.2.4 Arranging controls
After
adding
controls onto the form and setting the event handlings and data
bindings, you are fully functional. However, for the visual aspect of
your application, you might want to change the layout of the controls
on the form. You can do this by setting up physical locations of
controls with respect to the container to which the controls
belong,[5] or you can
dock or anchor the controls inside
the container.[5] This is similar to VB programming. Controls
initially have absolute positions on the form, but they can be
programmatically moved and resized while the application is
running.
Docking of a control is very simple. You can dock your control to the
top, left, right, or bottom of the container. If you dock your
control to the top or the bottom, the width of your control will span
the whole container. On the same token, if you dock the control to
the left or the right, its height will span the height of the
container. You can also set the Dock property to
DockStyle.Fill, which will adjust the control to
fill the container.The anchoring concept is a bit different. You can anchor your control
inside your container by tying it to one or more sides of the
container. The distance between the container and the control remains
constant at the anchoring side.You can also use a combination of these techniques by grouping
controls into multiple panels and then organizing these panels on the
form. With docking and anchoring, there is no need to
programmatically calculate and reposition or resize controls on the
form.If you've ever done Java Swing development, you
might notice that the current Microsoft .NET Windows Forms framework
is similar to JFC with respect to laying out controls; however, it is
missing the Layout Manager classes such as GridLayout and FlowLayout
to help lay out controls in the containers. We hope that in future
releases of the .NET SDK, some sort of layout manager will be
included.[6] Currently, if you are writing your Windows Forms
application using Visual Studio .NET, you will have more than enough
control over the layout of controls on your form.[6] Microsoft provides some interesting
examples of how you can develop layout managers. The URL is
http://msdn.microsoft.com/library/en-us/dndotnet/html/custlaywinforms.asp.
8.3.3 Visual Inheritance
Visual
inheritance was never before possible on the Windows platform using
Microsoft technologies. Prior to the release of Microsoft .NET (and
we are only talking about VB development here), developers used VB
templates to reuse a form. This is basically a fancy name for
copy-and-paste programming. Each copy of a VB template can be
modified to fit the current use. When the template itself is
modified, copies or derivatives of the template are not updated. You
either have to redo each one using copy-and-paste or just leave them
alone.With the advent of Microsoft .NET, where everything is now
object-oriented, you can create derived classes by inheriting any
base class. Since a form in Windows Forms application is nothing more
than a derived class of the base Form class, you can actually derive
from your Form class to create other Form classes.This is extremely good for something like a wizard-based application,
where each of the forms looks similar to the others. You can create
the common look-and-feel form as your base class and then create each
of the wizard forms by deriving from this base class.
8.3.4 MDI Applications
There are two main styles of user interfaces for Windows-based
applications: Single Document Interface (SDI)
and Multiple Document Interface
(MDI).[7]
For SDI applications, each instance of the application can have only
one document. If you would like more than one open document, you must
have multiple instances of the application running. MDI, on the other
hand, allows multiple documents to be open at one time in one
instance of the application. Another good thing about MDI application
is that, depending of the type of document currently open, the main
menu for the application changes to reflect the operations that you
can perform on the document.[7] Other styles are Explorer, Wizard, etc., but
we are not going discuss all of them in this book.
While it is easy to implement both SDI and MDI applications using the
Windows Forms architecture, we only show an example of MDI in this
section.MDI application architecture borrows the same pattern of Windows
Forms architecture. Basically, you have one form acting as the
container form and other forms acting as child forms.The
Form
class provides a number of properties and methods to help in the
development of MDI applications, including
IsMdiContainer,
IsMdiChild,
MdiParent,
MdiChildren,
ActiveMdiChild, and
LayoutMdi( ).The first thing we want to show you is the bare minimum main form for
our MDI application:using System;
using System.Windows.Forms;
public class MdiMainForm : Form
{
public MdiMainForm( )
{
this.Text = "MDI App for Text and Images";
// This is the MDI container.
this.IsMdiContainer = true;
}
public static void Main(string[] args)
{
Application.Run(new MdiMainForm( ));
}
}
Believe it or not, this is basically all you have to do for the main
form of the MDI application! For each of the child forms that we will
be spawning from this main form, we will set its MdiParent property
to point to this main form.In the following code excerpt, we load a child form of the main form: . . .
Form a = new Form( );
a.MdiParent = this;
a.Show( );
Form b = new Form( );
b.MdiParent = this;
b.Show( );
. . .
Again, all it takes to spawn a child form of the MDI application is a
single property, MdiParent. In your application, you will replace the
type for forms a and b with
your own form classes. (As shown later in this chapter, we have
ImageForm and TextForm.)One other point that makes MDI applications interesting is the fact
that there is one set of main menus and it is possible for child
forms to merge their menus with the MDI frame. We also show you how
to incorporate menus into our main MDI form, and later in this
section, how the child form's menus are merged to
this main menu.The whole menu architecture in Windows Forms application revolves
around two classes:[8] MainMenu and MenuItem. MainMenu represents the complete
menu for the whole form. A MenuItem represents one menu item;
however, each menu item contains child menu items in the MenuItems
property. Again, you start to see the pattern of
controls and
containers here,
too, although the menu classes are not controls.[9] For example, if we are to have two top-level menus (e.g.,
File and Window), then basically, we have to set up the MainMenu
object so that it contains two menu items in its MenuItems property.
We can do so using the Add method of the MenuItems property to insert
menu items dynamically into the collection. If we know ahead of time
the number of menu items, we can declaratively assign an array of
menu items to this property. Recursively, we can have the File or the
Window menu items contain a number of sub-menu items in their
MenuItems property the same way we set up the main menu.[8] The third class is ContextMenu,
but we won't discuss it in the scope of this
book.[9] MainMenu, MenuItem and ContextMenu derive from Menu.
Let's take a look at the source code:using System;
using System.Windows.Forms;
public class MdiMainForm : Form
{
// Menu Items under File Menu
private MenuItem mnuOpen, mnuClose, mnuExit;
// Menu Items under the Window Menu
private MenuItem mnuCascade, mnuTileHorz, mnuTileVert,
mnuSeparator, mnuCloseAll, mnuListMDI;
// The File and Window Menus
private MenuItem mnuFile, mnuWindow;
// The Main Menu
private MainMenu mnuMain;
public MdiMainForm( )
{
this.Text = "MDI App for Text and Images";
// File Menu Item
mnuFile = new MenuItem( );
mnuFile.Text = "&File";
mnuFile.MergeOrder = 0;
// Window Menu Item
mnuWindow = new MenuItem( );
mnuWindow.MergeOrder = 2;
mnuWindow.Text = "&Window";
// Main Menu contains File and Window
mnuMain = new MainMenu( );
mnuMain.MenuItems.AddRange(
new MenuItem[2] {mnuFile, mnuWindow});
// Assign the main menu of the form.
this.Menu = mnuMain;
// Menu Items under File menu
mnuOpen = new MenuItem( );
mnuOpen.Text = "Open";
mnuOpen.Click += new EventHandler(this.OpenHandler);
mnuClose = new MenuItem( );
mnuClose.Text = "Close";
mnuClose.Click += new EventHandler(this.CloseHandler);
mnuExit = new MenuItem( );
mnuExit.Text = "Exit";
mnuExit.Click += new EventHandler(this.ExitHandler);
mnuFile.MenuItems.AddRange(
new MenuItem[3] {mnuOpen, mnuClose, mnuExit});
// Menu Items under Window menu
mnuCascade = new MenuItem( );
mnuCascade.Text = "Cascade";
mnuCascade.Click += new EventHandler(this.CascadeHandler);
mnuTileHorz = new MenuItem( );
mnuTileHorz.Text = "Tile Horizontal";
mnuTileHorz.Click += new EventHandler(this.TileHorzHandler);
mnuTileVert = new MenuItem( );
mnuTileVert.Text = "Tile Vertical";
mnuTileVert.Click += new EventHandler(this.TileVertHandler);
mnuSeparator = new MenuItem( );
mnuSeparator.Text = "-";
mnuCloseAll = new MenuItem( );
mnuCloseAll.Text = "Close All";
mnuCloseAll.Click += new EventHandler(this.CloseAllHandler);
mnuListMDI = new MenuItem( );
mnuListMDI.Text = "Windows . . . ";
mnuListMDI.MdiList = true;
mnuWindow.MenuItems.AddRange(
new MenuItem[6] {mnuCascade, mnuTileHorz, mnuTileVert,
mnuSeparator, mnuCloseAll, mnuListMDI});
// This is the MDI container.
this.IsMdiContainer = true;
}
public static void Main(string[] args)
{
Application.Run(new MdiMainForm( ));
}
. . .
}
(Note that this source-code listing is completed in the event
handlers listing that follows.)We first declare all the menu items that we would like to have, along
with one MainMenu instance in the class scope. In the
main-application constructor, we then instantiate the menu items and
set their Text properties. For the two top-level menu items, we also
set the MergeOrder property so that we can
control where the child forms will merge their menu to the main form
menu. In this case, we've set up the File menu to be
of order 0 and the Window menu to be of order
2. As you will see later, we will have the child
menu's MergeOrder set to 1 so
that it is between the File and Window menus.We then add both the File and the Window menus to the main
menu's MenuItems collection by using the
AddRange(
) method: mnuMain.MenuItems.AddRange(
new MenuItem[2] {mnuFile, mnuWindow});
Note that at this time, the File and Window menus are still empty. We
then assign mnuMain to the MainMenu property of the Form object. At
this point, we should be able to see the File and Window menus on the
main form; however, there is no drop-down yet.Similar to how we create menu items and add them to the main
menu's MenuItems collection, we add menu items into
both the File and Window menu. However, one thing is different. We
also bind event handlers to the Click events of the menu items.
Let's take one example, the Open menu item:mnuOpen = new MenuItem( );
mnuOpen.Text = "Open";
mnuOpen.Click += new EventHandler(this.OpenHandler);
Note that the syntax for binding the event handler OpenHandler to the
event Click of the MenuItem class is similar to any other
event binding that we've seen so far. Of course, we
will have to provide the function body in the MDI main class.While we are talking about menus, another interesting piece of
information is the mnuListMDI MenuItem at the end of the Window menu.
We set the MdiList property of this MenuItem to
true, as shown in the following code fragment, so
that it will automatically show all the opened documents inside the
MDI application.mnuListMDI.Text = "Windows . . . ";
mnuListMDI.MdiList = true;
See Figure 8-6 for an example of how this feature
shows up at runtime.
Figure 8-6. MdiList autogenerated menu entries

The following code is for the event handlers that
we've set up for various menu items in this main
form (this completes the MdiMainForm class listing):protected void OpenHandler(object sender, EventArgs e)
{
//MessageBox.Show("Open clicked");
OpenFileDialog openFileDlg = new OpenFileDialog( );
if(openFileDlg.ShowDialog( ) == DialogResult.OK)
{
try
{
String sFN = openFileDlg.FileName;
String sExt = sFN.Substring(sFN.LastIndexOf("."));
sExt = sExt.ToUpper( );
//MessageBox.Show(sFN + " " + sExt);
if(sExt == ".BMP" || sExt == ".JPG" || sExt == ".GIF")
{
ImageForm imgForm = new ImageForm( );
imgForm.SetFileName(sFN);
imgForm.MdiParent = this;
imgForm.Show( );
}
else if(sExt == ".TXT" || sExt == ".VB" || sExt == ".CS")
{
TextForm txtForm = new TextForm( );
txtForm.SetFileName(sFN);
txtForm.MdiParent = this;
txtForm.Show( );
}
else
{
MessageBox.Show("File not supported.");
}
}
catch(Exception ex)
{
MessageBox.Show ("Error: " + ex.ToString( ));
}
}
}
protected void CloseHandler(object sender, EventArgs e)
{
if(this.ActiveMdiChild != null)
{
this.ActiveMdiChild.Close( );
}
}
protected void ExitHandler(object sender, EventArgs e)
{
this.Close( );
}
protected void CascadeHandler(object sender, EventArgs e)
{
this.LayoutMdi(MdiLayout.Cascade);
}
protected void TileHorzHandler(object sender, EventArgs e)
{
this.LayoutMdi(MdiLayout.TileHorizontal);
}
protected void TileVertHandler(object sender, EventArgs e)
{
this.LayoutMdi(MdiLayout.TileVertical);
}
protected void CloseAllHandler(object sender, EventArgs e)
{
int iLength = MdiChildren.Length;
for(int i=0; i<iLength; i++)
{
MdiChildren[0].Dispose( );
}
}
The functionality of the OpenHandler event handler is simple. We
basically open a common file dialog box to allow the user to pick a
file to open. For simplicity's sake, we will support
three image formats (BMP, GIF, and JPG) and three text file
extensions (TXT, CS, and VB). If the user picks the image-file
format, we open the ImageForm as the child form of the MDI
application. If a text-file format is selected instead, we use the
TextForm class. We will show you the source for both the ImageForm
and TextForm shortly.To arrange the children forms, we use the
LayoutMdi method of the
Form class. This method accepts
an enumeration of type MdiLayout. Possible values are Cascade,
ArrangeIcons, TileHorizontal, and TileVertical.The form also supports the ActiveMdiChild property to indicate the
current active MDI child form. We use this piece of information to
handle the File Close menu item to close the currently selected MDI
child form.To handle the CloseAll menu click event, we loop through the
collection of all MDI child forms and dispose them all.The following is the source for ImageForm class:using System;
using System.Drawing;
using System.Windows.Forms;
public class ImageForm : System.Windows.Forms.Form
{
private MenuItem mnuImageItem;
private MenuItem mnuImage;
private MainMenu mnuMain;
private Bitmap m_bmp;
public ImageForm( )
{
mnuImageItem = new MenuItem( );
mnuImageItem.Text = "Image Manipulation";
mnuImageItem.Click += new EventHandler(this.HandleImageItem);
mnuImage = new MenuItem( );
mnuImage.Text = "&Image";
mnuImage.MergeOrder = 1; // Merge after File but before Window.
mnuImage.MenuItems.AddRange(new MenuItem[1] {mnuImageItem});
mnuMain = new MainMenu( );
mnuMain.MenuItems.AddRange( new MenuItem[1] {mnuImage});
this.Menu = mnuMain;
}
public void SetFileName(String sImageName)
{
try
{
m_bmp = new Bitmap(sImageName);
Invalidate( );
this.Text = "IMAGE: " + sImageName;
}
catch(Exception ex)
{
MessageBox.Show ("Error: " + ex.ToString( ));
}
}
protected override void OnPaint(PaintEventArgs e)
{
if(m_bmp != null)
{
Graphics g = e.Graphics;
g.DrawImage(m_bmp, 0, 0, m_bmp.Width, m_bmp.Height);
}
}
protected void HandleImageItem(object sender, EventArgs e)
{
MessageBox.Show("Handling the image.");
}
}
Because this ImageForm class needs to draw the image file on the
form, we include a reference to the System.Drawing namespace. To
render the image file onto the form, we rely on the Bitmap and
Graphics classes. First of all, we get the input filename and
construct the Bitmap object with the content of the input file. Next,
we invalidate the screen so that it will be redrawn. In the overriden
OnPaint method, we obtained a pointer to the Graphics object and
asked it to draw the Bitmap object on the screen.One other point that we want to show you is the fact that the Image
menu item has its MergeOrder property set to
1. We did this to demonstrate the menu-merging
functionality of MDI applications. When this form is displayed, the
main menu of the MDI application changes to File, Image, and Window.To complete the example, following is the source to the TextForm
class:using System;
using System.Windows.Forms;
using System.IO;
public class TextForm : Form
{
private MenuItem mnuTextItem;
private MenuItem mnuText;
private MainMenu mnuMain;
private TextBox textBox1;
public TextForm( )
{
mnuTextItem = new MenuItem( );
mnuTextItem.Text = "Text Manipulation";
mnuTextItem.Click += new EventHandler(this.HandleTextItem);
mnuText = new MenuItem( );
mnuText.Text = "&Text";
mnuText.MergeOrder = 1; // Merge after File but before Window.
mnuText.MenuItems.AddRange(new MenuItem[1] {mnuTextItem});
mnuMain = new MainMenu( );
mnuMain.MenuItems.AddRange(new MenuItem[1] {mnuText});
this.Menu = mnuMain;
textBox1 = new TextBox( );
textBox1.Multiline = true;
textBox1.Dock = System.Windows.Forms.DockStyle.Fill;
this.Controls.Add (this.textBox1);
}
public void SetFileName(String sFileName)
{
StreamReader reader = File.OpenText(sFileName);
textBox1.Text = reader.ReadToEnd( );
reader.Close( );
textBox1.SelectionLength = 0;
this.Text = "TEXT: " + sFileName;
}
protected void HandleTextItem(object sender, EventArgs e)
{
MessageBox.Show("Handling the text file.");
}
}
Similar to the ImageForm class, the TextForm class also has its menu
inserted in the middle of File and Window. When a TextForm becomes
the active MDI child form, the menu of the MDI application becomes
File, Text, and Window. This menu-merging is done automatically. All
we have to do is set up the MergeOrder properties of the menu items.For the functionality of the TextForm, we have a simple TextBox
object. We set its Multiline property to
true to simulate a simple text editor and have its
docking property set to fill the whole form. When the main form
passes the text filename to this form, we read the input file and put
the content into the text box.Figure 8-7 illustrates the screen shot for this MDI
application at runtime. In this instance, we have three TextForms and
three ImageForms open concurrently.
Figure 8-7. The MDI application

The following script is used to build this MDI application. As you
can see, the target parameter is set to winexe to
indicate that the result of the compilation will be an executable
instead of library, which would result in a DLL.
Because we make use of the graphics package for our image rendering,
we also have to add the reference to the
System.drawing.dll assembly. We have three forms
in this application: the main form, which is named MDIApp, and the
two MDI child forms, ImageForm and TextForm (make sure you type these
commands all on one line).[10][10] Again, you can compile the
executable without specifying the target type or the
references.
csc /t:winexe
/r:System.Windows.Forms.dll
/r:system.drawing.dll
MDIApp.cs
ImageForm.cs
TextForm.cs
8.3.5 Stage Deployment
Imagine the MDI application from the
previous example with hundreds of different types of files supported.
For each of the file types, we'd probably have a
class similar to the ImageForm and TextForm classes. Instead of
compiling and linking all of these classes into your application,
wouldn't it be nice if we could download and install
a class on the fly when we use the appropriate file type? In this
section, we show how you can convert the previous MDI application to
allow just that.Conceptually, we will have the main executable act as a controller.
It will be the first assembly downloaded onto the client machine.
Once the user chooses to open a particular file type, this controller
determines which supporting DLLs should be downloaded to handle the
file type, downloads and installs the DLL, and then asks the class in
the downloaded assembly to display the selected file.In order for the controller to communicate with all supporting
classes, we abstract the commonality out of ImageForm and TextForm to
create the abstract class BaseForm:[11][11] Because we will
demonstrate running MDIApp.exe from a browser later,
BaseForm.dll has to be explicitly flagged to
grant MDIApp.exe usage via the
AllowPartiallyTrustedCallers attribute. This is to accommodate the
Version 1.1 security changes for Microsoft .NET Framework from
RCs.
using System;
using System.Windows.Forms;
using System.Security;
[assembly:AllowPartiallyTrustedCallers]
namespace BaseForm
{
abstract public class BaseForm : Form
{
abstract public void SetFileName(String sFileName);
}
}
We will rewrite both ImageForm and TextForm to make them inherit from
BaseForm. Any other form that you will need to write for other file
types will have to also inherit from BaseForm. This way, all the
controller module has to do is to know how to talk to BaseForm and
everything should be fine.The followings are the changes to ImageForm.cs
source file. All we did was to wrap the class inside a namespace call
ImageForm, make ImageForm class inherit from BaseForm, and providing
the implementation for the abstract method SetFileName( ). The rest
of the code is the same with the original
ImageForm.cs.// . . .
namespace ImageForm
{
public class ImageForm : BaseForm.BaseForm
{
// . . .
override public void SetFileName(String sImageName)
{
// . . .
}
// . . .
}
}
We apply the same thing to TextForm.cs as we do
for ImageForm.cs. The following summarizes the
changes:// . . .
namespace TextForm
{
public class TextForm : BaseForm.BaseForm
{
// . . .
override public void SetFileName(String sFileName)
{
// . . .
}
// . . .
}
}
Now, instead of having the MDIApp compiled and linked with
ImageForm.cs and
TextForm.cs and directly using ImageForm and
TextForm class in OpenHandler( ) function, we utilize the reflection
namespace to load each assembly from a predetermined URL on the fly.
Replace the old code for handling images in OpenHandler( ):ImageForm imgForm = new ImageForm( );
imgForm.SetFileName(sFN);
with:Assembly ass = Assembly.LoadFrom("http://localhost/MDIDLLS/ImageForm.dll");
BaseForm.BaseForm imgForm =
(BaseForm.BaseForm) ass.CreateInstance("ImageForm.ImageForm");
imgForm.SetFileName(sFN);
The three lines of code essentially do the following:Download the assembly from the specified URL if the client does not
already have the latest version of the assembly.Create an instance of ImageForm class in ImageForm namespace and cast
it to BaseForm.Instruct the BaseForm derived class to deal with the image file.
For the TextForm, replace the following old code for handling text
files in OpenHandler( ):TextForm txtForm = new TextForm( );
txtForm.SetFileFile(sFN);
with:Assembly ass = Assembly.LoadFrom("http://localhost/MDIDLLS/TextForm.dll");
BaseForm.BaseForm txtForm =
(BaseForm.BaseForm) ass.CreateInstance("TextForm.TextForm");
txtForm.SetFileName(sFN);
The last thing to do is to make MDIApp use the Reflection namespace
by inserting the following line of code:using System.Reflection;
You will have to compile BaseForm.cs,
ImageForm.cs, and
TextForm.cs into
BaseForm.dll,
ImageForm.dll, and
TextForm.dll in that order because ImageForm and
TextForm derive from BaseForm.The following command lines will do the job:csc /t:library /r:System.Windows.Forms.dll baseform.cs
csc /t:library /r:System.Windows.Forms.dll;baseform.dll imageform.cs
csc /t:library /r:System.Windows.Forms.dll;baseform.dll textform.cs
You will also have to compile the main module
MDIApp.cs. Notice that the only thing the
MDIApp.cs needs to know is BaseForm:csc /t:winexe
/r:System.Windows.Forms.dll;system.drawing.dll;baseform.dll MDIApp.cs
Once you have all the components compiled, copy them all into a
virtual directory call MDIDLLs because this is the predefined
location where these components are to be downloaded.[12][12] Make sure this virtual directory execute permission does not
have executable enabled. In other words, just create the virtual
directory with the default settings.
One last step you will have to do is to use the .NET Framework
Configuration tool to give file I/O permission to
"adjust zone security" and give
Local Intranet full trust level. The tool can be found at
Administrative Tools/Microsoft .NET Framework Configuration. Choose
to Configure Code Access Security and then Adjust Zone Security. Give
Local Intranet full trust level for the duration of this experiment.
It is not recommended that you use this setting without knowing all
consequences; however, this is the simplest thing to do to quickly
demonstrate the experiment. For your enterprise application, consult
the .NET security documentation for further recommendations.And now everything is set up. All you have to do is to point your
browser to http://localhost/MDIDLLs/MDIApp.exe
and see how the components are automatically downloaded and run.Another utility you might want to use to inspect the global assembly
cache (where the downloaded components reside) is
gacutil. Use gacutil
/ldl to list all downloaded assemblies and
gacutil /cdl to clear the
downloaded assemblies. When you first see the MDIApp main form, the
only assembly downloaded is MDIApp. But as soon as you try to open an
image file or a text file, ImageForm or TextForm along with BaseForm
will be downloaded.As you can see, with this setup, you won't need to
have hundreds of DLLs when you only use a couple of file types.
Another cool thing is that when the DLLs on the server are updated,
you will automatically have the latest components.Of course, you can make this MDIApp more production-like by having
the main module accessing a Web Service that lists the file
types this application supports and the class
names as well as the assembly
filenames needed to be downloaded. Once this is setup, you
can have conditioning code based on the file type; you can pass the
assembly filename URL to Assembly.LoadFrom( )
method; you can use the class name to create the class instance; all
of these make the main module more generic. Suppose the Web Service
that lists the supporting file types for this MDIApp reads
information from a database or an XML file. When you need to
introduce new file type and supporting assembly to deal with the new
file type, all you have to do is add an entry to your database or
your XML file and add the assembly file into the virtual directory.
We think this is an exercise you should definitely try.