
- •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

ADD THE PHOTOS IN AN ALBUM TO THE LIST
|
Action |
Result |
|
|
|
1 |
Modify the LoadPhotoData |
private void LoadPhotoData(PhotoAlbum album) |
|
method to simply return if the |
{ |
|
given album is null or empty. |
. . . |
|
// Handle null or empty album |
|
|
|
|
|
|
if (album == null || album.Count == 0) |
|
|
return; |
|
|
|
2 |
Iterate over the photographs in |
// Load the photo items |
|
the album. |
for (int i = 0; i < album.Count; i++) |
|
How-to |
{ |
|
|
|
|
Use a for loop to permit access |
|
|
to the index values. |
|
|
|
|
3 |
Create a new ListViewItem for |
Photograph photo = album[i]; |
|
each photo. |
ListViewItem item = new ListViewItem(); |
|
|
|
4 |
Assign the caption as the item |
item.Text = photo.Caption; |
|
label, and the image list index to |
item.Tag = i; |
|
our small photograph image. |
item.ImageIndex = MainForm.PhotoIndex; |
|
|
|
|
|
|
5 |
Add the subitem values. |
// Add the subitems |
|
How-to |
item.SubItems.Add(photo. |
|
DateTaken.ToShortDateString()); |
|
|
a. Use the short date format |
item.SubItems.Add(photo.Photographer); |
|
for the Taken column. |
item.SubItems.Add(photo.FileName); |
|
|
|
|
b. Also place the photo’s index |
|
|
value in a hidden subitem. |
|
|
|
|
6 |
Add the new item to the control. |
listViewMain.Items.Add(item); |
|
|
} |
|
|
} |
|
|
|
This code initializes the control with the contents of the open album. Note in particular how we define the Tag property to hold the integer index. Since the Tag property holds an object instance, this line boxes the integer value in order to store it as a reference type. Boxing was mentioned in chapter 5, and is discussed in detail in appendix A.
You can compile and run this code if you like. Double-click on an album to activate it and display the contained photographs. Most of the support for photographs is still missing, so you’ll find it rather easy to cause an error.
The remainder of this section implements the support required for both albums and photographs to coexist in our ListView control. We begin with column sorting.
14.5.4SORTING A COLUMN (AGAIN)
Our users will want to sort the columns for both the album and photograph display, so we need to make some changes in our MyListViewComparer class to enable this support. Of key importance is the ability to tell which type of object we are comparing. When comparing photos, we also need to know the PhotoAlbum they come
ITEM ACTIVATION |
477 |

from. We can handle both requirements through a private album field. When the album is null, we are comparing PhotoAlbum objects. When an album is assigned, we are comparing Photograph instances.
Let’s add this field and update our Compare method to make use of this value.
IDENTIFY THE TYPE OF OBJECT TO COMPARE
|
Action |
Result |
|
|
|
1 |
Locate the MyListViewComparer |
private class MyListViewComparer |
|
class defined in the MainForm.cs |
: IComparer |
|
source file. |
{ |
|
. . . |
|
|
|
|
|
|
|
2 |
Add a PhotoAlbum field and |
PhotoAlbum _album = null; |
|
corresponding property. |
public PhotoAlbum CurrentAlbum |
|
|
{ |
|
|
get { return _album; } |
|
|
set { _album = value; } |
|
|
} |
|
|
|
3 |
Use this property to identify which |
public int Compare(object a, object b) |
|
object to compare in the Compare |
{ |
|
method. |
. . . |
|
// Handle the nonDetails case |
|
|
|
|
|
Note: Since the label for both types |
if (ListView.View != View.Details) |
|
of items is a string, the existing |
{ |
|
return CaseInsensitiveComparer. |
|
|
code for the non-Details case will |
|
|
Default.Compare( |
|
|
work for both objects. |
item1.Text, item2.Text); |
|
|
} |
|
|
if (CurrentAlbum == null) |
|
|
return CompareAlbums(item1, item2); |
|
|
else |
|
|
return ComparePhotos(item1, item2); |
|
|
} |
|
|
} |
|
|
|
Now all we have to do is implement the ComparePhotos method to compare two Photograph items. Much of this will be similar to the CompareAlbums method. The one difference is when we need to compare items using the Taken column. This column holds a date value, so a string comparison is not appropriate. It turns out the DateTime structure provides a Compare method for just this purpose.
We can use this method in the ComparePhotos method to our comparer class.
478 |
CHAPTER 14 LIST VIEWS |

