Chapter 23

Conversions and Calculations

by Evangelos Petroutsos


CONTENTS

The last part of the book contains a collection of examples you will find useful both in learning VBScript and applying it to your projects. These examples demonstrate key features of the language, some of them rather trivial and others more advanced. All the examples in this section were designed to show how to handle common situations in application development with a language like VBScript, as well as how to use some of the more advanced ActiveX controls. Converting metric units, for instance, is a trivial operation that can be carried out with a single multiplication. The Conversions utility of the next section, however, deploys an elaborate user interface based on the TabStrip control. In the discussion of the examples, you will see how this control is used in building a user interface. Even if you're not interested in learning how to write calculators, in the following chapters you will certainly find useful and practical information and VBScript programming techniques.

The first example of this chapter discusses a unit conversion utility. As simple as the actual conversions may be, the program was designed so that you can easily expand it to perform additional conversions. The second example is a financial calculator. Again, the main topic isn't the actual code for calculating monthly payments, but how to manipulate dates programmatically, a common task in financial calculations. The last example in this chapter is a math calculator. You will see the basics for developing a calculator that performs the basic arithmetic operations, and you learn how to simulate a hand-held calculator. Again, this application was designed so that it can be easily expanded, and you will see how to enhance the basic calculator by making it capable of manipulating hexadecimal numbers.

Metric Conversions

The first example presents a utility that converts metric units. The actual calculations are trivial, but the example provides a few programming issues to explore. The first issue is the user interface. As you can see in Figure 23.1, the Conversions utility deploys a sophisticated user interface based on the TabStrip control. You could have designed this application in many different ways, but the one suggested here has distinctive advantages.

Figure 23.1 : The Conversions utility lets the user convert different types of units, grouped by category.

The User Interface

First, the application's window stays quite small. Placing all the units on a single layout would result in a waste of valuable screen estate, not to mention that you can fit only so much in a window.

By grouping the various units as Figure 23.1 shows, the application becomes easier to use. People usually want to convert specific units from one system to another; they don't convert units at large, just for the sake of it. The user of the Conversions application will activate the appropriate tab and see on it the units he or she wants to convert.

Finally, once you have implemented the application, you can easily add tabs to accommodate other types of units. You just repeat the existing code and replace the prompts on the layout.

The Conversions application relies on the TabStrip control, which comes with Internet Explorer 3.0 and is one of topics discussed in this chapter. The TabStrip control is a collection of tabbed pages that contain related information. Each page in a TabStrip control contains identically structured information corresponding to a different group. If you wanted to display statistical data about various cities on a TabStrip control, you would use a different page per city. The structure of the data for each city is the same, just the data is different. In other words, all pages will contain the same controls, but different data. Of course, you can have variations on the basic structure of the data displayed on a TabStrip control. For instance, some cities may have unique features that must be displayed on separate controls, which will be visible on certain tabs and invisible on others.

The TabStrip Control

Now start by designing the application's user interface with the ActiveX Control Pad. Start with Control Pad, select New HTML Layout from the File menu, and when the new Layout appears on-screen, select the TabStrip tool and draw a relatively large TabStrip control on the Form. When you place a new TabStrip on an HTML Layout, it has two tabs, named Tab1 and Tab2. To change their names or add new tabs, right-click one of the tabs, and the shortcut menu seen in Figure 23.2 appears. The four commands on this shortcut menu enable you to

Figure 23.2 : To add, remove, or rearrange the tabs on a TabStrip control, right-click one of the tabs, and you see this shortcut menu.

Create an additional tab and name the three tabs Length, Weight, and Area. (Refer back to Figure 23.1.)

TabStrip Control's Properties

You can also adjust the appearance of the tabs on the TabStrip control using the Properties window. The Style property determines the look of the tabs and can have one of the following values (see Figure 23.3):

Figure 23.3 : There are three styles you can choose for your TabStrip control.

0 (default): Each page is identified with a tab.
1: Each page is identified with a button.
3: Pages have no marks on them.

