Chapter 17. Writing Smarter Code

In Chapter 16, you dove headfirst into the world of Visual Basic code, writing routines that could show messages, respond to events, and modify forms. Along the way, you learned a fair bit about the Visual Basic language, and the object-based system that gives VB its mojo.

There’s still more to explore. In this chapter, you’ll learn how to use VB code to solve some of the most common problems Access experts face. You’ll focus on improving the Boutique Fudge database, which you’ve worked with throughout this book. However, the solutions you’ll use are so useful that you’ll want to incorporate them into your own databases.

But first, before you tackle these more advanced examples, you’ll start by brushing up on the Visual Basic language, learning how to defang errors, and taking a closer look at objects. These topics complete the Visual Basic picture, and prepare you to become a true Access programmer.

Exploring the VB Language

Although you now know enough to react to events and change control properties, there’s still a lot to learn about the Visual Basic language itself. In the following sections, you’ll learn how to use variables, conditional logic, and loops to write more powerful code. Finally, you’ll see how to use these features to build a more complex code routine that checks for invalid credit card numbers.

Storing Information in Variables

Every programming language includes the concept of variables, which are temporary storage containers where you can keep track of important information.

Suppose you want to swap the content in two fields. On the surface, this operation seems fairly straightforward. All you need to do is take the text from one text box, place it in the other, and then insert the second box’s text in the first box. Here’s a first crack at a solution:

	TextBoxOne.Value = TextBoxTwo.Value
	TextBoxTwo.Value = TextBoxOne.Value

To make this code work, you need to put it in the right subroutine. In this example, the code runs when someone clicks a button in a form. You can create the sub-routine you want for the On Click event using the Property Sheet. (See Section 16.2 for a refresher.)

Sadly, this code’s doomed from the start. Figure 17-1 illustrates the problem.

Top: Initially, each text box has its own information.Bottom: After running your swap code routine, you don’t get the result you expect. Once you paste the new content into the second text box, you end up overwriting the content you want to put in the first text box. The end result’s two text boxes with the same content.

Figure 17-1. Top: Initially, each text box has its own information. Bottom: After running your swap code routine, you don’t get the result you expect. Once you paste the new content into the second text box, you end up overwriting the content you want to put in the first text box. The end result’s two text boxes with the same content.

The easiest way around this problem is to use a variable to keep track of the information you need. To create a variable in VB, you use the oddly named Dim keyword (short for dimension, which is programmer jargon for “create a new variable”). After the word Dim, you enter the variable’s name.

Here’s how you’d create a variable named TextContent:

	Dim TextContent

Once you’ve created the variable, you’re free to put information in it, and take information out. To perform both these operations, you use the familiar equal sign, just as you would with properties.

Here’s an example that stores some text in a variable:

	TextContent = "Test text"

The following code puts all these concepts together. It uses a variable to swap the content of two text boxes.

	Dim TextContent
	' Copy the text from the first text box for later use.
	TextContent = TextBoxOne.Value

	' Change the text in the first text box.
	TextBoxOne.Value = TextBoxTwo.Value

	' Change the text in the second text box, using the variable.
	TextBoxTwo.Value = TextContent

Making Decisions

Conditional logic, another programming staple, is code that runs only if a certain condition’s true. There’s no limit to the number of ways you can use conditional logic. You may want to prevent an update if newly entered data doesn’t check out. Or you may want to configure the controls on a form differently, depending on its data. You can do all this, and more, using conditional logic.

All conditional logic starts with a condition: a simple expression that can turn out to be true or false (programmers call this process evaluating to true or false). Your code can then make a decision to execute different logic depending on the condition’s outcome. To build a condition, you need to compare a variable or property using a logical operator like = (equal to), < (less than), > (greater than), and <> (not equal to). For example, Price = 10 is a condition. It can be true (if the Price field contains the number 10), or false (if Price contains something else). You’ve already used conditions to create validation rules (Section 4.3) and filter records in a query (Section 6.2.1.1). Visual Basic conditions follow a very similar set of rules.

On its own, a condition can’t do anything. However, when used in conjunction with other code, it can become tremendously powerful. Once you’ve created a suitable condition, you can put it inside a special structure called the If block. The If block evaluates a condition, and runs a section of code if the condition’s true. If the condition isn’t true, Access completely ignores the code.

Here’s an If Block that checks whether the Price field’s value is greater than 100. If it is, Access displays a message:

	If Price > 100 Then
	MsgBox "I hope you budgeted for this."
	End If

Note that the If block always starts with If and ends with End If. Inside the If block, you can put as much code as you want. This is the conditional code—it runs only if the condition’s true.

An If block can also evaluate several different conditions. Here’s an example that calculates the fully taxed price of a product, and then displays that in a label. The trick’s that the tax rate depends on another field (the Country), which is where the conditional logic comes into play.

	' Store the tax rate you want to use in this variable.
	Dim TaxRate

	If Country = "U.S.A." Then
	    ' Taxes are charged for U.S. customers (7%).
	    TaxRate = 1.07
	ElseIf Country = "Canada" Then
	    ' Even more taxes are charged for Canadian customers (14%).
	    TaxRate = 1.14
	Else
	    ' Everyone else gets off with no tax.
	    TaxRate = 1
	End If
	
	' Display the final total in a label.
	TotalWithTax.Caption = Price * TaxRate

Only one segment of code runs in an If block. In this example, Access works its way through the block, testing each condition until one matches. As soon as it finds a match, it runs the conditional block of code, jumps down to the closing End If, and then continues with any other code that’s in the subroutine. If no condition matches, then Access runs the code in the final Else clause (if you’ve added it). Figure 17-2 shows this code in action.

Thanks to conditional logic, this subform shows the correctly calculated total price at all times, taking into account both the current price and the customer’s country. It works by responding to the On Current event, which occurs every time a record’s displayed in the form.

Figure 17-2. Thanks to conditional logic, this subform shows the correctly calculated total price at all times, taking into account both the current price and the customer’s country. It works by responding to the On Current event, which occurs every time a record’s displayed in the form.

These examples only scratch the surface of what careful conditional logic can do. You can use And and Or keywords to combine conditions, put one conditional block inside another, and much more.

In Chapter 15, you saw an example that performed a specific type of validation with customer records (Section 15.6.2). This validation worked using two fields: WantsEmail and EmailAddress. If the WantsEmail field was set to Yes, then the EmailAddress field couldn’t be empty. However, if WantsEmail was set to No, then a blank EmailAddress was completely acceptable. You can implement the identical validation logic using VB code, but there’s a twist—it uses two If blocks (a line-by-line explanation follows the code):

	1 Private Sub Form_BeforeUpdate(Cancel As Integer)

	      ' Check if this person wants the email.
	
	2     If WantsEmail = True Then

	          ' Make sure the EmailAddress isn't blank or null.
	3         If EmailAddress = "" Or IsNull(EmailAddress) Then
	              ' This is considered invalid.
	              ' Cancel the change and show a message.
	4             MsgBox "You can't be notified without an email address."
	5             Cancel = True
	6         End If

	7     End If
	8 End Sub

