- •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
CREATE A CLICK EVENT HANDLER FOR THE NEW MENU
|
Action |
Result |
|
|
|
1 |
In the MainForm.cs [Design] window, |
private void menuNew_Click |
|
add a Click event handler for the |
(object sender, System.EventArgs e) |
|
New menu. |
{ |
|
|
|
|
|
|
2 |
In this handler, dispose of the existing |
if (_album != null) |
|
album and create a new one. |
_album.Dispose(); |
|
Note: This really is poor design, |
_album = new PhotoAlbum(); |
|
|
|
|
since we throw away any changes to |
|
|
the existing album. We will fix this in |
|
|
chapter 8 when we discuss the Mes- |
|
|
sageBox class. |
|
|
|
|
3 |
Initialize the application title bar. |
// Set the application title bar |
|
|
SetTitleBar(); |
|
|
|
4 |
Invalidate the current window. |
this.Invalidate(); |
|
|
} |
|
|
|
5 |
Add a call to this method in the |
menuNew_Click(this, EventArgs.Empty); |
|
MainForm constructor. |
Note: The static EventArgs.Empty property |
|
|
|
|
|
provides an empty EventArgs instance for |
|
|
use when calling event handlers from your |
|
|
code. |
|
|
|
6 |
Remove the code to set the title bar |
The initial title bar is now set as part of the |
|
from the MainForm constructor. |
menuNew_Click method. |
|
|
|
We have made a few changes to our MainForm constructor here. To make sure we are all on the same page (so to speak), your constructor in Visual Studio should now look something like the following:
public MainForm()
{
//
//Required for Windows Form Designer support
InitializeComponent();
//Additional Form initialization DefineContextMenu(); menuNew_Click(this, EventArgs.Empty);
}
With this infrastructure in place, we can turn our attention to the methods required in the PhotoAlbum class.
6.6SAVE FILE DIALOGS
So far we have used the MyPhotoAlbum library to support the creation and manipulation of an album in memory. At this point, we would like to preserve this album by storing it on disk. In this section we will handle the Save menu item to do just this. In
SAVE FILE DIALOGS |
181 |
the next section we will implement an Open menu handler to allow such an album to be reloaded and used at a later time.
We have already seen how the OpenFileDialog class is used to locate image files. As you might expect, .NET provides a SaveFileDialog class to store information to a file. A summary of this class is shown in .NET Table 6.5.
.NET Table 6.5 SaveFileDialog class
The SaveFileDialog class represents a common file dialog box for saving a file to disk, and is part of the System.Windows.Forms namespace. This class inherits from the FileDialog class. See the FileDialog class description in .NET Table 1.2 on page 24 for a list of inherited members.
|
CreatePrompt |
Gets or sets whether the dialog should prompt |
|
|
|
the user for permission to create a specified file |
|
|
|
that does not exist. The default is false (do not |
|
Public Properties |
|
prompt). |
|
OverwritePrompt |
Gets or sets whether the dialog should prompt |
||
|
|||
|
|
the user for permission to overwrite a specified |
|
|
|
file that already exists. The default is true |
|
|
|
(always prompt). |
|
|
|
|
|
Public Methods |
OpenFile |
Returns a Stream object with read/write |
|
|
|
permission of the file selected by the user. |
|
|
|
|
To save an album to disk, we need to implement two types of methods. The first is a Click event handler for both the Save and Save As menus. These handlers will use the SaveFileDialog class to allow a file to be selected. Second is a PhotoAlbum.Save method to write the album information into the selected file. Separating the user interface portion, in this case the file selection, from the data manipulation portion, here the actual file writes, is a common design technique that allows us to change either aspect of the task without overly affecting the other. As we shall see in future chapters, changes to how the data is stored by the PhotoAlbum.Save method will not affect the menu handlers implemented here.
6.6.1WRITING ALBUM DATA
The Click handlers for our Save and Save As menus will rely on a Save method in the PhotoAlbum class to actually save the data, so let’s implement this first. This method will accept the name of a file in which to store the data. We rely on the user interface in MainForm to provide a file name approved by the user, so if the file already exists we will simply overwrite it.
182 |
CHAPTER 6 COMMON FILE DIALOGS |
Set the version number of the MyPhotoAlbum library to 6.6.
ADD PHOTOALBUM.SAVE METHOD
|
Action |
Result |
|
|
|
1 |
Display the PhotoAlbum.cs file. |
|
|
|
|
2 |
At the end of the file, add the |
public void Save(string fileName) |
|
new Save method. |
{ |
|
|
} |
|
|
Note: This method is void since an error is not |
|
|
expected. If something goes wrong, an Exception |
|
|
will be thrown. |
|
|
|
The format to use when creating such a file is always a question. One possibility would be to write an XML file to hold this album information. This is a good idea, but beyond the scope of this chapter, so we will stick with a simple text format. Since the file format will likely change, especially in this book, we will allow for possible future changes.
With these issues in mind, we will store each photograph in the album on a separate line, with a version number at the beginning of the file. This section will use 66 as the version number, since we are in section 6.6 of the book. The resulting file looks like this:
66
<path to photograph 0>
<path to photograph 1>
<path to photograph 2>
. . .
Our version number is likely to change in future chapters, so we will provide a constant to hold the current version.
ADD A CURRENT VERSION CONSTANT
|
Action |
Result |
|
|
|
3 |
Add a static constant integer called |
private const int _CurrentVersion = 66; |
|
_CurrentVersion to hold the version |
|
|
number. |
|
|
|
|
The Save method will store the version number followed by the file name of each Photograph written as a simple string.
SAVE FILE DIALOGS |
183 |
IMPLEMENT PHOTOALBUM.SAVE METHOD
|
Action |
Result |
|
|
|
4 |
Implement the Save method to |
public void Save(string fileName) |
|
store the album in the given file |
{ |
|
using the agreed-upon format. |
FileStream fs = new FileStream(fileName, |
|
FileMode.Create, |
|
|
|
|
|
|
FileAccess.ReadWrite); |
|
|
StreamWriter sw = new StreamWriter(fs); |
|
|
try |
|
|
{ |
|
|
sw.WriteLine(_CurrentVersion.ToString()); |
|
|
// Store each file on a separate line. |
|
|
foreach (Photograph photo in this) |
|
|
{ |
|
|
sw.WriteLine(photo.FileName); |
|
|
} |
|
|
} |
|
|
finally |
|
|
{ |
|
|
sw.Close(); |
|
|
fs.Close(); |
|
|
} |
|
|
} |
|
|
|
5 |
Implement an alternate Save |
public void Save() |
|
method that uses the default file |
{ |
|
name. |
// Assumes FileName is not null |
|
Save(this.FileName); |
|
|
|
|
|
|
} |
|
|
|
This code uses some classes we have not seen before, so let’s break our main Save method down piece by piece. Our first line opens or creates the given file name as a FileStream object. This class provides file I/O using simple byte arrays, and supports the well-known standard in, standard out, and standard error streams familiar to C and C++ programmers. Files can be open in various modes (via the FileMode enumeration), with various access levels (via the FileAccess enumeration). Different sharing options can be specified as well (not shown here) via the FileShare enumeration.
public void Save(string fileName)
{
FileStream fs = new FileStream(fileName,
FileMode.Create,
FileAccess.ReadWrite);
Next, we create a StreamWriter instance using the new FileStream object. Since we are using strings and not byte arrays, we need a class that provides simple string operations. The StreamWriter class does just this, and includes a constructor that accepts a FileStream instance.
StreamWriter sw = new StreamWriter(fs);
184 |
CHAPTER 6 COMMON FILE DIALOGS |
The new StreamWriter instance is used to write our data into the file. We encapsulate the code to write the actual data in a try block to catch any exception that might occur.
try
{
First we write the version number as a string on the first line of the file. This line is a bit more magical than it looks. We are using a constant integer as an object here. While permitted, it requires the conversion of the value type
into a reference type that can be treated as an object instance on the heap. This conversion is called boxing, since the value is “boxed” into a reference type on the heap. More information on boxing is provided in appendix A.
sw.WriteLine(_CurrentVersion.ToString());
The Photograph objects in the album are written using a foreach loop to iterate through the array. This code relies on the fact that our album contains Photograph objects and implements the IEnumerable interface. The WriteLine method from the StreamWriter class (actually, this method is inherited from the base TextWriter class) writes a given string onto a single line of the file and adds the appropriate line termination characters.
// Store each file on a separate line. foreach (Photograph photo in this)
{
sw.WriteLine(photo.FileName);
}
You may think the magic of garbage collection obviates the need to explicitly clean up system resources such as files. As we have seen, this just isn’t so. Normally the pose method is used to clean up nonmemory resources. For file objects such as FileStream and StreamWriter, the more traditional Close method is used. By definition, Close is equivalent to Dispose in the .NET Framework. Classes that provide a Close method are automatically disposed of when the Close method is called. We will discuss this notion in more detail in chapter 8.
Since the files must be closed even when an exception occurs, we encapsulate these lines in a finally block. As you may know, while a finally block does not catch any exceptions, any code in the block is executed regardless of whether an exception occurs or not.
finally
{
sw.Close();
fs.Close();
}
}
Note that closing the objects in the reverse order of which they were opened is critical. Once the FileWriter is closed, the StreamWriter is not able to write any
SAVE FILE DIALOGS |
185 |
remaining data into the file. Calling the Close methods in the proper order ensures all data is properly written to the file and avoids this potential error.
More .NET In this book we take a rather straightforward approach to reading and writing files, and will stick with a simple text file to represent our album throughout the book. There are some summaries of using the System.IO namespace in the .NET documentation if you are interested in more details. Search for the “Working with I/O” section in the .NET Framework Developer’s Guide.
We could also have stored our file in XML using classes from the System.XML namespace. The use of XML, for eXtensible Markup Language, is a great way to organize data, and is particularly useful when interacting with database systems or interfacing with remote computer systems. We opted for a simple text file in our application since many readers may not be familiar with XML. You can read up on XML in general at www.xml.org, or look up the XmlReader class and other members of the System.XML namespace in the .NET documentation.
Our new Save method can now be used in our MyPhotos application to save an album via our Save and Save As menus.
6.6.2SAVING AN ALBUM AS A NEW FILE
Let’s implement a handler for the Save As menu first. This handler should prompt the user to select a file where the album should be stored (using the SaveFileDialog class) and then use this file name to save the actual data. There are some questions to answer here about how photo albums should be saved. These questions apply more generally to any file, so are presented generically to apply to any file and not just our albums.
SaveFileDialog: questions to answer
• Where are photo albums stored?
Even though you may allow the user to select any location on disk, it is a good idea to encourage a standard location for the files in your application. In our case, this location is specified by the static DefaultDir property in the PhotoAlbum class.
• What is the file extension?
The selection of extension is a bit subjective. On Windows platforms, the following conventions normally apply:
•Use three-letter extensions. The one exception is .html files for HTML files, but even here the .htm extension is preferred.
•Keep the first letter. Typically, the first letter of the type of file should be the first letter of your extension. In our case, the extension for album file should begin with the letter ‘a’.
186 |
CHAPTER 6 COMMON FILE DIALOGS |
•Avoid numbers. At a minimum, start the extension with a letter. Use a number only if it is a critical aspect the type file you are creating.
•Avoid well-known extensions. You will avoid confusion by using a somewhat unique combination of letters. You would not want to use extensions such as
.txt (already used for Text files) or .jpg (for JPEG files). To see the list of file types currently registered on your computer, open Windows Explorer and select the Folder Options… item under the Tools menu. Click on the File Types tab to see the extensions currently in use.
•Use an acronym. It helps if your extension has some meaning to your users. If it makes sense, use an acronym of the full name. For example, the .gif extension is used for Graphics Interchange Format files.
•Leave out the vowels. Another common tactic is to leave out any vowels in the name. Examples of this include the .txt (Text) and .jpg (JPEG) extensions.
Based on these conventions, we could use alb or abm here, which both derive from Album without the vowel “u’). We will use the extension .abm.
• What is the right title bar text?
Don’t forget to set a custom title bar that makes sense for your dialog. The default title bar is “Save,” which is not very descriptive. We will use “Save Album” for our title.
• How should existing or nonexistent files be handled?
By default, the user will be prompted if they select a file that already exists (the OverwritePrompt property) and will not be told if the file is new (the CreatePrompt property). Often the default behavior is fine, but it is worth making a conscious decision for your application. We will (consciously!) use the defaults in our code.
Now that we understand the right behavior to provide, we can implement the Save As menu handler.
SAVE FILE DIALOGS |
187 |