Figure 23.3 illustrates the three possible values of the Style property. If the Style property is 0, you must select the page to appear on top of the others programmatically. If the control contains too many pages and their tabs, or buttons, can't be displayed in a single row, two buttons with arrows to the left and right are automatically attached next to the tabs; these arrows enable users to scroll and locate the one they need. If you prefer, you can set the MultiRow property to True, to make all tabs visible, even if they have to occupy multiple lines. This arrangement works in address book types of applications, as shown in Figure 23.4. The tabs on the TabStrip control of Figure 23.4 appear to the right side of the control by setting the TabOrientation to 3 (Right). The other values of the TabOrientation property are 0 (top), 1 (bottom), and 2 (left).

Figure 23.4 : The TabStrip control is ideal for address book types of applications.

Finally, you can set the width and height of each tab with the TabFixedWidth and TabFixedHeight properties respectively (both properties are expressed in pixels). By default, each tab can have its own dimensions, depending on the caption displayed. To set all tabs to the same width, set these two properties to a value large enough to accommodate all the tabs.

TabStrip Control's Methods

So far you have seen the properties for adjusting the appearance of the control at design time. To control the TabStrip programmatically, you must first understand the nature of this control, which is a bit unusual. The TabStrip control represents a collection of Tab objects, and each Tab object in the collection has its own index in the Tabs collection. Tabs is an array, whose elements are the individual tabs. To access the first tab and read or set its properties with VBScript commands, use the expression


TabStrip1.Tabs(0)

where TabStrip1 is the name of the control. The second tab has index 1, and so on.

The Tabs collection has a single property, Count, which returns the total number of tabs. To manipulate the control's tags at runtime, the Tabs collection provides four methods, which correspond to the commands of its shortcut menu. (See Figure 23.2.) The Tabs collection's methods are

Add adds a new tab to the Tabs collection.
Clear removes all objects from the Tabs collection.
Item returns a member of the Tabs collection.
Remove removes a Tab object from the collection.

To add a new tab at runtime, use a statement like


Set TabObject = TabStrip1.Tabs.Add([ Name [, Caption [, index]]])

where TabStrip1 is the name of the control and the bracket indicates that the arguments enclosed are optional. The simplest form of the Add method is


TabStrip1.Tabs.Add

which appends a new tab to the control and names it Tabx, where x is a number. If the control already has 2 tabs, Tab1 and Tab2, the previous call to the Add method will add a third one and name it Tab3. You can specify the name, caption, and order of the tabs added to the collection.

The Clear method removes all the tabs from the control, similar to the Clear method of the ListBox control, which removes all items in a List control. The Remove method removes a Tab object from the collection, and its syntax is


TabStrip1.Tabs.Remove(index)

where index is the order of the tab to be removed in the collection. The statement


TabStrip1.Tabs.Remove(0)

removes the first tab of the TabStrip1 control.

Finally, the Item method returns a member of the Tabs collection, and its syntax is


Set ThisTab = TabStrip1.Tabs.Item(index)

The Caption property controls the string that identifies each member of the Tabs collection. To set the caption of the first tab in the TabStrip1 control, you can use the statement


TabStrip1.Tabs(0).Caption = "Printers"

or extract the appropriate member of the collection and then set the property:


ThisTab=TabStrip1.Tabs(0).Item

ThisTab.Caption = "Printers"

Implementing the Conversions Utility

Place a TabStrip control on the layout and add a third tab with the Add command of the shortcut menu. Then change the captions of all three tabs to match the ones shown in Figure 23.1. To change the font of the captions, right-click the control and select properties from the shortcut menu that will appear. In the Properties window, set the Font property to the appropriate value.

Now you're ready to place the various controls on the TabStrip control. The controls aren't really placed on the TabStrip control. Instead, they are placed on the layout and the TabStrip control enables them to appear. You can draw each one of the controls on the Layout outside the TabStrip control and then move them with the mouse to their places. You can also move the TabStrip control out of view while creating the rest of the user interface, and then place it on top of the controls and resize it. If the TabStrip hides the remaining controls, place it behind the other controls with the Send to Back command of the Format menu.

The box on which the conversion factors are placed is an ImageBox. The ImageBox control contains eight labels in two columns: the Label controls of the first column are named Units11, Units12, Units13, and Units14, and the labels of the second column are named Units21, Units22, Units23, and Units24. They hold the conversion factors for various pairs of units. The captions of these labels must change every time the user clicks a different tab, so they must be set from within the code. Don't bother to change them at this point. Just notice that the captions of the labels in the first column are left-aligned, and those of the second column are right-aligned.