Here’s how it works:

  • Line 1 declares a code routine that handles the Before Update event of the form. Notice that this event handler gets one piece of information—a true or false value named Cancel, which you can set to stop the update.

  • Line 2 starts an If block that checks if the WantsEmail checkbox has a check-mark in it.

  • Line 3 performs a second check. It’s a bit more complex because there are two things that can cause the conditional code to run. It runs if the email address is a blank value (which happens if someone enters an email address, and then deletes it) or if the email address is null (which means that an email was never entered in the first place; see Section 4.1.2 for a discussion of null values).

  • Line 4 shows an explanatory error message. Remember, the code gets to this spot only if both the If blocks evaluate to true. If either check turns out false (the WantsEmail checkbox isn’t turned on, or the EmailAddress is supplied), Access breezes right on past.

    Note

    Technically, you could combine both these If blocks into a single If block by writing a more complex condition that checks for everything at once. However, getting this right (and understanding what you’ve written later on) is more difficult. Veteran programmers know that it’s always better to write code clearly, even if that makes the code a little more verbose.

  • Line 5cancels the update using the Cancel parameter that the On Before Update event provides. That way, the change doesn’t go ahead and the record remains in edit mode.

  • Lines 6 to 8 finish up by closing both If blocks, and ending the subroutine.

Access has many events you can cancel, like On Before Update. Look for the Cancel parameter between the parentheses after the subroutine name. If it’s there, you can set it to True to stop the action that’s about to occur.

Repeating Actions with a Loop

A loop is a tool that lets you repeat an operation as many times as you want. Visual Basic has several types of loops you can use. The most popular are the Do/Loop block, and the For/Next block, both of which you’ll see in this section.

Here’s an example Do/Loop block that’s sure to infuriate people:

	Do
	    MsgBox "Ever ever get that nagging deja vu feeling?"
	Loop

When Access enters this block of code, it starts by displaying the Message box and pausing your code. Once you click OK, the code continues until Access reaches the final Loop statement at the bottom of the loop. At this point, Access automatically jumps back to the beginning (the Do statement) and repeats your code, showing a second Message box. However, there’s one problem—this process continues forever! If you make the mistake of running this piece of code, your database will be locked up indefinitely (until you press the emergency-stop key combination, Ctrl+Break).

To avoid this situation, you should build all loops with an exit condition, a condition that signals when the loop should end. Here’s a rewritten version of the same loop that stops after it’s shown a message five times:

	' Keep track of how many times you've looped.
	Dim NumberOfTimes

	' Start the count at 0.
	NumberOfTimes = 0

	Do
	    MsgBox "Ever ever get that nagging deja vu feeling?"

	    ' Up the count by 1.
	    NumberOfTimes = NumberOfTimes + 1
	Loop Until NumberOfTimes = 5

The important bit’s the final clause at the end of the loop block, Until NumberOfTimes = 5. This clause defines a condition, and as soon as it’s true (the NumberOfTimes variables reaches 5) and Access reaches the end of the loop, it jumps out and continues running the rest of your subroutine.

If you have this type of code, where you’re looping a fixed number of times, then you may be interested in the For/Next loop. The For/Next loop is exactly the same as the Do/Next loop, except that it has a built-in counter, which it increments for you.

Here’s how you could rewrite the previous example in a more compact form with a For/Next loop:

	Dim NumberOfTimes

	For NumberOfTimes = 1 To 5
	    MsgBox "Ever ever get that nagging deja vu feeling?"
	Next

The important part’s NumberOfTimes = 1 To 5, which tells Access to start NumberOfTimes at 1, to increment it by 1 at the beginning of each pass through the loop, and to stop after the fifth pass.

The Do/Loop block works well when you need to move through a collection of data. You can use the loop to keep going until you run out of information, even though you don’t know how much information there is when you first start the loop. You’ll see an example of this technique at the end of this chapter (Section 17.4.5), when you perform a batch update on your database using code.

On the other hand, the For/Next loop shines when you can determine at the out-set exactly how many times you want to repeat a loop. You’ll see an example where this is true later in this chapter (Section 17.1.5), when you test credit card numbers.

Creating Custom Functions

You’ve already learned how to create your subroutines. But you haven’t yet seen how to create their big brother, functions.

Like a subroutine, a function’s a self-contained piece of code that can hold as many or as few statements as you want. And like subroutines, you add functions to modules. In fact, any number of subroutines and functions can exist side by side in a module.

	Function DoSomething()
	    ' Function code goes here.
	End Function

The key difference between functions and subroutines is that a function produces a final result. In other words, functions give you a piece of information that you may need.

You set the result by writing a line of code that assigns the result value to the function name. (Essentially, you pretend that the function name’s a variable where you can stuff some data.) Here’s an example:

	Function GetMyFavoriteColor()
	   GetMyFavoriteColor = "Magenta"
	End Function

This function’s named GetMyFavoriteColor. The result’s the text string “Magenta”.

Calling a function is slightly different than calling a subroutine. To call a subroutine, you use the module name, followed by a period, followed by the subroutine name. You can use the same technique with a function, as shown here:

	MyModule.GetMyFavoriteColor

However, there’s a problem. This step triggers the GetMyFavoriteColor function, causing its code to run, but it tosses away the result (the string with the text “Magenta”).

If you’re interested in the result, then you can call your function as part of an assignment statement. The following code creates a variable, uses it to store the result, and then displays the result in a Message box:

	' Create a variable to store the result in.
	Dim Color

	' Call the function and hold on to the result in the variable.
	Color = MyModule.GetMyFavoriteColor

	' Show the result in a Message box.
	MsgBox "Your favorite color is " & Color

If you’re really clever, you can shorten this code to a single line and avoid using the Color variable altogether:

	MsgBox "Your favorite color is " & MyModule.GetMyFavoriteColor

The GetMyFavoriteColor function’s particularly simple because it doesn’t use any arguments. But there’s no reason you can’t get a little fancier. Consider the following custom function, which takes two arguments—length and width—and calculates the total area by multiplying them together:

	Function Area(Length, Width)
	    Area = Length * Width
	End Function

The two parameters are defined in the parentheses after the function name. You can add as many parameters as you want, as long as you separate each one with a comma.

Here’s how you call this function and show the result. In this example, fixed numbers are used for the Length and Width parameters. However, there’s no reason you can’t substitute a field name, variable, or property that you want to use with the Area function instead.

	MsgBox "The area of a 4x4 rectangle is " & Area(4, 4)

This displays the message “The area of a 4x4 rectangle is 16.”

Neither the GetMyFavoriteColor( ) nor Area( ) function shows you anything particularly impressive. But in the next section of this chapter, you’ll build a much more powerful custom function that tests credit card numbers.

Putting It All Together: A Function for Testing Credit Cards

Now that you’ve made your way around the Visual Basic language, it’s time to wrap up with an example that demonstrates everything you’ve learned about VB (and a little bit more).

In this example, you’ll consider a custom function called ValidateCard that examines a credit card number. The ValidateCard function returns one of two results: True (which means the card number’s valid) and False (which means it’s not).

It’s important to understand that a valid credit card number’s simply a number that meets all the not-so-secret rules of credit card numbering (the box in Section 17.1.5 tells you more). This number may be attached to a real credit card, or it may not. The ValidateCard function’s just smart enough to catch inadvertent errors and not-so-bright computer hackers. Truly sneaky people can find programs that let them generate potentially valid credit card numbers.

Here’s the full code for the ValidateCard function. Each code statement’s numbered so you can break it down one piece at a time (a line-by-line explanation follows the code):

	1 Function ValidateCard(CardNumber As String)

	      ' This is the running total (created using Luhn's algorithm).
	2     Dim SumOfDigits
	3     SumOfDigits = 0

	      ' This keeps track of whether you're at an odd or even position.
	      ' You start on an odd number position (1).
	4       Dim OddNumbered
	5       OddNumbered = True

	6       Dim i
	7       For i = Len(CardNumber) To 1 Step -1
	8           Dim CurrentNumber
	9           CurrentNumber = Mid(CardNumber, i, 1)

	10          If OddNumbered = False Then
	                ' Double the digit.
	11              CurrentNumber = CurrentNumber * 2

	12              If CurrentNumber >= 10 Then
	                    ' If this number is two digits, add them together.
	                    ' This is the wacky part, because you need to use
	                    ' string conversion functions.
	13                  Dim NumText As String
	14                  NumText = CurrentNumber
	15                  CurrentNumber = Val(Left(NumText, 1)) +
	16                    Val(Right(NumText, 1))
	17              End If
	18          End If
	            ' Add the number to the running total.
	19          SumOfDigits = SumOfDigits + CurrentNumber

	            ' Switch from odd to even or even to odd.
	            ' This line of code changes True to False or
	            ' False to True
	20          OddNumbered = Not OddNumbered
	21      Next

	        ' If the sum is divisible by 10, it's a valid number.
	22      If SumOfDigits Mod 10 = 0 Then
	23          ValidateCard = True
	24      Else
	25          ValidateCard = False
	26      End If

	27 End Function

