The Visual Programmer

Joshua Trupin

Joshua Trupin is technical editor of Microsoft Interactive Developer magazine (MIND). He was formerly a software developer at Hyperion Software.

VBScripting Your Existing App

It's been a couple of months since the last Visual Programmer column, and there's a good reason—I've recently assumed the role of technical editor at Microsoft Interactive Developer magazine (MIND), the sister magazine to MSJ. So if you've written to me and I haven't sent you a reply, it's not because I'm ignoring you. It's because I'm busy and I'm ignoring you.

But I have been reading the mail that's come in. One of the things that people are quickly becoming more interested in is the level of difficulty it will take to get an existing Visual Basic¨ program to work as an applet in VBScript. It sounds like it should be pretty easy, and yet there's a nagging feeling that it's going to be dreadfully hard. For this month's column, I took a sample program I wrote several years ago—a talking clock I coded in Visual Basic 2.0—and converted it to VBScript. As it turns out, the experience landed squarely between facile and onerous. Most of VBScript performed just like I expected, but a few gotchas I encountered were very informative, and could help you get past them quickly.

The Original Program

Originally, the MSJ talking clock consisted of two control components, a hidden timer, and a button. The timer was set to trigger an event every 1000 milliseconds (1 second). The Timer event code simply set the button caption to the current time.

That was the easy part. Making it talk was somewhat more complex. Through the Windows¨ 3.1 sndPlaySound API call you can tell a machine to play a WAV file, so this was added to the project as a Declare. I then recorded the sound files needed for every possible time combination. This list turned out to be the numbers 1–19, 20, 30, 40, and 50. (The program didn't need a spoken zero, for instance, because you don't say, "It's five-zero-zero" in common usage, you say, "It's five o'clock." The program did need an "oh" for times like "It's five-oh-three.") A few extra sounds were also recorded for the clock: "it's," "and," "o'clock," "second," and "seconds." Other numbers, such as 43, were made by combining two WAV files. The resulting files provide rich monaural sound for the application.