Then, place the TextBox controls where the user will enter the amounts to be converted. Place the Command buttons and the TextBox controls where the results will be displayed. The TextBox controls on the first column are named From1, From2, From3, and From4, and the controls on the second column are named To1, To2, To3, and To4. These text boxes hold the units to be converted and the equivalent amount in the new system. Place all the controls on the form, align them with the commands of the Format menu, and assign a common font name and size for them all.

Programming the Application

Now you can look at the code of the application. First, you must supply the subroutine that changes the captions of the Label controls. This subroutine must be invoked every time the user clicks a tab, an action signaled by the TabStrip control's Click event. The subroutine for this event provides an argument, which is the index of the tab that was clicked. Depending on the value of the Index argument, the captions are set to different values:


Sub TabStrip1_Click(Index)

    If Index=0 Then

        Unit11.Caption="1 inch (in)"

        Unit12.Caption="1 foot (ft)"

        Unit13.Caption="1 yard (yd)"

        Unit14.Caption="1 mile (mi)"

        Unit21.Caption="2.54 cm"

        Unit22.Caption="0.3048 m"

        Unit23.Caption="0.9144 m"

        Unit24.Caption="1.6093 km"

        ClearBoxes

    End If



    If Index=1 Then

        Unit11.Caption="1 sq. inch"

        Unit12.Caption="1 sq. foot"

        Unit13.Caption="1 sq. yard"

        Unit14.Caption="1 acre"

        Unit21.Caption="6.4526 sq. cm"

        Unit22.Caption="0.093 sq. m"

        Unit23.Caption="0.8361 sq. m"

        Unit24.Caption="4046.86 sq. m"

        ClearBoxes

    End If    



    If Index=2 Then

        Unit11.Caption="1 ounce (oz)"

        Unit12.Caption="1 pound (lb)"

        Unit13.Caption="1 short ton"

        Unit14.Caption="1 long ton"

        Unit21.Caption="28.35 gr"

        Unit22.Caption="0.4536 kg"

        Unit23.Caption="0.9072 t"

        Unit24.Caption="1.0161 t"

        ClearBoxes

    End If    

End Sub

The ClearBoxes() subroutine clears the contents of the TextBox controls every time a new tab is displayed, because the amounts converted earlier wouldn't agree with the new units on the page. This subroutine takes care of all the elements that change as the user switches to a different page. When the layout is loaded for the first time, this subroutine must also be called, or else the labels will be blank until the user actually clicks on one of them. In the window_OnLoad event, enter the following lines:


Sub Window_onLoad()

    TabStrip1_Click(0)

End Sub

Finally, the actual unit conversions take place every time the user clicks on the corresponding Command button. The subroutines behind each Command button could be long Select Case statements (or series of If statements), but a simpler and more elegant way of converting the units exists. The conversion factor for each unit is always stored in the caption of the corresponding label in the second column. You can extract this number from the label's Caption property and use it in the calculations. The result is a short subroutine that is totally independent of the converted units. The program reads the conversion factor from the corresponding label and then the number of units (the number entered by the user in the appropriate TextBox control). Then it multiplies the two values (units times conversion factor) and stores the result to the other TextBox control (in the column with the results). You can add more tabs to the control or change the units on each page, and you won't have to touch the code. It will always work, as long as the conversion can be carried out with a simple multiplication. Here's the code behind the first Command button:


Sub CommandButton1_Click()

    If NOT IsNumeric(From1.Text) Then Exit Sub        

    SpacePos=Instr(Unit21, " ")

    factor=left(Unit21, SpacePos-1)

    To1.Text=CDbl(From1.Text) * factor

End Sub

The code behind the remaining Command buttons is nearly identical, except for the names of the From and To TextBox controls. The first line makes sure the TextBox contains a valid number; if not, the execution of the subroutine is aborted. Then, it isolates the first element in the string, which is the conversion factor, and assigns it to the factor variable. This variable gets multiplied by the contents of the first TextBox, and the result is assigned to the second TextBox.

To run and test your application, save the layout as Conversions.alx, open a new HTML file, and insert the layout with the Insert HTML Layout command on the Edit menu. After the insertion of the HTML layout, the new document will display as follows:


