MCAD/MCSD: Error Handling for the User Interface
Date: Feb 14, 2003
Objectives
This chapter covers the following Microsoft-specified objectives for the Creating User Services section of the Visual Basic .NET Windows-Based Applications exam:
Implement error handling in the UI.
Create and implement custom error messages.
Create and implement custom error handlers.
Raise and handle errors.
When you run a Windows application, it might encounter problems that should not crop up in the normal course of operations. For example, a file might be missing, or a user might enter nonsensical values. A good Windows application must recover gracefully from these problems instead of abruptly shutting down. This exam objective covers the use of exception handling to create robust and fault tolerant applications. The Microsoft .NET Framework provides some predefined exception classes to help you catch these unusual situations in your programs. You can also create your own exception handling classes and error messages that are specific to your own applications.
Validate user input.
It's a truism of computer programming that garbage-in is garbage-out. The best place to avoid incorrect data in an application is at the source, right where the data enters. The Windows Forms library includes an ErrorProvider control, which can be used to display messages, icons, and other information in response to data entry errors. This exam objective covers the ErrorProvider control and various other input validation techniques.
Outline
-
Introduction
-
Understanding Exceptions
-
Handling Exceptions
-
The Try Block
-
The Catch Block
-
The Throw Statement
-
The Finally Block
-
-
Custom Exceptions
-
Managing Unhandled Exceptions
-
User Input Validation
-
Keystroke-Level Validation
-
The KeyPreview Property
-
-
Field-Level Validation
-
The Validating Event
-
The CausesValidation Property
-
ErrorProvider
-
-
Enabling Controls Based On Input
-
Other Properties for Validation
-
The CharacterCasing Property
-
The MaxLength Property
-
-
-
Chapter Summary
-
Apply Your Knowledge
Study Strategies
Review the "Exception Handling Statements" and "Best Practices for Exception Handling" sections of the Visual Studio .NET Combined Help Collection.
Experiment with code that uses Try, Catch, and Finally blocks. Use these blocks with various combinations and inspect the differences in your code's output.
Know how to create custom Exception classes and custom error messages in your program.
Experiment with the ErrorProvider component, the Validating event, and other validation techniques. Use these tools in various combinations to validate data entered in the controls.
Introduction
The .NET Framework adopts the Windows structured exception handling model. Exception handling is an integral part of the .NET Framework. The Common Language Runtime and your code can throw exceptions within an application, across languages, and across machines. Visual Basic .NET provides a structured method for handling exceptions with the use of the Try, Catch, Finally, and Throw statements. The .NET Framework Class Library provides a huge set of exception classes to deal with various unforeseen or unusual conditions in the normal execution environment. If you feel the need to create custom Exception classes to meet specific requirements of your application, you can derive from the ApplicationException class.
Every program must validate the data it receives before it proceeds with further data processing or storage. In this chapter I'll discuss the various techniques you can use to validate data and maintain the integrity of your application. This isn't just a matter of ensuring that your application delivers the proper results. If you don't validate input, your application can represent a serious security hole on the system.
Understanding Exceptions
An exception occurs when a program encounters any serious problem such as running out of memory or attempting to read from a file that no longer exists. These problems are not necessarily caused by a coding error, but can result from the violation of assumptions that you might have made about the execution environment.
When a program encounters an exception, its default behavior is to throw the exception, which generally translates to abruptly terminating the program after displaying an error message. This is not a characteristic of a robust application and won't make your program popular with users. Your program should be able to handle these exceptional situations and if possible gracefully recover from them. This process is called exception handling. The proper use of exception handling can make your programs robust and easy to develop and maintain. If you don't use exception handling properly, you might end up having a program that performs poorly, is harder to maintain, and can potentially mislead its users.
Whenever possible, an application should fail to a safe state when an exception occurs. You should attempt to prevent your program from doing any damage in case of failure. For example, if you can't be sure that a particular file is no longer needed, don't delete that file if an exception occurs.
Step By Step 3.1 shows the effect of an unhandled exception in an application. Later in this chapter you'll learn how to handle exceptions.
STEP BY STEP 3.1 Exception in a Windows Application
Create a new Visual Basic .NET Windows Application.
Add a new Windows Form to the project.
-
Place three TextBox controls (txtMiles, txtGallons, and txtEfficiency), four Label controls, and a Button control (btnCalculate) on the form's surface. Figure 3.1 shows a design for this form.
Figure 3.1 Mileage Efficiency Calculator.
-
Add the following code to the Click event handler of btnCalculate:
Private Sub btnCalculate_Click( _ ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles btnCalculate.Click ' This code has no error checking. If something ' Goes wrong at run time, it will throw an exception Dim decMiles As Decimal = _ Convert.ToDecimal(txtMiles.Text) Dim decGallons As Decimal = _ Convert.ToDecimal(txtGallons.Text) Dim decEfficiency As Decimal = decMiles / decGallons txtEfficiency.Text = _ String.Format("{0:n}", decEfficiency)
End Sub
-
Set the form as the startup object for the project.
-
Run the project. Enter values for miles and gallons and click the Calculate button to calculate the mileage efficiency as expected. Now enter the value zero in the gallons text box and click the button. You will see the program abruptly terminate after displaying the error message shown in Figure 3.2.
Figure 3.2 DivideByZeroException thrown by the development environment.
When you run an application from the development environment and the program throws an exception, the development environment gives you the opportunity to analyze the problem by debugging the program. If you compile the application and launch it from Windows Explorer, the program will terminate after displaying a message box with error message and some debugging information as shown in Figure 3.3.
Figure 3.3 DivideByZeroException thrown outside the development environment.
The CLR views an exception as an object that encapsulates information about any problems that occurred during program execution. The .NET Framework Class Library provides two categories of exceptions:
ApplicationException: Represents the exceptions thrown by the user programs.
SystemException: Represents the exceptions thrown by the CLR.
Both of these exception classes derive from the base Exception class. The Exception class implements common functionality for exception handling. Neither the ApplicationException class nor the SystemException class adds any new functionality to the Exception class. These classes exist just to differentiate exceptions in user programs from exceptions in the CLR. Table 3.1 lists the important properties of all three classes.
Table 3.1 Important Members of the Exception Class
Property |
Description |
HelpLink |
A URL to the help file associated with this exception. |
InnerException |
Specifies an exception associated with this exception. This property is helpful when a series of exceptions is involved. Each new exception can preserve the information about the previous exception by storing it in the InnerException property. |
Message |
A message that explains the error and possibly offers ways to resolve it. |
Source |
The name of the application that caused the error. |
StackTrace |
Specifies where an error occurred. If debugging information is available, the stack trace includes the source file name and program line number. |
TargetSite |
The method that threw the exception. |
Handling Exceptions
Implement error handling in the UI: Raise and handle errors.
Implement error handling in the UI: Create and implement custom error messages.
Abruptly terminating the program when an exception occurs is not a good idea. Your application should be able to handle the exception and (if possible) recover from it. If recovery is not possible you can take other steps, such as notifying the user and then gracefully terminating the application.
The .NET Framework allows exception handling to interoperate among languages and across machines. You can throw an exception in code written in VB .NET and catch it in code written in C#, for example. In fact, the .NET framework also allows you to handle common exceptions thrown by legacy COM applications and legacy nonCOM Win32 applications.
Exception handling is such an integral part of .NET framework that when you look up a method in the product documentation, a section will always specify what exceptions a call to that method might throw.
You can handle exceptions in Visual Basic .NET programs by using a combination of the exception handling statements: Try, Catch, Finally, and Throw. In this section of the chapter I'll show how to use these statements.
The Try Block
You should place the code that can cause exception in a Try block. A typical Try block will look like this:
Try ' Code that may cause an exception End Try
You can place any valid Visual Basic .NET statements inside a Try block. That can include another Try block or a call to a method that places some of its statement inside a Try block. Thus at runtime you can have a hierarchy of Try blocks placed inside each other. When an exception occurs at any point, the CLR will search for the nearest Try block that encloses this code. The CLR will then pass control of the application to a matching Catch block (if any) and then to the Finally block associated with this Try block.
A Try block cannot exist on its own; it must be immediately followed by one or more Catch blocks or by a Finally block.
The Catch Block
You can have several Catch blocks immediately following a Try block. Each Catch block handles an exception of a particular type. When an exception occurs in a statement placed inside a Try block, the CLR looks for a matching Catch block capable of handling that type of exception. The formula that CLR uses to match the exception is simple. It will look for the first Catch block with either an exact match for the exception or for any of the exception's base classes. For example, a DivideByZeroException will match with any of these exceptions: DivideByZeroException, ArithmeticException, SystemException, and Exception (progressively more general classes from which DivideByZeroException is derived). In the case of multiple Catch blocks, only the first matching Catch block will be executed. All other Catch blocks will be ignored.
NOTE
Unhandled Exceptions If No Catch block matches a particular exception, that exception becomes an unhandled exception. The unhandled exception is propagated back to the code that called the current method. If the exception is not handled there, it will propagate further up the hierarchy of method calls. If the exception is not handled anywhere, it goes to the CLR for processing. The Common Language Runtime's default behavior is to terminate the program immediately.
When you write multiple Catch blocks, you must arrange them from specific exception types to more general exception types. For example, the Catch block for catching DivideByZeroException should always precede the Catch block for catching ArithmeticException because the DivideByZeroException derives from ArithmeticException and is therefore more specific. You'll get a compiler error if you do not follow this rule.
A Try block need not necessarily have a Catch block associated with it, but in that case it must have a Finally block associated with it. Step by Step 3.2 demonstrates the use of multiple Catch blocks to keep a program running gracefully despite errors.
STEP BY STEP 3.2 Handling Exceptions
-
Add a new Windows Form to your Visual Basic .NET project.
-
Create a form similar to the one in Step By Step 3.1 with the same names for controls (see Figure 3.1).
-
Add the following code to the Click event handler of btnCalculate:
Private Sub btnCalculate_Click( _ ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles btnCalculate.Click ' put all the code that may require graceful ' error recovery in a try block Try Dim decMiles As Decimal = _ Convert.ToDecimal(txtMiles.Text) Dim decGallons As Decimal = _ Convert.ToDecimal(txtGallons.Text) Dim decEfficiency As Decimal = decMiles / decGallons txtEfficiency.Text = _ String.Format("{0:n}", decEfficiency) ' each try block should at least ' have one catch or finally block ' catch blocks should be in order ' of specific to the generalized ' exceptions otherwise compilation ' generates an error Catch fe As FormatException Dim msg As String = String.Format( _ "Message: {0}\n Stack Trace:\n {1}", _ fe.Message, fe.StackTrace) MessageBox.Show(msg, fe.GetType().ToString()) Catch dbze As DivideByZeroException Dim msg As String = String.Format( _ "Message: {0}\n Stack Trace:\n {1}", _ dbze.Message, dbze.StackTrace) MessageBox.Show(msg, dbze.GetType().ToString()) ' catches all CLS-compliant exceptions Catch ex As Exception Dim msg As String = String.Format( _ "Message: {0}\n Stack Trace:\n {1}", _ ex.Message, ex.StackTrace) MessageBox.Show(msg, ex.GetType().ToString()) ' catches all other exception including ' non-CLS-compliant exceptions Catch ' just rethrow the exception to the caller Throw End Try
End Sub
-
Set the form as the startup object for the project.
-
Run the project. Enter values for miles and gallons and click the Calculate button to calculate the mileage efficiency as expected. Now enter the value zero in the gallons control and run the program. You will see that instead of abruptly terminating the program (as in the earlier example), the program shows a message about a DivideByZeroException (see Figure 3.4). After you dismiss the message, the program will continue running. Now enter some alphabetic characters in the fields instead of numbers and hit the calculate button again. This time you'll get a FormatException message and the program will continue to run. Now try entering a very large value for both the fields. If the values are large enough, the program will encounter an OverflowException, but since the program is catching all types of exceptions it will continue running.
Figure 3.4 DivideByZeroException.
The program in Step By Step 3.2 displays a message box when an exception occurs. The StackTrace property lists the methods that led up to the exception in the reverse order of their calling sequence to help you understand the flow of logic of the program. In a real application, you might choose to try to automatically fix the exceptions as well.
TIP
CLS- and Non-CLSCompliant Exceptions All languages that follow the Common Language Specification (CLS) throw exceptions whose type is or derives from System.Exception. A non-CLScompliant language might throw exceptions of other types as well. You can catch those types of exceptions by placing a general Catch block (one that does not specify any exception) within a Try block. In fact, a general Catch block can catch exceptions of all types, so it is most generic of all Catch blocks. This means that it should be the last Catch block among the multiple Catch blocks associated with a Try block.
When you write a Catch block to catch exceptions of the general Exception type, it will catch all CLS-compliant exceptions. That includes all exceptions unless you're interacting with legacy COM or Win32 API code. If you want to catch all exceptions, CLS-compliant or not, you can use a Catch block with no specific type. This Catch block must be last in the list of Catch blocks because it is the most generic.
If you are thinking that it's a good idea to catch all sorts of exceptions in your code and suppress them as soon as possible, think again. A good programmer will only catch an exception in code if the answer is yes to one or more of these questions:
Will I attempt to recover from this error in the Catch block?
Will I log the exception information in system event log or any other log file?
Will I add relevant information to the exception and rethrow it?
Will I execute cleanup code that must run even if an exception occurs?
If you answered no to all those questions, you should not catch the exception but rather just let it go. In that case, the exception will propagate up to the code that is calling your code. Possibly that code will have a better idea of what to do with the exception.
The Throw Statement
A Throw statement explicitly generates an exception in your code. You can use Throw to handle execution paths that lead to undesired results.
You should not throw an exception for anticipated cases, such as the user entering an invalid username or password. This occurrence can be handled in a method that returns a value indicating whether the logon was successful. If you don't have enough permissions to read records from the user table, that's a better candidate to raise an exception because a method to validate users should normally have read access to the user table.
Using exceptions to control the normal flow of execution is bad for two reasons:
It can make your code difficult to read and maintain because using Try and Catch blocks to deal with exceptions forces you to separate the regular program logic between separate locations.
It can make your programs slower because exception handling consumes more resources than just returning values from a method.
You can use the Throw statement in two ways. In its simplest form, you can just rethrow an exception that you've caught in a Catch block:
Catch ex As Exception ' TODO: Add code to write to the event log Throw
This usage of the Throw statement rethrows the exception that was just caught. It can be useful when you don't want to handle the exception yourself but want to take other actions (such as recording the error in an event log or sending an email notification about the error) when the exception occurs. Then you can pass the exception unchanged to the next method in the calling chain.
TIP
Custom Error Messages When creating an exception class, you should use the constructor that allows you to associate a custom error message with the exception instead of using the default constructor. The custom error message can pass specific information about the cause of the error and a possible way to resolve it.
The second way to use a Throw statement is to throw explicitly created exceptions. For example, this code creates and throws an exception:
Dim strMessage As String = _ "EndDate should be greater than the StartDate" Dim exNew As ArgumentOutOfRangeException = _ New ArgumentOutOfRangeException(strMessage) Throw exNew
In this example I first created a new instance of the Exception object and associated a custom error message with it Then I threw the newly created exception.
You are not required to put this usage of the Throw statement inside a Catch block because you're just creating and throwing a new exception rather than rethrowing an existing one. You will typically use this technique in raising your own custom exceptions. I'll show how to do that later in this chapter.
An alternate way to throw an exception is to throw it after wrapping it with additional useful information, for example:
Catch ane As ArgumentNullException ' TODO: Add code to create an entry in the log file Dim strMessage As String = "CustomerID cannot be null" Dim aneNew As ArgumentNullException = _ New ArgumentNullException(strMessage) Throw aneNew
You might need to catch an exception that you cannot handle completely. You would then perform any required processing and throw a more relevant and informative exception to the calling code, so that it can perform the rest of the processing. In this case, you can create a new exception whose constructor wraps the previously caught exception in the new exception's InnerException property. The calling code will now have more information available to handle the exception appropriately.
Because InnerException is also an exception, it might also store an exception object in its InnerException property, so what you are propagating is a chain of exceptions. This information can be very valuable at debugging time because it allows you to trace the problem to its origin.
The Finally Block
The Finally block contains the code that always executes whether any exception occurred. You can use the Finally block to write cleanup code to maintain your application in a consistent state and preserve the external environment. As an example you can write code to close files, database connections, or related I/O resources in a Finally block.
TIP
No Code in Between Try, Catch, and Finally Blocks When you write Try, Catch, and Finally blocks, they must be in immediate succession. You cannot write any code between the blocks, although you can place comments between them.
It is not necessary for a Try block to have an associated Finally block. However, if you do write a Finally block, you cannot have more than one, and the Finally block must appear after all the Catch blocks.
Step By Step 3.3 illustrates the use of the Finally block.
STEP BY STEP 3.3 Using a Finally Block
Add a new Windows Form to your Visual Basic .NET project.
-
Place two TextBox controls (txtFileName and txtText), two Label controls, and a Button control (btnSave) on the form's surface and arrange them as shown in Figure 3.5.
Figure 3.5 An application to test a Finally block.
-
Double-click the Button control to open the form's module. Enter a reference at the top of the module:
Imports System.IO
-
Enter this code to handle the Button's Click event:
Private Sub btnSave_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles btnSave.Click ' A StreamWriter writes characters to a stream Dim sw As StreamWriter Try sw = New StreamWriter(txtFilename.Text) ' Attempt to write the textbox contents in a file Dim strLine As String For Each strLine In txtText.Lines sw.WriteLine(strLine) Next ' This line only executes if ' there were no exceptions so far MessageBox.Show( _ "Contents written, without any exceptions") ' Catches all CLS-complaint exceptions Catch ex As Exception Dim msg As String = String.Format( _ "Message: {0}\n Stack Trace:\n {1}", _ ex.Message, ex.StackTrace) MessageBox.Show(msg, ex.GetType().ToString()) GoTo endit ' The finally block is always ' executed to make sure that the ' resources get closed whether or ' not an exception occurs. ' Even if there is a goto statement ' in catch or try block the ' final block is first executed before ' the control goes to the label Finally If Not sw Is Nothing Then sw.Close() End If MessageBox.Show( _ "Finally block always executes " & _ "whether or not exception occurs") End Try EndIt: MessageBox.Show("Control is at label: end")
End Sub
-
Set the form as the startup object for the project.
-
Run the project. Enter a file name and some text. Watch the order of the messages and note that the message box from the Finally block will always be displayed prior to the message box from the end label.
TIP
Finally Block Always Executes If you have a Finally block associated with a Try block, the code in the Finally block will always execute, regardless of whether an exception occurs.
Step By Step 3.3 illustrates that the Finally block always executes, even if a transfer of control statement, such as GoTo, is within a Try or Catch block. The compiler will not allow a transfer of control statement within a Finally block.
NOTE
Throwing Exceptions from a Finally Block Although throwing exceptions from a Finally block is perfectly legitimate, you should avoid doing so. A Finally block there might already have an unhandled exception waiting to be handled.
The Finally statement can be used in a Try block with no Catch block. For example:
Try ' Write code to allocate some resources Finally ' Write code to Dispose all allocated resources End Try
This usage ensures that allocated resources are properly disposed of no matter what happens in the Try block.
An exception occurs when a program encounters any unexpected problem during normal execution.
The Framework Class Library (FCL) provides two main types of exceptions: SystemException and ApplicationException. A SystemException represents an exception thrown by the Common Language Runtime while an ApplicationException represents an exception thrown by the your own code.
The System.Exception class is the base class for all CLS-compliant exceptions and provides the common functionality for exception handling.
A Try block consists of code that might raise an exception. A Try block cannot exist on its own but should be immediately followed by one or more Catch blocks or a Finally block.
The Catch block handles any exception raised by the code in the Try block. The runtime looks for a matching Catch block to handle the exception and uses the first Catch block with either the exact same exception or any of the exception's base classes.
If multiple Catch blocks are associated with a Try block, then the Catch blocks should be arranged in order from specific to general exception types.
The Throw statement is used to raise an exception.
The Finally block is used to enclose any code that needs to be run irrespective of whether an exception is raised.
Custom Exceptions
Implement error handling in the UI: Create and implement custom error messages.
Implement error handling in the UI: Create and implement custom error handlers.
TIP
Use ApplicationException As Base Class for Custom Exceptions Although you can derive directly from the Exception class, Microsoft recommends that you derive any custom exception class you create for your Windows application from the ApplicationException class.
In most cases, .NET's built-in exception classes, combined with custom messages that you create when instantiating a new exception, will suffice your exception handling requirements. However, in some cases you need exception types specific to the problem you are solving.
The .NET Framework allows you to define custom exception classes. To make your custom Exception class work well with the .NET exception handling framework, Microsoft recommends that you consider the following when you're designing a custom exception class:
-
Create an exception class only if no existing exception class satisfies your requirement.
-
Derive all programmer-defined exception classes from the System.ApplicationException class.
-
End the name of custom exception class with the word Exception (for example, MyOwnCustomException).
-
Implement three constructors with the signatures shown in the following code:
Public Class MyOwnCustomException Inherits ApplicationException ' Default constructor Public Sub New() End Sub ' Constructor accepting a single string message Public Sub New(ByVal message As String) MyBase.New(message) End Sub ' Constructor accepting a string message ' and an inner exception ' that will be wrapped by this custom exception class Public Sub New(ByVal message As String, _ ByVal inner As Exception) MyBase.new(message, inner) End Sub End Class
Step By Step 3.4 shows how to create a custom exception.
STEP BY STEP 3.4 Creating a Custom Exception
Add a new Windows Form to your Visual Basic .NET project.
-
Place two Label controls, a TextBox control named txtDate, and a Button control named btnIsLeap on the form. Figure 3.6 shows a design for this form. Name the empty Label control lblResults.
Figure 3.6 Leap Year Finder.
-
Add a new class to your project. Add code to create a custom exception class:
Public Class MyOwnInvalidDateFormatException Inherits ApplicationException ' Default constructor Public Sub New() End Sub ' Constructor accepting a single string message Public Sub New(ByVal message As String) MyBase.New(message) Me.HelpLink = _ "file://MyOwnInvalidDateFormatExceptionHelp.htm" End Sub ' Constructor accepting a string ' message and an inner exception ' that will be wrapped by this custom exception class Public Sub New(ByVal message As String, _ ByVal inner As Exception) MyBase.new(message, inner) Me.HelpLink = _ "file://MyOwnInvalidDateFormatExceptionHelp.htm" End Sub
End Class
-
Add a second new class to your project. Add code to create a class named LeapDate:
Public Class LeapDate ' This class does elementary date handling for the ' leap year form Private day, month, year As Integer Public Sub New(ByVal strDate As String) If strDate.Trim().Length = 10 Then ' Input data might be in invalid ' format; in that case ' the Convert.ToDateTime method will fail Try Dim dt As DateTime = _ Convert.ToDateTime(strDate) day = dt.Day month = dt.Month year = dt.Year ' Catch any exception, attach ' it to the custom exception and ' throw the custom exception Catch e As Exception Throw New MyOwnInvalidDateFormatException( _ "Custom Exception: Invalid Date Format", e) End Try Else ' bad input, throw a custom exception Throw New MyOwnInvalidDateFormatException( _ "The input does not match the required " & _ "format: MM/DD/YYYY") End If End Sub ' Find if the given date belongs to a leap year Public Function IsLeapYear() As Boolean IsLeapYear = (year Mod 4 = 0) And _ ((year Mod 100 <> 0) Or (year Mod 400 = 0)) End Function End Class
-
Add the following event handler for the Click event of btnIsLeap:
Private Sub btnIsLeap_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles btnIsLeap.Click Try Dim dt As LeapDate = New LeapDate(txtDate.Text) If dt.IsLeapYear() Then lblResult.Text = "This date is in a leap year" Else lblResult.Text = _ "This date is NOT in a leap year" End If ' Catch the custom exception and ' display an appropriate message Catch dte As MyOwnInvalidDateFormatException Dim msg As String ' If some other exception was also ' attached with this exception If Not dte.InnerException Is Nothing Then msg = String.Format( _ "Message:" & vbCrLf & "{0}" & _ vbCrLf & vbCrLf & "Inner Exception:" & _ vbCrLf & "{1}", _ dte.Message, dte.InnerException.Message) Else msg = String.Format("Message:" & _ vbCrLf & "{0}" & _ vbCrLf & vbCrLf & _ "Help Link:" & vbCrLf & "{1}", _ dte.Message, dte.HelpLink) End If MessageBox.Show(msg, dte.GetType().ToString()) End Try End Sub
-
Set the form as the startup object for the project.
-
Run the project. Enter a date and click the button. If the date was in the correct format, you'll see a result displayed in the Results label. Otherwise, you'll get a message box showing the custom error message thrown by the custom exception, as shown in Figure 3.7.
Figure 3.7 Custom error message thrown by the custom exception.
In Guided Practice Exercise 3.1 you'll create a custom defined Exception class that helps implement custom error messages and custom error handling in your programs.
GUIDED PRACTICE EXERCISE 3.1
Your goal is to create a keyword-searching application. The application should ask for a filename and a keyword and search for the keyword in the file. The application should then display the number of lines that contain the keyword. The application assumes that the entered keyword will be a single word. If not, you must create and throw a custom exception for that case.
Try this on your own first. If you get stuck or would like to see one possible solution, follow these steps:
Add a new form to your Visual Basic .NET Project.
-
Place three Label controls, two TextBox controls, and two Button controls on the form, arranged as shown in Figure 3.8. Name the TextBox for accepting filenames txtFileName and the Browse button btnBrowse. Name the TextBox for accepting keywords txtKeyword and the search button btnSearch. Name the results label lblResult.
Figure 3.8 Form allowing a keyword search in a file.
-
Add an OpenFileDialog control to the form and change its name to dlgOpenFile.
-
Create a new class named BadKeywordFormatException that derives from ApplicationException and place the following code in it:
Public Class BadKeywordFormatException Inherits ApplicationException ' Default constructor Public Sub New() End Sub ' Constructor accepting a single string message Public Sub New(ByVal message As String) MyBase.New(message) End Sub ' Constructor accepting a string ' message and an inner exception ' that will be wrapped by this custom exception class Public Sub New(ByVal message As String, _ ByVal inner As Exception) MyBase.new(message, inner) End Sub End Class
-
Create a method named GetKeywordFrequency in the form's module. This class accepts a filename and returns the number of lines containing the keyword. Add the following code to the method:
Private Function GetKeywordFrequency( _ ByVal strPath As String) As Integer If Me.txtKeyword.Text.Trim().IndexOf(" ") >= 0 Then Throw New BadKeywordFormatException( _ "The keyword must only have a single word") End If Dim count As Integer = 0 If File.Exists(strPath) Then Dim sr As StreamReader = _ New StreamReader(txtFileName.Text) Do While (sr.Peek() > -1) If sr.ReadLine().IndexOf(txtKeyword. _ Text) >= 0 Then count += 1 End If Loop End If GetKeywordFrequency = count End Function
-
Add the following code to the Click event handler of the btnBrowse button:
Private Sub btnBrowse_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles btnBrowse.Click If dlgOpenFile.ShowDialog() = DialogResult.OK Then txtFileName.Text = dlgOpenFile.FileName End If End Sub
-
Add the following code to the Click event handler of the btnSearch Button:
Private Sub btnSearch_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles btnSearch.Click If txtKeyword.Text.Trim().Length = 0 Then MessageBox.Show( _ "Please enter a keyword to search", _ "Missing Keyword") Exit Sub End If Try lblResult.Text = String.Format("The keyword: '{0}'" & _ "was found in {1} lines", _ txtKeyword.Text, _ GetKeywordFrequency(txtFileName.Text)) Catch bkfe As BadKeywordFormatException Dim msg As String = _ String.Format("Message:" & vbCrLf & _ "{0}" & vbCrLf & vbCrLf & "StackTrace:" & _ vbCrLf & "{1}", _ bkfe.Message, bkfe.StackTrace) MessageBox.Show(msg, bkfe.GetType().ToString()) End Try End Sub
-
Set the form as the startup object for the project.
-
Run the project. Click the Browse button and select an existing file. Enter a keyword to search for in the file and press the Search button. If the keyword entered is in wrong format (contains a space) then the custom exception will be raised.
Managing Unhandled Exceptions
Managed applications (that is, applications that use the services of the CLR) execute in an isolated environment called an AppDomain. The AppDomain class provides a set of events that allows you to respond when an assembly is loaded, when an application domain is about to be unloaded, or when an application throws an unhandled exception. I'll discuss the AppDomain class in more detail in Chapter 4, "Creating and Managing Components and Assemblies," but for now I'll use its UnhandledException event. This event fires when no other exception handler catches an exception. The CLR passes this event an instance of the UnhandledExceptionArgs class. The properties of the UnhandledExceptionEventArgs class are listed in Table 3.2.
Table 3.2 Important Properties of the UnhandledExceptionEventArgs Class
Property |
Description |
ExceptionObject |
It gets the unhandled exception object corresponding to the current domain. |
IsTerminating |
It has a Boolean value that indicates whether the Common Language Runtime is terminating. |
You can attach an event handler to this event to take custom actions such as logging the exception-related information. A log maintained over a period can help analyze and find patterns that can give you useful debugging information. You can log the information related to an event in several ways:
Windows event log
Custom log files
Databases such as SQL Server 2000
Email notifications
Among these ways, the Windows event log offers a very robust way of event logging. It requires the minimum infrastructure for logging the event. The other cases are not as fail-safe because your application can lose connectivity with a database or with the SMTP server, because or you might have problems writing an entry in a custom log file.
The .NET Framework provides access to the Windows event log through the EventLog class. Windows 2000 (and later versions) has three default logs: Application, System, and Security. You can use the EventLog class to create custom event logs. These event logs can be easily viewed using the Windows Event Viewer utility.
You should know about the important members of the EventLog class listed in Table 3.3. See Step By Step 3.5 to use the event log to log unhandled exceptions.
NOTE
When Not to Use the Event Log The Windows event log is not available on older version of Windows, such as Windows 98. If your application needs to support those computers, you might want to create a custom error log in a file. Also, in case of a distributed application, you might want to log all events centrally in a SQL Server database. To make the scheme more robust, you could choose to log locally if a database is not available and transfer the log to the central database when it is available again.
Table 3.3 Important Members of the EventLog Class
Member |
Type |
Description |
Clear |
Method |
Removes all entries from the event log to make it empty |
CreateEventSource |
Method |
Creates an event source that you can use to write to a standard or custom event log |
Entries |
Property |
Gets the contents of the event log |
Log |
Property |
The name of the log to read from or write to |
MachineName |
Property |
The name of the computer on which to read or write events |
Source |
Property |
The event source name to register and use when writing to the event log |
SourceExists |
Method |
Specifies whether the event source exists on a computer |
WriteEntry |
Method |
Writes an entry in the event log |
STEP BY STEP 3.5 Logging Unhandled Exceptions in the Windows Event Log
-
Add a new Windows Form to your Visual Basic .NET project.
-
Place three TextBox controls (txtMiles, txtGallons, and txtEfficiency) and a Button (btnCalculate) on the form's surface and arrange them as was shown in Figure 3.1. Add the label controls as necessary.
-
Double-click the Button control and add the following code to handle its Click event:
Private Sub btnCalculate_Click( _ ByVal sender As System.Object, _ ByVal e As System.EventArgs) _ Handles btnCalculate.Click ' This code has no error checking. If something ' goes wrong at runtime, it will throw an exception Dim decMiles As Decimal = Convert.ToDecimal( _ txtMiles.Text) Dim decGallons As Decimal = Convert.ToDecimal( _ txtGallons.Text) Dim decEfficiency As Decimal = _ decMiles / decGallons txtEfficiency.Text = String.Format( _ "{0:n}", decEfficiency) End Sub
-
Add the following event handler to the end of the form's module:
Private Sub UnhandledExceptionHandler( _ ByVal sender As Object, _ ByVal ue As UnhandledExceptionEventArgs) Dim unhandledException As Exception = + CType(ue.ExceptionObject, Exception) ' If no event source exist, create an event source If Not EventLog.SourceExists( _ "Mileage Efficiency Calculator") Then EventLog.CreateEventSource( _ "Mileage Efficiency Calculator", _ "Mileage Efficiency Calculator Log") End If ' Create an EventLog instance and assign its source. Dim el As EventLog = New EventLog() el.Source = "Mileage Efficiency Calculator" ' Write an informational entry to the event log. el.WriteEntry(unhandledException.Message) MessageBox.Show( _ "An exception occurred: Created an entry in the log file")
End Sub
-
Modify the form's New method as follows:
Public Sub New() MyBase.New() 'This call is required by the Windows Form Designer. InitializeComponent() ' Add any initialization after the ' InitializeComponent() call ' Create an AppDomain object Dim adCurrent As AppDomain = AppDomain.CurrentDomain ' Attach the UnhandledExceptionEventHanlder ' to the UnhandledException of the AppDomain object AddHandler adCurrent.UnhandledException, _ AddressOf UnhandledExceptionHandler
End Sub
-
Set the form as the startup object for the project.
-
Run the project. Enter a zero for the number of gallons and click the button. An unhandled exception will occur, and a message box will be displayed notifying you that the exception has been logged. You can view the logged message by selecting Start, Control Panel, Administrative Tools, Event Viewer. The Event Viewer will display the Mileage Efficiency Calculator Log in the left pane of the window as shown in Figure 3.9. The right pane of the window shows the events that have been logged. You can double-click an event to view its description and other properties as shown in Figure 3.10.
NOTE
Refreshing the List of Event Logs If you have the Event Viewer open already when you run this code, you'll need to close and reopen it to see the new event log category.
Figure 3.9 EventViewer showing the MileageEfficiencyCalculatorLog.
Figure 3.10 Event Properties of a particular event.
If the existing exception classes do not satisfy your exception handling requirements, you can create new exception classes specific to your application. Custom exceptions should be derived from the ApplicationException class.
You can use the UnhandledException event of the AppDomain class to manage unhandled exceptions.
You can use the EventLog class to log events to the Windows event log.
User Input Validation
Validate user input.
It's a truism of the computer world that garbage-in is garbage-out. When designing an application that interacts with the user to accept data, you must ensure that the entered data is acceptable to the application. The most obvious time to ensure the validity of data is at the time of data entry itself. You can use various techniques for validating data, including these:
Restrict the values that a field can accept by using standard controls like combo boxes, list boxes, radio buttons and check boxes. These allow users to select from a set of given values rather than permitting free keyboard entry.
Capture the user's keystrokes and analyze them for validity. Some fields might require the user to enter only alphabetic values but no numeric values or special characters; in that case, you can accept the keystrokes for alphabetic characters while rejecting others.
Restrict entry in some data fields by enabling or disabling them depending on the state of other fields.
Analyze the contents of the data field as a whole and warn the user of any incorrect values when the user attempts to leave the field or close the window.
You saw how to use the various controls to limit input in Chapter 2, "Controls." I'll cover the rest of the techniques in this section.
Keystroke-Level Validation
When you press a key on a control, three events take place in the following order:
KeyDown
KeyPress
KeyUp
You can add code to the event handlers for these events to perform keystroke-level validation. You will choose which event to handle based on the order in which the events are fired and the information passed in the event argument of the event handler.
The KeyPress event happens after the KeyDown event but before the KeyUp event. Its event handler receives an argument of type KeyPressEventArgs. Table 3.4 lists the properties of the KeyPressEventArgs class.
Table 3.4 Important Properties of the KeyPressEventArgs Class
Property |
Description |
Handled |
Setting this property to True indicates that the event has been handled. |
KeyChar |
Gets the character value corresponding to the key. |
The KeyPress event only fires if the key pressed generates a character value. This means you won't get a KeyPress event from keys such as function keys, control keys, and the cursor movement keys; you must use the KeyDown and KeyUp events to trap those keys.
The KeyDown and KeyUp events occur when a user presses and releases a key on the keyboard. The event handlers of these events receive an argument of the KeyEventArgs type; this argument provides the properties listed in Table 3.5.
Table 3.5 Important Properties of the KeyEventArgs Class
Property |
Description |
Alt |
Returns True if Alt key is pressed, otherwise False. |
Control |
Returns True if Ctrl key is pressed, otherwise False. |
Handled |
Indicates whether the event has been handled. |
KeyCode |
Gets the keyboard code for the event. Its value is one of the values specified in the Keys enumeration. |
KeyData |
Gets the key code for the pressed key, along with modifier flags that indicate the combination of Ctrl, Shift, and Alt keys that were pressed at the same time. |
KeyValue |
Gets an integer representation of the KeyData property. |
Modifiers |
Gets the modifier flags that indicate which combination of modifier keys (Ctrl, Shift, and Alt) were pressed. |
Shift |
Returns True if Shift key is pressed, otherwise False. |
The KeyPreview Property
By default, only the active control will receive keystroke events. The Form object also has KeyPress, KeyUp, and KeyDown events, but they are fired only when all the controls on the form are either hidden or disabled. However, you can modify this behavior.
When you set the KeyPreview property of a form to True, the form will receive all three events (KeyDown, KeyPress, and KeyUp) just before the active control receives them. This allows you to set up a two-tier validation on controls. If you want to discard a certain type of characters at the form level itself, you can set the Handled property for the event argument to True (This will not allow the event to propagate to the active control.); otherwise, the events will propagate to the active control. You can then use keystroke events at the control level to perform field-specific validation such as restricting the field to only numeric digits.
Field-Level Validation
Field-level validation ensures that the value entered in the field as a whole is in accordance with the application's requirement. If it isn't, you can alert the user to the problem. Appropriate times to perform field-level validations are
When the user attempts to leave the field.
When the content of the field changes for any reason. This isn't always a feasible strategy. For example, if you're validating a date to be formatted as "mm/dd/yy", it won't be in that format until all the keystrokes are entered.
When a user enters and leaves a field, events occur in the following order:
Enter
GotFocus
Leave
Validating
Validated
LostFocus
The Validating Event
The Validating event is the ideal place for performing field-level validation logic on a control. The event handler for the Validating event receives an argument of type CancelEventArgs. Its only property is the Cancel property. When set to True, this property cancels the event.
Inside the Validating event, you can write code to
Programmatically correct any errors or omissions made by the user.
Show error messages and alerts to the user so that he can fix the problem.
Inside the Validating event, you might also want to retain the focus in the current control, thus forcing user to fix the problem before proceeding further. You can use either of the following techniques to do this:
Use the Focus method of the control to transfer the focus back to the field.
Set the Cancel property of the CancelEventArgs object to True. This will cancel the Validating event, leaving the focus in the control.
A related event is the Validated event. The Validated event is fired just after the Validating event and enables you to take actions after the control's contents have been validated.
The CausesValidation Property
When you use the Validating event to retain the focus in a control by canceling the event, you must also consider that you are making your control sticky.
Consider what can happen if you force the user to remain in a control until the contents of that control are successfully validated. Now when the user clicks on the Help button in the toolbar to see what is wrong, nothing will happen unless the user makes a correct entry. This can be an annoying situation for users and one that you want to avoid in your application.
NOTE
Validating Event and Sticky Forms The Validating event also fires when you close a form. If you set the Cancel property of the CancelEventArgs object to True inside this event, it will cancel the close operation as well.
This problem has a work-around: Inside the Validating event you should set the Cancel property of CancelEventArgs argument to True only if the mouse pointer is in the form's Client area. The close box is in the title bar outside the client Area of form. Therefore, when the user clicks the close box, the Cancel property will not be set to True.
The CausesValidation property comes to your rescue here. The default value of the CausesValidation property for any control is True, meaning that the Validating event will fire for any control requiring validation before the control in question receives focus.
When you want a control to respond irrespective of the validation status of other controls, set the CausesValidation property of that control to False. So in the previous example, the Help button in the toolbar should have its CausesValidation property set to False.
ErrorProvider
The ErrorProvider component in the Visual Studio .Net toolbox is useful when showing validation-related error messages to the user. The ErrorProvider component can display a small icon next to a field when the field contains an error. When the user hovers the mouse pointer over the icon, it also displays an error message as a ToolTip. This is a better way of displaying error messages as compared to the old way of using message boxes because it eliminates at least two serious problems with message boxes:
If you have errors in multiple controls, several message boxes popping up simultaneously can annoy or scare users.
After the user dismisses a message box, the error message is no longer available for reference.
Table 3.6 lists some important members of the ErrorProvider class that you should be familiar with.
Table 3.6 Important Members of the ErrorProvider Class
Member |
Type |
Description |
BlinkRate |
Property |
Specifies the rate at which the error icon flashes. |
BlinkStyle |
Property |
Specifies a value indicating when the error icon flashes. |
ContainerControl |
Property |
Specifies the parent control of the ErrorProvider control. |
GetError |
Method |
Returns the error description string for the specified control. |
Icon |
Property |
Specifies an icon to display next to a control. The icon is displayed only when an error description string has been set for the control. |
SetError |
Method |
Sets the error description string for the specified control. |
SetIconAlignment |
Method |
Sets the location to place an error icon with respect to the control. It has one of the ErrorIconAlignment values. |
SetIconPadding |
Method |
Sets the amount of extra space to leave between the specified control and the error icon. |
The ErrorProvider object displays an error icon next to a field when you set the error message string. The error message string is set by the SetError method. If the error message is empty, no error icon is displayed and the field is considered to be correct. You'll see how to use the ErrorProvider component in Step By Step 3.6.
STEP BY STEP 3.6 Using the ErrorProvider Control and Other Validation Techniques
-
Add a new Windows Form to your Visual Basic .NET project.
-
Place three TextBox controls (txtMiles, txtGallons, and txtEfficiency) and a Button (btnCalculate) on the form's surface and arrange them as was shown in Figure 3.1. Add the label controls as necessary.
-
Add an ErrorProvider (ErrorProvider1) control to the form. The ErrorProvider control will be displayed in the component tray.
-
Double-click the form and add the following code to handle the form's Load event:
Private Sub StepByStep3_6_Load( _ ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles MyBase.Load ' Set the ErrorProvider's Icon ' alignment for the textbox controls ErrorProvider1.SetIconAlignment( _ txtMiles, ErrorIconAlignment.MiddleLeft) ErrorProvider1.SetIconAlignment( _ txtGallons, ErrorIconAlignment.MiddleLeft)
End Sub
-
Add code to handle the Validating events of the txtMiles and txtGallons controls:
Private Sub txtMiles_Validating(ByVal sender As Object, _ ByVal e As System.ComponentModel.CancelEventArgs) _ Handles txtMiles.Validating Try Dim decMiles As Decimal = _ Convert.ToDecimal(txtMiles.Text) ErrorProvider1.SetError(txtMiles, "") Catch ex As Exception ErrorProvider1.SetError(txtMiles, ex.Message) End Try End Sub Private Sub txtGallons_Validating(ByVal sender As Object, _ ByVal e As System.ComponentModel.CancelEventArgs) _ Handles txtGallons.Validating Try Dim decGallons As Decimal = _ Convert.ToDecimal(txtGallons.Text) If (decGallons > 0) Then ErrorProvider1.SetError(txtGallons, "") Else ErrorProvider1.SetError(txtGallons, _ "Please enter a positive non-zero value") End If Catch ex As Exception ErrorProvider1.SetError(txtGallons, ex.Message) End Try
End Sub
-
Add the following code to the Click event handler of btnCalculate:
Private Sub btnCalculate_Click( _ ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles btnCalculate.Click ' Check whether the error description is not empty 'for either of the textbox controls If (ErrorProvider1.GetError(txtMiles) <> "") Or _ (ErrorProvider1.GetError(txtGallons) <> "") Then Exit Sub End If Try Dim decMiles As Decimal = _ Convert.ToDecimal(txtMiles.Text) Dim decGallons As Decimal = _ Convert.ToDecimal(txtGallons.Text) Dim decEfficiency As Decimal = decMiles / decGallons txtEfficiency.Text = _ String.Format("{0:n}", decEfficiency) Catch ex As Exception Dim msg As String = _ String.Format("Message: {0}" & vbCrLf & _ "Stack Trace:" & vbCrLf & "{1}", _ ex.Message, ex.StackTrace) MessageBox.Show(msg, ex.GetType().ToString()) End Try
End Sub
-
Set the form as the startup object for the project.
-
Run the project. Enter values for miles and gallons and run the program. It will calculate the mileage efficiency as expected. You will notice that when you enter an invalid value into any of the TextBox controls, the error icon starts blinking. It will display an error message when the mouse is hovered over the error icon, as shown in Figure 3.11.
Figure 3.11 ErrorProvider control showing the error icon and the error message.
Enabling Controls Based On Input
One of the useful techniques for restricting user input is the selective enabling and disabling of controls. Some common cases in which this is useful are
Your application might have a Check Here If You Want to Ship to a Different Location check box. When the user checks the check box, you should allow her to enter values in the fields for a shipping address. Otherwise, the shipping address is same as the billing address.
In a Find dialog box, you have two buttons labeled Find and Cancel. You want to keep the Find button disabled initially and enable it only when the user enters some search text in the text box.
The Enabled property for a control is True by default. When you set it to False, the control cannot receive the focus and will appear grayed out.
For some controls, such as the TextBox, you can also use the ReadOnly property to restrict user input. One advantage of using the ReadOnly property is that the control will still be able to receive focus so that users will be able to scroll through the text in the control if it is not completely visible. In addition, users can also select and copy the text to the clipboard if the ReadOnly property is true.
Other Properties for Validation
In addition to already mentioned techniques, the CharacterCasing and MaxLength properties allow you to enforce some restrictions on the user input.
The CharacterCasing Property
The CharacterCasing property of a TextBox control changes the case of characters in the text box as required by your application. For example, you might want to convert all characters entered in a text box used for entering a password to lowercase to avoid case confusion.
This property can be set to CharacterCasing.Lower, CharacterCasing.Normal (the default value) or CharacterCasing.Upper.
The MaxLength Property
The MaxLength property of a TextBox or a ComboBox specifies the maximum number of characters the user can enter into the control. This property comes in handy when you want to restrict the size of some fields such as a telephone number or a ZIP code.
TIP
MaxLength The MaxLength property only affects the text entered into the control interactively by the user. Programmatically, you can set the value of the Text property to a value that is larger than the value specified by the MaxLength property. So you can use the MaxLength property to limit user input without limiting the text that you can programmatically display in the control.
When the MaxLength property is zero (the default), the number of characters that can be entered is limited only by the available memory. In practical terms, this means that the number of characters is generally unlimited.
In Guided Practice Exercise 3.2 you will learn to use some of the validation tools such as the Enabled property, the Focus method of the TextBox control, and the ErrorProvider control.
GUIDED PRACTICE EXERCISE 3.2
In this exercise, your job is to add some features to the keyword searching application you created in Guided Practice Exercise 3.1. The Keyword text box and the Search button should be disabled when the application is launched. These controls should be enabled as the user progresses through the application. The application assumes that the entered keyword must be a single word. If it is not, you must display an error icon and set the error message. The keyword control should not lose focus unless the user enters valid data in the text box.
Try this on your own first. If you get stuck, or would like to see one possible solution, follow these steps:
-
Add a new form to your Visual Basic .NET Project.
-
Place three Label controls, two TextBox controls, and two Button controls on the form, arranged as was shown in Figure 3.8. Name the text box for accepting file names txtFileName and the Browse button btnBrowse. Name the text box for accepting keywords txtKeyword and the search button btnSearch. Name the results label lblResult.
-
Add an OpenFileDialog control to the form and change its name to dlgOpenFile. Add an ErrorProvider (ErrorProvider1) control to the form. The ErrorProvider control will be placed in the component tray.
-
Double-click the form and add the following code to handle the Form's Load event:
Private Sub GuidedPracticeExercise3_2_Load( _ ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles MyBase.Load ' Disable the Keyword textbox and Search button txtKeyword.Enabled = False btnSearch.Enabled = False ErrorProvider1.SetIconAlignment( _ txtKeyword, ErrorIconAlignment.MiddleLeft) End Sub
-
Attach TextChanged and Validating event handlers to the txtKeyword control by adding the following code:
Private Sub txtKeyword_TextChanged(ByVal sender As Object, _ ByVal e As System.EventArgs) Handles txtKeyword.TextChanged If txtKeyword.Text.Length = 0 Then btnSearch.Enabled = False Else btnSearch.Enabled = True End If End Sub Private Sub txtKeyword_Validating(ByVal sender As Object, _ ByVal e As System.ComponentModel.CancelEventArgs) _ Handles txtKeyword.Validating If txtKeyword.Text.Trim().IndexOf(" ") >= 0 Then ErrorProvider1.SetError(txtKeyword, _ "You may only search with a single word") txtKeyword.Focus() txtKeyword.Select(0, txtKeyword.Text.Length) Else ErrorProvider1.SetError(txtKeyword, "") End If End Sub
-
Create a method named GetKeywordFrequency that accepts a string and returns the number of lines containing it. Add the following code to the method:
Private Function GetKeywordFrequency( _ ByVal strPath As String) As Integer Dim count As Integer = 0 If File.Exists(strPath) Then Dim sr As StreamReader = _ New StreamReader(txtFileName.Text) Do While (sr.Peek() > -1) If sr.ReadLine().IndexOf( _ txtKeyword.Text) >= 0 Then count += 1 End If Loop End If GetKeywordFrequency = count End Function
-
Add the following code to the Click event handler of the btnBrowse button:
Private Sub btnBrowse_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles btnBrowse.Click If dlgOpenFile.ShowDialog() = DialogResult.OK Then txtFileName.Text = dlgOpenFile.FileName txtKeyword.Enabled = True txtKeyword.Focus() End If End Sub
-
Add the following code to the Click event handler of the btnSearch button:
Private Sub btnSearch_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles btnSearch.Click If ErrorProvider1.GetError(txtKeyword) <> "" Then Exit Sub End If Try lblResult.Text = String.Format( _ "The keyword: '{0}', found in {1} lines", _ txtKeyword.Text, _ GetKeywordFrequency(txtFileName.Text)) Catch ex As Exception Dim msg As string = _ String.Format("Message:" & vbCrLf & "{0}" & _ vbCrLf & vbCrLf & "StackTrace:" & vbCrLf & "{1}", _ ex.Message, ex.StackTrace) MessageBox.Show(msg, ex.GetType().ToString()) End Try End Sub
-
Set the form as the startup object for the project.
-
Run the project. Note that the Keyword text box and the Search button are disabled. Click the Browse button and select an existing file to enable the Keyword text box. Enter the keyword to search in the file to enable the Search button. Click the Search button. If the keyword entered is in wrong format (if it contains a space), the ErrorProvider control will show the error message and the icon.
-
It is generally a good practice to validate user input at the time of data entry. Thoroughly validated data will result in consistent and correct data stored by the application.
-
When a user presses a key three events are generated: KeyDown, KeyPress, and KeyUp in that order.
-
The Validating event is the ideal place for storing the field-level validation logic for a control.
-
The CausesValidation property specifies whether validation should be performed. If False, the Validating and Validated events are suppressed.
-
The ErrorProvider component in the Visual Studio .Net toolbox shows validation-related error messages to the user.
-
A control cannot receive the focus and appears grayed out if its Enabled property is set to False.
Chapter Summary
The .NET Framework includes thorough support for exception handling. In fact, it allows you to raise exceptions in one language and catch them in a program written in another language. A Try block should enclose any code that can cause exceptions. A Catch block is used to handle the exceptions raised by the code in the Try block, and the Finally block contains code that will be executed irrespective of the occurrence of an exception.
The Framework Class Library (FCL) provides a large number of Exception classes that represent most of the exceptions that your program can encounter. If you prefer to create your own custom exception class, you can do so by deriving your exception class from the ApplicationException class.
You also learned variety of ways in which to validate user input. The Windows Forms library includes an ErrorProvider component to inform the user of errors. You can also associate custom icons and error messages with the ErrorProvider object.
KEY TERMS
Exception
Exception handling
Input validation
Apply Your Knowledge
Exercises
3.1 Handling Exceptions
Recall Step By Step 2.5 ("Common Dialog Boxes") from Chapter 2. This Step By Step demonstrated the use of common dialog boxes by creating a simple rich text editor. This editor allows you to open and save a rich text file. You can edit the text or change its fonts and colors. The program worked fine in all cases unless you tried to open or save a file that was already open; in this case the program throws a System.IO.IOException.
The objective of this exercise is to make a more robust version of this program that generates a warning about the open file rather than abruptly terminating the program.
Estimated Time: 15 minutes.
Create a new Visual Basic .NET Windows application.
Add a Windows Form to the project. Name this form Exercise3_1.
-
Place five Button controls to the form. Name them btnOpen, btnSave, btnClose, btnColor, and btnFont and change their Text property to &Open..., &Save..., Clos&e..., &Color..., and &Font..., respectively. Place a RichTextBox control and name it rtbText. Arrange all controls as shown in Figure 3.12.
Figure 3.12
A simple rich text editor.
-
Drag and drop the following components from the Toolbox to the form: OpenFileDialog, SaveFileDialog, ColorDialog, and FontDialog.
-
Switch to the Code view and add the following line of code at the top of the form's module:
Imports System.IO
-
Double-click on the Open button to attach an event handler to its Click event. Add the following code to the event handler:
Private Sub btnOpen_Click( _ ByVal sender As System.Object, _ ByVal e As System.EventArgs) _ Handles btnOpen.Click ' Allow the user to select ' only *.rtf files OpenFileDialog1.Filter = _ "Rich Text Files (*.rtf)|*.rtf" If OpenFileDialog1.ShowDialog() = _ DialogResult.OK Then Try ' Load the file contents ' in the RichTextBox rtbText.LoadFile( _ OpenFileDialog1.FileName, _ RichTextBoxStreamType.RichText) Catch ioe As System.IO.IOException MessageBox.Show(ioe.Message, _ "Error opening file") End Try End If End Sub
-
Add the following code to handle the Click event of the Save button:
Private Sub btnSave_Click( _ ByVal sender As System.Object, _ ByVal e As System.EventArgs) _ Handles btnSave.Click ' Default choice to save file ' is *.rtf but user can select ' All Files to save with other extension SaveFileDialog1.Filter = _ "Rich Text Files (*.rtf)|*.rtf|" & _ "All Files (*.*)|*.*" If SaveFileDialog1.ShowDialog() = _ DialogResult.OK Then Try ' Save the RichText ' content to a file rtbText.SaveFile( _ SaveFileDialog1.FileName, _ RichTextBoxStreamType.RichText) Catch ioe As System.IO.IOException MessageBox.Show(ioe.Message,_ "Error saving file") End Try End If End Sub
-
Add the following code to handle the Click event of the Close button:
Private Sub btnClose_Click( _ ByVal sender As System.Object, _ ByVal e As System.EventArgs) _ Handles btnClose.Click ' Close the form Me.Close() End Sub
-
Add the following code to handle the Click event of the Color button:
Private Sub btnColor_Click( _ ByVal sender As System.Object, _ ByVal e As System.EventArgs) _ Handles btnColor.Click If ColorDialog1.ShowDialog() = _ DialogResult.OK Then ' Change the color of selected text ' If no text selected, ' change the active color rtbText.SelectionColor = _ ColorDialog1.Color End If End Sub
-
Add the following code to handle the Click event of the Font button:
Private Sub btnFont_Click( _ ByVal sender As System.Object, _ ByVal e As System.EventArgs) _ Handles btnFont.Click If FontDialog1.ShowDialog() = _ DialogResult.OK Then ' Change the font of selected text ' If no text selected, ' change the active font rtbText.SelectionFont = _ FontDialog1.Font End If End Sub
-
Set this form as the startup object for the project.
-
Run the project. Click on the Open button and try to open an already opened file. You'll see an error message warning about the file being already open, as shown in Figure 3.13.
Figure 3.13
Error message warning user about an already open file.
3.2 Validating User Input
One of the techniques for input validation is to force the user to fix an erroneous field before allowing her to move to another field. To achieve this, you can set the Cancel property of the CancelEventArgs argument of the Validating event of a field to True.
In this exercise, you'll create a login form (see Figure 3.14) that accepts a username and a password. It forces the user to enter the username. The user should be also able to close the application by clicking the Cancel button irrespective of the validation status of the fields.
Estimated Time: 15 minutes.
Add a new form to your Visual Basic .NET application.
-
Place three Label controls, two TextBox controls named txtUserName and txtPassword, two Button controls named btnLogin and btnCancel and an ErrorProvider component named ErrorProvider1 on the form. The ErrorProvider component will be placed in the component tray. Arrange the controls in the form as shown in Figure 3.14.
Figure 3.14 The login form.
-
Change the ControlBox property of the form to False, the CharacterCasing property of the txtPassword control to Lower and the CausesValidation property of btnCancel to False.
-
Double-click the form to add an event handler for the Load event:
Private Sub Exercise3_2_Load( _ ByVal sender As System.Object, _ ByVal e As System.EventArgs) _ Handles MyBase.Load ErrorProvider1.SetIconAlignment( _ txtUserName, _ ErrorIconAlignment.MiddleLeft) ErrorProvider1.SetIconAlignment( _ txtPassword, _ ErrorIconAlignment.MiddleLeft) End Sub
-
Declare the following variable outside a method block in the class:
' closingFlag is used to check ' if user has clicked on the Close button Private closingFlag As Boolean = False
-
Add the following code to the Click event handler of the Cancel button:
Private Sub btnCancel_Click( _ ByVal sender As System.Object, _ ByVal e As System.EventArgs) _ Handles btnCancel.Click closingFlag = True Me.Close() End Sub
-
Add the following code to the Click event handler of the Login button:
Private Sub btnLogin_Click( _ ByVal sender As System.Object, _ ByVal e As System.EventArgs) _ Handles btnLogin.Click Dim strMessage As String = _ String.Format( _ "The following information:" & _ vbCrLf & vbCrLf & "UserName: {0}" & _ vbCrLf & vbCrLf & _ "Password: {1}" & vbCrLf & vbCrLf & _ " can be now passed to " & _ " the middle-tier for validation", _ txtUserName.Text, txtPassword.Text) MessageBox.Show(strMessage, _ "User Input Validation Succeeded") End Sub
-
Add code to handle the Validating events of the txtUserName and txtPassword controls:
Private Sub txtUserName_Validating( _ ByVal sender As Object, _ ByVal e As System.ComponentModel. _ CancelEventArgs) _ Handles txtUserName.Validating If Not closingFlag Then If txtUserName.Text.Trim(). _ Length = 0 Then ErrorProvider1.SetError( _ txtUserName, _ "Please enter a value " & _ "for this field") e.Cancel = True ElseIf txtUserName.Text.Trim(). _ IndexOf(" ") >= 0 Then ErrorProvider1.SetError( _ txtUserName, _ "You can NOT have spaces " & _ "in this field") txtUserName.Select(0, _ txtUserName.Text.Length) e.Cancel = True End If End If End Sub Private Sub txtPassword_Validating( _ ByVal sender As Object, _ ByVal e As System.ComponentModel. _ CancelEventArgs) _ Handles txtPassword.Validating If Not closingFlag Then If txtPassword.Text.Trim(). _ Length = 0 Then ErrorProvider1.SetError( _ txtPassword, _ "Please enter a value " & _ "for this field") e.Cancel = True ElseIf txtPassword.Text.Trim(). _ IndexOf(" ") >= 0 Then ErrorProvider1.SetError( _ txtPassword, _ "You can NOT have spaces " & _ "in this field") txtPassword.Select(0, _ txtPassword.Text.Length) e.Cancel = True End If End If End Sub
-
Add code to handle the Validated event of the txtUserName and txtPassword controls:
Private Sub txtUserName_Validated( _ ByVal sender As Object, _ ByVal e As System.EventArgs) _ Handles txtUserName.Validated ErrorProvider1.SetError(txtUserName, "") End Sub Private Sub txtPassword_Validated( _ ByVal sender As Object, _ ByVal e As System.EventArgs) _ Handles txtPassword.Validated ErrorProvider1.SetError(txtPassword, "") End Sub
-
Set this form as the startup object for the project.
-
Run the project. If you click directly on the Login button, you will be forced to enter a username. However, you can click the Cancel button to close the application even without entering a valid username.
Review Questions
What is the default behavior of the .NET framework when an exception is raised?
Which is the base class of all the exceptions that provides the basic functionality for exception handling? What are the two main types of exception classes and their purposes?
Explain the Message and InnerException properties of the Exception class.
What is the purpose of Try and Catch blocks?
How many Catch blocks can be associated with a single Try block? How should they be arranged?
What is the importance of the Finally block?
Can you associate custom error messages with the exception types defined by the CLR? If yes, how can you do this?
What are some of the points you should consider before creating custom exceptions?
What is the importance of the Validating event?
Explain the purpose of the ErrorProvider component.
Exam Questions
-
You are creating a data import utility for a personal information system that you designed recently. When the record in the source data file is not in the required format, your application needs to throw a custom exception. You will create an exception class with the name InvalidRecordStructureException. Which of the following classes would you choose as the base class for your custom exception class?
-
ApplicationException
-
Exception
-
SystemException
-
InvalidFilterCriteriaException
-
-
You are assisting your colleague in solving the compiler error that his code is throwing. The problematic portion of his code is
Try Dim success As Boolean = GenerateNewtonSeries(500, 0) ' more code here Catch dbze As DivideByZeroException ' exception handling code Catch nfne As NotFiniteNumberException ' exception handling code Catch ae As ArithmeticException ' exception handling code Catch e As OverflowException ' exception handling code End Try
To remove the compilation error, which of the following ways would you modify the code?
-
Try Dim success As Boolean = GenerateNewtonSeries(500, 0) ' more code here Catch dbze As DivideByZeroException ' exception handling code Catch ae As ArithmeticException ' exception handling code Catch e As OverflowException ' exception handling code End Try
-
Try Dim success As Boolean = GenerateNewtonSeries(500, 0) ' more code here Catch dbze As DivideByZeroException ' exception handling code Catch ae As Exception ' exception handling code Catch e As OverflowException ' exception handling code End Try
-
Try Dim success As Boolean = GenerateNewtonSeries(500, 0) ' more code here Catch dbze As DivideByZeroException ' exception handling code Catch nfne As NotFiniteNumberException ' exception handling code Catch e As OverflowException ' exception handling code Catch ae As ArithmeticException ' exception handling code End Try
-
Try Dim success As Boolean = GenerateNewtonSeries(500, 0) ' more code here Catch dbze As DivideByZeroException ' exception handling code Catch nfne As NotFiniteNumberException ' exception handling code Catch ae As Exception ' exception handling code Catch e As OverflowException ' exception handling code End Try
-
- You need to debug a program containing some exception handling code. To understand the program better, you created a stripped down version of it and included some MessageBox statements that give clues about the flow of its execution. The program has the following code:
Try Dim num As Integer = 100 dim den as Integer = 0) MessageBox.Show("Message1") Try Dim res As Integer = num / den MessageBox.Show("Message2") Catch ae As ArithmeticException MessageBox.Show("Message3") End Try Catch dbze As DivideByZeroException MessageBox.Show("Message4") Finally MessageBox.Show("Message5") End Try
Which of these is the order of messages that you receive?
-
Message1 Message2 Message3 Message4 Message5
-
Message1 Message3 Message5
-
Message1 Message4 Message5
-
Message1 Message2 Message4 Message5
-
-
What is the output displayed by the MessageBox in the following code segment?
Try Try Throw _ New ArgumentOutOfRangeException() Catch ae As ArgumentException Throw New ArgumentException( _ "Out of Range", ae) End Try Catch ex As Exception MessageBox.Show( _ ex.InnerException.GetType().ToString()) End Try
-
System.Exception
-
System.ApplicationException
-
System.ArgumentException
-
System.ArgumentOutOfRangeException
-
- The Validating event of a text box in your Windows application contains the following code. The MyValidatingCode method validates the contents of the text box. If the contents are invalid, this method throws an exception and retains the focus in the text box. The line numbers in the code sample are for reference purpose only.
01 Private Sub TextBox1_Validating( _ 02 ByVal sender As System.Object, _ 03 ByVal e As System.EventArgs) _ 04 Handles TextBox1.Validating 05 Try 06 MyValidatingCode() 07 Catch ex As Exception 08 09 TextBox1.Select(0, _textBox1.Text.Length) 10 ErrorProvider1.SetError(textBox1, _ex.Message) 11 End Try 12 End Sub
Which of the following line of code should be in line 8?
-
e.Cancel = True
-
e.Cancel = False
-
TextBox1.CausesValidation = True
-
TextBox1.CausesValidation = False
-
-
You have designed a Windows Form that works as a login screen. The form has two TextBox controls named txtUserName and txtpassword. You want to ensure that the user can only enter lowercase characters in the controls. Which of the following methods should you use?
-
Set the form's KeyPreview property to True and program the KeyPress event of the form to convert uppercase letters to lowercase letters.
-
Create a single event handler attached to the KeyPress event of both txtUserName and txtpassword. Program this event handler to convert the uppercase letters to lowercase.
-
Use the CharacterCasing property of the controls.
-
Use the Char.ToLower method in the TextChanged event handlers of the controls.
-
You must create a custom exception class in your Windows application. You have written the following code for the exception class:
Public Class KeywordNotFound Inherits ApplicationException Public Sub New() ' Code here End Sub Public Sub New( _ ByVal message As String, _ ByVal inner As Exception) MyBase.New(message, inner) ' Code here End Sub End Class
A code review suggests that that you did not followed some of the best practices for creating a custom exception class. Which of these changes should you make? (Select two.)
-
Name the exception class KeywordNotFoundException.
-
Derive the exception class from the base class Exception instead of ApplicationException.
-
Add one more constructor to the class with the following signature:
Public Sub New(ByVal message As String) MyBase.New(message) ' Code here End Sub
-
Add one more constructor to the class with the following signature:
Public Sub New(ByVal inner As Exception) MyBase.New(inner) ' Code here End Sub
-
Derive the exception class from the base class SystemException instead of ApplicationException.
-
-
In your Windows application, you have created a dialog box that allows the user to set options for the application. You have also created a Help button that the user can press to get help on various options in the dialog box. You validate the data entered by the user in a TextBox control labeled Complex Script; if the user enters an invalid value in this text box, you set the focus back in the control by setting the cancel property of the CancelEventArgs object to True. While testing the application, you discovered that once you enter invalid data in the text box, you could not click on the Help button without correcting the data first. What should you do to correct the problem?
-
Set the CausesValidation property of the text box to False.
-
Set the CausesValidation property of the text box to True.
-
Set the CausesValidation property of the Help button to False.
-
Set the CausesValidation property of the Help button to True.
-
-
You are writing exception handling code for an order entry form. When the exception occurs, you want to get information about the sequence of method calls and the line number in the method where the exception occurs. Which property of your exception class can help you?
-
HelpLink
-
InnerException
-
Message
-
StackTrace
-
Your code uses the Throw statement in this fashion:
Catch e As Exception Throw
Which of these statements is true about this code?
-
The Throw statement catches and rethrows the current exception.
-
The Throw statement catches, wraps, and then rethrows the current exception.
-
The Throw statement must be followed by an exception object to be thrown.
-
The Throw statement transfers control to the Finally block following the Catch block.
-
-
You are creating a Windows Form that works as a login screen for an Order Entry system designed for the sales department of your company. Which of the following strategies should you follow?
-
Design a ValidateUser method. Throw a new custom exception named EmployeeNotFound when the entered username is not in the database.
-
Design a ValidateUser method. Throw an ArgumentException exception when the user types special characters in the User name or Password text boxes.
-
Design a ValidateUser method. It returns True if the username and password are correct; otherwise it returns False.
-
Design a ValidateUser method. Throw an ApplicationException when the entered username is not in the database.
-
-
You want to capture all the exceptions that escape from the exception handling code in your application and log them to Windows event log. Which of the following techniques would you use?
-
Write all the code of the application inside a Try block, attach a generic Catch block to that Try block, and handle the exception there.
-
Write all the code of the application inside a Try block, attach a Catch block that catches the super type of all exceptions (the Exception objects), and write code to make entries in the event log there.
-
Program the ProcessExit event handler of the AppDomain class.
-
Program the UnhandledException event handler of the AppDomain class.
-
-
Which of the following is generally the most robust way to record the unhandled exceptions in your application?
-
Create an entry in Windows event log.
-
Create an entry in the application's custom event log.
-
Create an entry in a table in Microsoft SQL Server 2000 database.
-
Send an email using SMTP.
-
-
The structured exception handling mechanism of the .NET Framework allows you to handle which of the following types of exceptions? (Select all that apply.)
-
Exceptions from all CLS-compliant languages
-
Exceptions from non-CLScompliant languages
-
Exceptions from unmanaged COM code
-
Exceptions from unmanaged nonCOM code
-
What is the result of executing this code snippet?
Const someVal1 As Int32 = Int32.MaxValue Const someVal2 As Int32 = Int32.MaxValue Dim result As Int32 result = someVal1 * someVal2
-
The code will generate an OverflowException.
-
The code will execute successfully without any exceptions.
-
The code will cause a compile-time error
- The code executes successfully but the value of the variable result is truncated.
-
Answers to Review Questions
-
The .NET framework terminates the application after displaying an error message when an exception is raised.
-
The Exception class is the base class that provides common functionality for exception handling. The two main types of exceptions derived from Exception class are SystemException and ApplicationException. SystemException represents the exceptions thrown by the common language runtime, whereas ApplicationException represents the exceptions thrown by user code.
-
The Message property describes the current exception. The InnerException property represents an exception object associated with the exception object; this property is helpful when a series of exceptions are involved. Each new exception can preserve information about a previous exception by storing it in InnerException property.
-
The Try block encloses code that might raise an exception. The Catch block handles any exception raised by the code in the Try block.
-
Zero or more Catch blocks can be associated with a single Try block. If no Catch block is associated with a Try block, a Finally block should follow the Try block; otherwise a compile-time error occurs. The Catch blocks should be arranged from specific to general exception types to avoid a compile-time error.
-
The code contained by the Finally block always executes regardless of any exception in the application. Therefore, you can use the Finally block to write cleanup code to complete tasks, such as closing data connections, closing files, and so on, that need to be performed regardless of whether an exception occurred.
-
Yes, you can associate custom error messages with the exception classes defined by the Common Language Runtime to provide more meaningful information to the caller code. The constructor of these classes that accepts the exception message as its parameter can be used to pass the custom error message.
-
Custom exceptions should be derived from ApplicationException and should be created only if existing classes do not meet the requirements of your application. They should have a name ending with the word Exception and should implement the three constructors (default, Message, and Message and Exception) of their base class.
-
The Validating event is the ideal place to store the field-level validation logic for a control. The Validating event handler can be used to cancel the event if validation fails, thus forcing the focus to the control. This forces the user to enter correct data.
- The ErrorProvider component in the Visual Studio .Net toolbox can be used to show validation-related error icons and error messages to the user.
Answers to Exam Questions
-
A. When creating a class for handling custom exceptions in your programs, the best practice is to derive it from the ApplicationException class. The SystemException class is for system-defined exceptions. The Exception class is the base class for both ApplicationException and SystemException classes and should not generally be derived from directly.
-
C. When you have multiple Catch blocks associated with a Try block, you must write them in order from the most specific to the most general one. The Catch block corresponding to the ArithmeticException should come at the end because it is a more general class than the three (DivideByZeroException, NotFiniteNumberException and the OverFlowException) classes derived from it.
-
B. When an exception occurs in a Try block, it will search for a matching Catch block associated with that Try block. In this case, the ArithmeticException generates a match for DivideByZeroException because DivideByZeroException is derived from ArithmeticException, and the exception is handled right there. In all cases the Finally block is executed.
-
D. The message box displays System.ArgumentOutOfRangeException because you caught and wrapped that exception in the InnerException property of the exception caught later by the outer Catch block.
-
A. When you want to retain the focus inside a control after the Validating event is processed, you must set the Cancel property of the CancelEventArgs argument in the Validating event, so the correct answer is e.Cancel = True. The CausesValidation property has a different purpose: It decides whether a Validating event will be fired for a control.
-
C. The CharacterCasing property when set to CharacterCasing.Lower for a text box will convert all uppercase letters to lowercase as you type them. It is the preferred way to enforce either lowercase or uppercase input in a text box.
-
A, C. The best practices for exception handling recommend that you end the name of your exception class with the word Exception. In addition, an exception class must implement three standard contructors. The missing constructor is the one given in option C.
-
C. When you want a control to respond regardless of the validation statuses of other controls, set the CausesValidation property of that control to True. Then the Help button should have its CausesValidation property set to False.
-
D. The StackTrace property of the Exception class and the classes that derive from it contains information about the sequence of method calls and the line numbers in which the exception occurred. Therefore, it is the right property to use.
-
A. The Throw statement just catches and throws the current exception.
-
C. It is obvious that user can make typing mistakes while typing her username or password. You should not throw exceptions for these situations; you should rather design a ValidateUser method that returns a result indicating whether the login was successful.
-
D. To capture all unhandled exceptions for an application, you must program the UnhandledEvent event handler of its AppDomian class.
-
A. Logging to the Windows event handler is the most robust solution because the other solutions have more assumptions that can fail. Sometimes your application can loose connectivity with the database or with the SMTP server, or you might have problems writing an entry in a custom log file.
-
A, B, C, D. The .NET Framework allows you to handle all types of exceptions including cross-language exceptions for both CLS- and nonCLS-complaint languages. It also allows you to handle exceptions from unmanaged code, both COM and nonCOM.
- C. Since you are multiplying two maximum possible values for integers, the result cannot be stored inside an integer. The compiler will detect it and will flag a compile-time error.
Suggested Readings and Resources
-
Ritcher, Jeffery. Applied Microsoft .NET Framework Programming. Microsoft Press, 2001.
-
Siler, Brian and Jeff Spotts. Special Edition Using Visual Basic .NET. Que, 2002.
-
Visual Studio .NET Combined Help Collection
-
Best Practices for Exception Handling
-
Exception Handling Statements
- Exception Management in .NET
-