Here’s how it works:

  • Line 1 declares the function. Notice that the function takes one parameter, which is the text with the credit card number. This parameter’s explicitly identified as a string with the As String clause. This way, you avoid errors where someone might try to pass in an actual number.

  • Lines 2–3 create the variable that stores the running total during the whole process.

  • Lines 4–5 create the variable that keeps track of whether you’re on an odd number position or an even number position. Remember, all second numbers must be doubled.

  • Lines 6–7 start a For/Next loop. This loop looks a little different from the ones you saw earlier, because it has the Step -1 clause at the end. This clause tells the loop to subtract 1 from the counter after every pass (rather than adding 1, which is the standard behavior). You can work your way from the end of the number to the front.

Note

The For/Next loop uses another trick. The lower limit’s set using the Len function (Section 7.2.5), which gets the length of a piece of text. In other words, if the credit card is 11 digits, this code runs 11 times (once for each digit).

  • Lines 8–9 grab the number at the current position, as indicated by the loop counter. The Mid function lets you snip out a single digit. (You learned about the Mid function in Section 7.2.5.)

  • Line 10 checks if you’re on a second number.

  • Lines 11–17 run only if you’re on a second number. In this case, the number needs to be doubled (line 11). If the doubled number has two digits, these digits must then be combined (lines 13–15).

  • Line 19 adds the current number to the running total. If you were in an even-numbered position, then the number hasn’t been changed. If you were in an odd-numbered position, then it’s been doubled and combined.

  • Line 20 makes sure that if you just processed an even-numbered position, you’re switched to an odd-numbered position (and vice versa).

  • Line 21 returns to line 6 and repeats the loop for the next digit in the credit card number.

  • Lines 22–26 check the final total. If it’s divisible by 10, it’s valid. To find out, this code uses the Mod operator, which performs division, and then gets the remainder. (If there’s no remainder when you divide a number by 10, you know it divided evenly without a problem.)

It may take some time to work through the function and figure out exactly what’s going on, but in the end it’s all about VB basics like conditions, loops, and variables. If you really want to study this example, you can watch it in action, using the debugging techniques that are covered in Section 17.2.1.

Once you’ve finished creating a function like ValidateCard, you can call it to test whether a credit card checks out. Here’s an example that reacts when credit card information’s entered into a text box named CardNumber:

	Private Sub CardNumber_BeforeUpdate(Cancel As Integer)

	    If ValidateCard(CardNumber) Then
	        MsgBox "Your card is valid."
	    Else
	        MsgBox "Your card is invalid. " & _
	          "Did you forget a number, or are you trying to cheat us?"
	        Cancel = True
	    End If

	End Sub

To try it out, run this code and supply one of your credit card numbers in the CardNumber field, as shown in Figure 17-3. Or send your credit card numbers to the author so he can conduct his own extensive testing.

Dealing with Trouble

It would be nice to pretend that Access always sails through your code without the slightest hiccup. But the truth is, errors do occur, and they occur often. This fact shouldn’t frighten you. After all, one of the reasons you’re using Visual Basic code instead of ordinary macros is so that you can detect and respond to errors gracefully.

This form shows the ValidateCard function at work on the AddCreditCard form in the Boutique Fudge database. Whenever the CardNumber field’s changed, a subroutine checks if it’s valid, and cancels the change if it isn’t.

Figure 17-3. This form shows the ValidateCard function at work on the AddCreditCard form in the Boutique Fudge database. Whenever the CardNumber field’s changed, a subroutine checks if it’s valid, and cancels the change if it isn’t.

You’ll face two types of errors with your code:

  • Mistakes. These errors are coding errors that you introduce accidentally. Usually, you catch these while testing your database. (If you’re lucky, the Visual Basic editor spots the problem as soon as you type it in, and then warns you with a message.)

  • Unexpected limitations. These errors arise under specific circumstances that you may not have anticipated. Say you create two forms: Order, and Order_ Subform. Order_Subform’s designed to be used as a subform in Order, and it includes code that accesses the controls in Order. However, if someone opens Order_Subform directly, the Order form isn’t available, and this code fails.

As a conscientious programmer, it’s your job to correct all your mistakes and deal with unforeseen limitations in the best possible way. Visual Basic gives you two tools to help out. You can use debugging to diagnose bizarre problems and fix them, and you can use error handling code to catch unexpected problems, and alert other people.

Debugging

Debugging’s a nifty feature that lets you walk through your code, watch what it does, and spot errors. Code debugging’s similar to macro debugging (Section 15.1.3) in that it lets you run your logic one statement at a time. However, code debugging’s much more powerful, because it lets you make your way through complex routines, loops, and conditional statements. It even lets you see what’s currently stored in your variables.

Tip

Debugging’s real benefit is that it helps you test your assumptions. Every programmer has assumptions about how a piece of code works. However, if code did exactly what you expected, you wouldn’t ever have an error. With debugging, you can find the exact point where code does something that you don’t expect—when a calculation provides a strange result, a conditional statement sends you the wrong way, a loop’s repeated one time too many, and so on. Then you can correct the mistake.

The easiest way to perform debugging’s to set a breakpoint—a special marker that tells Access where you want to start debugging. When Access reaches a line of code that has a breakpoint, it pauses your code. Access then lets you step through the code at your own pace, one line at a time.

Here’s how to use a breakpoint:

  1. Find the first line in your code that you want to debug.

    If you want to debug an entire routine, start with the opening Sub or Function statement. If you want to look at a specific section of your code, go there.

  2. Click the margin on the left to place a breakpoint on this line (Figure 17-4).

    Each breakpoint’s a signal that tells Access you want to start debugging here.

    Some lines can’t accommodate a breakpoint. These lines don’t contain executable code, line blank spaces, comments, and variable declarations. Everything else is fair game.

    Every breakpoint looks like a red circle. You can remove a breakpoint by clicking it. In this example, the breakpoint (circled) is placed at the beginning of the ValidateCard function.

    Figure 17-4. Every breakpoint looks like a red circle. You can remove a breakpoint by clicking it. In this example, the breakpoint (circled) is placed at the beginning of the ValidateCard function.

    Note

    When you close your database and open it later, all your breakpoints disappear.

  3. Trigger your code.

    You can get your code to run in the normal way. If you’re debugging an event handler for a button click, open the appropriate form, and then click the button.

    When Access reaches your breakpoint, it pauses and switches into break mode. Everything in your application’s put on hold.

