
- •brief contents
- •about this book
- •The Windows Forms namespace
- •Part 1: Hello Windows Forms
- •Part 2: Basic Windows Forms
- •Part 3: Advanced Windows Forms
- •Who should read this book?
- •Conventions
- •Action
- •Result
- •Source code downloads
- •Author online
- •acknowledgments
- •about .NET
- •Casting the .NET
- •Windows Forms overview
- •about the cover illustration
- •Hello Windows Forms
- •1.1 Programming in C#
- •1.1.1 Namespaces and classes
- •1.1.2 Constructors and methods
- •1.1.3 C# types
- •1.1.4 The entry point
- •1.1.5 The Application class
- •1.1.6 Program execution
- •1.2 Adding controls
- •1.2.1 Shortcuts and fully qualified names
- •1.2.2 Fields and properties
- •1.2.3 The Controls property
- •1.3 Loading files
- •1.3.1 Events
- •1.3.2 The OpenFileDialog class
- •1.3.3 Bitmap images
- •1.4 Resizing forms
- •1.4.1 Desktop layout properties
- •1.4.2 The Anchor property
- •1.4.3 The Dock property
- •1.5 Recap
- •2.1 Programming with Visual Studio .NET
- •2.1.1 Creating a project
- •Action
- •Result
- •2.1.2 Executing a program
- •Action
- •Result
- •2.1.3 Viewing the source code
- •View the code generated by Visual Studio .NET
- •Action
- •Result
- •2.2 Adding controls
- •2.2.1 The AssemblyInfo file
- •Action
- •Results
- •2.2.2 Renaming a form
- •Action
- •Result
- •2.2.3 The Toolbox window
- •Action
- •Result
- •2.3 Loading files
- •2.3.1 Event handlers in Visual Studio .NET
- •Action
- •Result
- •2.3.2 Exception handling
- •Action
- •Result
- •Action
- •Results and Comments
- •2.4 Resizing forms
- •2.4.1 Assign the Anchor property
- •Action
- •Result
- •2.4.2 Assign the MinimumSize property
- •Action
- •Result
- •2.5 Recap
- •Basic Windows Forms
- •Menus
- •Action
- •Result
- •Action
- •Result
- •Action
- •Result
- •Action
- •Result
- •3.3 Click events
- •Action
- •Result
- •Action
- •Result
- •Action
- •Result
- •Action
- •Result
- •Action
- •Result
- •Action
- •Result
- •3.5 Context menus
- •Action
- •Result
- •Action
- •Result
- •3.6 Recap
- •Status bars
- •4.1 The Control class
- •4.2 The StatusBar class
- •Action
- •Result
- •Action
- •Result
- •4.3.1 Adding panels to a status bar
- •Action
- •Result
- •Action
- •Result
- •Action
- •Result
- •Action
- •Result
- •4.5 Recap
- •Reusable libraries
- •5.1 C# classes and interfaces
- •5.2 Class libraries
- •Action
- •Result
- •Action
- •Result
- •Action
- •Result
- •Action
- •Result
- •Action
- •Result
- •Action
- •Result
- •Action
- •Result
- •Action
- •Result
- •Action
- •Result
- •5.3 Interfaces revisited
- •Action
- •Result
- •Action
- •Result
- •Action
- •Result
- •5.4 Robustness issues
- •Action
- •Result
- •Action
- •Result
- •Action
- •Result
- •Action
- •Result
- •Action
- •Result
- •Action
- •Result
- •Common file dialogs
- •Action
- •Results
- •Action
- •Result
- •Action
- •Result
- •Action
- •Result
- •Action
- •Result
- •6.3 Paint events
- •Action
- •Result
- •Action
- •Result
- •6.4 Context menus revisited
- •Action
- •Result
- •Action
- •Result
- •6.5 Files and paths
- •Action
- •Result
- •Action
- •Result
- •Action
- •Result
- •6.6 Save file dialogs
- •Action
- •Result
- •Action
- •Result
- •Action
- •Result
- •Action
- •Result
- •Action
- •Result
- •6.7 Open file dialogs
- •Action
- •Result
- •Action
- •Result
- •6.8 Recap
- •Drawing and scrolling
- •7.1 Form class hierarchy
- •Action
- •Result
- •Action
- •Result
- •Action
- •Result
- •Action
- •Result
- •Action
- •Result
- •Action
- •Result
- •Action
- •Result
- •Action
- •Result
- •Action
- •Result
- •Action
- •Result
- •Action
- •Result
- •Action
- •Result
- •7.4 Panels
- •Action
- •Result
- •Action
- •Result
- •Action
- •Result
- •Action
- •Result
- •Action
- •Result
- •Dialog boxes
- •8.1 Message boxes
- •Action
- •Result
- •Action
- •Result
- •8.1.4 Creating A YesNoCancel dialog
- •Action
- •Result
- •Action
- •Result
- •8.2 The Form.Close method
- •8.2.1 The relationship between Close and Dispose
- •Action
- •Result
- •8.3 Modal dialog boxes
- •Action
- •Result
- •Action
- •Result
- •8.3.2 Preserving caption values
- •Action
- •Result
- •Action
- •Result
- •Action
- •Result
- •Action
- •Result
- •Action
- •Result
- •Action
- •Result
- •Action
- •Result
- •Action
- •Result
- •Action
- •Result
- •Action
- •Result
- •Action
- •Result
- •Action
- •Result
- •Action
- •Result
- •Action
- •Result
- •Action
- •Result
- •Action
- •Result
- •Action
- •Result
- •Basic controls
- •Action
- •Result
- •Action
- •Result
- •Action
- •Result
- •9.1.2 Creating a derived form
- •Action
- •Result
- •9.2 Labels and text boxes
- •Action
- •Result
- •Action
- •Result
- •Action
- •Result
- •Action
- •Result
- •Action
- •Result
- •Action
- •Result
- •Action
- •Result
- •Action
- •Result
- •Action
- •Result
- •Action
- •Result
- •Action
- •Result
- •Action
- •Result
- •Action
- •Result
- •Action
- •Result
- •Action
- •Result
- •Action
- •Result
- •Action
- •Result
- •Action
- •Result
- •9.3.6 Adding AlbumEditDlg to our main form
- •Action
- •Result
- •Action
- •Result
- •9.4 Recap
- •List controls
- •10.1 List boxes
- •Action
- •Result
- •Action
- •Result
- •Action
- •Result
- •Action
- •Result
- •10.2 Multiselection list boxes
- •10.2.1 Enabling multiple selection
- •Action
- •Result
- •Action
- •Result
- •Action
- •Result
- •Action
- •Result
- •10.3 Combo boxes
- •Action
- •Result
- •Action
- •Result
- •10.4 Combo box edits
- •Action
- •Result
- •Action
- •Result
- •Action
- •Result
- •10.5 Owner-drawn lists
- •Action
- •Result
- •Action
- •Result
- •Action
- •Result
- •Action
- •Result
- •More controls
- •Action
- •Result
- •Action
- •Result
- •11.2 Tab pages
- •Action
- •Result
- •Action
- •Result
- •11.3.1 Dates and times
- •Action
- •Result
- •Action
- •Result
- •Action
- •Result
- •Action
- •Result
- •Action
- •Result
- •Action
- •Result
- •11.5 Recap
- •A .NET assortment
- •12.1 Keyboard events
- •Action
- •Result
- •Action
- •Result
- •12.2 Mouse events
- •Action
- •Result
- •Action
- •Result
- •Action
- •Result
- •12.3 Image buttons
- •Action
- •Result
- •Action
- •Result
- •Action
- •Result
- •Action
- •Result
- •Action
- •Result
- •Action
- •Result
- •Action
- •Result
- •Action
- •Result
- •Action
- •Result
- •12.4 Icons
- •Action
- •Result
- •Action
- •Result
- •Action
- •Result
- •12.5 Recap
- •Toolbars and tips
- •13.1 Toolbars
- •Action
- •Result
- •Action
- •Result
- •Action
- •Result
- •Action
- •Result
- •Action
- •Result
- •Action
- •Result
- •Action
- •Result
- •Action
- •Result
- •Action
- •Result
- •13.4.2 Creating tool tips
- •Action
- •Result
- •Action
- •Result
- •Advanced Windows Forms
- •List views
- •14.2 The ListView class
- •Action
- •Result
- •Action
- •Result
- •Action
- •Result
- •Action
- •Result
- •14.2.3 Populating a ListView
- •Action
- •Result
- •Action
- •14.3 ListView columns
- •Action
- •Result
- •Action
- •Result
- •Action
- •Result
- •Action
- •Result
- •Action
- •Result
- •Action
- •Result
- •Action
- •Result
- •Action
- •Result
- •Action
- •Result
- •Action
- •Result
- •Action
- •Result
- •Action
- •Result
- •Action
- •Result
- •Action
- •Result
- •Action
- •Result
- •Action
- •Result
- •Action
- •Result
- •Action
- •Result
- •Action
- •Result
- •14.6 Recap
- •Tree views
- •Action
- •Result
- •Action
- •Result
- •Action
- •Result
- •15.3 Dynamic tree nodes
- •Action
- •Result
- •Action
- •Result
- •Action
- •Result
- •Action
- •Result
- •Action
- •Result
- •15.4 Node selection
- •Action
- •Result
- •Action
- •Result
- •Action
- •Result
- •Action
- •Result
- •Action
- •Result
- •15.5 Fun with tree views
- •Action
- •Result
- •Action
- •Result
- •Action
- •Result
- •Multiple document interfaces
- •Action
- •Result
- •Action
- •Result
- •Action
- •Result
- •16.3 Merged menus
- •Action
- •Result
- •Action
- •Result
- •Action
- •Result
- •Action
- •Result
- •16.4 MDI children
- •Action
- •Result
- •Action
- •Result
- •Action
- •Result
- •Action
- •Result
- •Action
- •Result
- •Action
- •Result
- •Action
- •Result
- •Action
- •Result
- •Action
- •Result
- •Action
- •Result
- •16.5 MDI child window management
- •Action
- •Result
- •Action
- •Result
- •16.6 Recap
- •Data binding
- •17.1 Data grids
- •Action
- •Result
- •Action
- •Result
- •17.2 Data grid customization
- •Action
- •Result
- •Action
- •Result
- •Action
- •Result
- •Action
- •Result
- •Action
- •Result
- •Action
- •Result
- •Action
- •Result
- •Action
- •Result
- •Action
- •Result
- •Action
- •Result
- •Action
- •Result
- •Odds and ends .NET
- •Action
- •Result
- •Action
- •Result
- •Action
- •Result
- •Action
- •Result
- •18.2 Timers
- •Action
- •Result
- •Action
- •Result
- •18.3 Drag and drop
- •Action
- •Result
- •Action
- •Result
- •18.4 ActiveX controls
- •Action
- •Result
- •Action
- •Result
- •Action
- •Result
- •Action
- •Result
- •Action
- •Result
- •18.5 Recap
- •C# primer
- •A.1 C# programs
- •A.1.1 Assemblies
- •A.1.2 Namespaces
- •A.2 Types
- •A.2.1 Classes
- •A.2.2 Structures
- •A.2.3 Interfaces
- •A.2.4 Enumerations
- •A.2.5 Delegates
- •A.3 Language elements
- •A.3.1 Built-in types
- •A.3.2 Operators
- •A.3.3 Keywords
- •A.4 Special features
- •A.4.1 Exceptions
- •A.4.2 Arrays
- •A.4.3 Main
- •A.4.4 Boxing
- •A.4.5 Documentation
- •.NET namespaces
- •B.1 System.Collections
- •B.2 System.ComponentModel
- •B.3 System.Data
- •B.4 System.Drawing
- •B.5 System.Globalization
- •B.6 System.IO
- •B.7 System.Net
- •B.8 System.Reflection
- •B.9 System.Resources
- •B.10 System.Security
- •B.11 System.Threading
- •B.12 System.Web
- •B.13 System.Windows.Forms
- •B.14 System.XML
- •Visual index
- •C.1 Objects
- •C.2 Marshal by reference objects
- •C.3 Components
- •C.4 Common dialogs
- •C.7 Event data
- •C.8 Enumerations
- •For more information
- •bibliography
- •Symbols
- •Index

