Making Generic Classes - System Interactions - C# 24-Hour Trainer (2015)

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

List drives dialog box. It is divided into two sections. Left: A list of drives. Right: The details of the drive, namely, label, root, total space, total free space, available free space, format, type, and readiness status.

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.

Search for Files dialog box presenting text boxes for directory, pattern, and word to be searched atop a Search button. Below is a box listing the results.

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.