<HTML>

<HEAD>

<TITLE>New Page</TITLE>

</HEAD>

<BODY>



<OBJECT CLASSID="CLSID:812AE312-8B8E-11CF-93C8-00AA00C08FDF"

ID="conversions_alx" STYLE="LEFT:0;TOP:0">

<PARAM NAME="ALXPATH" REF VALUE="file:C:\WINDOWS\Desktop\Projects\Calc\conversions.alx">

</OBJECT>



</BODY>

</HTML>

Notice that the ActiveX Control Pad inserted the complete path name in the definition of the HTML Layout object. If your HTML files and the layouts they contain are stored in the same folder, you should edit the HTML file and remove the path name, or replace it with a relative path, such as ..\Layouts\Conversions.alx.

A Financial Calculator

In this and the following section, you build two calculators: a financial one and a simple math calculator. Calculators, among the most common utilities in every environment, certainly have their place on the Web. The financial calculator is a simple application that enables you to enter the various parameters of a loan (starting and ending dates, amount, interest rate, and monthly payment) and calculates the one you haven't specified. For example, you can enter the amount, the duration, and the interest rate; then ask the program to calculate your monthly payment. Or, instead of the loan's duration, you can enter the monthly payment you can afford to pay, and the program will calculate how long it will take you to pay off the loan. This example can begin as a starting point for a more advanced financial calculator. As usual, the calculations are simple (provided you know about loans, mortgages, and so on), but you will see a few interesting points about VBScript's date manipulation functions demonstrated in this example. Financial calculations rely on date manipulation operations, and VBScript's date functions can simplify your code a good deal.

The User Interface

The user interface of the Financial Calculator application appears in Figure 23.5. It's much simpler than the one from the previous example and should be fairly easy to implement. It consists of five Label controls and five TextBox controls next to them. The user may enter values in some of the TextBox controls and then click the Command button next to the quantity to be calculated. Even if the corresponding box contains a value from previous calculations, the program will ignore it and calculate based on the values of the other controls.

Figure 23.5 : The Financial Calculator utility calculates the duration of a loan, or the monthly payment, according to the date you provide.

The application has straightforward code, except perhaps for the actual calculations. The functions that calculate the loan's parameters are a bit involved, but the goal here is not to teach you the secrets of banking. If you develop financial applications, you will find the calculations trivial. Even if you've never written any financial applications in the past, you can still follow this example. Just treat the CalculatePayment() and CalculateMonths() functions as black boxes, which calculate the loan's monthly payment and duration in months, respectively. The goal of this project is to show you a few interesting VBScript programming techniques, and not teach you how to calculate loans. The duration of a loan's amortization is calculated with the formula:


MPay=LoanAmount * RRate / (1-(1+RRate)^-N))