of an architecture is not its ability to work as designed, but rather its ability to perform tasks for which it was not designed. The coding techniques we have used throughout the book are useful in any application to accommodate future requirements. These techniques include frequent encapsulation of tasks into separate methods; sketching a user interface design or enumerating the steps required before writing any code; and building reusable libraries and methods where possible.
As a result, our code has some advantages for new changes such as this in that we have consistently tried to use good coding practices and not duplicate our tasks in multiple places. While perhaps not always successful, I believe we have done a reasonable job.
In the PixelDlg form, for example, we were careful to only update this form in the UpdatePixelData method of the MainForm class. Similarly, the only location where the PixelDlg form is created right now is in the Click event handler for the menuPixelData object. Such organization occasionally requires a little extra work, or in our case a few more pages, but this effort often pays off as the code is maintained and updated in the future.
Stepping off my soap box and returning to the topic at hand, we will make our changes in the order shown in the previous list, beginning with a global PixelDlg instance. For this we will provide a static property in the PixelDlg class that returns a shared form.
CREATE A SHARED PIXELDLG INSTANCE
|
Action |
Result |
|
|
|
1 |
In the PixelDlg.cs code window, |
static private Form _mdiForm = null; |
|
create static members to hold |
static private PixelDlg _globalDlg; |
|
the shared instance and an MDI |
|
|
parent form, if any. |
|
|
|
|
2 |
Create an internal property to |
static internal Form GlobalMdiParent |
|
assign and retrieve an MDI |
{ |
|
container form. |
get { return _mdiForm; } |
|
set { _mdiForm = value; } |
|
|
|
|
|
|
} |
|
|
|
3 |
Create a public property to |
static public PixelDlg GlobalDialog |
|
retrieve the shared form. |
{ |
|
How-to |
get |
|
{ |
|
|
If the current _globalDlg value |
if (_globalDlg == null |
|
is invalid, then create a new |
|| _globalDlg.IsDisposed) |
|
{ |
|
|
instance of the Form. |
|
|
_globalDlg = new PixelDlg(); |
|
|
|
_globalDlg.MdiParent = GlobalMdiParent; |
|
|
} |
|
|
return _globalDlg; |
|
|
} |
|
|
} |
|
|
|
The GlobalDialog property provides the mechanism by which all child MainForm instances can access the same PixelDlg form. Recall that our PixelDlg form is
MDI CHILDREN |
549 |