Whenever a user clicked the time button, the program got the current time, parsed out hours, minutes, and seconds to determine what sounds had to be played, then played them. As a whole, the program is relatively simple. Figure 1 shows what the project looks like converted to Visual Basic 4.0; its code appears in Figure 2. (A very small side note—Visual Basic 1.0 programs can't be read into the Visual Basic 4.0 IDE. If you want to convert them, you have to open them in either version 2.0 or 3.0, save them, then reopen them in 4.0. This is only a problem if you have really old programs or download old samples from somewhere.)

Figure 1

Figure 2 Clock 96FRM


 . . .

Attribute VB_Name = "LittleBen"
Attribute VB_Creatable = False
Attribute VB_Exposed = False

Private Declare Function sndPlaySound Lib "winmm.dll" Alias "sndPlaySoundA" (ByVal lpszSoundName As String, ByVal uFlags As Long) As Long

' sndPlaySndSound constants (defined in WINDOWS.H)

Const SND_SYNC = (0)
Const SND_ASYNC = (1)
Const SND_NODEFAULT = (2)
Const SND_MEMORY = (4)
Const SND_LOOP = (8)
Const SND_NOSTOP = (16)

Private Sub PlaySnd(Snd$)

' PlaySnd plays the soundfile named in its argument. By
' default, the .WAV should exist in your home Windows
' directory.

    rc% = sndPlaySound(Snd$ + ".WAV", SND_NODEFAULT)
End Sub

Private Sub TimeLbl_Click()
' Whenever the user clicks on the time label, set into
' motion the code for saying the time.

    TimeStr$ = Time$
     
    ' Get the parsing positions within TimeStr$

    Colon1 = InStr(TimeStr$, ":")
    Colon2 = InStr(Colon1 + 1, TimeStr$, ":")
    Space1 = InStr(Colon2 + 1, TimeStr$, " ")

    ' Parse the three components. Here, Hrs is taken Mod 12 to avoid
    ' the 24-hour clock if it exists

    Hrs = Val(Left$(TimeStr$, Colon1 - 1)) Mod 12
    Mins = Val(Mid$(TimeStr$, Colon1 + 1, 2))
    Secs = Val(Mid$(TimeStr$, Colon2 + 1, 2))

    If Hrs = 0 Then Hrs = 12

    ' Pronounce the "it's"

    PlaySnd ("its")

    ' The Hrs is simple - it is a digit between one and twelve, all of
    ' which exist in separate sound files

    PlaySnd (Format$(Hrs, "0"))
    
    ' Pronounce the "o'clock" on the hour

    If Mins = 0 Then
        PlaySnd ("oc")

    ' Pronounce "oh-mins" if mins is a single digit

    ElseIf Mins < 10 Then
        PlaySnd ("oh")
        PlaySnd (Format$(Mins, "0"))

    ' Pronounce a teen digit in its entirety

    ElseIf Mins < 20 Then
        PlaySnd (Format$(Mins, "0"))

    ' If the Mins are over 20, pronounce the tens part first, followed
    ' by the ones part. For instance, 34 becomes "thirty" "four"

    ElseIf Mins >= 20 Then
        PlaySnd (Format$((Mins \ 10) * 10, "00"))
        PlaySnd (Format$(Mins Mod 10, "0"))
    End If

    ' Don't pronounce "and zero seconds"

    If Secs = 0 Then GoTo Done

    ' Pronounce your "and" after the minutes

    PlaySnd ("and")

    ' You can just pronounce any number below twenty straight from the file,
    ' becase you don't need to preface a single digit with an "oh"

    If Secs < 20 Then
        PlaySnd (Format$(Secs, "0"))

    ' If there are more than 20 seconds, pronounce it in two chunks, just
    ' like above for the minutes segment

    ElseIf Secs >= 20 Then
        PlaySnd (Format$((Secs \ 10) * 10, "00"))
        PlaySnd (Format$(Secs Mod 10, "0"))
    End If

    ' Say the closing "second" or "seconds," depending upon the number
    ' of seconds elapsed

    If Secs = 1 Then
        PlaySnd ("sec")
    Else
        PlaySnd ("secs")
    End If

Done:

End Sub

Private Sub TimeLbl_DblClick()
' Li'l Ben terminates when you double-click on the label.
    End
End Sub

Private Sub Timer1_Timer()
' Every time the Timer goes off (each x/1000 second as
' set as its Interval property), update the caption
' on the TimeLbl label.
    TimeLbl.Caption = Time$
End Sub

VBScript Considerations

Under no stretch of the imagination is converting a Visual Basic 4.0 application to VBScript as easy as copying, pasting, and running. For one, you have to do a bit of work to get your controls onto a form correctly. The Microsoft ActiveXª Layout Pad will make this a great deal easier, but I did it by hand. This is a tedious process that involves finding each control's CLSID and adding it to an <OBJECT> tag in HTML. I used the OLE2VW32 program included with Microsoft Visual C++¨.

None of the controls that you're used to in Visual Basic are intrinsically available to VBScript in HTML. Of course, the program needs a timer control and a button on it. Microsoft Forms 2.0 contains a number of controls matching the ones you get with Visual Basic, but there's no timer to be found. Eventually, I found one called IETimer (more on this later).

The second roadblock is the lack of Declares in VBScript. This is a necessary bow to the idea of "sandboxing"—containing a browser script in a virtual machine so that it can't do any malicious damage to the operating system. Declares, obviously, allow you to call almost any procedure contained in any DLL on a system, including ones in the Win32¨ API. Since scripts aren't syntax-checked until they're actually run, a browser has no way of knowing whether a Declare is going to be used in any particular script.

ActiveX controls, however, are supported by VBScript (and by any language following the ActiveX Scripting mechanism). Just as I've demonstrated in the past how to wrap system functions with OLE controls to make them accessible to Visual Basic, so you can also wrap functions in ActiveX controls for the sake of VBScript. You're probably saying to yourself, "wait—doesn't that make the script potentially unsafe again?" In theory it does, but there isn't much you can do without getting to the system at some point, including playing sounds. ActiveX controls can be digitally signed by their creator to ensure security, and a compliant browser will notify a user when a component is about to be installed. These steps make it far more difficult to sneak something onto a user's machine with ill intent. If you wrap safe APIs, the control can still be safe.

Why am I telling you about all this? The talking clock needs a mechanism to play the WAV files that provide its compelling functionality. Since the sndPlaySound (or better, the Win32 API PlaySound) API call can't be used directly in VBScript, I wrote a simple ActiveX control that, when installed, plays a WAV file that exists in the user's Windows default directory. This process was extremely easy. I opened Visual C++ 4.1, created a new ActiveX control, and added a single method to it: PlaySound. Ctrl.PlaySound takes a WAV filename as an argument and calls the Win32 API PlaySound to play it synchronously (otherwise, the sounds might not be played in order). I also changed the OnDraw member function of the control so that it always makes sure that it's hidden on the form (using ShowWindow(FALSE)). I compiled to generate the control, noted its CLSID from the REG file Visual C++ created, and got out. The source for PLAYSND.OCX is shown in Figure 3.

Figure 3 PLAYSND.OCX

Excerpt from WAVCTRL.H


 
class CWavctrlCtrl : public COleControl
{
    DECLARE_DYNCREATE(CWavctrlCtrl)

// Constructor
public:
    CWavctrlCtrl();

// Overrides
    // ClassWizard generated virtual function overrides
    //{{AFX_VIRTUAL(CWavctrlCtrl)
    public:
    virtual void OnDraw(CDC* pdc, const CRect& rcBounds, 
                        const CRect& rcInvalid);
    virtual void DoPropExchange(CPropExchange* pPX);
    virtual void OnResetState();
    virtual DWORD GetControlFlags();
    //}}AFX_VIRTUAL

// Implementation
protected:

. . .

// Dispatch maps
    //{{AFX_DISPATCH(CWavctrlCtrl)
    afx_msg void Play(LPCTSTR lpWAV);
    //}}AFX_DISPATCH
    DECLARE_DISPATCH_MAP()

    afx_msg void AboutBox();

. . .

// Dispatch and event IDs
public:
    enum {
    //{{AFX_DISP_ID(CWavctrlCtrl)
    dispidPlay = 1L,
    //}}AFX_DISP_ID
    };
};

Excerpt from WAVCTRLCTL.CPP


 
// WavctrlCtl.cpp : Implementation of the CWavctrlCtrl OLE control class.

#include "stdafx.h"
#include <mmsystem.h>
#include "wavctrl.h"
#include "WavctrlCtl.h"
#include "WavctrlPpg.h"

. . .

/////////////////////////////////////////////////////////////////////////////
// Dispatch map

BEGIN_DISPATCH_MAP(CWavctrlCtrl, COleControl)
    //{{AFX_DISPATCH_MAP(CWavctrlCtrl)
    DISP_FUNCTION(CWavctrlCtrl, "Play", Play, VT_EMPTY, VTS_BSTR)
    DISP_STOCKPROP_READYSTATE()
    //}}AFX_DISPATCH_MAP
    DISP_FUNCTION_ID(CWavctrlCtrl, "AboutBox", DISPID_ABOUTBOX, AboutBox,
                     VT_EMPTY, VTS_NONE)
END_DISPATCH_MAP()

. . .

/////////////////////////////////////////////////////////////////////////////
// CWavctrlCtrl::OnDraw - Drawing function

void CWavctrlCtrl::OnDraw(CDC* pdc, const CRect& rcBounds, 
                          const CRect& rcInvalid)
{
    if (m_hWnd)
        ShowWindow(FALSE);
}

. . .

/////////////////////////////////////////////////////////////////////////////
// CWavctrlCtrl message handlers

void CWavctrlCtrl::Play(LPCTSTR lpWAV) 
{
    ::PlaySound(lpWAV, NULL, SND_FILENAME | SND_SYNC);
}

Creating the HTML

To create the Web page, I manually entered three <OBJECT> tags, one each for the Timer, Button, and Playsnd controls. Let's look at the Timer control tag as an example.


 <OBJECT
    CLASSID="clsid:59ccb4a0-727d-11cf-ac36-00aa00a47dd2"
    CODEBASE="http://www.microsoft.com/ie/download/activex/ietimer.ocx#version=4,70,0,1086"
    id=Timer1
    align=middle
>
<param name="TimeOut" value="100">
<param name="Enabled" value="True">
</OBJECT>

In a couple of years you'll look back at this and laugh that you had to do any of it, but let me explain anyway. The first argument, CLASSID, contains the unique CLSID identifier for the IETimer control. If a browser is able to match up this value with the list in the registry and it can find the control installed on the user's machine, it doesn't need to retrieve it. The second argument, CODEBASE, indicates where this component can be found if it's not properly installed. I found the IETimer while poking around on www.microsoft.com. Use this only as an example; the timer control has now been added to the IE 3.0 distribution package. (You don't need to supply a component locally if it's available elsewhere—the ActiveX component download mechanism lets you use parts distributed anywhere that's reachable by URL.) A version number is also added so that a browser can update controls if necessary. The third argument, ID, indicates this control's name as referenced in any VBScript code on the page. ALIGN indicates where the control should be placed on the page; it's not important for a hidden control like this one.

Two <param> tags follow this <OBJECT> tag. They provide persistent startup values for properties of the control. In this case, the IETimer control fires a Time event every 100 milliseconds (1/10 second), and it's enabled—the Time events will be generated normally. This is all closed with the </OBJECT> tag.

I've also added my WavCtrl control to the page as WavCtl1; any VBScript code that says


 WavCtl1.PlaySound "BITEME.WAV" 

will play the BITEME.WAV file if it's installed in the user's Windows directory. Actually getting the proper WAV files downloaded is possible, again, through the ActiveX component download specification. Without explaining this in too much depth (Mary Kirtland wrote an entire article about the process in the July 1996 MSJ), the WAV files can all be compressed into a single CAB file, then a CODEBASE tag can reference where this CAB can be found. If they're not on the user's machine already, the CAB is automatically downloaded and its contents are installed to the proper places. If a file has multiple dependencies (for instance, a control that needs an associated DLL to function properly) an INF file can be referenced within a CODEBASE tag. INF files let you set up dependencies in a convenient INI-file-type format, and they can in turn reference CAB files where the files can be found.

Language Differences

Now that I have the HTML page with the controls I need on it, it's time to activate them. VBScript can, for the most part, handle Visual Basic functions you've designed elsewhere. However, there are a few important differences in addition to the lack of Declares. The most important is that VBScript is a typeless language—everything's a variant. Variables like Strg$ aren't allowed because there are no format specifiers in the language. Strg is used as a variant, and it'll be coerced to a string type when needed.

A big problem I found was the lack of a few commands like Format. In my original code, I used Format(n, "0") when I parsed a time string. This converted the minute text in "7:09" to a single nine so I could then play 9.WAV instead of 09.WAV. Fortunately, the talking clock only used Format to convert down (from "09" to "9") and not up ("9" to "09"). Since variants rule in VBScript, a statement like


 WavCtl1.Play (Mins + 0) & ".wav"

called when Mins="09", converts Mins to 9, adds 0, converts it back to "9", and appends ".wav" to it. This obviated the need for Format in my code. The result is a shorter, if somewhat more annoying, script. (It's annoying for people like me who are also versed in a language like C++ where, if you try to pass an LPSTR to a function expecting a LPCSTR, the compiler will reject it as unconvertable. Come to think of it, that's even more annoying.)

VBScript procedures (as well as those written in any language) are wrapped as a block with the <SCRIPT> tag. A browser can tell what code interpreter to use by checking the language argument; ActiveX scripting can find the proper DLL to interpret any supported language. (For an explanation of how this happens, see my piece on Microsoft Internet Explorer 3.0 in the September 1996 issue of MIND.) Event procedures are named the same way as in Visual Basic.

Here's the main code for creating a working (if mute) clock on a page containing an IETimer named Timer1 and a button named cmdbtn:


 <SCRIPT language="VBScript">
Sub Timer1_Timer
   ' This is all you need to create a working clock 
   ' with VBScript
   cmdbtn.caption = Time
End Sub    
</SCRIPT>

The rest of the code exists in a subroutine named cmdbtn_Click. Triggered whenever the button is clicked on the form, it gets the current time with the aptly named Time intrinsic function. It then parses the string and plays the appropriate WAV files. Cmdbtn_Click was copied almost exactly from the original Visual Basic form, although the changes above were made to it. Looking back at it, I also realized I had been an idiot to waste my time parsing a time string by searching for colons within it. Instead, I simplified the code greatly by using the built-in Hour, Minute, and Second VBScript functions. After adding a bit of sparkle to the form, the complete HTML listing for the page is shown in Figure 4. And, if you act right now, I'll throw in—for free—the attractive screen shot of the application working in Figure 5!

Figure 4 Clock.htm


 <!DOCTYPE HTML PUBLIC "-//W3O//DTD W3 HTML 2.0//EN">

<HTML>
<head>
<title>
VBScript Talking Clock
</title>
</head>

<body>
<STYLE>
   HTML {color: white; background: #AAAAFF; margin-left: 8% }
   P    {font: 11pt/14pt Arial; color: #F6E60A; font-style: bold } 
   H2   {font: 18pt Arial; color: #730861; font-style: bold }
   H2 SPAN { font: 40pt Arial }
   BODY {font: 14pt Arial; color:#310c5a; font-style: bold }
   PRE  {font: 7pt Courier; color:#008800 }
</STYLE>

<h2>
<SPAN>T</SPAN>he <SPAN>T</SPAN>alking <SPAN>C</SPAN>lock<br>
A VBScript demo program written by Joshua Trupin<br>
(c) 1996, Microsoft Corporation
</h2>

<hr>
This demo shows the conversion of an existing Visual Basic program to VBScript.
To do this, the project needed a new (hidden) ActiveX control to play WAV files,
and some minor changes made to string functions. In addition, the problems
with distributing components (such as WAV files) had to be solved through
ActiveX code download services.
<hr>

Here's the clock:   


<!-- This is the button control  -->

<OBJECT ID="cmdbtn" WIDTH=96 HEIGHT=32
 CLASSID="CLSID:D7053240-CE69-11CD-A777-00DD01143C57">
    <PARAM NAME="Caption" VALUE="Speak!">
    <PARAM NAME="Size" VALUE="2540;846">
    <PARAM NAME="FontCharSet" VALUE="0">
    <PARAM NAME="FontPitchAndFamily" VALUE="2">
    <PARAM NAME="ParagraphAlign" VALUE="3">
    <PARAM NAME="FontWeight" VALUE="0">
</OBJECT>

<!-- This is the WavCtl control  -->

<OBJECT 
    CLASSID="clsid:5BB5C4E3-D723-11CF-A521-00A024A652FA"
    WIDTH=1
    HEIGHT=1
    hspace=0
    vspace=0
    ID=WavCtl1 
>
</OBJECT>

<!-- This is the IETimer control  -->

<OBJECT
    CLASSID="clsid:59ccb4a0-727d-11cf-ac36-00aa00a47dd2"
    id=Timer1
    align=middle
    width=1
    height=1
>
<PARAM NAME="_ExtentX" VALUE="10">
<PARAM NAME="_ExtentY" VALUE="10">
<param name="TimeOut" value="100">
<param name="Enabled" value="True">
</OBJECT>

<SCRIPT language="VBScript">
<!--


'==================================================================
' A late change in the IETimer control changed the event name from
' "Timer" to "Time" and the property from "Interval" to "TimeOut"

Sub Timer1_Time
   ' This is all you need to create a working clock with VBScript
   cmdbtn.caption = Time
End Sub    

'==================================================================
        
Sub cmdbtn_Click
    ' Whenever the user clicks on the time label, set into
    ' motion the code for saying the time.

    ' This is a lot easier than the original parsing mechanism

    Hrs = Hour(Time)
    Mins = Minute(Time)
    Secs = Second(Time)
    
    If Hrs = 0 Then Hrs = 12

    ' Pronounce the "it's"

    WavCtl1.Play "its.wav"

    ' The Hrs is simple - it is a digit between one and twelve, all of
    ' which exist in separate sound files

    WavCtl1.Play (Hrs + 0) & ".wav"
    
    ' Pronounce the "o'clock" on the hour

    If Mins = 0 Then
        WavCtl1.Play "oc.wav"

    ' Pronounce "oh-mins" if mins is a single digit

    ElseIf Mins < 10 Then
        WavCtl1.Play "oh.wav"
        WavCtl1.Play (Mins + 0) & ".wav"

    ' Pronounce a teen digit in its entirety

    ElseIf Mins < 20 Then
        WavCtl1.Play (Mins + 0) & ".wav"

    ' If the Mins are over 20, pronounce the tens part first, followed
    ' by the ones part. For instance, 34 becomes "thirty" "four"

    ElseIf Mins >= 20 Then
        WavCtl1.Play ((Mins \ 10) * 10) & ".wav"
        WavCtl1.Play (Mins Mod 10) & ".wav"
    End If

    ' Don't pronounce "and zero seconds"

    If Secs = 0 Then Exit Sub

    ' Pronounce your "and" after the minutes

    WavCtl1.Play "and.wav"

    ' You can just pronounce any number below twenty straight from the file,
    ' because you don't need to preface a single digit with an "oh"

    If Secs < 20 Then
        WavCtl1.Play (Secs + 0) & ".wav"

    ' If there are more than 20 seconds, pronounce it in two chunks, just
    ' like above for the minutes segment

    ElseIf Secs >= 20 Then
        WavCtl1.Play ((Secs \ 10) * 10)  & ".wav"
        WavCtl1.Play (Secs Mod 10) & ".wav"
    End If

    ' Say the closing "second" or "seconds," depending upon the number
    ' of seconds elapsed

    If Secs = 1 Then
        WavCtl1.Play "sec.wav"
    Else
        WavCtl1.Play "secs.wav"
    End If

End Sub
-->
</SCRIPT>

(Press the button to hear the clock speak.)
<hr>
Here's the code:
<PRE>
&lt;!-- This is the button control  --&gt;

&lt;OBJECT ID="cmdbtn" WIDTH=96 HEIGHT=32
 CLASSID="CLSID:D7053240-CE69-11CD-A777-00DD01143C57"&gt;
    &lt;PARAM NAME="Caption" VALUE="Speak!"&gt;
    &lt;PARAM NAME="Size" VALUE="2540;846"&gt;
    &lt;PARAM NAME="FontCharSet" VALUE="0"&gt;
    &lt;PARAM NAME="FontPitchAndFamily" VALUE="2"&gt;
    &lt;PARAM NAME="ParagraphAlign" VALUE="3"&gt;
    &lt;PARAM NAME="FontWeight" VALUE="0"&gt;
&lt;/OBJECT&gt;

&lt;!-- This is the WavCtl control  --&gt;

&lt;OBJECT 
    CLASSID="clsid:5BB5C4E3-D723-11CF-A521-00A024A652FA"
    WIDTH=1
    HEIGHT=1
    hspace=0
    vspace=0
    ID=WavCtl1 
&gt;
&lt;/OBJECT&gt;

&lt;!-- This is the IETimer control  --&gt;

&lt;OBJECT
    CLASSID="clsid:59ccb4a0-727d-11cf-ac36-00aa00a47dd2"
    id=Timer1
    align=middle
    width=1
    height=1
&gt;
&lt;PARAM NAME="_ExtentX" VALUE="10"&gt;
&lt;PARAM NAME="_ExtentY" VALUE="10"&gt;
&lt;param name="TimeOut" value="100"&gt;
&lt;param name="Enabled" value="True"&gt;
&lt;/OBJECT&gt;

&lt;SCRIPT language="VBScript"&gt;
&lt;!--

'==================================================================
' A late change in the IETimer control changed the event name from
' "Timer" to "Time" and the property from "Interval" to "TimeOut"

Sub Timer1_Time
   ' This is all you need to create a working clock with VBScript
   cmdbtn.caption = Time
End Sub    

'==================================================================
        
Sub cmdbtn_Click
    ' Whenever the user clicks on the time label, set into
    ' motion the code for saying the time.

    ' This is a lot easier than the original parsing mechanism

    Hrs = Hour(Time)
    Mins = Minute(Time)
    Secs = Second(Time)
    
    If Hrs = 0 Then Hrs = 12

    ' Pronounce the "it's"

    WavCtl1.Play "its.wav"

    ' The Hrs is simple - it is a digit between one and twelve, all of
    ' which exist in separate sound files

    WavCtl1.Play (Hrs + 0) & ".wav"
    
    ' Pronounce the "o'clock" on the hour

    If Mins = 0 Then
        WavCtl1.Play "oc.wav"

    ' Pronounce "oh-mins" if mins is a single digit

    ElseIf Mins &lt; 10 Then
        WavCtl1.Play "oh.wav"
        WavCtl1.Play (Mins + 0) & ".wav"

    ' Pronounce a teen digit in its entirety

    ElseIf Mins &lt; 20 Then
        WavCtl1.Play (Mins + 0) & ".wav"

    ' If the Mins are over 20, pronounce the tens part first, followed
    ' by the ones part. For instance, 34 becomes "thirty" "four"

    ElseIf Mins &gt;= 20 Then
        WavCtl1.Play ((Mins \ 10) * 10) & ".wav"
        WavCtl1.Play (Mins Mod 10) & ".wav"
    End If

    ' Don't pronounce "and zero seconds"

    If Secs = 0 Then Exit Sub

    ' Pronounce your "and" after the minutes

    WavCtl1.Play "and.wav"

    ' You can just pronounce any number below twenty straight from the file,
    ' because you don't need to preface a single digit with an "oh"

    If Secs &lt; 20 Then
        WavCtl1.Play (Secs + 0) & ".wav"

    ' If there are more than 20 seconds, pronounce it in two chunks, just
    ' like above for the minutes segment

    ElseIf Secs &gt;= 20 Then
        WavCtl1.Play ((Secs \ 10) * 10)  & ".wav"
        WavCtl1.Play (Secs Mod 10) & ".wav"
    End If

    ' Say the closing "second" or "seconds," depending upon the number
    ' of seconds elapsed

    If Secs = 1 Then
        WavCtl1.Play "sec.wav"
    Else
        WavCtl1.Play "secs.wav"
    End If

End Sub
--&gt;
&lt;/SCRIPT&gt;
</pre>
<hr>

</BODY>
</HTML>

Figure 5 Talking Clock

Obviously, this is a rough guide to the Visual Basic-to-VBScript conversion process. There's a lot more you can do with VBScript when combined judiciously with ActiveX controls. You can pretty much do anything from stupid little talking clocks to full-blown conferencing apps built on controls that provide WinSock support. In previous columns, I've shown how you can make Visual Basic 4.0 do anything you want if you're willing to put in just a bit of work to create a purpose-built OLE control. After converting my first program to VBScript, I've found that the same thing holds true. There's almost nothing you can't do with VBScript—provided you're willing to invest a couple of minutes creating an ActiveX control that will plug in the minor functional holes you might encounter along the way.

Have a question about programming in Visual Basic, Visual FoxPro, Access, Office, or stuff like that? Send your questions via email to Joshua Trupin: joshuat@microsoft.com.

This article is reproduced from Microsoft Systems Journal. Copyright © 1995 by Miller Freeman, Inc. All rights are reserved. No part of this article may be reproduced in any fashion (except in brief quotations used in critical articles and reviews) without the prior consent of Miller Freeman.

To contact Miller Freeman regarding subscription information, call (800) 666-1084 in the U.S., or (303) 447-9330 in all other countries. For other inquiries, call (415) 358-9500.