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.
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):
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 instant will stop.
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.
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.
This is all 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 onclick 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 onclick 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 onclick 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, because we’ve already registered for the click event, our event handler is called. 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.
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 the same substrate, the CLR in both environments.
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 Example 8-1.
Example 8-1. The C# source 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 BindingManagerBase 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;uid=sa;pwd=;"; 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 last name binding m_lm = m_txtLastName.DataBindings[0].BindingManagerBase; } 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; } }
Because all UI controls derive from the Control class, they 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
.
Example 8-1 shows you 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, use the BindingManagerBase object. Each of the Binding objects has a reference to a BindingManagerBase object. The following excerpt of code shows you how to get to the binding manager for the data source bound to the last name text box. Because the same data source is bound to the first name and the phone number, there is no need to get a separate binding manager to traverse the data.
// Obtain the list manager from the last name binding
m_lm = m_txtLastName.DataBindings[0].BindingManagerBase;
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 list—in this case, the current record in the Authors table—the TextBox control will be updated with new FirstName value. Figure 8-3 illustrates the user interface for the simple controls data-binding example.
Let’s now 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;uid=sa;pwd=;"; 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) { Text = "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 IList interfaces. As it turns out, the following can be used as data source: DataTable, DataView, DataSet, DataSetView, and single-dimension array.
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 control 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.
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 control 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,[47] or you can dock or anchor the controls inside the container.
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
across 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. 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.
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 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.
There are two main styles of user interfaces for Windows-based applications: Single Document Interface (SDI) and Multiple Document Interface (MDI). 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.
While it is easy to implement both SDI and MDI applications using the Windows Forms architecture, we only show you 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 probably
replace the type for forms a
and
b
with your own form classes. (As shown later in
this chapter, we have ImageForm and TextForm.)
However, at this point the MDI application is not really functional. MDI is interesting because the MDI application contains 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: 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. 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.
Let’s take a look at the source code:
using System; using System.Windows.Forms; public class MdiMainForm : Form { // Menu Items under File Menuprivate MenuItem mnuOpen, mnuClose, mnuExit;
// Menu Items under the Window Menuprivate MenuItem mnuCascade, mnuTileHorz, mnuTileVert,
mnuSeparator, mnuCloseAll, mnuListMDI;
// The File and Window Menusprivate MenuItem mnuFile, mnuWindow;
// The Main Menuprivate MainMenu mnuMain;
public MdiMainForm( ) { this.Text = "MDI App for Text and Images"; // File Menu ItemmnuFile = new MenuItem( );
mnuFile.Text = "&File";
mnuFile.MergeOrder = 0;
// Window Menu ItemmnuWindow = new MenuItem( );
mnuWindow.MergeOrder = 2;
mnuWindow.Text = "&Window";
// Main Menu contains File and WindowmnuMain = new MainMenu( );
mnuMain.MenuItems.AddRange(
new MenuItem[2] {mnuFile, mnuWindow});
// Assign the main menu of the formthis.Menu = mnuMain;
// Menu Items under File menumnuOpen = 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 menumnuCascade = 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, there is one thing that is different here. 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. See Figure 8-6 for an example of
how this feature shows up at runtime.
mnuListMDI.Text = "Windows..."; mnuListMDI.MdiList = true;
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.SetImageName(sFN); imgForm.MdiParent = this; imgForm.Show( ); } else if(sExt == ".TXT" || sExt == ".VB" || sExt == ".CS") { TextForm txtForm = new TextForm( ); txtForm.SetTextFile(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 SetImageName(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 SetTextFile(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 all 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.
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 will 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):
csc /t:winexe /r:System.Windows.Forms.dll /r:system.drawing.dll MDIApp.cs ImageForm.cs TextForm.cs
[47] 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.
18.222.21.139