- •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 these, the first Album 1 node contains a single TreeNode object representing the Photo 1 node.
The point here is to see firsthand how TreeNode objects are created and appear on the form. In the next section we will create nodes for our actual albums and photographs programmatically. The TreeNode Editor we used here is useful for creating a fixed set of nodes, or for creating the top-level nodes for a tree. For example, in a program with a large number of application settings, you might organize these settings into a hierarchy and display them in a tree view. The user could then interact with the tree to modify the application’s settings. In this case, the TreeView.CheckBoxes property might be useful to enable or disable each setting via a check box.
15.3DYNAMIC TREE NODES
In this section we will look at programmatically creating and modifying the set of tree nodes associated with a tree view control. We have already decided to display a toplevel “Default Albums” node, under which the albums in the default album directory will be displayed. Within each album the set of photos in the album will appear. The result should look something like figure 15.7. This section will focus on populating the tree view with the appropriate set of tree nodes. Section 15.4 will examine how to coordinate the contents of our TreeView and ListView controls.
To make this change, we will first create some index constants for use when accessing our image list. Once this is done, we will look at how to create the album nodes and photograph nodes in code.
Figure 15.7
A TreeView automatically displays horizontal and vertical scroll bars as required. Note that the contents of the TreeView and ListView controls are not yet synchronized.
15.3.1ASSIGNING INDEX CONSTANTS
Before we talk about how to create this tree, recall that we created constants for the image indices in chapter 14. Figure 15.7 shows the closed book icon for each unselected album, and the open book icon for the selected “leeds” album. Let’s create
DYNAMIC TREE NODES |
497 |
some constants for the remaining images in our ImageList objects so that we can use them in this section.
This is done with the following step.
Set the version number of the MyAlbumExplorer application to 15.3.
ASSIGN IMAGE INDEX CONSTANTS
|
Action |
Result |
|
|
|
1 |
In the MainForm.cs code |
private const int PhotoIndex = 0; |
|
window, update the image index |
private const int AlbumIndex = 1; |
|
constants to account for |
private const int ErrorIndex = 2; |
|
private const int SelectedPhotoIndex = 3; |
|
|
unselected and selected items. |
|
|
private const int SelectedAlbumIndex = 4; |
|
|
|
private const int AlbumDirectoryIndex = 5; |
|
|
|
With these constants in place, we are ready to discuss adding the actual albums to our tree.
15.3.2CREATING THE ALBUM NODES
The proper way to add nodes to a tree depends somewhat on the size of a tree. For a small set of nodes, it makes sense to add the entire hierarchy of nodes at one time, and then allow the control to manage the nodes as they are expanded and collapsed by the user. For a large hierarchy, adding a huge number of nodes can use up a lot of memory. Imagine if the Windows Explorer program created a TreeNode object for every directory and file on your computer. This would be a lot of nodes on most computers.
Instead, applications typically add only the nodes a user initially requires, and then insert additional nodes based on the user’s actions. This saves both time and memory, since less work is required to initialize the tree when the application starts, and memory is only allocated as new nodes are added to the tree.
In our application, the number of albums present could be few or many. We could create the entire hierarchy all at once as shown in listing 15.1. This requires that we open each album file and iterate through every photograph in every album. Since this could be expensive for a large number of albums, we will not use this method nor discuss this code in any detail. Hopefully, it is instructive to see how the entire hierarchy might be created in a single method.
Listing 15.1 Create the entire set of tree nodes required (not our approach)
private void InitTreeData()
{
treeViewMain.BeginUpdate();
treeViewMain.Nodes.Clear();
// Create the top-level node
TreeNode defaultRoot = new TreeNode("Default Albums",
AlbumDirectoryIndex, AlbumDirectoryIndex);
treeViewMain.Nodes.Add(defaultRoot);
498 |
CHAPTER 15 TREE VIEWS |
// Create a node for each album file foreach (string s in Directory.GetFiles(
PhotoAlbum.DefaultDir, "*.abm"))
{
String baseName = Path.GetFileNameWithoutExtension(s); TreeNode albumNode = new TreeNode(baseName) defaultRoot.Nodes.Add(albumNode);
// Open the album
PhotoAlbum album = OpenAlbum(s); if (album == null)
{
// Bad album, so adjust the image index settings albumNode.ImageIndex = ErrorIndex; albumNode.SelectedImageIndex = ErrorIndex; continue;
}
// Create a node for each photo in this album foreach (Photograph p in album)
{
string text = album.GetDisplayText(p); TreeNode photoNode = new TreeNode(text, PhotoIndex, SelectedPhotoIndex);
albumNode.Nodes.Add(photoNode);
}
album.Dispose();
}
treeViewMain.EndUpdate();
}
Instead, we will take an “as-needed” approach to our tree nodes. Initially we will create only the album nodes, and then add the photographs for an album only when the user expands that album’s node.
To begin this process, we need to modify our OnLoad method to create the initial tree structure.
MODIFY THE ONLOAD METHOD
|
Action |
Result |
|
|
|
1 |
In the MainForm.cs code |
protected override void OnLoad(EventArgs e) |
|
window, update the OnLoad |
{ |
|
method to initialize the tree view |
. . . |
|
|
|
|
control before the form is |
// Initialize the tree and list controls |
|
displayed. |
InitTreeData(); |
|
|
LoadAlbumData(PhotoAlbum.DefaultDir); |
|
|
} |
|
|
|
DYNAMIC TREE NODES |
499 |
The InitTreeData method will clear the nodes we created with the TreeNode Editor and add the top-level default node and set of albums from the default album directory. This table continues the steps from the previous table.
ADD THE INITTREEDATA METHOD
|
Action |
Result |
|
|
|
2 |
Add an InitTreeData method to the |
private void InitTreeData() |
|
MainForm.cs code window. |
{ |
|
|
|
3 |
To implement this method, first clear |
treeViewMain.BeginUpdate(); |
|
any existing nodes in the tree view |
treeViewMain.Nodes.Clear(); |
|
control. |
|
|
Note: The BeginUpdate method |
|
|
should be used when adding multi- |
|
|
ple nodes to a TreeView control so |
|
|
that it will not repaint its window |
|
|
while the new nodes are added. |
|
|
|
|
4 |
Create the top-level node for the tree. |
// Create the top-level node |
|
How-to |
TreeNode defaultRoot |
|
= new TreeNode("Default Albums", |
|
|
a. Use the label “Default Albums.” |
AlbumDirectoryIndex, |
|
b. Set the image indices to use the |
AlbumDirectoryIndex); |
|
defaultRoot.Tag = PhotoAlbum.DefaultDir; |
|
|
album directory icon. |
treeViewMain.Nodes.Add(defaultRoot); |
|
c. Add the node as a root of the tree. |
treeViewMain.SelectedNode = defaultRoot; |
|
|
|
|
d. Select this node by default. |
|
|
|
|
5 |
Create a node for each album file in |
foreach (string s in Directory.GetFiles( |
|
the default album directory. |
PhotoAlbum.DefaultDir, "*.abm")) |
|
|
{ |
|
|
|
6 |
Create a new TreeNode for this |
// Create a node for this album |
|
album using the base file name as the |
String baseName = Path. |
|
label text for the node. |
GetFileNameWithoutExtension(s); |
|
TreeNode albumNode |
|
|
|
|
|
|
= new TreeNode(baseName, |
|
|
|
7 |
Create a child node in each album |
new TreeNode[] { |
|
node with the label text “child.” |
new TreeNode("child") |
|
|
}); |
|
|
|
8 |
Set the Tag property for the node to |
albumNode.Tag = s; |
|
contain the album file path. |
Note: We will use the Tag property to identify |
|
|
|
|
|
the album related to a specified node when |
|
|
handling events for the TreeView control. |
|
|
|
9 |
Add the new node to the collection of |
defaultRoot.Nodes.Add(albumNode); |
|
nodes under the default root node. |
} |
|
|
|
10 |
Allow the TreeView to repaint by |
treeViewMain.EndUpdate(); |
|
calling the EndUpdate method. |
} |
|
|
|
500 |
CHAPTER 15 TREE VIEWS |
This code uses a few tricks to ensure that our application will perform as expected. When a new album node is created, a single child node is added to ensure that the TreeView control will allow the node to be expanded.
TreeNode albumNode = new TreeNode(baseName,
new TreeNode[] { new TreeNode("child") });
Without this child, the control would presume that our node has no children, and would not display a plus sign next to the album to permit the user to expand the node. We will make use of this in the next section, where we implement the expansion of an album node. This line simply ensures that the user can initiate this step.
We also assign the Tag property for each node to contain the file path corresponding to the node. For the root node, this path is the default album directory. For each album, this path is the fully qualified album file name.
. . .
defaultRoot.Tag = PhotoAlbum.DefaultDir;
foreach (string s in Directory.GetFiles(. . .)
{
. . .
albumNode.Tag = s; defaultRoot.Nodes.Add(albumNode);
}
This setting will permit us to identify the object corresponding to a given node while processing a user action on behalf of the tree view. Like other Tag properties we have seen for .NET, this property can be set to any object instance.
Notice as well that we use the default image index and selected image index for all album files. Since we do not open the corresponding PhotoAlbum during our initialization step, we have no way to know which albums can be opened and which will generate an error. We start by assuming that all albums can be opened, and will update the image index values if we discover any problems.
It is also worth noting that using the Tag property as we do in the previous code is not always a practical solution. Another common tactic, especially in more complex applications, is to derive a new class from the TreeNode class, and use this new class to populate the tree. This alternate approach can encapsulate node-specific functionality in the derived class, and can improve the maintenance and readability of the resulting code.
Compile and run your application to verify that the albums appear in the tree. Our next topic is the insertion of photographs when the user expands an album node.
15.3.3CREATING THE PHOTOGRAPH NODES
So far our TreeView control displays the albums from the default album directory during start-up. We created a default child node within each album to permit the user to expand these nodes. The next step is to handle this expansion and replace the default child node with the set of photos in the album.
DYNAMIC TREE NODES |
501 |
There are a number of ways tree nodes can be expanded and collapsed. These include the following:
•From the mouse. The user can double-click on a tree node to toggle between expand and collapse operations. When the ShowPlusMinus property is true, a click on a plus ‘+’ sign will expand the node while a click on a minus
‘–’ sign will collapse a node.
•From the keyboard. The user can press the right arrow key to expand the selected node in the tree, and the left arrow key to collapse the selected node.
•From code. The TreeNode class includes an Expand method to expand the node, a Collapse method to collapse the node, and a Toggle method to switch the node to the opposite of its current state. The TreeView class includes the ExpandAll and CollapseAll methods to expand or collapse all nodes in the tree.
In addition, the TreeNode.EnsureVisible method will expand nodes as required to have the node appear within the containing TreeView control.
Regardless of how a node is expanded or collapsed, the BeforeExpand, AfterExpand, BeforeCollapse, and AfterCollapse events occur in the TreeView class for each node as it alters its state. The before events receive a TreeViewCancelEventArgs class instance as their event parameter, while the after events receive a TreeViewEventArgs class instance. The TreeViewCancelEventArgs class is summarized in .NET Table 15.4. The TreeViewEventArgs class provides the same two Action and Node properties shown in the table, but inherits from the System.EventArgs class rather than the CancelEventArgs class. The CancelEventArgs class is discussed in chapter 8.
.NET Table 15.4 TreeViewCancelEventArgs class
The TreeViewCancelEventArgs class is a CancelEventArgs object that contains event data for events in the TreeView class that occur before an operation takes place. The event handler receiving this class has the opportunity to cancel the operation by setting the inherited Cancel property to true. This class is part of the System.Windows.Forms namespace, and inherits from the System.ComponentModel.CancelEventArgs class.
|
Action |
Gets the TreeViewAction enumeration member |
|
Public Properties |
|
representing the action that caused this event to occur. |
|
Node |
Gets the TreeNode object that is the target of the current |
||
|
|||
|
|
operation. |
|
|
|
|
Let’s get back to our application and make use of some of these constructs. We would like to insert a set of nodes for the photos in an album whenever the album node is expanded. We can do this by handling the BeforeExpand event for our tree.
502 |
CHAPTER 15 TREE VIEWS |