disposed whenever the user clicks the Close button. For this reason, this property recreates the dialog whenever it is null or disposed.
The GlobalMdiParent property provides a method for turning this global dialog into an MDI child form. We can use this property in our ParentForm class to establish the MDI container for our global dialog.
SET THE MDI PARENT FOR THE GLOBAL PIXELDLG FORM
|
Action |
Result |
|
|
|
4 |
In the ParentForm.cs code |
protected override void OnLoad(EventArgs e) |
|
window, override the OnLoad |
{ |
|
method to assign this form as |
PixelDlg.GlobalMdiParent = this; |
|
|
|
|
the MDI parent for the global |
base.OnLoad(e); |
|
PixelDlg form. |
} |
|
|
|
This ensures that whenever a new global dialog is created, the ParentForm object is assigned as the MDI parent. With this in place, we are ready to access the global dialog from the MainForm class. As mentioned earlier, right now the dialog is created only in the menuPixelData_Click method, so this is the only place we need to call our new property.
ACCESS THE GLOBAL PIXELDLG FROM THE MAINFORM CLASS
|
Action |
Result |
|
|
|
5 |
Locate the |
private void menuPixelData_Click |
|
menuPixelData_Click method |
(object sender, System.EventArgs e) |
|
in the MainForm.cs code |
{ |
|
|
|
|
window. |
|
|
|
|
6 |
Modify the creation of the |
if (_dlgPixel == null |
|
PixelDlg form to use the new |
|| _dlgPixel.IsDisposed) |
|
GlobalDialog property. |
{ |
|
_dlgPixel = PixelDlg.GlobalDialog; |
|
|
|
|
|
|
} |
|
|
_nPixelDlgIndex = _album.CurrentPosition; |
|
|
Point p = pnlPhoto.PointToClient( |
|
|
Form.MousePosition); |
|
|
UpdatePixelData(p.X, p.Y); |
|
|
AssignPixelToggle(true); |
|
|
_dlgPixel.Show(); |
|
|
} |
|
|
|
This change simply retrieves the global dialog rather than creating a new instance. You can compile and run this if you like. You will see that our code works fine for a single MDI child window. When a second window is added, the PixelDlg form is not associated with this window, and no longer works.
550 |
CHAPTER 16 MULTIPLE DOCUMENT INTERFACES |