.
IMPLEMENT METHOD TO COMPARE TWO PHOTO ITEMS
|
Action |
Result |
|
|
|
4 |
Add a new ComparePhotos |
public int ComparePhotos |
|
method to the MyListView- |
(ListViewItem item1, ListViewItem item2) |
|
Comparer class. |
{ |
|
ListViewItem.ListViewSubItem sub1; |
|
|
|
|
|
|
ListViewItem.ListViewSubItem sub2; |
|
|
switch (SortColumn) |
|
|
{ |
|
|
|
5 |
For the columns containing text |
case MainForm.PhotoCaptionColumn: |
|
strings, use the default |
case MainForm.PhotoPhotographerColumn: |
|
comparer provided by the |
case MainForm.PhotoFileNameColumn: |
|
sub1 = item1.SubItems[SortColumn]; |
|
|
CaseInsensitiveComparer |
|
|
sub2 = item2.SubItems[SortColumn]; |
|
|
class. |
return CaseInsensitiveComparer. |
|
|
Default.Compare(sub1.Text, |
|
|
sub2.Text); |
|
|
|
6 |
For the Taken column, determine |
case MainForm.PhotoDateTakenColumn: |
|
the index into the album for each |
// Find the indices into the album |
|
photo. |
int index1 = (int)item1.Tag; |
|
int index2 = (int)item2.Tag; |
|
|
|
|
|
|
|
7 |
Then determine the |
// Look up the dates for each photo |
|
corresponding DateTime value |
DateTime date1 |
|
for each photo. |
= CurrentAlbum[index1].DateTaken; |
|
DateTime date2 |
|
|
|
|
|
|
= CurrentAlbum[index2].DateTaken; |
|
|
|
8 |
Use the Compare method |
return DateTime.Compare(date1, date2); |
|
provided by the DateTime |
|
|
structure to calculate the result. |
|
|
|
|
9 |
Throw an exception if an |
default: |
|
unrecognized column is |
throw new IndexOutOfRangeException( |
|
provided. |
"unrecognized column index"); |
|
} |
|
|
|
|
|
|
} |
|
|
|
The last change required for column sorting is to update the CurrentAlbum property for our comparer field whenever the contents of the ListView control are refreshed. This ensures that our Compare implementation performs the proper comparison based on the contents of the control.
UPDATE THE CURRENTALBUM PROPERTY WHEN REQUIRED
|
Action |
Result |
|
|
|
10 |
Update the LoadPhotoData |
private void LoadPhotoData(PhotoAlbum album) |
|
method to assign the current |
{ |
|
album to the comparer. |
. . . |
|
_albumsShown = false; |
|
|
|
|
|
|
_album = album; |
|
|
_comparer.CurrentAlbum = _album; |
|
|
. . . |
|
|
} |
|
|
|
ITEM ACTIVATION |
479 |

UPDATE THE CURRENTALBUM PROPERTY WHEN REQUIRED (continued)
|
Action |
Result |
|
|
|
11 |
Update the LoadAlbumData |
private void LoadAlbumData(string dir) |
|
method to assign a null album |
{ |
|
to the comparer. |
listViewMain.Clear(); |
|
_comparer.CurrentAlbum = null; |
|
|
|
|
|
|
. . . |
|
|
} |
|
|
|
Our application can now sort both photographs and albums. Once again you can compile and run the program if you are careful not to use any photograph functionality we have not yet implemented. Our next task is the Properties dialog.
14.5.5UPDATING THE PROPERTIES MENU
You may think we are moving through this code rather quickly, and you would be right. While these changes are required as a result of our defined item activation behavior, there are not a lot of new concepts to cover. This is especially true here. As a result, we will simply run through the steps in the following table and then move on to our final topic of editing the item label.
UPDATE THE CLICK HANDLER FOR THE PROPERTIES MENU
|
Action |
Result |
|
|
|
1 |
In the MainForm.cs code |
private void menuProperties_Click |
|
window, update the Click event |
(object sender, System.EventArgs e) |
|
handler for the Properties menu |
{ |
|
if (listViewMain.SelectedItems.Count <= 0) |
|
|
to call a new DisplayPhoto- |
|
|
return; |
|
|
Properties method when |
ListViewItem item |
|
photographs are shown in the |
|
|
control. |
= listViewMain.SelectedItems[0]; |
|
if (this._albumsShown) |
|
|
|
|
|
|
DisplayAlbumProperties(item); |
|
|
else |
|
|
DisplayPhotoProperties(item); |
|
|
} |
|
|
|
2 |
Add the new DisplayPhoto- |
private void DisplayPhotoProperties |
|
Properties method. |
(ListViewItem item) |
|
|
{ |
|
|
|
3 |
Determine the index of the |
if (!(item.Tag is int)) |
|
selected photo in the current |
return; |
|
album. |
int index = (int)item.Tag; |
|
|
|
|
Note: While the is keyword |
|
|
works fine with integer types, |
|
|
the as keyword can only be used |
|
|
with reference types. |
|
|
|
|
4 |
Assign the current position in the |
_album.CurrentPosition = index; |
|
album to this index. |
|
|
|
|
480 |
CHAPTER 14 LIST VIEWS |