Once you’re in break mode, you have several options:

  • You can single-step through your code. That means you run one statement at a time, pausing after each statement. To try this out, press the F8 key. This action runs the current statement (which is highlighted with the yellow arrow), moves to the next executable statement, and then pauses again (Figure 17-5). You can continue for as long as you want, pressing F8 to run each line of code.

    Tip

    Single-step debugging lets you follow how your code works. If you try it with the ValidateCard function shown earlier, you’ll see how Access moves through the loop several times, and how it branches into different conditional sections depending on whether it’s processing a number in an odd or even position.

    In this example, the breakpoint stopped the code at the beginning of the ValidationCard function. Then, the person debugging this code hit F8 a few times to move on through the code. Right now, the code’s paused at the beginning of the For/Next loop (circled).

    Figure 17-5. In this example, the breakpoint stopped the code at the beginning of the ValidationCard function. Then, the person debugging this code hit F8 a few times to move on through the code. Right now, the code’s paused at the beginning of the For/Next loop (circled).

  • You can stop running your code. Press the Stop button (it looks like a square) in the Visual Basic toolbar to shut your code down.

  • You can make changes. If you find what’s wrong, you can edit your code, and then keep running with the new changes. Of course, there are certain types of edits that force Access to stop debugging. If you make one of these changes, then you see a Message box that warns you that “This action will reset your project.” If you click OK, then Access stops your code just as if you had clicked the Visual Basic toolbar’s Stop button.

    You can see what’s stored inside a variable. To do so, just hover over the variable name somewhere in your code (Figure 17-6).

    By hovering over the CurrentNumber variable, you can see that it’s currently storing the number 4. You can hover over variables on any line in your code, not just the current line. However, you’ll see only the current contents of the variable. If you use F8 to single-step through your code, then you can watch a value change as you perform operations.

    Figure 17-6. By hovering over the CurrentNumber variable, you can see that it’s currently storing the number 4. You can hover over variables on any line in your code, not just the current line. However, you’ll see only the current contents of the variable. If you use F8 to single-step through your code, then you can watch a value change as you perform operations.

  • You can resume normal execution. If you’ve found the source of your problem and you don’t want to keep debugging, just hit F5 (or click the Visual Basic toolbar’s Play button). Access runs the current line, and then continues on its merry way (at least until it meets another breakpoint).

Tip

You can pull off a wacky trick with the yellow arrow. You can use it to run code in a completely different place. Just drag the yellow arrow to the line you want to run next, and then hit F5 to switch out of debug mode, and resume running your code normally.

The Visual Basic editor has many more debugging tools. However, breakpoints are really all you need to start exploring what’s taking place under the hood when you run your code.

Error Handling

Some errors occur through no fault of your own. Perhaps you’re trying to perform a task with information someone else gave you, and that information just isn’t valid. Imagine what happens if someone calls ValidateCard and passes in a credit card number that contains letters and punctuation!

Although this sort of error can occur as a result of somebody else’s carelessness, it’s up to you to deal with it in the best way possible. You need to explain the problem with a helpful Message box, and end the current task (or jump ahead to the next step). You can take care of this job with error handling code.

Tip

The best way to perfect a piece of code’s to use debugging to find and fix all its problems. Once you’ve finished this process, you can add error handling code to deal with the unexpected problems. If you add error handling code earlier, then you may find it a bit harder to debug your application.

Ordinarily, when Access encounters an error, it jumps to the offending code, enters break mode, and shows you an error message. This behavior’s helpful if you’re planning to debug the problem, but it’s a bit traumatic for the ordinary people who may be using your database. Not only have they never seen code before, they’re in danger of changing it and introducing a new problem.

Instead, you need a way to deal with the error the way you want, using code. Visual Basic has a special statement that tells Access how to deal with errors. It’s the On Error statement.

The On Error gives you several options. You can tell Access to skip over any errors and try to run the next line of code like this:

	On Error Resume Next

This option’s almost always a bad idea. If one error’s occurred, more are likely to follow. At worst, this could cause your program to do something you don’t intend it to do.

You can also tell Access to jump to a specific place in your code. Here’s an example:

	On Error Goto ErrorHandlingCode

This example tells Access to jump to the section named ErrorHandlingCode as soon as it encounters any problem. You need to identify this section by adding the section name, followed by a colon (:) on a separate line, like this:

	ErrorHandlingCode:
	    ' If an error occurs, Access starts running your code here.

You can most easily understand how this error handling system works when you consider how you can use it in the ValidateCard function:

	Function ValidateCard(CardNumber As String)

	    On Error Goto ErrorHandlingCode

	    ' (The code for Luhn's algorithm goes here.)

	    Exit Function

	ErrorHandlingCode:
	    MsgBox "Oops. Did your credit card number have letters?"
	    ValidateCard = False

	End Function

Here are several important details. First, the On Error statement’s placed at the very beginning of the code routine, so you can catch mistakes in any of the code that follows. Second, notice that after the number-checking code finishes, an Exit Function statement ends the routine. That statement prevents Access from drifting into the error handling code that follows if an error hasn’t happened. Finally, the error handling code shows a Message box that explains that something went wrong, and returns a result that clearly indicates the problem. People most often handle errors this way. Just remember to always use an Exit Sub or Exit Function statement to make sure you don’t run your error handling code by accident.

Note

As written, the person using the AddCreditCard form may get two error messages—one explaining the letters-or-punctuation problem, and the second stating the obvious fact that validation failed. If this message seems like unnecessary punishment, then you can move the error-handling code out of the ValidateCard function and into the On Update event handler code, which is where it really belongs. That way, the On Update event handler can choose exactly how to deal with the problem. To see the slightly rear-ranged code, check out the downloadable samples for this chapter.

You have only one other option for handling errors. You can tell Access to stop immediately and enter debug mode using this statement:

	On Error Goto 0

Of course, this behavior’s already the standard error handling behavior. You need to use this statement only if you’re switching back and forth between different error handling approaches in the same routine.

Deeper into Objects

There comes a point in every Access programmer’s life when you realize you’ve learned enough about the VB language to get by. From that point on, you spend most of your time learning about different objects, which is a much larger task.

Access has several dozen built-in objects which, taken together, make up what programmers call an object model. Along with the control and form objects you know so well, it has objects representing queries, projects, reports, smart tags, printers, and much more. You can’t cover all these objects in a single chapter. Even if you could, you’d find that many of them just don’t interest you. However, you need to know enough so that you can hunt down the features you need when you’re tackling a particularly sticky VB challenge.

You can explore the Access object model in several ways:

Even if you work your way through the sprawling Access object model, there are still many more objects out there. If you’re a black belt VB programmer, then you may choose to create your own objects. And even if you aren’t, you may decide to use another component that gives you even more objects to play with.

Note

In programmer-speak, a component’s just a file that has some objects you can use in your code. The file acedao.dll has the objects you can use to interact directly with your database (see Section 17.4.5).

Later in this chapter, you’ll learn how to use DAO (the data access objects) to interact with your database. DAO’s such a common part of Access programming that most people treat it as a built-in part of the Access object model. But technically, DAO consists of a set of objects provided by a separate component, which Access provides. Many more components are waiting for you to discover them.

To use a new component, you need to add a reference to that component in your database. To do so, in the Visual Basic editor’s menu, choose Tools → References. You’ll see the References dialog box shown in Figure 17-7.

To add a reference to a component you want to use, find it in the list, and then place a checkmark next to it. The currently referenced components appear at the top of the list. Here, you can see the objects that are automatically referenced in every database—the objects built in Visual Basic, those that come with Access, and the data access objects you can use to read and edit the database directly (Section 17.4.5).

Figure 17-7. To add a reference to a component you want to use, find it in the list, and then place a checkmark next to it. The currently referenced components appear at the top of the list. Here, you can see the objects that are automatically referenced in every database—the objects built in Visual Basic, those that come with Access, and the data access objects you can use to read and edit the database directly (Section 17.4.5).

The References dialog box’s problem is that you need to know exactly what component you want to use. The Available References list’s full of neat-sounding components that aren’t designed for use in Access, and won’t work right with your code. Among the components you can use are ones from Microsoft that let you interact with files, show Web pages, and interact with other Office applications. However, you won’t get far experimenting on your own. Instead, you’ll need to find sample code online or in the Access Help.

