- Key Classes Related to File I/O
- Directory and File Operations
- Reading and Writing to Files and Streams
- Learning By Example: Adding Open and Save to FontPad
- Summary
Directory and File Operations
In past versions of Visual Basic, we were relegated to the FileSystemObject (or a similar third-party control) or the Win32 API to implement most file operations. The System.IO name-space provides a robust set of objects for our tool belt. These objects can be leveraged to solve a host of problems and ease and simplify interaction with the file system.
As programmers, it is important for us to understand the key aspects of the Windows file system. A file, in Windows, is an ordered and named collection of a sequence of bytes having persistent storage. When you think of files, you think of a filename, file size, attributes, and directory path. Directories in Windows are simply another type of file that contains other files and subdirectories. This section illustrates how to interact with the Windows file system using the System.IO namespace.
Access and Attributes
To access directories and files using the .NET Class Library you work with two primary classes: DirectoryInfo and FileInfo. These classes are designed to provide most of the file system functionality and information that your applications need.
The DirectoryInfo and Directory classes are used to create, move, and enumerate through directories and subdirectories. The Directory class contains the associated static methods, while DirectoryInfo contains the instance methods. An instance of the DirectoryInfo class represents one physical directory. With it, you can call the GetDirectories method to return a list of the directory's subdirectories. You can also return a list of files in the directory with the GetFiles method. Of course, there are a number of other important properties and methods inside the DirectoryInfo class.
The FileInfo and File classes are used to create, copy, delete, move, and open files. The File class contains the associated static methods, where FileInfo contains the instance methods. Using an instance of the FileInfo class, you can return specific attributes of a given file. For example, you can read the file's name, size, and parent directory. The actual content of a file is accessed via the FileStream object. The FileStream class allows for both synchronous and asynchronous read and writes to a file.
To best illustrate the use of these classes, we will create a simple directory and file listing application. The application will read a list of directories, and for each directory, display a list of files contained by it. Additionally, for both the selected file and the selected directory, we will list a number of key attributes. Figure 7.1 illustrates the form that we will use to display the application's information. Of course, I use the term application loosely. This is just some sample code with an attached form.
Figure 7.1 Access and attributes form.
The code for the example is provided in Listing 7.1. It involves code to create the form, a form load event, and a pair of index change events for the two list boxes.
The code starts with a few global variable declarations to store a path, directory, and filename. This is followed by the basic form creation code.
LISTING 7.1 Access and Attributes
Public Class Form2 Inherits System.Windows.Forms.Form 'form-level scope declarations Dim myPath As String = "c:\" Dim myDirName As String Dim myFileName As String # Region " Windows Form Designer generated code " Public Sub New() MyBase.New() 'This call is required by the Windows Form Designer. InitializeComponent() 'Add any initialization after the InitializeComponent() call End Sub 'Form overrides dispose to clean up the component list. Public Overloads Overrides Sub Dispose() MyBase.Dispose() If Not (components Is Nothing) Then components.Dispose() End If End Sub Private WithEvents label1 As System.Windows.Forms.Label Private WithEvents label2 As System.Windows.Forms.Label Private WithEvents labelDirectoryInfo As System.Windows.Forms.Label Private WithEvents listBoxFiles As System.Windows.Forms.ListBox Private WithEvents labelFileInfo As System.Windows.Forms.Label Private WithEvents listBoxDirectories As System.Windows.Forms.ListBox Private WithEvents buttonClose As System.Windows.Forms.Button 'Required by the Windows Form Designer Private components As System.ComponentModel.Container 'NOTE: The following procedure is required by the Windows Form Designer 'It can be modified using the Windows Form Designer. 'Do not modify it using the code editor. <System.Diagnostics.DebuggerStepThrough()> Private Sub _ InitializeComponent() Me.labelFileInfo = New System.Windows.Forms.Label() Me.listBoxFiles = New System.Windows.Forms.ListBox() Me.labelDirectoryInfo = New System.Windows.Forms.Label() Me.listBoxDirectories = New System.Windows.Forms.ListBox() Me.buttonClose = New System.Windows.Forms.Button() Me.label1 = New System.Windows.Forms.Label() Me.label2 = New System.Windows.Forms.Label() Me.labelFileInfo.BorderStyle = System.Windows.Forms.BorderStyle.Fixed3D Me.labelFileInfo.Location = New System.Drawing.Point(248, 216) Me.labelFileInfo.Size = New System.Drawing.Size(200, 160) Me.labelFileInfo.TabIndex = 4 Me.labelFileInfo.Text = "label3" Me.listBoxFiles.Location = New System.Drawing.Point(8, 216) Me.listBoxFiles.Size = New System.Drawing.Size(232, 160) Me.listBoxFiles.TabIndex = 1 Me.labelDirectoryInfo.BorderStyle = _ System.Windows.Forms.BorderStyle.Fixed3D Me.labelDirectoryInfo.Location = New System.Drawing.Point(248, 24) Me.labelDirectoryInfo.Size = New System.Drawing.Size(200, 160) Me.labelDirectoryInfo.TabIndex = 4 Me.labelDirectoryInfo.Text = "label3" Me.listBoxDirectories.Location = New System.Drawing.Point(8, 24) Me.listBoxDirectories.Size = New System.Drawing.Size(232, 160) Me.listBoxDirectories.TabIndex = 0 Me.buttonClose.Location = New System.Drawing.Point(376, 384) Me.buttonClose.TabIndex = 5 Me.buttonClose.Text = "Close" Me.label1.Location = New System.Drawing.Point(8, 8) Me.label1.TabIndex = 2 Me.label1.Text = "Directories" Me.label2.Location = New System.Drawing.Point(8, 200) Me.label2.TabIndex = 3 Me.label2.Text = "Files" Me.AcceptButton = Me.buttonClose Me.AutoScaleBaseSize = New System.Drawing.Size(5, 13) Me.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog Me.ClientSize = New System.Drawing.Size(453, 410) Me.Controls.AddRange(New System.Windows.Forms.Control() _ {Me.buttonClose, Me.labelFileInfo, Me.listBoxFiles, _ Me.listBoxDirectories, Me.labelDirectoryInfo, Me.label2, _ Me.label1}) Me.Text = "Directory And Files" End Sub
Inside the form's load event we return a list of directories. To do so, first we instantiate a DirectoryInfo class with the line
Dim myDirectory As New System.IO.DirectoryInfo(path:=myPath).
where myPath is a valid path (c:\). Next, we call the GetDirectories method of the DirectoryInfo class. This returns an array of DirectoryInfo objects that represent the path's subdirectories. Finally, we select an item in the directory list box. This fires the index-changed event of the directory list box.
Private Sub Form1_Load(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles MyBase.Load 'purpose: load the form, set the control values 'local scope Dim myDirectories() As System.IO.DirectoryInfo Dim i As Integer 'create a new directory object pointed at c: Dim myDirectory As New System.IO.DirectoryInfo(path:=myPath) 'return the sub directories of c: from the global directory object myDirectories = myDirectory.GetDirectories() 'loop through the directories and add them to the list box For i = 0 To UBound(myDirectories) - 1 'add the directory name to the list listBoxDirectories().Items.Add(myDirectories(i).Name) Next 'select the first directory in the list ' note: this will trigger the events that fill the label control ' and the files list box and its label control listBoxDirectories().SelectedIndex = 0 End Sub
Once a user selects a directory, we must return to her a list of files. In our example, we do this inside the listBoxDirectories index change event (listBoxDirectories_SelectedIndexChanged). We first create a new DirectoryInfo object based on our starting path value and the user's selected directory:
myDirectory = New System.IO.DirectoryInfo(path:=(myPath & myDirName))
Next we return an array of FileInfo objects using the method GetFiles of the DirectoryInfo class. Finally, we loop through the array and add the filenames to the list box and select the first file in the list.
Private Sub listBoxDirectories_SelectedIndexChanged(ByVal sender As _ System.Object, ByVal e As System.EventArgs) Handles _ listBoxDirectories.SelectedIndexChanged 'purpose: synchronize the files list box with the selected directory ' and display the selected directory's information 'local scope Dim myDirectory As System.IO.DirectoryInfo Dim dirInfo As String Dim myFiles() As System.IO.FileInfo Dim i As Integer 'clear the listbox of its current contents listBoxFiles().Items.Clear() 'get a directory info object based on the user's selection myDirName = listBoxDirectories().SelectedItem.ToString & "\" myDirectory = New System.IO.DirectoryInfo( _ path:=(myPath & myDirName)) 'set the dir info dirInfo = dirInfo & "Path: " & myDirectory.FullName & vbCrLf dirInfo = dirInfo & "Attributes: " & myDirectory.Attributes.ToString _ & vbCrLf labelDirectoryInfo().Text = dirInfo 'get the files in the directory myFiles = myDirectory.GetFiles() 'check for files If UBound(myFiles) >= 0 Then 'loop through the files array and add to the listbox For i = 0 To UBound(myFiles) - 1 listBoxFiles().Items.Add(myFiles(i).Name) Next 'select the file in the list, this will trigger the event to change ' the file info label control listBoxFiles().SelectedIndex = 0 End If End Sub
As a filename is selected (listBoxFiles_SelectedIndexChanged event) we update the contents of the file information label. To do this we create an instance of the FileInfo class based on the file's path and name. We then read some of the file's properties and display them to a label control.
Private Sub listBoxFiles_SelectedIndexChanged(ByVal sender As _ System.Object, ByVal e As System.EventArgs) Handles _ listBoxFiles.SelectedIndexChanged 'purpose: change the contents of the file properties label 'local scope Dim myFile As System.IO.FileInfo Dim fileInfo As String 'set the file name myFileName = listBoxFiles().SelectedItem.ToString 'create a new file object myFile = New System.IO.FileInfo(fileName:=myPath & myDirName & _ myFileName) 'set the file info to display fileInfo = fileInfo & "Directory: " & myFile.DirectoryName & vbCrLf fileInfo = fileInfo & "Created Time: " & myFile.CreationTime & vbCrLf fileInfo = fileInfo & "Size: " & myFile.Length & vbCrLf fileInfo = fileInfo & "Last Accessed: " & myFile.LastAccessTime & _ vbCrLf fileInfo = fileInfo & "Attributes: " & myFile.Attributes.ToString & _ vbCrLf 'set the label to the file's info labelFileInfo().Text = fileInfo End Sub Private Sub buttonClose_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles buttonClose.Click 'purpose: end the application 'close the form Me.Close() End Sub #End Region End Class
In the prior example we used the FileInfo class to display file attributes to the user. This class has a number of properties that are useful when working with files. Some of these that are of keen interest are listed in Table 7.2.
TABLE 7.2 Properties of the FileInfo Class
Property |
Data Type |
Description |
Attributes |
FileSystemInfo.Attributes |
The Attributes property is used to get or set the file's attributes. The value of the property is based on a combination of the file-attribute flags found in FileSystemInfo.Attributes. Values include: archive, compressed, directory, hidden, offline, read-only, system, and temporary. |
CreationTime |
Datetime |
The CreationTime property returns or sets the time that a file was created. |
Directory |
Directory class |
The Directory property returns an instance of the file's parent directory as the Directory class. |
DirectoryName |
String |
The DirectoryName property returns the full path to the file. |
Exists |
Boolean |
The Exists property returns true if a file physically exists and false if it does not. |
Extension |
String |
The Extension property returns the file's extension (.txt for text files). |
FullName |
String |
The FullName property returns the complete path to the file and its name. |
LastAcccessTime |
DateTime |
The LastAccessTime property returns or sets the time that the file was last accessed. |
LastWriteTime |
DateTime |
The LastWriteTime property returns or sets the time the file was last written to. |
Length |
Long |
The Length property returns the size of the file in bytes. |
Name |
String |
The Name property returns the name of the file. |
Creation, Deletion, and Manipulation
So far we've looked at how we access directories and files and return their attributes. Now we will explore the basic tasks of creating, deleting, and moving files and directories. The .NET Framework's System.IO offers us all the necessary methods to perform these tasks.
Directories
For manipulating directories we primarily use five basic methods: Create, CreateSubdirectory, Delete, MoveTo, and Refresh. These methods are called from the DirectoryInfo class:
The Create method creates the directory to which the DirectoryInfo object refers. The CreateSubdirectory method accepts the subdirectory's path as a parameter. It then creates the subdirectory and returns it as a DirectoryInfo instance.
The Delete method has two overloaded parameter sets. The first takes no parameters and simply deletes the directory and its contents to which the DirectoryInfo instance refers. The second takes a Boolean value that indicates whether the delete call should also be recursive; that is, whether it should delete all its subdirectories and their contents.
The MoveTo method takes a string as a parameter (destDirName) that represents the destination path. The destination path, as you may have guessed, defines the directory and its path to which the current directory (defined by the DirctoryInfo instance) should be moved.
The Refresh method refreshes the DirectoryInfo object instance. The method reloads the directory information. This is useful for long-running objects to maintain the most up-to-date information on the directory attributes.
Listing 7.2 is a procedure that demonstrates these methods. The procedure can be copied into a console application and executed.
LISTING 7.2 Directories Create, Move, and Delete
Module Module1 Sub Main() 'purpose: demonstrate code that does the following: ' 1. creates a directory ' 2. creates a sub directory ' 3. moves the sub directory under the first directory ' 4. deletes the sub directory 'local scope Dim myDirectoryParent As System.IO.DirectoryInfo Dim myDirectory2 As System.IO.DirectoryInfo 'create a new instance of the directory info object myDirectoryParent = New System.IO.DirectoryInfo(path:="c:\") 'create a sub directory under the parent directory myDirectoryParent.CreateSubdirectory(path:="new directory 1") 'create another sub directory under the parent directory myDirectoryParent.CreateSubdirectory(path:="new directory 2") 'create a directory object based on the 2nd directory myDirectory2 = New System.IO.DirectoryInfo( _ path:="c:\new directory 2") 'move the second directory under the first 'note: you must supply its new name (can be the same) myDirectory2.MoveTo( _ destDirName:="c:\new directory 1\new directory 2") 'delete the second directory and its contents 'note: you could set the recursive property to true ' to delete its sub directories as well myDirectory2.Delete() End Sub End Module
Files
For working with files we use a similar set of methods found in the FileInfo class. The Create, Delete, MoveTo, and Refresh methods are all present. However, programming with files requires a few additional methods: AppendText, CopyTo, CreateText, Open, OpenRead, and OpenWrite (all of which will be defined later in this chapter).
The Create, MoveTo, and Delete methods are very similar to the DirectoryInfo class. The CopyTo method copies a version of the file to which the FileInfo instance refers. The method is overloaded with two parameter sets. The first copies an instance of the given file to the new directory specified in the parameter destFileName. This method raises an exception if a file with the same name already exists in the destination directory. Upon success, the method returns an instance of the FileInfo class based on the copied file. The second overloaded method takes both the destFileName and the parameter overwrite as a Boolean. Set this parameter to True if you want the Copy method to overwrite any existing file with the same name and False if you do not. Sample code that illustrates these methods is provided for you in Listing 7.3.
Note the return type of the Create method. On file create, we are returned an instance of the FileStream object. This object is a stream object based on the new file. The stream object provides us the ability to read and write to the file. Streams, as well as reading and writing to files, are discussed later in this chapter.
LISTING 7.3 Files Create, Move, CopyTo, and Delete
Imports System.IO Module Module1 Sub Main() 'purpose: demonstrate code that does the following: ' 1. creates a file ' 3. moves the file to a different directory ' 4. copies the file to another directory ' 5. deletes the file 'local scope Dim myFileStream As FileStream Dim myFile As FileInfo Dim myCopiedFile As FileInfo 'check if file exists, if so delete it If File.Exists(path:="c:\newFile.txt") Then File.Delete(path:="c:\newFile.txt") End If 'create a new file using, set = to file stream object myFileStream = File.Create(path:="c:\newFile.txt") 'close the file stream object myFileStream.Close() 'get the newly created file myFile = New FileInfo(fileName:="c:\newFile.txt") 'check if move to file exists, if so delete it If File.Exists(path:="c:\moveFile.txt") Then File.Delete(path:="c:\moveFile.txt") End If 'move the file myFile.MoveTo(destFileName:="c:\moveFile.txt") 'check to see if directory exists, else create it If Not System.IO.Directory.Exists(path:="C:\new directory 1") Then System.IO.Directory.CreateDirectory( _ path:="C:\new directory 1") End If 'copy the file back to the directory 'overwrite an existing file if need be myCopiedFile = myFile.CopyTo( _ destFileName:="C:\new directory 1\copyFile.txt", _ overwrite:=True) 'wait for the user to stop the console application 'this allows you to view the directories and the results Console.WriteLine("Enter 's' to stop the application.") 'loop until user presses s key Do While Console.ReadLine <> "s" : Loop 'delete the files myFile.Delete() myCopiedFile.Delete() End Sub End Module
The level of access users are granted to a file is controlled by the FileAccess enumeration. The parameter is specified in many of the constructors of the File, FileInfo, and FileStream classes. Table 7.3 lists the members of the enumeration and a brief description of each.
TABLE 7.3 FileAccess Enumeration Members
Member |
Description |
Read |
The Read member indicates that data can be read from a file. |
ReadWrite |
The ReadWrite member defines both read and write access to a given file. |
Write |
The Write member provides write access to a file. |
The various attribute values for files and directories are controlled using the FileAttributes enumeration. It should be noted that not all members are applicable to both files and directories. Table 7.4 lists the members of the FileAttributes enumeration.
TABLE 7.4 FileAttributes Enumeration Members
Member |
Description |
Archive |
The Archive member indicates a file's archive status. The archive status is often used to mark files for backup or removal. |
Compressed |
The Compressed member indicates that a file is compressed. |
Directory |
The Directory file attribute indicates that a file is actually a directory. |
Encrypted |
The Encrypted member indicates that a file is encrypted. |
Hidden |
The Hidden value indicates that the file is hidden, and thus not included in an ordinary directory listing. |
Normal |
The Normal value indicates the file has no other attributes set. |
NotContentIndexed |
The NotContentIndexed value indicates that the file will not be indexed by Windows' indexing service. |
Offline |
The Offline value indicates that the file's data is not readily accessible (offline). |
ReadOnly |
The ReadOnly member indicates that the file cannot be modified; it is for reading only. |
ReparsePoint |
The ReparsePoint member indicates that the file contains a block of user-defined data (reparse point). |
SparsePoint |
The SparsePoint member indicates that the file is a sparse file. Sparse files are large files whose data are mostly zero values. |
System |
The System member marks a file as being part of the operating system or used exclusively by the operating system. |
Temporary |
The Temporary value indicates that a file is temporary. Temporary files should be created and deleted by applications as they are needed and no longer needed. |
To control whether a file is created, overwritten, opened, or some combination thereof, you use the FileMode enumeration. It is used in many of the constructors of the FileStream, File, FileInfo, and IsolatedStorageFileStream classes. Table 7.5 lists the members of the FileMode enumeration.
TABLE 7.5 FileMode Enumeration Members
Member |
Description |
Append |
The Append parameter specifies that a file be opened or created, and its end searched (seek) out. FileMode.Append can only be used in conjunction with FileAccess.Write. Any attempt to read fails and throws an ArgumentException. |
Create |
The Create parameter specifies that Windows create a new file. If the file already exists, it will be overwritten. This requires FileIOPermissionAccess.Write and FileIOPermissionAccess.Append FileIOPermission. |
CreateNew |
The CreateNew parameter indicates that Windows should create a new file. This requires FileIOPermissionAccess.Read and FileIOPermissionAccess.Append FileIOPermission. If the file already exists, an IOException is thrown. |
Open |
The Open member indicates that Windows should open an existing file. This requires FileIOPermissionAccess.Read FileIOPermission. |
OpenOrCreate |
The OpenOrCreate member indicates that Windows should open a file if it exists; otherwise, a new file should be created. If the file is opened with FileAccess.Read, FileIOPermissionAccess.Read FileIOPermission is required. If file access is FileAccess.ReadWrite and the file exists, FileIOPermissionAccess.Write FileIOPermission is required. If file access is FileAccess.ReadWrite and the file does not exist, FileIOPermissionAccess.Append FileIOPermission is required in addition to Read and Write. |
Truncate |
The truncate member indicates that Windows should open an existing file and truncate its size to zero bytes. This requires FileIOPermissionAccess.Write FileIOPermission. |
Use the FileShare enumeration to control whether two processes can access a given file simultaneously. For example, if a user opens a file marked FileShare.Read, other users can open the file for reading but cannot save or write to it. The FileShare enumeration is used in some of the constructors for the FileStream, File, FileInfo, and IsolatedStorageFileStream classes. Table 7.6 lists the FileShare enumeration members.
TABLE 7.6 FileShare Enumeration Members
Member |
Description |
None |
The None value indicates that the file should not be shared in any way. All requests to open the file will fail until the file is closed. |
Read |
The Read member allows users to open a given file for reading only. Attempts to save the file (or write to it) but read-only processes will fail. |
ReadWrite |
The ReadWrite parameter indicates that a file can be opened for both reading and writing by multiple processes. Obviously this can cause problems because the last user to save has his changes applied. |
Write |
The Write parameter indicates that a file can be open for writing but not necessarily reading. This can be combined with Read to mimic the ReadWrite parameter. |
Monitoring the File System
It seems there is always a need to monitor a directory for file drops or respond to file system events. For example, suppose your team needs to be notified whenever a documentation artifact for the project you're working on is created or modified. Wouldn't it be nice if you could write a simple monitoring service that responds to these events and notifies the team?
One of the most interesting objects in the namespace is the FileSystemWatcher class. This class can be easily configured to monitor events within directories. Before the file watcher class, Visual Basic programmers often had to implement a message queue application, monitor SMTP, or write a service which, at different intervals, queried a directory to check for changes. .NET provides the FileSystemWatcher as an easy-to-use class to solve these common programming dilemmas.
Identifying What to Monitor
The FileSystemWatcher can be used to monitor changes made to files and subdirectories of a specified parent directory. The component can be configured to work with the local file system, a directory on the local network, or a remote machine.
The FileSystemWatcher class allows you to
Watch files from either a Windows 2000 or an NT 4 install.
Watch all files, including those marked hidden.
The FileSystemWatcher does not allow you to
Watch a remote NT 4 computer from another NT 4 computer.
Work with CDs or DVDs because files on these devices are fixed and cannot change.
There are a number of properties of the FileSystemWatcher class that allow us to target the objects we wish to watch. The properties developers will use most often are Filter and Path. Path simply indicates the directory path to watch. Filter indicates the type of files to watch. The format for Filter uses Windows' standard wildcard notation to set its value. If you do not set the filter property, its default is *.* (star dot star), which indicates that you are monitoring all files in the directory. If you are trying to monitor only Microsoft Excel files, then you'd set the Filter property to *.xls. Table 7.7 lists additional properties and describes when you would implement their use.
TABLE 7.7 Configuration Properties of the FileSystemWatcher Class
Property |
Scenario Description |
Path |
The Path property lets you indicate what directory to watch. You can indicate a path using standard directory path notation (c:\myDirectory) or in UNC format (\\serverName\directory\ name). |
Filter |
Set the Filter property to watch for changes made to a file or directory that are of a specific type. |
Target |
Use the Target property to watch for changes on only a file, only a directory, or both a file and a directory. By default, the Target property is set to watch for changes to both directory and file-level items. |
IncludeSubDirectories |
Set the IncludeSubDirectories property to True to monitor changes made to subdirectories that the root directory contains. The watcher will watch for the same changes in all directories. |
ChangedFilter |
Use the ChangedFilter property to watch for specific changes to a file or directory when handling the Changed event. Changes can apply to Attributes, LastAccess, LastWrite, Security, or Size. |
Once you've decided what objects you are monitoring you'll need to indicate the events or actions to which you are listening. The changes that the FileSystemWatcher can monitor include changes in the directory's or file's properties, size, last write time, last access time, and security settings.
You use the NotifyFilters enumeration of the FileSystemWatcher class to specify changes for which to watch on files and directories. The members of this enumeration can be combined using BitOr (bitwise or comparisons) to watch for multiple kinds of changes. An event is raised when any change you are watching for is made. Table 7.8 lists the members of the NotifyFilters enumeration and provides a brief description of each.
TABLE 7.8 NotifyFilters Enumeration Members
Member |
Description |
Attributes
|
The Attributes member allows you to watch for changes made to the attributes of a file or directory. |
CreationTime
|
The CreationTime member allows you to watch for changes made to the time the file or directory was created. |
DirectoryName
|
The DirectoryName member allows you to watch for changes made to the name of the file or directory. |
FileName
|
The FileName member allows you to watch for changes made to the name of a file. |
LastAccess
|
The LastAccess member allows you to watch for changes made to the date the file or directory was last opened. |
LastWrite
|
The LastWrite member allows you to watch for changes made to the date the file or directory had data written to it. |
Security
|
The Security member allows you to watch for changes made to the security settings of the file or directory. |
Size
|
The Size member allows you to watch for changes made to the size of the file or directory. |
Responding to File System Events
At this point, we've indicated what we are watching and what events interest us; now we must hook up those events to our application. The FileSystemWatcher raises an event when files or directories are created, deleted, renamed, or otherwise changed. The events are raised and stored in a buffer before being passed to our instance of the FileSystemWatcher.
NOTE
You might notice that some common tasks such as file copy or move do not correspond directly to an event raised by the component. However, upon closer examination, you will notice that when a file is copied, the system raises a created event to the directory to which the file was copied. Similarly, when a file is moved, the system raises both a deleted event in the file's original directory and a created event in the file's new directory. These events serve in place of actual copy and move to events.
This buffer becomes very important in high-volume monitoring applications. It has the potential to receive a lot of events. Let's examine this further. Every change to a file in a directory raises a separate event. This sounds simple enough, but we have to be careful. For example, if we are monitoring a directory that contains 25 files and we reset the security settings on the directory, we will get 25 separate change events. Now if we write an application that renames those files and resets their security we'll get 50 events: one for each file for both change and rename.
All these events are stored in the FileSystemWatcher's internal buffer, which has a maximum size limit and can overflow. If this buffer overflows, the FileSystemWatcher will raise the InternalBufferOverflow event. Fortunately, the component allows us to increase this buffer size.
The default buffer size is set to 8KB. Microsoft indicates that this can track changes on approximately 160 files in a directory. You can reset the buffer size to better match your needs using the InternalBufferSize property. For best performance, this property should be set in increments of 4K (4096, 8192, 12288, and so on) because this corresponds to the operating system's (Windows 2000) default page size.
Increasing this internal buffer comes at a cost. The buffer uses non-paged memory that cannot be swapped to disk. Therefore, we need to keep the buffer size as small as possible. Strategies to limit the buffer's size include the NotifyFilter and IncludeSubDirectories properties to filter out those change notifications in which we have no interest. It should be noted that the Filter property actually has no effect on the buffer size since the filter is applied after the notifications are written to the buffer.
To actually connect to the FileSystemWatcher's events we add handlers in our code as we would with any other event. For example, to hook into the Changed event, we add code similar to the following:
AddHandler myWatcher.Changed, AddressOf watcher_OnChange
This tells your application to intercept the Changed event and process through a custom event called watcher_onChange. The custom event need only have the correct function signature. Table 7.9 lists the events to which you can subscribe and their associated function signatures.
TABLE 7.9 FileSystemWatcher Events
Event |
VB Handler and Function Signature |
Changed |
AddHandler myWatcher.Changed, AddressOf watcher_OnChange Sub watcher_OnChange(ByVal source As Object, _ByVal e As IO.FileSystemEventArgs) |
Created |
AddHandler myWatcher.Created, AddressOf watcher_OnCreate Sub watcher_OnCreate(ByVal source As Object, _ByVal e As IO.FileSystemEventArgs) |
Deleted |
AddHandler myWatcher.Deleted, AddressOf watcher_OnDelete Sub watcher_OnChange(ByVal source As Object, _ByVal e As IO.FileSystemEventArgs) |
Error |
AddHandler myWatcher.Error, AddressOf watcher_OnError Sub watcher_OnError(ByVal source As Object, _ByVal e As IO.ErrorEventHandler) |
Renamed |
AddHandler myWatcher.Renamed, AddressOf watcher_OnRename Sub watcher_OnRename(ByVal source As Object, _ByVal e As IO. RenamedEventHandler) |
Once inside the event, we have access to a number of properties related to the event and event type. These properties come from the event arguments that are passed to us when the event is raised. They include things like the change type and the path and name to the file or directory.
The WatcherChangeTypes enumeration is used by events of the FileSystemWatcher class. The enumeration's members indicate to the event the type of change that occurred to a file or directory. Table 7.10 lists the members of the WatcherChangeTypes enumeration.
TABLE 7.10 WatcherChangeTypes Enumeration Members
Member |
Description |
All |
The All member indicates that any of the creation, deletion, change, or renaming of a file or folder actions occurred. |
Changed |
The Changed member indicates that a change action occurred to a file or event. Changes can include: size, attributes, security, last write, and last access time. |
Created |
The Created member indicates that a file or folder was created. |
Deleted |
The Deleted member indicates that a file or folder was deleted. |
Renamed |
The Renamed member indicates that a file or folder was renamed. |
Listing 7.4 provides a sample FileSystemWatcher application that serves to further illustrate these concepts. The code can be executed inside a console application (and downloaded from http://www.samspublishing.com). The application monitors a directory for changes to text files. When a change occurs, the user is notified with a simple call to Console.WriteLine from within the intercepted change event.
LISTING 7.4 FileSystemWatcher Directory Monitor
Imports System.IO Module Module1 Sub Main() 'Call directory() Call watchDirectory(watchPath:="c:\watch") End Sub Sub watchDirectory(ByVal watchPath As String) 'purpose: watch a directory for changes to files 'local scope Dim myWatcher As FileSystemWatcher Dim stopValue As String 'check if directory exits, no = create If Not Directory.Exists(path:=watchPath) Then Directory.CreateDirectory(path:=watchPath) End If 'instantiate a new system watcher object myWatcher = New FileSystemWatcher() 'set the path of directory to watch myWatcher.Path = watchPath 'tell the watcher object to watch only for text files myWatcher.Filter = "*.txt" 'tell the watcher the type of changes to watch for myWatcher.NotifyFilter = IO.NotifyFilters.DirectoryName _ Or IO.NotifyFilters.LastAccess _ Or IO.NotifyFilters.LastWrite _ Or IO.NotifyFilters.FileName 'intercept the watcher events AddHandler myWatcher.Changed, AddressOf watcher_OnChange AddHandler myWatcher.Deleted, AddressOf watcher_OnChange AddHandler myWatcher.Created, AddressOf watcher_OnChange AddHandler myWatcher.Renamed, AddressOf watcher_OnRename 'tell the watcher to start watching myWatcher.EnableRaisingEvents = True 'tell the user that watching is on Console.WriteLine("Watching " & watchPath & "...") 'wait for the user to stop the console application Console.WriteLine("Press 's' to stop the application.") 'loop until user presses s key Do While Console.ReadLine <> "s" : Loop End Sub Sub watcher_OnChange(ByVal source As Object, _ ByVal e As IO.FileSystemEventArgs) 'purpose: respond to the changed notify event ' (created, changed, deleted) 'indicate that a file is changed, created, or deleted Console.WriteLine("File: " & e.FullPath & " " & e.ChangeType) End Sub Sub watcher_OnRename(ByVal source As Object, _ ByVal e As IO.RenamedEventArgs) 'purpose: respond to the renamed watcher notify event 'tell the user the file that was renamed Console.WriteLine("File: {0} renamed to {1}", e.OldFullPath, _ e.FullPath) End Sub End Module
Suggestions for Further Exploration
To read similar information about working with file I/O in .NET, read the MSDN chapter found at: Visual Studio .NET/.NET Framework/Programming with the .NET Framework/Working with I/O.
For additional information on .NET security issues (for user, file, directory, and code access), start with the System.Security namespace. Of course, you will also want to read Appendix D, ".NET Framework Base Data Types," which deals specifically with security in .NET.
You will want to check out the FileDialog class found in System.Windows.Forms. This class allows you to easily display a window's dialog that allows users to select a file. The class is the replacement of the old common dialog control.