where MPay is the monthly payment, LoanAmount is the principal (the loan's initial amount), N is the loan's duration in months and RRate is given by


RRate=Interest/1200

where Interest is the interest rate. To calculate the duration of the loan in months, the previous equation is solved for N.

With the calculations out of the way, we can concentrate on the application's user interface, by adding some flexibility to it. The user will have the option to enter either the duration of the loan (in months) or the ending date. If the user supplies the duration of the loan, the starting date field will be ignored. If, however, the user wants to calculate the duration of the loan and has entered a starting date, the program will display the ending date.

VBScript Date Functions

Before understanding the actual code of the application, you should review the VBScript date manipulation functions. The Date and Time functions return the current date and time, respectively, and the Now function returns both date and time. The Date function will get used from within the window's OnLoad event to display the current date in the Starting Date field:


Sub window_OnLoad()

    Date1.Text=Date

End Sub

Date calculations have been a sore point in programming for many years and with nearly every language. The built-in support for manipulating dates was minimal, and programmers had to supply lengthy procedures for operations, such as the difference between two days, or to calculate the date 60 days from today. VBScript simplifies date manipulation with the DateValue() and SerialDate() functions. The DateValue() function accepts a date value as an argument and returns a numeric value, which represents the date. Using the DateValue() function, you can perform arithmetic operations with dates. For example, to calculate the number of days between two dates, you can use the following statements:


date1="3/8/95"

date2="9/21/97"

Difference=DateValue(date2)-DateValue(date1)

(The value of the Difference variable after the execution of the previous lines becomes 928.) If you attempt to subtract two dates directly, you get an error message instead.

The DateSerial() function accepts three numeric arguments that correspond to a year, a month, and a day value and returns the corresponding date. The statement


MsgBox DateSerial(1995, 10, 3)

will display the string 10/3/95 in a message box.

Like the DateValue() function, the DateSerial() function can handle arithmetic operations with dates. For example, you can find out what date it will be on the 90th day of the year by calling DateSerial with the following arguments:


DateSerial(1996, 1, 90)

(30/3/96, if you are curious). To find out the date 1,000 days from now, call the DateSerial() function as


DateSerial(Year(Date), Month(Date), Day(Date)+1000)

You can also add (or subtract) a number of months to the month argument and a number of years to the year argument. Say that the duration of a loan made on August 23, 1996 goes 75 months. You can calculate the exact day of the loan's final payment with the statement


FinalDate=DateSerial(1996, 8+75, 23)

The variable FinalDate is a date variable (11/23/2002, to be exact). Because hardcoding data does not represent a good programming technique, a more complicated statement that enables you to calculate the date M months from today appears next:


NewDate=DateSerial(Year(Date), Month(Date)+M, Day(Date))

where M is the number of months between the two dates; it can be negative, too, if you want to calculate a date in the past.

TIP
VBScript provides two similar functions for manipulating time: the TimeValue() and TimeSerial() functions. These functions won't be used in this example, but they function identically to the date functions, except that they use hours, minutes, and seconds, instead of years, months, and days.

With this background on the VBScript date functions, you can examine the Financial Calculator application. The user interface is quite simple. You must place five Label and five Textbox controls next to each other, and two Command buttons next to the Ending Date and Monthly Payment fields. The labels indicate the type of input that each TextBox accepts. The user must supply a date to all fields that don't have a Command button next to them and to one of the other two. Then, the user may click one of two Command buttons to calculate the ending date or the monthly payment for the specified amount.

Notice that users can enter not only the duration of the loan in months, but they can enter an ending date, too. The program will use the duration of the loan in the first case, and it calculates the duration by subtracting the starting date from the ending date in the second case. The result also gets displayed in two formats. If the Starting Date field has no information, the program prints the duration of the loan in months. If the Starting Date field contains a valid date, the program displays the ending date.

Programming the Application

Now you can look at the code of the application. The code behind the Click event of the two Command buttons remains quite simple:


Sub CalcMonths_Click()

    PayMonths=Int(CalculateMonths()+0.5)

    if IsDate(Date1.Text) Then

        Date2.Text=DateSerial(Year(Date1.Text), Month(Date1.Text)+PayMonths,

        Day(Date1.Text))

    Else

        Date2.Text=PayMonths

    End If        

End Sub



Sub CalcPayment_Click()

    MPayment.Text=Int((CalculatePayment()+0.5)*100)/100

End Sub

The CalcMonths Command button calls the CalculateMonths()function, which reads the data from the controls and calculates the duration of the loan. Then it displays the duration in the third TextBox in two different formats, depending on the contents of the first TextBox. If the first TextBox control contains a valid date, the result appears as a date. The CalculateMonths() function returns the duration of the loan in months, and the program figures out the date so many months from the starting date by adding the duration of the loan to the starting date; then the program displays it. If the first TextBox doesn't contain a valid date, the duration is displayed in months.

The CalcPayment Command button calls the CalculatePayment() function, which also reads the data from the controls and calculates the monthly payment. The amount of the monthly payment is rounded to two decimal digits and displays on the last TextBox.

Both the CalculateMonths() and CalculatePayment() functions assume that the interest is compounded daily. The actual calculations don't matter for the example. If you make your living by approving loans, you should double check the program, or supply your own functions for calculating the loan parameters. The CalculateMonths() function appears next:


Function CalculateMonths()

    MPay=MPayment.Text    

    LoanAmount=Amount.Text

    IRate=Rate.Text

    RRate=IRate/1200

    If LoanAmount*RRate/MPay >= 1 Then

        MsgBox "Too small monthly payment for the amount requested"

        CalculateMonths=0

        Exit Function

    End If

    T1=log(1-LoanAmount*RRate/MPay)

    T2=log(1+RRate)

    CalculateMonths=-T1/T2    

End Function

Notice the If statement that compares the quantity LoanAmount*RRate/Mpay to 1. If this quantity is larger than 1, the argument of the logarithm in the formula for calculating T1 is negative, and the logarithm of a negative number can't be calculated. To prevent an error message, make sure that the logarithm can be computed before going to this line. If the arguments are such that the logarithm of the quantity 1-LoanAmount*RRate/Mpay can't be calculated, the function ends prematurely and returns the value zero. In practice, this result will happen if you attempt to pay off a loan with a monthly payment that doesn't even cover the monthly interest. The monthly payment should be such that the total amount you owe to the bank (principal and interest) decreases every month. Otherwise, you have made a loan that will never be paid off, and then why bother making payments?

The CalculatePayment() function starts by computing the duration of the loan and then proceeds with the calculations. The DateDifference() function reads the values of the controls and returns the duration of the loan in months (you see shortly the implementation of the DateDifference() function). This number then gets used in the calculations of the monthly payment.


Function CalculatePayment()

    Duration=DateDifference()

    If Duration<0 Then Exit Function

    LoanAmount=Amount.Text

    IRate=Rate.Text

    RRate=IRate/1200

    MPay=LoanAmount*RRate/(1-(1+RRate)^(-Duration))

    CalculatePayment=MPay    

End Function

The DateDifference() function is the most interesting one in this application. It uses the DateValue() function to compute the difference between the starting and ending dates in months and returns this value to the calling program. First, it checks the contents of the Date2 TextBox control to see if it contains a valid date. If the user has specified the ending date of the loan instead of its duration, the program subtracts the two dates and divides the result by 30 to come up with the duration of the loan in months. If the Date2 field contains a number, the program uses this value as the duration of the loan. Finally, if the Date2 field contains neither a date nor a numeric value, the program lets the user know he has made an invalid entry, and then doesn't proceed with the calculations. The following text shows the code for the DateDifference() function.


Function DateDifference()

    Difference=-1

    If IsDate(date2) then

        Difference=int((DateValue(Date2)-DateValue(Date1))/30+0.5)

    Else

         if IsNumeric(date2) then

            Difference=date2

        else

            MsgBox "Please enter a valid ending date or a duration for the loan"

        End If

    End If

    DateDifference=Difference

End Function

A Math Calculator

The last example in this chapter is a math calculator. As you probably expect, the code for carrying out the operations represents the simplest part of the application. This calculator only performs basic arithmetic operations, such as addition and subtraction; however, it's not a trivial application. As you will see, the application has a design you can easily expand and customize. After looking at the code of the application, you build a hexadecimal calculator based on this example by adding a few lines of code.

The User Interface

The application is called Calculator, and its user interface appears in Figure 23.6. The calculator has a Label control display, which means that users can't enter data by typing it. They must click the calculator's buttons with the mouse. In addition to the 10 digits, the period, and the usual arithmetic operators, a few additional buttons exist for keeping running totals. The M+ button adds the current number (the number currently displayed) to a running total in memory, the MR button recalls the total from the memory, and the MC button resets the memory.

Figure 23.6 : The Calculator utility mimics a hand-held calculator from within any Web page.

The user interface of the Calculator application might seem trivial, but it will take you a while. To speed things up, design one Command button for a digit, set its size, font, and any other common properties; then copy and paste it on the layout over and over. Just change the Caption property of each new Command button pasted. Once all the buttons appear on the layout, use the Align commands of the Format menu to align the Command button controls (refer to Figure 23.6). If you don't like this arrangement, place the buttons in a way that suits you.

There are situations in which you may have to align a large number of controls on a layout, like the command buttons of the Calculator application. To align a large number of controls in a tabular arrangement on a layout, you must first make all the controls the same size, then make the horizontal and vertical spacing between adjacent controls the same and finally, adjust the lefts of the controls on the same column, and the tops of the controls on the same row.

Select all the controls you want to align with the mouse while holding down the Shift key. You can also select multiple controls by drawing a rectangle that encloses all the controls you want to adjust with the mouse. Then select the Make Same Size command of the Format menu.

Next, select all the buttons of the first row and align their tops with the Align/Tops command of the Format menu. Then make the spacing between pairs of adjacent buttons the same, using the Horizontal Spacing/Make Equal command from the Format menu. Do the same with the buttons in the first column. This time, align their left sides and make the vertical spacing the same. Once the buttons of the first row and column are in place, you can easily align the rest of them. Select all the button in each row and align their tops, and select all buttons in each column and align their left sides. Just make sure that the first button you select has the alignment you want applied to the other ones.

Programming the Application

The code of the Calculator application mimics the operation of a hand-held calculator. It expects the user to type a number (the first operand), then the symbol of an operation (the operator), and then another number (the second operand). These three entities then get combined to produce the result as follows:


result=operand1 operator operand2

As the user clicks on digit buttons, the corresponding digit displays. This result occurs by appending the new digit to the label's Caption property. When the user clicks an operator's button, the current number is stored in a global variable, and the display clears in anticipation for the second operand. The subroutine for the Command button's Click event is


Sub Digit1_Click()

    DisplayDigit("1")

End Sub

The subroutines for the click events of the other digit buttons are similar.

The DidplayDigit() subroutine does more than just append a digit to the display. It checks the global variable MustClear to see if the display must be cleared first:


Sub DisplayDigit(digit)

    If MustClear Then

        Display.Caption=""

        MustClear=False

    End If

    Display.Caption=Display.Caption+digit

End Sub

The variable MustClear is set to True every time an operator's button is clicked. Say the user enters the number 381 and then clicks the Addition button to signal the intention to add two numbers. The display doesn't clear immediately after the Plus button gets clicked. Instead, the MustClear variable is set to True and the display actually clears only when the user clicks the next digit button (the first digit of the second operand). The code for the Plus button's Click event appears next:


Sub Plus_Click()

    op1=Display.Caption

    MustClear=True

    op="PLUS"

End Sub

op1 and op are global variables, whose values are set by the various subroutines of the application; they get used during the calculation of the result. When the user clicks the Plus button, the contents of the display become the first operand, the operator is set according to the button that was pressed, and the MustClear variable is set to True. This variable must be set to True so that the next time the user clicks a digit button, the display clears and a new number is entered.

All the action takes place from within the Equals button's Click event. That subroutine appears in the following code:


Sub Equals_Click()

    Op2=Display.Caption

    Select Case Op

    Case "PLUS":

        result = CDbl(op1) + CDbl(op2)

    Case "MINUS":

        result = CDbl(op1) - CDbl(op2)

    Case "TIMES":

        result = CDbl(op1) * CDbl(op2)

    Case "DIV":

        If CDbl(op2) = 0 Then

            Display.Caption="ERROR!"

        Else

            result = CDbl(op1) / CDbl(op2)

        End If

    End Select

    If Display.Caption<>"ERROR!" then Display.Caption=result

    MustClear=True

End Sub

As soon as the Equals button is clicked, the number currently displayed becomes the second operand and the program checks the value of the op variable. If op is PLUS, it adds the two operands. If it is MINUS, the program subtracts the second from the first operand. If op is TIMES, it multiplies the two operands. In all cases, the result is stored in the result variable and gets displayed later.

If the value of the op variable is DIV, however, the program tests the value of the second operand against zero to prevent a runtime error (the Division by zero error). If the second operand is non-zero, it performs the division and displays the result. If it is zero, the program displays the string ERROR! instead of the result.

Finally, the memory buttons manipulate the contents of another global variable, the memory variable. The M+ button adds the current number to the memory variable, the MR button displays the current value of the memory variable, and the MC button resets the value to zero. The three subroutines appear next:


Sub MemAdd_Click()

    memory=memory + Display.Caption

    MustClear=True

End Sub



Sub MemRecall_Click()

    Display.Caption=memory

    MustClear=True

End Sub



Sub MemClear_Click()

    memory=0

    MustClear=True

End Sub

The code of the Calculator application is fairly straightforward. The actual calculations only require a few lines of code, and most of the code handles the display. You can easily expand the bare bones application presented here to include more operations. For example, you can add a +/- button that inverts the sign of the current number. This button can be implemented with a single line of code:


Display.Caption="-" & Display.Caption

Improving the Calculator

You can easily turn this utility into a scientific calculator. You will spend the most time adjusting the user interface to include more buttons, because the operations are straightforward. For instance, if you add a COS button (for calculating the cosine of a number) on the layout, you must enter the following line in the button's Click event:


Display.Caption=cos(CDbl(Display.Caption))

When the user clicks the COS button, the cosine of the displayed value is calculated and appears on the calculator's display. You can add trigonometric functions, square roots, and powers, because these operations don't require two operands. They just read the current value, calculate a function of the number and display the result. You must, however, add a good deal of error checking code so that the program won't attempt to calculate the square root or logarithm of a negative number.

Another useful variation of the basic Calculator application is a hexadecimal calculator, like the one shown in Figure 23.7. If you add a few more buttons for the hexadecimal digits A through F, you can perform the same operations, only in the hexadecimal number system. In the hexadecimal system, however, you can work with integers only. To perform operations in the hexadecimal system, you must prefix the numbers with the symbols &H and then take their value with the CInt function, which converts its argument to an integer. The arithmetic operations will be carried out in the decimal system and the result gets converted to the hexadecimal system with the function Hex.

The HexCalculator application, shown in Figure 23.7, is a variation on the Calculator that performs hexadecimal calculations. The Equals_Click() subroutine looks slightly different than before:


Sub Equals_Click()

    Op2="&H" & Display.Caption

    Select Case Op

    Case "PLUS":

        result = CInt(op1) + CInt(op2)

    Case "MINUS":

        result = CInt(op1) - CInt(op2)

    Case "TIMES":

        result = CInt(op1) * CInt(op2)

    Case "DIV":

        if CInt(op2) = 0 Then

            Display.Caption="ERROR!"

        else

            result = CInt(op1) / CInt(op2)

        End If

    End Select

    If Display.Caption<>"ERROR!" then Display.Caption=Hex(result)

    MustClear=True



End Sub

When you prefix the operands with the "&H" string, they get converted to hexadecimal numbers. The CInt() function converts these hex numbers to decimals, performs the operation, and then converts the result back to a hex number before displaying it.

Further Improvements

As you can see, it didn't take much to convert the simple decimal calculator into a hexadecimal one. To summarize, all you had to do was add the extra hex digits, convert the arguments to decimal numbers to carry out the calculations, and then convert the result back to a hexadecimal number to display the result. You can now try to combine both calculators in one by adding a toggle button that switches the mode of the calculator between the two systems. When the calculator is in decimal mode, you should disable the hex digits on the layout and restore them when the calculator is switched back to hexadecimal mode. As far as the calculations are concerned, you can copy both subroutines for the Equals button we presented earlier in your code and call one of them, depending on the calculator's current mode.

Review

In this chapter's examples you were shown how to design applications that perform simple calculations, like converting units between systems, or more complicated calculations like math operations. As you have realized by now, the functions that do the actual calculations are usually the shortest and simplest part of the application. The real challenge in programming in a visually rich environment like Windows 95 is to provide the simplest, most functional user interface. This requires visual design skills, as well as programming skills.

The code behind the various buttons of the Calculator application wasn't simple, considering what it accomplishes. Arranging the buttons on the layout and coming up with an attractive user interface will probably take you longer than writing the code. After the code of the Calculator application was written, it was surprisingly simple to add support for hexadecimal digits to the application, as well as extend it to handle trigonometric and other advanced math functions.

The Conversions utility is another typical example. Writing the code for converting units between systems is trivial, but with a well-designed user interface we were able to create a usable application (one that doesn't overwhelm the user with options). At the same time, by displaying the conversion factors on the program's form, we were able to design an application that can be expanded without actually touching the code that performs the calculations. You can add more options to the program by simply adding new labels with conversion factors. The logic of converting the units has been built into the application, and you needn't worry about it unless the conversion isn't as simple as a multiplication. The display of the conversion factors on the form not only simplifies the code but provides useful information to the user.

The types of applications we explored in this chapter were rather simple ones, but they are quite common and it's likely that you'll incorporate a similar functionality to some of your applications. No doubt, you've seen numerous applications that perform financial or arithmetic calculations. What will make the difference between yours and the other applications is simplicity and ease of use. Our examples weren't the ultimate examples of simplicity or flexibility, but they demonstrated some of the key features you should incorporate in even the simplest applications.