The DoCmd Object

The DoCmd object is the single most useful object in the Access programming world. It provides one-stop shopping for a variety of tasks, like opening forms and reports, launching other programs, finding records, and running macros.

Unlike the objects you’ve seen so far, the DoCmd object doesn’t have any properties. Instead, it’s made up of methods that perform different actions. If you want to open a form named ProductCatalog, you can use the OpenForm method like this:

	DoCmd.OpenForm "ProductCatalog"

Like most of the DoCmd methods, OpenForm can use several optional parameters. Visual Basic prompts you by showing the list of possible parameters as you type. Here’s an example that skips over the second and third parameters (note the commas with no values in between) but supplies a filter in the fourth parameter, and a data mode in the fifth parameter.

	DoCmd.OpenForm "ProductCatalog", , ," ID=5", acFormReadOnly

This command opens the ProductCatalog form, applies a filter to show only the record with the ID of 5, and uses read-only mode to prevent any changes.

Note

This example uses a acFormReadOnly, which is a constant. Constants are numeric values that are given more helpful names. So instead of remembering that the number represents read-only mode, you can use the more readable acFormReadOnly constant. Any time you see a variable that starts with ac or vb and you haven’t created it yourself, the odds are that it’s a constant. Of course, you still need to know the constant names to use them, but IntelliSense can help you out, as shown in Figure 17-8.

When you get to the data mode parameter, the Visual Basic editor pops up a list of all the valid constants that you can use. To find out what they really mean (if it’s not obvious), you need to consult the Access Help.

Figure 17-8. When you get to the data mode parameter, the Visual Basic editor pops up a list of all the valid constants that you can use. To find out what they really mean (if it’s not obvious), you need to consult the Access Help.

If the OpenForm method looks familiar, that’s because you’ve already seen the same functions with the OpenForm macro action (Section 15.3.1). In fact, all the methods of the DoCmd object line up with the macro actions you learned about in Chapter 15. Table 17-1 lists the most useful ones.

Table 17-1. Useful Methods of the DoCmd Object

Method

Description

ApplyFilter

Applies a filter to a table, form, query, or report, to focus on the records you’re interested in.

Beep

Makes some noise. Usually, you use this to get attention if a problem occurs.

Close

Closes the current database object (or a specific one you indicate).

CopyDatabaseFile

Gives you a quick way to make a database backup.

FindRecord, FindNext, and GoToRecord

Gives you different ways to search for the record you want.

Hourglass

Switches the hourglass mouse pointer on (or off). You can use this method to let someone know there’s a time-consuming task underway, and she should chill.

OpenForm, OpenQuery, OpenReport, and OpenTable

Opens the appropriate database object, in whatever view mode you want, with filter settings and other optional details. As you learned in Chapter 15, you can also use OpenReport to print a report, and OpenQuery to run an action query.

PrintOut

Offers one way to print the data from the current database object.

Quit

Exits Access.

RunCommand

A fill-in-the-gaps command that lets you run various Access commands available on the ribbon. You just need to supply the right constant. Section 17.4.4 shows an example where someone uses Run-Command to save the current record immediately.

RunMacro

Runs a macro.

RunSQL

Executes a raw SQL statement (see Section 6.2.3). You can’t use this command to get information out of your database. Instead, it lets you run commands that change records or tables.

ShowAllRecords

Removes the current filter settings so you can see all the records in a table, form, query, or report.

Converting a Macro to VB Code

If you want to learn a little more about Visual Basic and the DoCmd object, then you can take an existing macro and convert it into a pure code subroutine. Here’s how:

  1. In the navigation pane, select the macro you want to use.

  2. Select Database Tools → Macro → Convert Macros to Visual Basic. (You can also convert the embedded macros in a form by opening that form, and then choosing Database Tools → Macro → Convert Form’s Macros to Visual Basic.)

    A window with two options appears (Figure 17-9).

    You see this tiny window if you ask Access to convert the FindHayEater macro.

    Figure 17-9. You see this tiny window if you ask Access to convert the FindHayEater macro.

  3. If you want to add basic error handling (as described in Section 17.2.2), then make sure “Add error handling to generated functions” is turned on.

    A little bit of error handling’s always a good idea.

  4. If you want to turn your macro comments into VB comments, make sure “Include macro comments” is selected.

    If you’ve taken the time to add some explanatory text, it’s worth keeping it around.

  5. Click Convert.

    Access creates a new module for the converted code, and gives it a name like Converted Macro-[YourMacroName]. Inside the module, Access creates a function with the same name as your macro. If you convert a macro group (Section 15.4), Access adds one subroutine for each macro in the group.

    Once the conversion process is complete, Access opens your module in the Visual Basic editor so you can review the code.

The following example shows the result of converting a macro from Chapter 15 (shown in Section 15.3.1) which searches for specific text in the AnimalTypes table:

	Function FindHayEater( )

	On Error GoTo FindHayEater_Err

	    DoCmd.OpenForm "AnimalTypes", acNormal, "", "", , acNormal
	    DoCmd.GoToControl "Diet"
	    DoCmd.FindRecord "=""hay""", acAnywhere, False, , _
	      False, acCurrent, False

	FindHayEater_Exit:
	   Exit Function

	FindHayEater_Err:
	    MsgBox Error$
	    Resume FindHayEater_Exit

	End Function

You’ll notice that the converted code makes heavy use of the DoCmd object—in fact, almost every line uses the DoCmd object. First, it uses the OpenForm method to open a form, then it uses the GoToControl method to switch to the Diet field, and finally it looks for the first record that has the text “hay.” This line looks a little weird because it doubles up its quotations marks (“”). Quotations marks have a special meaning to Visual Basic (they show where text begins and ends). If you actually want to insert a quotation mark in your text, you need to put two quotation mark characters in a row. Strange, but true.

The code ends with an error-handling routine named FindHayEater_Err, which simply shows the problem in a Message box, and then ends the routine.

Note

When you convert a macro into code, Access always generates a function (Section 17.1.4), not a sub-routine. However, the function doesn’t return a result, so it’s not really necessary. (Presumably, Access works this way to give you the flexibility to decide later on that you want to return a value.)

Using VB to Run a Better Business

Over the last 16 chapters, you’ve come to know and love the Boutique Fudge data-base, which demonstrates a practical sales database that tracks customers, products, and orders. However, although the Boutique Fudge database stores all the information you need, it still doesn’t integrate seamlessly into company life. And before you can fix it, you need to understand why it comes up short.

Most people who work in a business like Boutique Fudge aren’t thinking about tables and data operations (like inserting, updating, and deleting records). Instead, they’re thinking about tasks, like placing an order, shipping an order, and handling a customer complaint.

Many tasks match quite closely with a data operation, in which case you really don’t have a problem. The “register a new customer” task’s clearly just a matter of opening the Customers table, and then inserting a new record. You can take care of it with a simple form. However, the “place an order” task’s a little trickier. This task involves inserting records in more than one table (the Orders and Order-Details tables), and using data from related tables (the Products and Customers tables) to complete the order. You can create an ordinary form to do the job, but the form doesn’t quite work the way salespeople want (see Figure 17-10).

The same’s true when it comes to the “ship an order” task. This task requires several steps—changing the status of the order, logging the shipment, and updating the units-in-stock numbers. You could deal with this task as several separate data operations, but it’s a lot nicer if you create a single form that takes care of the whole process.

Now VB’s truly useful. With the right code, you can design an intelligent form that fits the way people work. An intelligent form isn’t just a way to add, edit, and insert records in a table—it’s a tool that helps run your business.

