C# 24-Hour Trainer (2015)
Section V
System Interactions
The lessons up to this point have explained how you can do some pretty remarkable things. Using their techniques you can read inputs entered by the user, perform intricate calculations, repeat a sequence of commands a huge number of times, and even build your own classes to model complex situations.
All of the programs that you've written so far, however, are self-contained. They get input from the user, but otherwise they don't interact with the computer.
The lessons in this section explain some of the ways a program can interact with the system. They explain how to read and write files, explore the filesystem, and print.
· Lesson 29: Using Files
· Lesson 30: Printing
NOTE
A program can interact with the computer in lots of other ways. It can interact with hardware through serial ports and special devices and connect to websites or other programs over a network. It can use copy-and-paste and the clipboard to interact with other programs. It even has many different ways to interact with the same part of the system. For example, a program has many ways to manipulate files, read and modify the Windows registry, and save and restore program parameters. The lessons in this part of the book describe some of the ways a program can interact with the wider system, but these are by no means the only ways.
Lesson 29
Using Files
Files play an extremely important role on a computer. They hold text, pictures, Microsoft Word documents, spreadsheets, and all sorts of other data. They also hold executable programs including programs that you write, programs provided by Microsoft and other software vendors, and even the programs that make up the operating system itself.
In this lesson you learn how to explore the filesystem. You also learn some basic techniques for reading and writing files. Using some fairly simple techniques, you can use text files to store and retrieve data used by a program.
NOTE
This is one of those topics where there are many ways to perform the same tasks. There are lots of approaches to searching the filesystem and manipulating files. This lesson describes only a few.
Filesystem Classes
Before you can manipulate a file, you need to be able to find it. This section describes .NET Framework classes that let you search the computer's filesystem.
NOTE
These classes are in the System.IO namespace so you can make using them easier by including the directive:
using System.IO;
DriveInfo
The DriveInfo class provides information about the system's drives. Its static GetDrives function returns an array of DriveInfo objects describing all of the system's drives. Table 29.1 summarizes the DriveInfo class's most useful properties.
Table 29.1
Property |
Purpose |
AvailableFreeSpace |
The total number of bytes available. |
DriveFormat |
The drive format, as in NTFS or FAT32. |
DriveType |
The drive type, as in Fixed or CDRom. |
IsReady |
Returns true if the drive is ready. A drive must be ready before you can use the AvailableFreeSpace, DriveFormat, TotalSize, or VolumeLabel properties. |
Name |
The drive's name, as in C:\. |
RootDirectory |
A DirectoryInfo object representing the drive's root directory. |
TotalFreeSpace |
The number of bytes available, taking quotas into account. |
TotalSize |
The drive's total size in bytes. |
VolumeLabel |
The drive's label. |
The List Drives example program, which is in this lesson's code download and shown in Figure 29.1, uses DriveInfo properties and methods to show information about the computer's drives. For details about how the program works, download it from the book's website
Figure 29.1
DirectoryInfo
The DirectoryInfo class provides information about directories. Table 29.2 summarizes useful DirectoryInfo methods for manipulating directories.
Table 29.2
Method |
Purpose |
Create |
Creates a new directory. To use this, make a DirectoryInfo object, passing its constructor the name of the directory to create. Then call the Create method. |
CreateSubdirectory |
Creates a subdirectory inside this directory. |
Delete |
Deletes the directory. If you pass no parameters to this method, it deletes the directory only if it's empty. You can also pass it a boolean parameter indicating whether you want to delete all of the directory's files and subdirectories. |
GetDirectories |
Returns the directory's immediate subdirectories. Optionally you can include a search string to select particular subdirectories. |
GetFiles |
Returns the directory's files. Optionally you can include a search string to select particular files. |
MoveTo |
Moves the directory to a new path. |
The DirectoryInfo class also provides a few useful properties, which are summarized in Table 29.3.
Table 29.3
Property |
Purpose |
Attributes |
The directory's attributes, such as Compressed, Hidden, or System. |
CreationTime |
The time at which the directory was created. |
Exists |
Returns true if the directory actually exists. |
FullName |
Gives the directory's fully qualified path. |
LastAccessTime |
The time at which the directory was last accessed. |
LastWriteTime |
The time at which the directory was last written. |
Name |
The directory's name without the path. |
Parent |
A DirectoryInfo object representing this directory's parent directory. |
Root |
The directory's filesystem root. |
Example program Use DirectoryInfo (found in this lesson's code download) uses a DirectoryInfo object to display information about directories.
Directory
The Directory class provides static methods for manipulating directories. Table 29.4 lists the most used methods. For simple tasks these are sometimes easier to use than the comparable DirectoryInfo class methods because you don't need to create a DirectoryInfoobject to use them.
Table 29.4
Method |
Purpose |
CreateDirectory |
Creates the directory and any missing directories in its path up to the root. |
Delete |
Deletes a directory. |
Exists |
Returns true if the directory exists. |
GetCreationTime |
Returns the time at which the directory was created. |
GetDirectories |
Returns a directory's subdirectories. |
GetDirectoryRoot |
Returns the directory's root. |
GetFiles |
Returns a directory's files, optionally looking for files matching a pattern. |
GetLastAccessTime |
Returns the time at which a directory was last accessed. |
GetLastWriteTime |
Returns the time at which a directory was last written. |
GetParent |
Returns a DirectoryInfo object representing a directory's parent directory. |
Move |
Moves a file or directory to a new location. |
SetCreationTime |
Sets the directory's creation time. |
SetLastAccessTime |
Sets the directory's last access time. |
SetLastWriteTime |
Sets the directory's last write time. |
FileInfo
The FileInfo class, as you can probably guess at this point, provides information about files. Table 29.5 summarizes useful FileInfo methods for manipulating files.
Table 29.5
Method |
Purpose |
CopyTo |
Copies the file to a new location. |
Decrypt |
Decrypts a file that was encrypted by the Encrypt method. |
Delete |
Deletes the file. |
Encrypt |
Encrypts the file so it can only be read by the account used to encrypt it. |
MoveTo |
Moves the file to a new location. |
The FileInfo class also provides some useful properties, summarized in Table 29.6.
Table 29.6
Property |
Purpose |
Attributes |
The file's attributes, such as Compressed, Hidden, or System. |
CreationTime |
The time at which the file was created. |
Directory |
A DirectoryInfo object for the directory containing the file. |
Exists |
Returns true if the file exists. |
Extension |
Returns the file's extension. |
FullName |
Gives the file's fully qualified path. |
IsReadOnly |
Returns true if the file is marked read-only. |
LastAccessTime |
The time at which the file was last accessed. |
LastWriteTime |
The time at which the file was last written. |
Length |
The file's size in bytes. |
Name |
The file's name without the path. |
Example program Use FileInfo (which is in this lesson's code download) uses a FileInfo object to display information about files.
File
The File class provides static methods for manipulating files (see Table 29.7). For simple tasks these are sometimes easier to use than the comparable FileInfo class methods because you don't need to create a FileInfo object to use them.
Table 29.7
Method |
Purpose |
AppendAllText |
Appends a string to the end of a file. |
Copy |
Copies a file to a new file. |
Create |
Creates a file. |
Decrypt |
Decrypts a file that was encrypted by the Encrypt method. |
Delete |
Deletes a file. |
Encrypt |
Encrypts the file so it can only be read by the account used to encrypt it. |
Exists |
Returns true if a file exists. |
GetAttributes |
Returns a file's attributes, such as ReadOnly, System, or Hidden. |
GetCreationTime |
Returns the time at which the file was created. |
GetLastAccessTime |
Returns the time at which a file was last accessed. |
GetLastWriteTime |
Returns the time at which a file was last written. |
Move |
Moves a file to a new location. |
ReadAllBytes |
Returns a file's contents in an array of bytes. |
ReadAllLines |
Returns the lines in a text file as an array of strings. |
ReadAllText |
Returns a text file's contents in a string. |
SetAttributes |
Sets a file's attributes. |
SetCreationTime |
Sets a file's creation time. |
SetLastAccessTime |
Sets a file's last access time. |
SetLastWriteTime |
Sets a file's last write time. |
WriteAllBytes |
Writes a file's contents from an array of bytes. |
WriteAllLines |
Writes a text file's contents from an array of strings. |
WriteAllText |
Writes a text file's contents from a string. |
The AppendAllText, ReadAllLines, ReadAllText, WriteAllLines, and WriteAllText methods are particularly useful for reading and writing text files all at once, although you may still want to use the StreamReader and StreamWriter classes described later in this lesson if you need to manipulate files one line at a time.
Path
The Path class provides static methods that perform string operations on file paths. For example, you can use the ChangeExtension method to change the extension part of a file's name. Table 29.8 summarizes the Path class's most useful methods.
Table 29.8
Method |
Purpose |
ChangeExtension |
Changes a filename's extension. |
Combine |
Combines two path strings, adding a backslash between them if needed. |
GetDirectoryName |
Returns the directory name part of a path. |
GetExtension |
Returns the extension part of a filename. |
GetFileName |
Returns the filename part of a file's path. |
GetFileNameWithoutExtension |
Returns the filename part of a file's path without the extension. |
GetTempFileName |
Returns a name for a temporary file. |
GetTempPath |
Returns the path to the system's temporary folder. |
Streams
A computer can contain many kinds of files: web pages, video, audio, executable, and lots of others. At some level, however, files are all the same. They're just a series of bytes stored on a filesystem somewhere.
Thinking about files at this very low level lets you treat them uniformly. It lets you define common classes and methods that you can use to manipulate any kind of file.
Many programming languages, including C#, make working with files at a low level easier by defining the concept of a stream. A stream is simply an ordered series of bytes.
NOTE
Streams can also represent things other than files. For example, a stream could represent data being sent from one program to another, a series of bytes being downloaded from a website, or the flow of data as it moves through some complex process such as encryption or compression. This section focuses on file streams.
Stream objects provide methods for manipulating data at a low level. For example, the Stream class provides Read and Write methods that move bytes of data between the stream and an array of bytes in your program.
Working with streams at this low level is convenient for some programs, but it makes day-to-day file handling difficult. You probably don't want to read the bytes from a text file and then reassemble them into characters.
The StreamReader and StreamWriter classes make reading and writing text streams much easier. As you can probably guess from their names, StreamReader lets you read text from a stream and StreamWriter lets you write text into a stream. If that stream happens to represent a file, then you're reading and writing files.
NOTE
The StreamReader and StreamWriter classes are in the System.IO namespace. To make it easier to use these classes, you can add the following using directive to your code:
using System.IO;
Writing Files
The StreamWriter class provides several constructors to build a StreamWriter associated with different kinds of streams. One of the simplest constructors takes a filename as a parameter. It opens the file for writing and associates the new StreamWriter with it.
NOTE
Note that StreamWriter implements IDisposable, so you should use it inside a using block to call its Dispose method automatically.
The following code shows how a program can open the file Memo.txt for writing. If the file already exists, it is overwritten.
// Write into the file, overwriting it if it exists.
using (StreamWriter memoWriter = new StreamWriter("Memo.txt"))
{
// Write into the file.
...
}
NOTE
If you pass the constructor a filename without a path such as Memo.txt, the program creates the file in its current directory. You can use a fully qualified filename such as C:\Temp\Memo.txt to create the file in a particular directory.
Another version of the class's constructor takes a second bool parameter that indicates whether you want to open the file for appending. If you set this parameter to true, the StreamWriter opens the existing file and prepares to add text to the end. If the file doesn't exit, the object silently creates a new file and gets ready to append.
The StreamWriter class provides a Write method to add text to the file. The WriteLine method adds text followed by a new line. Both Write and WriteLine have overloaded versions that write various data types into the file: bool, char, string, int, decimal, and so on. They also provide versions that take a format string and parameters much as the string.Format method does.
The StreamWriter provides one other very important method that I want to cover here: Close. The Close method closes the StreamWriter and its associated file. When you use the Write and WriteLine methods, the StreamWriter may actually buffer its output in memory and only write to the file when it has enough data stored up. The Close method forces the StreamWriter to flush its buffer into the file, and until you call Close the data may not actually be in the file. If your program crashes or ends without calling Close, there's a very good chance that some or all of your text will be lost.
The following code shows how a program could save the contents of a TextBox in a file:
// Write the file, overwriting it if it exists.
using (StreamWriter memoWriter = new StreamWriter("Memo.txt"))
{
// Write the file.
memoWriter.Write(memoTextBox.Text);
memoWriter.Close();
}
Reading Files
The StreamReader class lets you easily read text from a file. Like the StreamWriter class, StreamReader provides a constructor that takes a parameter giving the name of the file to open.
The StreamReader constructor throws an exception if the file doesn't exist, so your program should verify that the file is there before you try to open it. For example, you can use the File class's static Exists method to see if the file exists.
The StreamReader class provides a Read method that lets you read from the file one or more bytes at a time, but usually you'll want to use its ReadLine and ReadToEnd methods.
As you may be able to guess, ReadLine reads the next line from the file and returns it as a string. ReadToEnd reads the rest of the file from the current position onward and returns it as a string.
The following code reads the file Memo.txt and displays its contents in a TextBox:
// Read the file.
using (StreamReader memoReader = new StreamReader("Memo.txt"))
{
memoTextBox.Text = memoReader.ReadToEnd();
memoReader.Close();
}
The StreamReader's EndOfStream property returns true if the reader is at the end of the stream. This is particularly useful when you're reading a stream of unknown length. For example, the program can enter a while loop that uses ReadLine to read lines and continue as long as EndOfStream is false.
Try It
In this Try It, you build the program shown in Figure 29.2 to let the user search for files that match a pattern and that contain a target string. Enter a directory at which to start the search, select or enter a file pattern in the Pattern combo box, and enter a target string in the Search For textbox. When you click Search, the program searches for files matching the pattern and containing the target string.
Figure 29.2
Lesson Requirements
In this lesson, you:
· Start a new project and arrange its form, as shown in Figure 29.2. Give the combo box the choices *.cs, *.txt, *.*, and any other patterns that you think would be useful.
· Give the form a Load event handler that places the application's startup path in the Directory textbox (just to have somewhere to start).
· Give the Search button a Click event handler that searches for the desired files.
NOTE
You can download the code and resources for this lesson from the website at www.wrox.com/go/csharp24hourtrainer2e.
Hints
· Use the DirectoryInfo class's GetFiles method to search for files matching the pattern.
· Use the FileInfo class's ReadAllText method to get the file's contents. Then use string methods to see if the text contains the target string.
· To ignore case, convert the target string and the files' contents to lowercase.
Step-by-Step
· Start a new project and arrange its form, as shown in Figure 29.2. Give the combo box the choices *.cs, *.txt, *.*, and any other patterns that you think would be useful.
1. This is reasonably straightforward.
· Give the form a Load event handler that places the application's startup path in the Directory textbox (just to have somewhere to start).
1. Use code similar to the following:
2. // Start at the startup directory.
3. private void Form1_Load(object sender, EventArgs e)
4. {
5. directoryTextBox.Text = Application.StartupPath;
}
· Give the Search button a Click event handler that searches for the desired files.
1. Use code similar to the following:
2. // Search for files matching the pattern
3. // and containing the target string.
4. private void searchButton_Click(object sender, EventArgs e)
5. {
6. // Get the file pattern and target string.
7. string pattern = patternComboBox.Text;
8. string target = targetTextBox.Text.ToLower();
9. // Clear the result list.
10. fileListBox.Items.Clear();
11. // Search for files.
12. DirectoryInfo dirinfo =
13. new DirectoryInfo(directoryTextBox.Text);
14. foreach (FileInfo fileinfo in
15. dirinfo.GetFiles(pattern, SearchOption.AllDirectories))
16. {
17. // See if we need to look for target text.
18. if (target.Length > 0)
19. {
20. // If this file contains the target string,
21. // add it to the list.
22. string content =
23. File.ReadAllText(fileinfo.FullName).ToLower();
24. if (content.Contains(target))
25. fileListBox.Items.Add(fileinfo);
26. }
27. else
28. {
29. // Just add this file to the list.
30. fileListBox.Items.Add(fileinfo);
31. }
32. }
}
Exercises
1. Write a program that sorts a text file. (Hint: Load the file's lines of text into an array and use Array.Sort to do the actual sorting.) Test the program on the file Names.txt included in this lesson's download.
2. Write a program that removes duplicate entries from a text file. (Hint: Copy the program you built for Exercise 2. After you sort the array, run through the entries, copying them into a new list. If you see a duplicate entry, skip it and write it to the Console window.) Test the program on the file Names.txt included in this lesson's download.
3. Make a program that has Labels and TextBoxes for first name, last name, street, city, state, and ZIP code. When the form closes, save the values in the TextBoxes in a text file. When the program loads, reload the values. (Hint: Write each TextBox's value on a separate line in the text file.)
4. Build a Memo program that saves and loads a single memo saved in the file in a multi-line TextBox. (This is so easy I wouldn't even bother using it as an exercise except it's actually useful. You can use it to record notes during the day and easily read them the next day.)
5. Make a program that lets the user select a number from a NumericUpDown control and then generates a text file containing a multiplication table that goes up to that number times itself. Use formatting to make the numbers line up in columns.
6. Build a program with a TextBox, a ListBox, an Add button, and a Save button. When the user enters a value in the TextBox and clicks Add, add the value to the ListBox. When the user clicks Save, write the values from the ListBox into a file and then clear the ListBox. When the form loads, make it read the values back into the ListBox.
7. Build a simple text editor. Give it a TextBox and a File menu with Open, New, and Save As commands. Use an OpenFileDialog and a SaveFileDialog to let the user select the file to open and save. (Don't worry about any of the other things a real editor would need to handle, such as locked files and ensuring that the user doesn't close the program with unsaved changes.)
NOTE
Please select the videos for Lesson 29 online at www.wrox.com/go/csharp24hourtrainer2evideos.