There are a couple ways to fix this problem. We will do so by observing that all mouse movement in each window is processed by the pnlPhoto_MouseMove event handler. This method in turn calls the UpdatePixelData method, as does all other updates to the dialog. As a result, we can associate an existing PixelDlg form with a new window by assigning the dialog at the start of our update method.
The following steps make this change in our application.
ENSURE AN EXISTING PIXELDLG FORM IS ASSIGNED TO NEW CHILD INSTANCES
|
Action |
Result |
|
|
|
7 |
Locate the UpdatePixelData |
protected void UpdatePixelData(int xPos, |
|
method. |
int yPos) |
|
|
{ |
|
|
|
8 |
Assign the _dlgPixel field at |
if (IsMdiChild) |
|
the beginning of the method. |
_dlgPixel = PixelDlg.GlobalDialog; |
|
|
. . . |
|
|
} |
|
|
|
This now guarantees that a child form will pick up the global PixelDlg form as needed. Of course, this change also causes the dialog to be created even when it is not used. Such a change might not be appropriate in a large application with multiple utility forms such as our pixel dialog. For our purposes, it is okay.
Compile and run the program to verify that our new code works. Also realize that these changes are consistent with our non-MDI application. When a single MainForm instance is present, it will now use the global PixelDlg instance to create the dialog, and all code will work as we originally intended in chapter 8. You can test this by modifying the MyPhotos project settings to use the MainForm.Main method as the entry point.
The PixelDlg form is now integrated into our MDI application. The next task is to ensure that we do not open multiple windows for the same album file.
16.4.3OPENING AN ALBUM TWICE
In our current code for the Open menu, the user selects an album and a new child window is created to contain this album. This is fine as long as the selected album has not been previously opened by the user. In the case where a MainForm window already exists for the selected album, it would be more appropriate to simply display the existing window at the top of the z-order.
This can be done by searching through the list of child windows for one that displays the selected album. The MdiChildren property in the Form class retrieves the collection of child forms assigned to an MDI container form. This property can be treated like any other array to search for a matching form.
This property is useful whenever a specific form is desired, as we do here. It can also be used to see if any child forms are present in an MDI application and to obtain the number of MDI child forms, although checking the ActiveMdiChild property is typically a more efficient mechanism for the former task.
MDI CHILDREN |
551 |