This form lets you insert records into the Orders and OrderDetails tables. However, it lacks a few frills people expect in an order form—like a way to automatically fill in the price of each product you’re ordering, the ability to calculate totals as you go, and an option to add a new product on the fly.

Figure 17-10. This form lets you insert records into the Orders and OrderDetails tables. However, it lacks a few frills people expect in an order form—like a way to automatically fill in the price of each product you’re ordering, the ability to calculate totals as you go, and an option to add a new product on the fly.

In the following sections, you’ll consider how to build better forms with some code-powered features. The following forms are covered:

  • PlaceOrder lets you create a new order. It works in conjunction with the PlaceOrder_Subform, which lets you add individual items to the order.

  • AddProduct lets you create a new product. You can use it directly from Place-Order form to add a product in mid-order.

  • ShipOrders lets you update an order with shipment information. It also works with a form named ReviewOrderDetails to show the items in the order.

You can check out the final result with the downloadable databases for this chapter (Section 3.4.2.3).

Tip

It’s always a good idea to name your form according to the task it performs (placing an order, shipping a task, and so on), rather than the table it uses. This design helps you remember who’s using each form, so you can tailor it to the right audience.

Keeping a Running Total

Few souls are brave enough to place an order without knowing exactly how much it costs. A typical order form shows how much each line item costs (by multiplying the price and quantity information) and the ever-important grand total (Figure 17-11).

The PlaceOrder form, with subtotals and a grand total.

Figure 17-11. The PlaceOrder form, with subtotals and a grand total.

Note

The PlaceOrder form also introduces a few refinements you already know about, like putting customer address information on a separate tab, moving the automatically generated fields (the order ID and the order date) to the bottom of the window where they won’t distract anyone, and setting their Locked property (Section 13.2.2) to Yes to prevent changes. The form also has its Data Entry property (Section 12.3.6) set to Yes, so you start creating a new order as soon as you open the form.

The line total’s the only detail that doesn’t require code. In fact, you can solve this problem by adding a text box that uses the following expression to the PlaceOrder_Subform:

	=Quantity * Price

This expression works because the information you need (Price and Quantity) is located on the same form as the calculate field. However, the grand total isn’t as straightforward.

To be even a bit nicer, you can combine this with the Format function (Section 7.2.5) to make sure you get the right number of decimal places and a currency symbol ($):

	=Format(Quantity * Price, "Currency")

In order to calculate the grand total, you need to use the Quantity and Price information in the OrderDetails table. Unfortunately, the PlaceOrder form doesn’t have any easy way to get this information. Not only is this information shown somewhere else (in a subform), but it also involves several separate records. Even if you retrieve the Quantity and Price information from the subform, you can get only the values for the current record, not for the whole list of ordered items.

To solve this problem, you need to use a specialized Access function called a domain function. A domain function can process an entire table and return a single piece of information. (To learn more, see the box below.)

To calculate the total of all the items in an order, you use the DSum function. The information you need’s in the OrderDetails table, but you want to select only those records where the OrderID field matches the current order. Finally, you need to add together the cost of each item. And as you know from before, you calculate the cost of a line by multiplying together the Price and Quantity fields.

With this information in mind, you can create the following calculated field:

	=DSum("Price*Quantity","OrderDetails","OrderID=" & [ID])

The first argument’s the calculated field that you’re taking from each record. The second argument’s the name of the table you’re using. The third argument filters out just those records that match the current order. If the current order has an ID of 455, then the final parameter matches all OrderDetails records where OrderID=455. Once again, you can wrap the whole thing with the Format function if you want the final number to look like a currency value.

This calculated field does the trick, but you need one more enhancement. Ordinarily, Access computes calculated fields the first time a record’s shown. However, you need to make sure that the grand total’s recalculated every time you make a change in the list of ordered items. To accomplish this, you need to call the Form. Recalc method when an OrderDetails record’s inserted, updated, or deleted. Here’s the code that does the trick:

	Private Sub Form_AfterInsert( )
	    Forms("PlaceOrder").Recalc
	End Sub

	Private Sub Form_AfterUpdate( )
	    Forms("PlaceOrder").Recalc
	End Sub

	Private Sub Form_AfterDelConfirm(Status As Integer)
	    Forms("PlaceOrder").Recalc
	End Sub

Now you can create and fill out an order, without wondering how much it’s going to cost.

Getting Price Information

As you learned in Chapter 5, sometimes a table needs to store point-in-time data—information that’s copied from one table to another because it might change over time. A good example is product prices, which evolve over time. (“Evolve” is a polite way to say, “increase relentlessly.”) So a product’s current isn’t necessarily the price at which you ordered it last week. In order to keep track of how much you owe the company, the selling price of a product needs to be stored in the OrderDetails table.

However, this system creates a headache when you fill out an order. Choosing an order item’s easy enough—you just need to select the product from a lookup list. However, the lookup list sets the ProductID field only for the OrderDetails record. It’s up to you to figure out the correct price, and then copy it from the Products table to your new record.

Fortunately, you can make this much easier. You can react to the On Change event in the ProductID list, which is triggered every time a product’s selected. Then, you can use the DLookup domain function to find the corresponding price, and insert it in the Price field automatically. Here’s the code that does it:

	Private Sub ProductID_Change( )
	Price = DLookup("Price", "Products", "ID=" & ProductID)
	Quantity = 1
	End Sub

This code also sets the Quantity field to 1, which is a reasonable starting point. If necessary, you can edit the Price and Quantity fields after you pick your product. Or, to create a more stringent form, you can set the Locked property of the Price control to Yes, so that no price changes are allowed (as in the Boutique Fudge database). This way, when you create an order, you’re forced to use the price that’s currently in affect, with no discounting allowed.

Note

You can use the same technique to fill in other point-in-time data. You can grab the address information for the current customer, and use that as a starting point for the shipping address. And you can even use the DLookup function to create more sophisticated validation routines. You could use this technique with the Cacophoné Music School database, to look up prerequisites and maximum class sizes before letting a student enroll in a class.

Adding a New Product During an Order

Boutique Fudge is a customer-driven company. If someone wants an innovative product that’s not yet in the product catalog (like fudge-dunked potatoes), the company’s willing to create it on demand.

Ordinarily, the ProductID lookup list doesn’t allow this sort of on-the-fly product creation. If you try to type in a product that doesn’t exist, then you get a stern reprimand from Access. However, adding new list items on the go is a common Access programming technique, and dedicated event’s designed to help you out: the On Not In List event.

If you type in a product that doesn’t exist and you’re using the On Not In List event, then Access starts by running your event handling code. You can create the item if you want, show a different message, or correct the problem before Access complains.

The On Not In List event has two parameters: NewData and Response. NewData is the information that was typed into the list box, which isn’t found in the list. Response is a value you supply to tell Access how to deal with the problem.

Here’s the basic skeleton of the subroutine that Access creates if you choose to handle the On Not In List event for the field named ProductID:

	Private Sub ProductID_NotInList(NewData As String, Response As Integer)
	End Sub

When the On Not In List event occurs, you should first ask the person using the form if they meant to enter a product that doesn’t exist. You can take this step using the familiar MsgBox function in a slightly different way. First, you need to add a second parameter that tells Access to create a Message box with Yes and No buttons. Then, you need to get hold of the return value from the MsgBox function to find out which button was clicked:

	Dim ButtonClicked
	ButtonClicked = MsgBox("Do you want to add a new product?", vbYesNo)

This code creates a variable named ButtonClicked, and then shows the message. When the person closes the Message box (by clicking Yes or No), Visual Basic puts a number into the ButtonClicked variable that tells you what happened. The number’s 6 if Yes was clicked, or 7 if No was clicked. But rather than deal directly with these numbers and risk making a mistake, you can use the helpful constants vbYes (which equals 6) and vbNo (which equals 7).

Here’s the partially completed code for the On Not In List event handler. It shows the message asking if a new item should be added (Figure 17-12), and then cancels the edit if the person using the form chooses No:

	Private Sub ProductID_NotInList(NewData As String, Response As Integer)

	    ' Show a Yes/No message and get the result.
	    Dim ButtonClicked
	    ButtonClicked = MsgBox("Do you want to add a new product for " & _
	      NewData & "?", vbYesNo)

	    ' Visual Basic gives you hand vbYes and vbNo constants
	    ' that you can use to find out what button was clicked.
	    If ButtonClicked = vbNo Then

	        ' Cancel the edit.
	        ProductID.Undo

	        ' Tell Access not to show the error message.
	        ' You've already dealt with it.
	        Response = acDataErrContinue

	    Else
	       ' (Put some code here to add a new product.)
	    End If

	End Sub

Then you supply the code that adds the new product. In this example, it doesn’t make sense for your code to add the product completely on its own—after all, a product needs other information (like price and category details) before it’s considered valid. Instead, you need to show another form for adding products. The DoCmd.OpenForm method’s the key:

	' Tell Access not to worry, because you're adding the missing item.
	Response = acDataErrAdded

	' Open the AddProduct form, with three additional arguments.
	DoCmd.OpenForm "AddProduct", , , , , acDialog, NewData
Fudge-Dunked Potatoes is not a currently offered product. When you type it in, and then hit Enter, your code asks whether you really intend to add this product.

Figure 17-12. Fudge-Dunked Potatoes is not a currently offered product. When you type it in, and then hit Enter, your code asks whether you really intend to add this product.

The two additional arguments you use with the OpenForm method are quite important:

  • acDialog opens the form in dialog mode, which means Access puts the code in the ProductID_NotInList on hold until the AddProduct form’s closed. That step’s important because once the adding process is finished, you’ll need to run more code to update the PlaceOrder form.

  • NewData takes the newly typed-in information, and sets it in the AddProduct. OpenArgs property. That way, the AddProduct form can retrieve this information when it starts up, and then adjust itself accordingly.

Here’s the code you need in the AddProduct form to copy the newly entered product name (the value you passed using the NewData variable in the previous code snippet) into the ProductName field when AddProduct first loads:

	Private Sub Form_Open(Cancel As Integer)
	    ProductName = Form.OpenArgs
	End Sub

Figure 17-13 shows what this form looks like.

Once you finish entering all the product information, you can close the Add-Product form. At that point, a little more code runs in the ProductID_NotInList subroutine. This code’s placed immediately after the DoCmd.OpenForm statement. Its job is to update the new order item to use the product you’ve just entered:

The AddProduct form lets you supply the rest of the information for the new product you want to create. Notice how the form opens as a pop-up form, and Access automatically assumes you’re inserting a new record (not reviewing existing products). Access acts this way because the Pop Up and Data Entry properties of the form are both set to Yes.

Figure 17-13. The AddProduct form lets you supply the rest of the information for the new product you want to create. Notice how the form opens as a pop-up form, and Access automatically assumes you’re inserting a new record (not reviewing existing products). Access acts this way because the Pop Up and Data Entry properties of the form are both set to Yes.

	' Cancel the edit. That's because you need to refresh the list
	' before you can select the new product.
	ProductID.Undo

	' Refresh the list.
	ProductID.Requery

	' Now find the ProductID for the newly added item using DLookup.
	ProductID = DLookup("ID", "Products", "ProductName='" & NewData & "'")

Note

This code works even if you cancel the new product by hitting the AddProduct form’s Esc key. In this case, the DLookup function can’t find anything, so it returns a null (empty value) to the ProductID field. As a result, you get the familiar Access warning message telling you the product you picked isn’t in the list.

There’s one more detail. By the time the On Not In List event occurs, the On Change event’s already taken place. So you just missed your chance to run the code you used earlier to insert the corresponding price into the Price field in the list of order items.

Fortunately, you can solve this problem quite easily. You just need to add one more line of code that tells Access to go ahead and run the event handler (the ProductID_Change subroutine) again:

	ProductID_Change

To see the complete code for this example in one place, refer to the sample Boutique Fudge database for this chapter.

Managing Order Fulfillment

Now that you’ve perfected the ordering process, you can turn your attention to what happens next.

In the Boutique Fudge database, every record in the Orders table has an Order-Status field that keeps track of the, well, status. Newly created order records have a New status. In the stock room, the warehouse workers look for orders with the New status, and pick one to start working on. At that point, they change the status of this order to In Progress, so nobody else tries to ship it at the same time. Finally, when the order’s complete, they change it to Shipped, and then record the exact time in the ShipDate field.

Logically, this model makes sense. However, it’s a bit tricky using ordinary tables and forms. In order to follow this workflow, the warehouse staff needs to modify the status of an order record several times, remember to record the ship date, and avoid changing other details. If they miss a step—say they never put the order into In Progress status—it’s possible that more than one employee could try to complete the same order.

The solution’s to create a ShipOrders form that guides the warehouse workers through the right steps. Initially, this form shows a list of orders with minimal information (Figure 17-14).

The list of orders is sorted so that the oldest orders (which should be processed first) appear at the top of the list. Each field’s Locked property’s set to Yes, so nobody can edit any data. However, next to each order is a Process button that starts the order fulfillment process. (You could also add filtering to this form, so that you see only orders with certain statuses.)

Figure 17-14. The list of orders is sorted so that the oldest orders (which should be processed first) appear at the top of the list. Each field’s Locked property’s set to Yes, so nobody can edit any data. However, next to each order is a Process button that starts the order fulfillment process. (You could also add filtering to this form, so that you see only orders with certain statuses.)

When someone clicks the Process button, several steps need to take place. Here’s a step-by-step walk-through of the code, one chunk at a time.

First, your code needs to refresh the record. That step catches whether someone else has started processing the order on another computer:

	Private Sub ProcessOrder_Click()

	    Form.Refresh

Next, your code needs to check the record’s status. If it’s anything other than New, that order isn’t available for processing:

	' The StatusID for New is 2.
	If StatusID <> 2 Then
	    MsgBox "This order is not available."

Otherwise, you need to switch the status to In Progress and save the record right away, to make sure no else tries to get it:

	Else
	    ' The StatusID for In Progress is 3.
	    StatusID = 3

	    ' Save the change.
	    DoCmd.RunCommand acCmdSaveRecord

Note

It’s extremely important to save the record (using the DoCmd.RunCommand method, as shown here) in this sort of situation. Otherwise, the order record remains in edit mode, and the new status isn’t saved in the database. Other people might start processing it, because they have no way of knowing that you’ve changed the status.

Now it’s time to launch the ReviewOrderDetails form, which displays a read-only view of all the items in the order (Figure 17-15). The form’s opened in dialog mode, which locks up the ShipOrders form until the order fulfillment process is complete:

	        DoCmd.OpenForm "ReviewOrderDetails", , , _
	        "OrderID =" & ID, , acDialog
	    End If

	End Function

The ReviewOrderDetails form gives the warehouse staff two choices. If they click Ship, then Access changes the order status to Shipped, and the process is complete:

	Private Sub Ship_Click( )
	    ' Close this form.
	    DoCmd.Close
	    ' Switch back to the ShipOrders form.
	    DoCmd.OpenForm "ShipOrders"

	    ' Update the order.
	    ' The StatusID for Shipped is 4.
	    Forms("ShipOrders").StatusID = 4
	    DoCmd.RunCommand acCmdSaveRecord
	End Sub