When implementing this change, we should keep in mind the fact that forms other than our MainForm class might be contained by this array. The following steps detail a solution for this change with this fact in mind.
HANDLE AN ATTEMPT TO OPEN A DISPLAYED ALBUM
|
Action |
Result |
|
|
|
1 |
In the MainForm.cs code |
public string AlbumFile |
|
window, add a new |
{ |
|
AlbumFile property to |
get { return _album.FileName; } |
|
} |
|
|
retrieve the file name of the |
|
|
|
|
|
displayed album. |
|
|
|
|
2 |
Locate the |
private void menuOpen_Click |
|
menuOpen_Click event |
(object sender, System.EventArgs e) |
|
handler in the ParentForm.cs |
{ |
|
|
|
|
code window. |
|
|
|
|
3 |
Before opening a new |
. . . |
|
MainForm window, search |
if (dlg.ShowDialog() == DialogResult.OK) |
|
through the set of existing |
{ |
|
try |
|
|
child forms. |
|
|
{ |
|
|
|
// See if album is already open |
|
|
foreach (Form f in MdiChildren) |
|
|
{ |
|
|
|
4 |
If a MainForm instance is |
if (f is MainForm) |
|
found, see if it displays the |
{ |
|
selected album. |
MainForm mf = (MainForm) f; |
|
if (mf.AlbumFile == dlg.FileName) |
|
|
|
|
|
|
{ |
|
|
|
5 |
If a match is found, bring the |
if (mf.WindowState |
|
existing album to the front of |
== FormWindowState.Minimized) |
|
the application window. |
{ |
|
mf.WindowState |
|
|
|
|
|
How-to |
= FormWindowState.Normal; |
|
} |
|
|
a. If the form is minimized, |
|
|
mf.BringToFront(); |
|
|
return it to a normal state. |
return; |
|
b. Display the form at the |
} |
|
} |
|
|
front of the MDI window. |
|
|
} |
|
|
|
|
6 |
If no matching window is |
// Open new child window for album |
|
found, the existing code will |
MainForm form |
|
create a new MainForm |
= new MainForm(dlg.FileName); |
|
. . . |
|
|
object for the album. |
|
|
} |
|
|
|
|
This code uses some properties we have not seen before. When a matching child Form is found, the WindowState property is used to assign or retrieve the current display state of the MDI child form within its container. For a top-level form, this affects the display state on the desktop. The WindowState property takes its values from the FormWindowState enumeration, summarized in .NET Table 16.2. In the previous table, we check to see if the MDI child is minimized, and if so return it to a Normal state.
552 |
CHAPTER 16 MULTIPLE DOCUMENT INTERFACES |

.NET Table 16.2 FormWindowState enumeration
The FormWindowState enumeration specifies the possible display states for a Form on the desktop or within an MDI application. This enumeration is part of the System.Windows.Forms namespace.
|
Maximized |
The form is maximized so that it fills the entire possible |
|
|
display area, either the entire desktop or the entire display |
|
|
window when the form is an MDI child form. |
Enumeration |
Minimized |
The form is minimized and is listed in the task bar or at the |
|
base of the MDI container form. |
|
Values |
|
|
|
|
|
|
Normal |
The form appears in its normal state. By default, the form is |
|
|
visible and not maximized. If a maximized Form is minimized, |
|
|
then setting the Form to Normal will return the form to a |
|
|
maximized state. |
|
|
|
We also use the BringToFront method to display the form at the top of the z-order within the MDI container. This method is part of the Control class and can be used to adjust the z-order position of any Windows Forms control within its container. There is also a corresponding SendToBack method to place a control at the bottom of the z-order.
Run the application to verify this feature works. Try minimizing or maximizing the form before opening the same album to verify that the proper behavior occurs. Also verify that when a new album is selected, a new MDI child form appears as before.
This completes the three tasks we set out at the start of section 16.4. We have added a toolbar to our parent form and hidden the toolbar in the child, turned the PixelDlg form into an MDI child when running as an MDI application, and ensured that an album can only be opened within a single MDI child form.
As a final change, and to create a slightly more polished application, let’s make one more addition here to place the current album in the title bar.
16.4.4UPDATING THE TITLE BAR
An MDI application should normally update its title bar to reflect the contents of the currently active child form. This provides good feedback to your users, especially when the application is minimized and only appears in the task bar. In our case, we will also include the version number on the title bar as is our custom. The result of this change is shown in figure 16.8.
We could like to update the title bar whenever a new form is activated within the MDI container. There is an event for just this purpose, namely the
vate event. This event occurs whenever an MDI child form is closed or becomes the active form within the container.
MDI CHILDREN |
553 |