You don’t need to include pricing details in the ReviewOrderDetails form. It’s simply designed to give the warehouse people the information they need as efficiently as possible. The ReviewOrderDetails form also uses a query join to get some related data, like the PartNumber, from the Products table.

Figure 17-15. You don’t need to include pricing details in the ReviewOrderDetails form. It’s simply designed to give the warehouse people the information they need as efficiently as possible. The ReviewOrderDetails form also uses a query join to get some related data, like the PartNumber, from the Products table.

In the ReviewOrderDetails form, the properties Control Box and Close Button are both set to No. That way, the window doesn’t give the warehouse staff any way to close it except to click the Ship or Cancel buttons. (If you don’t use this approach, then you need to write extra code that resets the order’s status if someone clicks the X icon in the top-right corner to close the ReviewOrderDetails form.)

Tip

This spot’s also a good place to use DoCmd.OpenReport to print out a report that creates a shipping insert with a list of all the products in the order.

But if they click Cancel (perhaps because they’ve discovered they don’t have the right items in stock), similar code’s used to return the order to New status:

	Private Sub Cancel_Click( )
	    ' Close this form.
	    DoCmd.Close

	    ' Switch back to the ShipOrders form.
	    DoCmd.OpenForm "ShipOrders"

	    ' Update the order.
	    Forms("ShipOrders").StatusID = 2
	    DoCmd.RunCommand acCmdSaveRecord
	End Sub

This part completes the code you need to coordinate order processing. Like the forms you learned about in Part Four of this book, the forms in this example draw all their information from your database’s tables. But unlike those Part Four examples, these use code to perform some of the work automatically. This difference changes your forms from mere data-entry tools into supercharged workflow tools.

Tip

You could also create a special status value to denote orders that have been attempted but couldn’t be completed (like On Hold or Waiting For Stock). That way, the warehouse employees would know not to keep trying the same orders. If you take this step, then make sure you modify the code in the ProcessOrder_Click subroutine, so people can process orders with this status.

Updating Stock Numbers

Thanks to the ever-so-smart ShipOrders form you saw in the previous section, business is running smoothly at Boutique Fudge. However, one day the warehouse employees come to senior management with a complaint. Although orders are sailing through without a hiccup, the product inventory information isn’t keeping up. No one remembers to adjust the UnitsInStock information, so it’s becoming increasingly useless.

A truly automated solution would automatically update the UnitsInStock information whenever an order ships. And after all, isn’t that what Access is designed to do best?

This challenge is entirely unlike the other problems you’ve solved so far, because it forces you to make a change in a completely different set of records—records that aren’t being displayed in any form.

You already know that you can use the domain functions (Section 17.4.1) to retrieve information from other tables. But unfortunately Access doesn’t have a similar set of functions that lets you make changes. Instead, you need to turn to a completely new set of objects, called the data access objects (or just DAO for short).

DAO lets you perform any data task you want, independent of your forms. However, DAO is a bit complex:

  • You need to use DAO in a very specific way. If you use the methods in the wrong order or leave out a step, then you run into an error. Often, it’s easiest to start with an example that works (like the sample code included with this chapter), copy it, and then alter it as needed.

  • DAO doesn’t use query objects. Instead, it forces you to write SQL statements. You learned how to write a basic SQL command in Chapter 6 (Section 6.2.3).

DAO involves two essential techniques. First, there’s the CurrentDb.Excecute method, which lets you run a direct SQL command by supplying it in a string:

	CurrentDb.Execute MyUpdateCommand

This method’s a quick and dirty way to make database changes, like sweeping update, delete, or insert operations.

The second essential technique’s to retrieve records using a specialized object called the Recordset. To use a Recordset, you must begin by using the CurrentDb. OpenRecordset method, and supplying a string with an SQL select command:

	Dim Recordset
	Set Recordset = CurrentDb.OpenRecordset(MySelectCommand)

The Recordset represents a group of records, but it lets you access only one at a time. To move from one record to the next, you use the Recordset.MoveNext method. To check if you’ve reached the end, you examine the Recordset.EOF property, which stands for end-of-file. When this property’s True, you’ve passed the last record.

You most often use a Recordset in a loop. You can use Recordset.EOF as the loop condition, so that the loop ends as soon as Access reaches the end of the Record-set. Inside the loop, you can retrieve field values for the current record. At the end of each pass, you must call MoveNext to move on:

	Do While Recordset.EOF = False

	    ' Display the value of the ProductName field.
	    MsgBox Recordset("ProductName")

	    ' Move to the next record.
	    Recordset.MoveNext

	Loop

With these bare essentials in mind, you can make your way through the following code, which adjust the product stock values based on a recently shipped order. (A line-by-line analysis follows the code.)

	1 Sub UpdateStock( )

	      ' If an error occurs, jump down to the DataAccessError section.
	2     On Error GoTo DataAccessError

	      ' Create a SELECT command.
	3     Dim Query
	4 Query = "SELECT ProductID, Quantity FROM OrderDetails WHERE OrderID=" & ID

	      ' Get a recordset using this command.
	5     Dim Recordset
	6     Set Recordset = CurrentDb.OpenRecordset(Query)

	      ' Move through the recordset, looking at each record.
	      ' Each record is a separate item in the order.
	7     Do Until Recordset.EOF

	          ' For each item, get the product ID and quantity details.
	8         Dim ProductID, Quantity
	9         ProductID = Recordset("ProductID")
	10        Quantity = Recordset("Quantity")

	          ' Using this information, create an UPDATE command that
	          ' changes the stock levels.
	11        Dim UpdateCommand
	12     UpdateCommand = "UPDATE Products SET UnitsInStock = UnitsInStock-" & _
	13          Quantity & " WHERE ID=" & ProductID

	          ' Run the command.
	14        CurrentDb.Execute UpdateCommand

	          ' Move to the next order item (if there is one).
	15        Recordset.MoveNext

	16     Loop

	       ' Time to clean up.
	17     Recordset.Close
	18     CurrentDb.Close

	19     Exit Sub

	20 DataAccessError:

	       ' You only get here if an error occured.
	       ' Show the error.
	21     MsgBox Err.Description

	22 End Sub

Here’s what takes place:

  • Line 1 declares a new subroutine. Because this code’s fairly complex, it makes sense to put it in a separate subroutine, which you can call when the Ship button’s clicked and the order’s shipped.

  • Line 2 tells Access to head down to the end of the subroutine if an error takes place. Errors are always possible with data access code, so it’s good to be on guard.

  • Lines 3–4 create the SQL command you need to select the OrderDetails records for the current order. (See Section 6.2.3.1 for more about SQL SELECT commands.)

  • Lines 5–6 execute that command, and get all the matching records in a Recordset.

  • Line 7 begins a loop that moves through the entire Recordset.

  • Lines 8–10 get the ProductID and Quantity fields for the current OrderDetails record (the first one in the Recordset).

  • Lines 11–13 use this information to build a SQL UPDATE command. The command subtracts the number of items ordered from the total number in stock. A sample completed command looks like this: UPDATE Products SET UnitsInStock = UnitsInStock-4 WHERE ID=14. This subtracts 4 units from product 14.

  • Line 14 performs the update.

  • Lines 15–16 move to the next record and repeat the update process (until no more order items remain in the Recordset).

  • Lines 17–18 perform cleanup.

  • Line 19 exits the procedure. If you made if here, congratulations—everything worked without a hitch!

  • Lines 20–22 are only processed if an error occurs somewhere. In this case, the error description’s shown in a Message box.

This code’s a fair bit more ambitious than anything you’ve seen so far. However, it builds on all the skills you’ve honed over the last three chapters. Once again, the best way to get comfortable with this code is to download the sample database, see it in action, and try changing it. Happy experimenting!

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.222.71.231