Using ActiveX and Java Applets Together

by Susie Adams

Applets, like ActiveX controls, often provide the ability to accept information from the document or the user at runtime. Applets provide this ability via an interface that allows the customization of applet behavior as it's browsed in a Web browser. You can accomplish this customization in two ways: first, input parameters and second, public methods and/or instance variables.

In the past, ActiveX developers using Java applets had only input parameters available to them. Unlike ActiveX controls, there was no way to call a method within an applet class with ActiveX script. The introduction of Microsoft's ActiveX Runtime for Java has changed this situation. Thanks to Internet Explorer, the ActiveX Runtime for Java allows access to public Java class methods and variables just as if the applet was an ActiveX control. As a result, your script can manipulate Java applets developed with any tool, not just those developed with Visual J++.

In this article, we'll demonstrate how to create an Active Server page that calls an already existing Microsoft Applet J++ applet. In the first example, we'll call the applet and then set its parameter. In the second example, we'll use ActiveX scripting to call two of its public methods.

The PARM tag

To pass the information from an HTML or ASP page to a Java applet you must use the <PARAM> tag. You can place this tag between the HTML <APPLET> and </APPLET> tags. The <PARAM> tag has two attributes, NAME and VALUE, which are used to pass data to a Java applet. Note that Documents can pass multiple parameters.

<APPLET CODE="displaytext.class" WIDTH=100 HEIGHT=100>
<PARAM> NAME="initialvalue" VALUE="Hi Susie">
</APPLET>

The Java applet then retrieves the parameters with the getParameter method:

String x=getParameter("initialvalue");

In the example above, we declare a variable x of type String in the .java file to hold the initialvalue parameter retrieved from the Web document. The parameter name is specified as the argument passed to getParameter in the applet code.

To demonstrate the use of parameters, we'll use a sample applet created by Microsoft called displaytext.class. This applet displays text in a Web page based on either an input parameter passed by the page or by input the user enters at runtime. To begin, let's create a base folder to hold the demo applications we'll build in this article. As you can see in Figure A, we named our folder D:\Cobbsart\JavaApplets\DisplayText.

Figure A

First, create a folder for your project.

To get the .class for this applet, open Visual J++ and select the Info View tab. Next, select the Samples, then Microsoft Samples, and click Scripting: VBScript Driving A Java Applet, as shown in Figure B. Follow the directions to copy the code from the J++ CD to the directory you created on your hard drive.

Figure B

Follow the instructions to load the sample J++ files to your hard drive.

You'll notice a number of files, including two HTML files. Feel free to look at them—but don't cheat. We're going to create the displaytext file as an ASP file later.

Now that we have our sample applet, let's begin by creating a new Web Project named DisplayTextVID. First, select the New option from Visual InterDev's file menu. In the dialog that appears, select the project tabs Web Project option and name the new Web project DisplayTextVID. Store the project in a subdirectory named DisplayTextVID under the demo directory you created earlier. In the dialogs that appear, select your web server and create the new web.

Next, create a new folder in the root of the Web named DisplayText and copy the displaytext.class file into it by right-clicking on the new folder and selecting the Add Files menu option. In the resulting dialog, navigate to the DisplayText directory and select the displaytext.class file. This class doesn't require the support of any additional files.

To call the displaytext.class, first create a new ActiveServer page named display.asp by selecting the Visual InterDev file menu's New option. In the dialog that appears select the file tab's Active Server Page menu option. Then add the following code to the ASP page:

<applet
    code=displaytext.class
    codebase=DisplayText
    width=600
    height=120 >
    <param name=initialValue value="Hello World">
</applet>

Notice the new PARAM tag. The value of this parameter is passed as the default text to display on the page. When you browse the display.asp page, you should see the text "Hello World" in the Web browser, as shown in Figure C. Open the page, change the parameter value, and browse the page again. The new parameter value should be displayed in the browser.

Figure C

On the browser page, you'll see "Hello World," from display.asp.

Manipulating a Java applet with VBScript

If you're familiar with scripting, you've probably already embedded an ActiveX control into a Web page and then manipulated it using VBScript. The ActiveX runtime for Java offers the same functionality for Java applets, making Java applets scriptable too. When a Java applet is running in a browser, all the public methods and variables of the applet automatically become available to VBScript or any other language supporting the ActiveX scripting protocol.

For example, consider the displaytext Java applet we used in the last example:

public class displaytext extends Applet
{
   // The value of the text to be displayed //by the applet.
   String m_phrase;

   public void setText(String string)
   {
      //Set the text to the string.
   }
   public void resetText()
   {
      //reset the text to the original //value.
   }
}

In our previous example, we included the applet in the ASP page by using the APPLET tag. To refer to the applet from the ActiveX scripting language you must add the ID attribute to the APPLET tag. This allows you to refer to public classes in an applet as you would refer to methods of an ActiveX control.

<applet>
    code=displaytext.class
   codebase=DisplayText
    id=TextDisplay
    width=600
    height=120 >
    <param name=initialValue value="Hello World">
</applet>

Next, let's add a few user interface items. We'll start with an input field to enter the intialvalue text and two buttons—one to set the text to the input value and one to reset the text to the parameter value originally passed by the parm tag:

<input name=Phrase value="Some new text">
<input type=button name=Set value="Set Display Text">
<input type=button name=Reset value="Reset to Initial Values">

In the scripting portion of your ASP page, define OnClick handlers for each button, allowing them to manipulate the applet:

<script language=VBScript>
<!--

' Handle the click event for the Set button
sub Set_OnClick
   ' Call the applet setText method,
   ' with the value in the Phrase <input> box
   document.TextDisplay.setText Phrase.value
end sub

' Handle the click event for the Reset button
sub Reset_OnClick
   ' Call the applet resetText method
   document.TextDisplay.resetText
end sub

-->
</script>

Whenever a user clicks a button or enters text in the input field, the appropriate VB Script handler is fired, which then allows the handler to manipulate the Java applet. Note: When you reference the applet from VBScript, you must always reference it by document.name. The complete code for display.asp is shown in Listing A.

Listing A:

The Display.ASP file modified to accept user input from an Input Field

<%@ LANGUAGE="VBSCRIPT" %>

<HTML>
<HEAD>
<META NAME="GENERATOR" Content="Microsoft Visual InterDev 1.0">
<META HTTP-EQUIV="Content-Type" content="text/html; charset=iso-8859-1">
<TITLE>Document Title</TITLE>
</HEAD>
<BODY>

<!-- Insert HTML here -->
<html>
<head>
<title>Display Text</title>
</head>

<script language=VBScript>
<!--

' Handle the click event for the Set button
sub Set_OnClick
   ' Call the applet setText method,
   ' with the value in the Phrase <input> box
   document.TextDisplay.setText Phrase.value
end sub

' Handle the click event for the Reset button
sub Reset_OnClick
   ' Call the applet resetText method
   document.TextDisplay.resetText
end sub

-->
</script>

<body>

<applet
    code=displaytext.class
   codebase=DisplayText
    id=TextDisplay
    width=600
    height=120 >
    <param name=initialValue value="Hello World">
</applet>
<p>

<input name=Phrase value="Some new text">
<input type=button name=Set value="Set Display Text">
<input type=button name=Reset value="Reset to Initial Values">

<hr>

</body>
</html>

</BODY>
</HTML>

To view the display.asp page, right-click on the page and select the Browse With menu option. You should now see the page, as displayed in Figure D.

Figure D

The finished display.asp file allows the user to update the text of the message.

Note: Only the Applet-derived class is directly accessible from scripting. If your Java applet includes other classes that you want to access, you must first define public methods in your Applet-derived class that call each of those classes.

Conclusion

As we've demonstrated, the addition of the ActiveX Runtime for Java takes a huge step towards making ActiveX and Java applets playground friends in the Web environment. We saw how Java applets could be driven just like ActiveX controls and how ActiveX script could read and write the public variables of the applet, as well as call its public methods.

You can even implement code that facilitates communication between Java applets and ActiveX controls using event handlers to connect them. Remember, we've only touched the surface of dealing with most Web developers' needs. To accommodate some of these needs, more sophisticated ways of allowing Applets and ActiveX to communicate have been developed. However, all of these methods require a working knowledge of how to use a COM object from Java.

Hopefully, we've just experienced the first wave of a flood of Java applet and ActiveX inter-operability tools that will be available in the near future to cover the gap between the two Web worlds. For now, anyway, these new tools are definitely a step in the right direction!

-----------------------------

Susie Adams is a senior consultant with Financial Dynamics, a client/server and Internet solutions consulting firm in McLean, Virginia. She has worked in the application development area for more than 11 years and currently focuses on the design and development of active content Web applications.

-----------------------------

This article is reproduced from the February 1998 issue of Active Server Developer's Journal. Active Server Developer's Journal is an independently produced publication of The Cobb Group. No part of this article may be used or reproduced in any fashion (except in brief quotations used in critical articles and reviews) without prior consent of The Cobb Group. To contact The Cobb Group, please call (800) 223-8720 or (502) 493-3200.

Copyright © 1998 The Cobb Group, a division of Ziff-Davis Inc. The Cobb Group and The Cobb Group logo are trademarks of Ziff-Davis Inc. All rights reserved. Reproduction in whole or in part in any form or medium without express written permission of Ziff-Davis is prohibited.


Making #INCLUDE Files "Hack-Proof"

by Jeremy Broyles

One of the many benefits of ASP and server-side scripting is that only the results of your scripts are sent to the client. This makes it tougher for hackers to copy your site’s design by simply viewing the source on the client side.

If you’re using ASP, chances are you’re using #INCLUDE directives somewhere in your site. Includes make Web development more efficient by reducing the need for redundant code. However, they can also provide a roundabout way for a hacker to view the code behind your site.

For example, let’s say that most of your site’s server-side script is contained in the file myscript.inc. If a hacker typed in the URL www.yoursite.com/myscript.inc, the file’s contents would be displayed to the client as-is, revealing the source code for the scripts along with everything else.

A simple way to avoid giving away your ingenious scripts in this manner is to use the .ASP-naming convention with include files that contain server-side script. Then, even when the correct file is requested, the Active Server engine will process the script and send only the results to the client.

Drats! Foiled again!

Jeremy Broyles is Editor-in-Chief of Active Server Developer's Journal and Microsoft Web Builder, both publications of The Cobb Group.

Copyright © 1998 The Cobb Group, a division of Ziff-Davis Inc. The Cobb Group and The Cobb Group logo are trademarks of Ziff-Davis Inc. All rights reserved. Reproduction in whole or in part in any form or medium without express written permission of Ziff-Davis is prohibited.


Implementing a Successful Registry Backup Strategy for Your NT Servers

By Mark Solomon

There's no substitute for making complete backups of your Windows NT Servers, but you'll probably despise having to restore Windows NT when you need to reset only the NT registry. For example, you might consider restoring Windows NT in the following situations, when you don't have a registry backup:

  • You might have installed the NetWare client for Windows NT, then had to do a repair on NT and found that NT's repair program was asking you for NetWare data you didn't have or couldn't supply. NT's repair program doesn't contain NetWare client information and may not repair your system.

  • You might have installed some virus-protection software on your NT server that didn't agree with your resident E-mail service. Because all core NT programs are installed as services, the programs would begin upon startup, before you had a chance to stop or disable the services, causing an endless cycle of system crashes.

  • If you're lucky, you might have simply installed some software that causes minor abnormalities in Windows NT's operation and that can't be removed cleanly.

Short of restoring the full Windows NT from a backup, you can restore just the Windows NT registry, provided you've backed up a good copy. In this article, we'll show you how to implement a complete strategy to back up and restore Windows NT's registry, should the need arise. You'll need the Windows NT 4.0 Resource Kit and administrative privileges to perform the steps in this article.

Overview

First, we'll show you how to automate registry backups by using the Windows NT Resource Kit's REGBACK command in combination with NT's Scheduler service. As you'll see, the Scheduler service will be responsible for running REGBACK on each NT Server with a registry you wish to back up.

Then, should you need to restore a Windows NT Registry, we'll explain how you can do so using various methods, including using the Windows NT Resource Kit's REGREST command. REGREST can take a fresh install of Windows NT and convert it into the NT system you're trying to recover.

Please note: You might think you can use the Registry Editor's Save Key… and Restore… commands to back up and restore an NT registry. However, these commands back up only portions of the registry at a time and don't back up the active portions of the registry. That's why the commands REGBACK and REGREST are very useful. They can completely and cleanly back up and restore a Windows NT registry.

Using REGBACK in batch files

We'll show you how to use the REGBACK command to back up an NT registry to a directory location. Unfortunately, REGBACK won't write over registry files it has previously written, so we'll have to create a batch file that first deletes those files and then executes the REGBACK command. You'll see how we use REGBACK to store registries from all our NT servers to a central location on the network. That way, you can use NTBACKUP or other backup software to easily restore those registries.

First, create the directory

NT_Registries

on one of the NT Servers whose registry you want to back up. You'll use this location to store registry backups of all your NT Servers. Next, create subdirectories in the NT_Registries directory and give them names that match the NT servers you wish to back up. For example, we created the subdirectories MICHAEL and DAVID to represent the computers in our domain, as shown in Figure A.

Figure A

We created the NT_Registries directory to store registries for all our NT Servers.

Now, share the NT_Registries directory as NT_Registries. If the NT_Registries directory is located on an NTFS partition, give only the Domain Admins group access and leave the default share permissions of Everyone and Full Control. However, if the NT_Registries dirory is on a FAT partition, give the Domain Admins group Full Control to the share. You don't want to give everyone access to your NT Server registries; with those registries, they could duplicate the structure of your NT server.

Please note: In order to back up registries, REGBACK.EXE must be executed by a user with at least Backup Operator privileges.

Now, we'll show you how to use the REGBACK command. First, copy REGBACK.EXE from its Resource Kit location on your hard drive into the \%SYSTEMROOT%\SYSTEM32 directory so it will be in NT's default path.

Next, suppose you created the NT_Registries share on an NT Server called MICHAEL. To execute REGBACK, you'd open a command line window on MICHAEL and type a command similar to the following:

REGBACK \\MICHAEL\NT_Registries\MICHAEL

As shown in Figure B, this command backs up MICHAEL's registry to the NT_Registries share's MICHAEL subdirectory. Also, if you had another NT Server on your network called DAVID that you wanted to back up, you'd copy REGBACK to its %SYSTEMROOT%\SYSTM32 directory. To do so, go to a command line prompt and type

REGBACK \\MICHAEL\NT_Registries\DAVID

which yields the same results.

Figure B

REGBACK stores the NT registry data in a location you specify.

Along these same lines, we're going to use REGBACK to back up our NT server registries from servers on our network to the share NT_Registries. However, as we mentioned, REGBACK won't write over previously written registries. To circumvent this, we created a batch file that first deletes these registry entries and then runs the REGBACK.EXE command, as shown in Figure C. Notice that we piped y into the deletion command to respond to NT's standard prompt when using wildcards to delete:

Are you sure (Y/N)?

Figure C

Create a batch file for each NT Server that deletes old registry files and uses REGBACK to create new registry backups.

You must create a similar batch file for each NT Server whose registry you wish to back up. After you've tailored a batch file for each of your NT Servers, save the batch file in each NT Server's \%SYSTEMROOT%\SYSTEM32 directory. We did so, and we named our batch files MICHAEL_REGBACK.CMD and DAVID_REGBACK.CMD.

Now, run the batch files to verify that they delete and back up registries properly. For example, we ran MICHAEL_REGBACK.CMD on the MICHAEL NT server, as shown in Figure D.

You might be wondering why we used a UNC name with the DEL command and with the REGBACK command. We did this to make batch files for DAVID and MICHAEL as similar as possible. That way, we can easily reproduce and modify the batch file for additional computers, should the need arise. Also, the AT job-scheduling command, which we'll use later, may have trouble connecting to resources over the network. Using the UNC name in a local batch file makes NT think it's connecting over the network, so you can test for network-connection problems on the same computer.

Figure D

Test the REGBACK batch files you're created for each of your NT Servers to verify they work properly.

Please note: To test our batch files, we placed the pause command at the end because we want to verify the results when we execute them with the AT command. This causes the command window to remain on the screen rather than disappearing. That way, you can see the output from REGBACK to determine if it executed properly. Otherwise, backed up or not, the command window will close, and you'll be left wondering whether your registries were actually backed up—you'll have to look at the registry file dates to find out.

Initializing the Scheduler service

Now that we've created batch files that will back up our NT registries, we'll show you how to use the AT command to automate executing those batch files on a daily basis. By default, the NT's Scheduler service is set to manual startup, as shown in Figure E.

Figure E

By default, when you install Windows NT, the Scheduler service isn't started and is set to manual.

So, open the Services applet and double-click the Scheduler service. Now, enable the Automatic radio button. Then, enable the This Account radio button and click the adjacent ellipsis button. Select an administrative account that you want to use for the Scheduler service. Then type the appropriate password for the administrative account and confirm it in the Password and Confirm Password text boxes, as shown in Figure F. Now, click OK to confirm your selections and close the Services dialog box. You can now restart the Scheduler service.

Please note: You may be wondering why we're adding an administrative account to the Scheduler service. If you ran some tests of your own, you'd find that the Scheduler service will run REGBACK just fine without any special permissions because it uses the local System account on NT. However, as you can see by the batch files we've created, we're copying registries over the network to the NT_Registries share. The local System account isn't authorized to do this—it has only guest privileges on remote NT systems.

You may find that Microsoft documentation recommends applying the NET USE command in conjunction with an administrative password to force the Scheduler service to connect to remote accounts. However, if you added this functionality to a batch file, the password would be visible for all to see. Adding the administrative account to the Scheduler service avoids this potential problem.

Figure F

Add an administrative account to the Scheduler service and set the service to start automatically.

You'll want to follow this procedure for every computer in your NT domain whose registry you want to back up. Not to worry, you can use Server Manager to modify your NT server's services, as shown in Figure G.

Figure G

You can easily modify the Scheduler service on other computers using Server Manager.

Once you've modified the Scheduler service for all your Windows NT Servers, you'll want to schedule jobs using the AT command—you can also do this from the command line of a single networked computer. For example, we scheduled the MICHAEL_REGBACK.CMD and DAVID_REGBACK.CMD batch files on the computers MICHAEL and DAVID, as shown in Figure H. We used the commands

AT \\MICHAEL 06:00 /INTERACTIVE /EVERY:M, T, W, Th, F "MICHAEL_REGBACK.CMD"
AT \\DAVID 06:00 /INTERACTIVE /EVERY:M, T, W, Th, F "DAVID_REGBACK.CMD"

Make sure you specify the appropriate computer name for each AT command, along with the appropriate batch file name for the computer.

We used the INTERACTIVE option with the AT command so that a command window will appear when our REGBACK jobs are executed. As we mentioned, we placed the pause command in those batch files so the command windows would remain after the batch execution, allowing you to look at the REGBACK results.

Interestingly, the INTERACTIVE option also works if you're logged off your machine. When you log back on, you'll see the command window detailing the results of the batch files that AT executed. Once you're satisfied that AT is working properly with these batch files, you can remove the INTERACTIVE option. Then, the AT command will run jobs in the background—whether you're logged on or not.

Unfortunately, the AT command won't leave a trace of its execution-successful or unsuccessful-in the event log. One way to check on the AT jobs after you've deleted the INTERACTIVE option is to verify the creation times on the newly created registry files in the NT_Registries directory. If all is working properly, the creation times will correspond to the times you've scheduled their associated AT counterparts to run.

Figure H

We scheduled REGBACK batch files to run at 6:00 a.m. on both MICHAEL and DAVID using the AT command in a command-line window on MICHAEL.

Please note: To learn more about the AT command, see the article "Scheduling Events with the AT Command" in the September 1996 issue of Exploring Windows NT.

Caveats when using the Scheduler service

Since the Scheduler service needs only backup operator privileges to run andyou don't want folks to be able to schedule jobs with AT that'll automatically be run at the administrative level, you'd think that you could add an account operator to the schedule service instead of an administrative account. But when we tried to add an account operator to the Scheduler service, the results were unacceptable.

First, when we added a Backup Operator account of any kind to the Scheduler service, we could not—even as administrator—schedule jobs. We'd get an access denied message, as shown in Figure I. So, we thought we'd be crafty and create our scheduled jobs before we added the Backup Operator account to the Scheduler service.

Figure I

We couldn't add a new AT job to the Scheduler service when we added a Backup Operator to the Scheduler service and restarted it.

Second, we stopped the Scheduler service, added the Backup Operator account, then restarted the service. We executed the AT command to list the scheduled jobs we created previously—they were still there. However, when our REGBACK job tried to run, the Scheduler service erred, giving the message box shown in Figure J.

To make a long story short, the only way to add REGBACK commands to the Scheduler service's AT command and have them access other computers on the network (short of using an embedded password in a NET USE command) is to add an administrative account to the Scheduler service. Now you're ready to restore a registry, should the need arise.

Windows NT gave us an error when we first added jobs to the Scheduler service and then added a Backup Operator account to the Scheduler service.

Restoring a Windows NT registry

Depending on which file system , your Windows NT system partition uses-FAT or NTFS-restoring a sick NT system to full health could be a tricky operation. For example, consider these three scenarios:

  • You've tried to repair a failing Windows NT system that won't start. The Windows NT system partition is NTFS.

  • You've tried to repair a failing Windows NT system that won't start. The Windows NT system partition is FAT.

  • You've tried to heal a Windows NT system that starts but has trouble running. The system partition can either be FAT or NTFS.

Scenario 1

Scenario 1 is clearly the toughest. You can't run NTBACKUP to immediately restore the system because Windows NT isn't running on the system. Windows NT doesn't have an NTFS boot floppy you can use to start your system and inspect or modify registry files.

Please note: Unsupported software does exist that will let you create an NTFS boot floppy. This software scares Microsoft to death because with it, anyone can break into an NTFS system. Subsequently, Microsoft is adding encryption to NT 5.0 to prevent break-in possibilities like this.

Here, there's only one thing you can do: Reinstall Windows NT in a separate directory, partition, or both. However, try reinstalling Windows NT in the same drive as your failing installation if you possibly can. Then, you can start the system using the new Windows NT installation and manipulate the registry of the old Windows NT installation using the following steps:

  • If your backed-up registry files are on a different system, copy them into a directory in the troubled NT system. For example, copy them to a directory called REGSAV.

  • Delete SYSTEM.* and *.EVT files from the REGSAV directory to remove any references to hardware.

  • Move your failing NT system's registry files in %SYSTEMROOT%\SYSTEM32\CONFIG to a different directory location, such as %SYSTEMROOT%\SYSTEM32\CONFIG\REGOLD.

  • Now, copy your saved registry files in REGSAV to %SYSTEMROOT%\SYSTEM32\CONFIG, restart your computer, and select your original system.

If this doesn't work, you'll probably want to restore Windows NT from a tape backup using the new Windows NT system you just installed. If you don't have a backup copy of your Windows NT, there's one more option.

Provided that you've installed the new Windows NT in the same partition as your old NT, you can use the REGREST command to restore your old NT registry on the newly installed NT system. Of course you'll want to have all the same services running—though not necessarily configured—on your new NT system so that it'll have the same executables, DLLs, and other files that your old system used.

As a final step, you'll want to rename your new NT's %SYSTEMROOT% directory to be that of your old one, which means you'll first have to rename your old NT %SYSTEMROOT% something else.

Please note: You may find that you didn't have enough disk space to create another copy of Windows NT in the same partition as your old one. You may be tempted to run REGREST on an NT system you've created in a new partition—and it may work, but beware: Programs and registry entries may expect Windows NT to be at its original drive and directory location. Unforeseen problems could arise. If you're tempted to do this because of a lack of disk space in your original NT partition, you'd be better of deleting the old NT, reinstalling a new NT in its place with the same services, and running REGREST to restore the registry.

Scenario 2

If your Windows NT partition uses FAT, you can boot your system with a DOS floppy and perform the four steps outlined in Scenario 1 to copy your backed-up registry files to your system, restart your system, and hope for the best. If this doesn't work as outlined in Scenario 1, you'll have to reinstall a fresh copy of Windows NT and use REGREST to restore your registry on that system.

Scenario 3

If your NT system is still functioning, albeit poorly, you can use REGREST to restore a saved registry to your sick NT system, as we'll show you next. REGREST works like a champ!

An example using REGREST

Now we'll show you how to easily restore an NT registry on a running NT system. For example, if we wanted to restore the registry on the NT server computer, MICHAEL, we'd type something like

REGREST E:\SAVEDREG E:\OLDREG

where SAVEDREG is a directory that contains the saved registry information we want to restore and OLDREG is a directory location where REGREST places the registry data that it replaces. As shown in Figure J, REGREST successfully restored our registry data. Now we need to reboot Windows NT to let the new registry take effect. You probably know this, but don't test REGREST on a production machine. Rather, find a test NT system you can use.

Figure J

Use REGREST in the same drive location as your NT installation to successfully restore a registry.

You may be wondering why we didn't use the UNC naming convention, resulting in \\MICHAEL\NT_Registries\MICHAEL as the location of the saved registries. The reason is that REGREST only works in the drive location where your NT installation resides, and our Windows NT installation resides in E:\%SYSTEMROOT%.

Please note: To learn more about using REGBACK and REGREST, type the commands

REGBACK | more

and

REGREST |more

We've found these command help screens to be better than the documentation in the Windows NT Resource Kit.

Conclusion

In this article, we've told you about the importance of registries, how to back them up on a regular basis, and how to restore them. Short of restoring Windows NT from a tape backup, restoring a Windows NT registry can be a quick and effective way to bring your Windows NT system back on line. A variety of methods exist for restoring your registry depending on the file system you're using and whether your Windows NT system is in working condition.

__________________

Mark Solomon is the former Editor-in-Chief of Exploring Windows NT. He's now a consultant for Kizan Technologies, Inc., a Microsoft Solution Provider.

__________________

This article is reproduced from the February 1998 issue of Exploring Windows NT. Exploring Windows NT is an independently produced publication of The Cobb Group. No part of this article may be used or reproduced in any fashion (except in brief quotations used in critical articles and reviews) without prior consent of The Cobb Group. To contact The Cobb Group, please call (800) 223-8720 or (502) 493-3200.

Copyright © 1998 The Cobb Group, a division of Ziff-Davis Inc. The Cobb Group and The Cobb Group logo are trademarks of Ziff-Davis Inc. All rights reserved. Reproduction in whole or in part in any form or medium without express written permission of Ziff-Davis is prohibited.


Sharing Files with Services for Macintosh

by Dominic Bosco

Do you have any Macintosh computers on your network? If so, you’ll be happy to know you can install NT’s Services for Macintosh (SFM) that allows you to make the same data accessible to both Windows and Macintosh users. In this article, we’ll discuss SFM’s system, client, and license requirements. We’ll show you how to install SFM and configure your system to share files with Macintosh, Windows 95, and Windows NT users. We’ll then explain how to monitor your server’s Macintosh client connections and tell you about other ways of managing Macintosh-Accessible volumes.

Overview

Unfortunately, you can’t set up a peer-to-peer network and expect a Macintosh to be able to access a share on a machine running Windows 95 or Windows NT Workstation. Instead, you have to use a client-server configuration, place all your data in a shared directory on a Windows NT Server. You can then use SFM to make the share accessible to Macintosh clients (this is known as creating a Macintosh-Accessible Volume). Network clients will then be able to work with the same files, even though some users may be using Windows while others are using a Mac. For example, suppose you create a document in Microsoft Word for Windows. Once you install and configure SFM, a coworker can access that document, use Word for the Mac to modify it, then place it back on the server so you can see those revisions using Word for Windows.

Caveats

Unlike Windows clients, Macintosh clients can’t access normal shares on an NT server. Instead, you must create a special directory for them on the NT server. These special directories, which will store all the files that you’ll use cross-platform, must reside on an NTFS or CDFS (Compact Disk Filing System) volume.

Like any other Windows NT service or component, SFM is going to consume some hard disk space. For the component alone, you’ll need an extra 2MB, and you must allocate additional storage space to hold any data you’re planning to put on the server.

Macintosh client requirements

Most Macintosh computers that use AppleShare networking software can connect to your NT server. The only exceptions are the Macintosh XL and Macintosh 128K models. The SFM component requires version 6.0.7 or later of the Macintosh operating system. It also supports versions 2.0 and 2.1 of the AppleTalk Filtering Protocol.

License requirements

Although you don’t need a special license to connect a Macintosh to an NT server, you do need a client license for each Macintosh that you plan to attach to the server. You can get these licenses from your Microsoft retailer.

Installing SFM

Installing SFM is easy. To do so, first double-click the Network icon in your NT server’s Control Panel. Next, select the Services tab on the Network Properties sheet, then click Add. In the Select Network Service dialog box, choose Services for Macintosh from the Network Service list, as shown in Figure A, and click OK.

Figure A

Select SFM from the Network Service list.

At this point, Windows NT Server may ask for your installation media. If it does, provide the path to the \I386 directory on your Windows NT Server CD or insert the appropriate disk, then click OK. When Windows NT Server finishes copying the necessary files, it will return you to the Network Properties sheet. Click Close, and Windows NT will reconfigure its bindings and display the Microsoft AppleTalk Protocol Properties sheet, shown in Figure B.

Figure B

Match your default network adapter with your default AppleTalk zone.

In order for SFM to communicate with other network computers that belong to the same AppleTalk zone, you must match your default network with your AppleTalk zone. If you’re unsure of which zone to choose, try accepting the default zone. If that selection doesn’t work for you, start picking various combinations until you find one that works. Then, click OK and restart your computer to effect the changes.

When your computer restarts, double-click the Services icon in Control Panel. In the resulting dialog box, shown in Figure C, you’ll notice that SFM has automatically installed a couple of components, including File Server for Macintosh and Print Server for Macintosh. These two services perform background functions like establishing and maintaining your AppleTalk connection. If your Macintosh machines have trouble connecting to your NT server, be sure that the File Server for Macintosh service is started. Click Close to dismiss this dialog box.

Figure C

SFM automatically installs File Server for Macintosh and Print Server for Macintosh.

If you look in Windows Explorer, you’ll notice that the Windows NT Server automatically creates a directory on your hard disk called Microsoft UAM Volume, as shown in Figure D. At this point in the procedure, this directory is the only directory on your hard disk that a Macintosh can access.

Please note: SFM shares folders for Macintosh clients only. If you want Windows 95 and Windows NT clients to be able to access a Macintosh-Accessible Volume, you must also create a share for it as you would in the normal manner.

Figure D

The Microsoft UAM Volume directory is Macintosh-accessible.

Creating, removing, and modifying Macintosh-Accessible Volumes

Installing SFM adds the MacFile menu to the Server Manager. To create, delete, or modify Macintosh-Accessible Volumes, open Server Manager and select the server containing the volumes. Then choose the Volumes option from the MacFile menu to display the Macintosh-Accessible Volumes window, shown in Figure E. Clicking either the Create Volume... or Remove Volume button will display dialog boxes that allow you to perform these tasks.

Figure E

The Macintosh-Accessible Volumes dialog box lets you create, remove, or modify Macintosh-Accessible Volumes.

Creating a Macintosh-Accessible Volume is analogous to creating a normal share. Before you create a Macintosh-Accessible Volume, you must first create the folder that will become the Macintosh-Accessible Volume. First, open the Macintosh-Accessible Volumes window, then click the Create Volume… button to display the Create Macintosh-Accessible Volume window. Then, enter the volume name (again, analogous to the share name for a normal shared directory) and path to the folder you created that you want to become the Macintosh-Accessible Volume.

Please note: You cannot create a Macintosh-Accessible Volume inside another Macintosh-Accessible Volume. In this respect, Macintosh-Accessible Volumes aren’t analogous to normal shared directories.

To remove a Macintosh-Accessible Volume, simply select the volume in the Macintosh-Accessible Volumes window and click the Remove Volume button shown in Figure E. When you do, you’ll be asked to confirm the volume’s removal. Removing a Macintosh-Accessible Volume doesn’t delete the directory, but simply makes it inaccessible to Macintosh clients.

Greetings

You can give your server a different identity for Macintosh clients and even designate a special greeting that users will see when they connect to your Macintosh-Accessible Volumes. Installing SFM creates the MacFile icon in your Server’s Control Panel. Double-click this icon to display the MacFile Properties dialog box and click the Attributes Button. The Macfile Attributes dialog box, shown in Figure F, will appear. Click the Change... button to assign a new name for the server that a Macintosh client will see when it uses Chooser to browse the AppleTalk network. In the Logon Message field, enter a polite greeting for Macintosh clients.

Figure F

You can specify a special greeting for Macintosh clients.

Monitoring Macintosh connections

When you open Server Manager and double-click on your NT server, you’ll see information about PC users who are connected to the server. However, you won’t see any Macintosh connections. To view these connections, double-click the MacFile icon in Control Panel to launch a utility that monitors Macintosh connections. This utility functions in a way nearly identical to Server Manager in regard to monitoring user connections.

Other ways of managing Macintosh-Accessible volumes

If you’re managing several Macintosh-Accessible volumes, you may find that File Manager, NT 3.51’s old, faithful friend, can be quite helpful. Installing SFM adds a MacFile menu to File Manager’s task bar—a menu that lets you create, view, and remove Macintosh-Accessible volumes and modify their permissions. Simply execute winfile.exe from NT 4.0’s Run command to open File Manager, and select a Macintosh-Accessible Volume. Then choose the appropriate command from the MacFile menu, shown in Figure G. This menu also features the Associate… command, which opens a dialog box that lets you associate DOS filename extensions with Macintosh Document Creator and file types. The only other way you can create these associations is to use the MACFILE command at the command prompt. Type MACFILE /? at the command prompt for a description of the command’s syntax.

Figure G

You can use File Manager’s MacFile menu to create, view, and remove Macintosh-Accessible volumes.

Conclusion

In this article, we’ve shown how you can use a Windows NT Server to share data among Windows NT, Windows 95, and Macintosh computers. We walked you through the process of installing and configuring NT’s Services For Macintosh, which makes

Dominic Bosco is Editor-in-Chief of Exploring Windows NT, a publication of The Cobb Group.

Copyright © 1998 The Cobb Group, a division of Ziff-Davis Inc. The Cobb Group and The Cobb Group logo are trademarks of Ziff-Davis Inc. All rights reserved. Reproduction in whole or in part in any form or medium without express written permission of Ziff-Davis is prohibited.


Transactions: Take the ACID Test

Gary DeWitt

Imagine going to the ATM to transfer money from your savings account to your checking account. Between the time the money comes out of savings and goes into checking, the computer crashes! Where’s your money? Don’t worry, transactions keep it safe. Gary explains how in the first of a series on transactions.

Think very carefully about what goes on behind the scenes at your bank. You take for granted that nothing will happen to your money in the middle of making deposits, withdrawals, and transfers. Corporations, banks, and nations routinely transfer millions of dollars electronically. World financial markets would crumble if those funds weren’t secure.

Let me construct a simple example to illustrate the importance of transactions. At the First National Bank of Dad, customers can deposit money into accounts at any time. They can also withdraw funds from their accounts, as long as the amount of the withdrawal is less than or equal to the balance in the account. Furthermore, customers with more than one account can transfer money from one to another.

The FoxPro application used to manage accounts at the bank does so by means of an Account class. This class has two methods: Deposit() and Withdraw(). It’s possible for either of these methods to fail to update the database if some system failure, such as a power outage, were to occur prior to a deposit or withdrawal being completed. A withdrawal could also fail if the account has insufficient funds to cover the withdrawal.

The real test of the system designer’s skill comes when funds are transferred between accounts. This requires a deposit into one account and a simultaneous withdrawal from another. Notice that I said simultaneous. The withdrawal and deposit can’t occur sequentially; they must occur simultaneously, as it’s absolutely unacceptable for the funds not to be in one account or the other. Either the deposit and the withdrawal both succeed, or they both fail, simultaneously. How do we do that?

The ACID test

A transaction is a protected group of database updates. All database changes made between the time a transaction begins and the time it ends are protected. Ideally, a transaction will be able to pass the ACID test of being Atomic, Consistent, Isolated, and Durable.

In chemistry, an atom is the smallest indivisible unit that possesses all the characteristics of an element. Likewise, an atomic transaction is the smallest indivisible unit of acceptable change. In the transfer example, a completed transaction requires changes to two accounts. A change to only one account wouldn’t be acceptable.

Consistency means that the transaction correctly transforms the system state. Either the funds leave one account and enter the other, or they stay put—the only two possible correct states. If the transfer state isn’t achieved, then the system reverts to the non-transfer state rather than stopping somewhere in between.

Isolation ensures that no other transaction can see a transaction in an incomplete state. Although there’s actually a tiny fraction of a second when funds have been withdrawn from one account but not yet deposited into another, no other transaction in the system can know that.

A durable transaction is one that’s capable of surviving system failures intact. A transaction should be able to survive all failures, including server, process, communication, and media failures.

Well, that’s the ideal, at least.

What can we as FoxPro developers do to provide transaction protection for our applications? Moreover, how can we do so in an efficient, productive manner? How can we integrate transaction protection into a variety of application architectures? In this series of articles, I’ll be taking a look at implementing transactions in robust, scalable, object-oriented applications. I’ll cover both local and remote data and take a look at new technologies in transaction processing.

VFP transaction basics

The first thing to remember about using transactions with local VFP data is that they don’t work with free tables. The tables in the transaction must belong to a database container (DBC). The second thing to remember is to keep transactions brief. Limit the time between the beginning and end of a transaction as much as possible, because other users will be prevented from accessing any records involved in the transaction for its duration.

To protect a group of updates in a transaction, issue the BEGIN TRANSACTION command. Every change you make to the data will be part of the transaction until END TRANSACTION or ROLLBACK is issued. If you issue END TRANSACTION, then all changes will be committed. Issuing ROLLBACK will revert all changes and end the transaction. Here’s a simple example (this and the other examples in this article are contained in the Subscriber Downloads at www.pinpub.com/foxtalk):

CREATE DATABASE mydatabase
SET DATABASE TO mydatabase
CREATE TABLE mytable (myfield I)
?"Record count = " + STR(RECCOUNT("mytable"))

*-- First transaction
BEGIN TRANSACTION
  INSERT INTO mytable (myfield) VALUES (1)
END TRANSACTION
?"Record count = " + STR(RECCOUNT("mytable"))

*-- Second transaction
BEGIN TRANSACTION
  INSERT INTO mytable (myfield) VALUES (2)
ROLLBACK
?"Record count = " + STR(RECCOUNT("mytable"))

In this example, a DBC is created and a table added to it. The table has one field with an integer data type. There are two transactions in this example. In the first transaction, a record is added to the table and the transaction is committed with END TRANSACTION. Notice that the record count, which is zero before the transaction, is one after the END TRANSACTION command.

The second transaction is rolled back with the ROLLBACK command. Notice that the record count remains one after the ROLLBACK command, despite the insertion of a new record within the transaction. The change wasn’t committed, and the table reverted to its previous condition.

If you’ve done any work with buffered tables or SQL views, then you’ve probably noticed that the use of a transaction in this example does nothing more than buffering. Combining buffering with transactions in sort of a double-buffering scheme will allow you to better diagnose and resolve errors in updates in ways that are impossible with buffering alone. Here’s an example:

USE mytable
CURSORSETPROP("Buffering", 3)
INSERT INTO mytable (myfield) VALUES (3)
INSERT INTO mytable (myfield) VALUES (4)
BEGIN TRANSACTION
IF TABLEUPDATE(1, .F.)
  END TRANSACTION
ELSE
  ROLLBACK
ENDIF

In this example, the table is buffered and changes are made in the buffer prior to starting the transaction. Within the transaction, I attempt to update the table from the buffer with TABLEUPDATE(). The TABLEUPDATE() function specifies all rows, so it will succeed only if each individual updated row succeeds, in which case I’ll commit the transaction. TABLEUPDATE() will fail if any row update fails, in which case I’ll roll back the transaction. Rolling back the transaction doesn’t revert the buffered records. After rolling back, the buffered records still contain all the changes. I can then find out why the TABLEUPDATE() failed and attempt to address the problem. If I solve the problem, I can begin another transaction and attempt the update again.

If you’ve done a lot of work with buffered tables, I’m sure you’ve noticed that you could have accomplished the same thing by issuing TABLEUPDATE() one row at a time rather than using this transaction business. The real power of transactions isn’t realized until you need to protect updates to multiple tables.

USE mytable IN 0
USE yourtable IN 0
CURSORSETPROP("Buffering", 3, "mytable")
CURSORSETPROP("Buffering", 3, "yourtable")
INSERT INTO mytable (myfield) VALUES (5)
INSERT INTO yourtable (yourfield) VALUES (6)
BEGIN TRANSACTION
IF TABLEUPDATE(1, .F., "mytable")
  IF TABLEUPDATE(1, .F., "yourtable")
    END TRANSACTION
  ELSE
    ROLLBACK
  ENDIF
ELSE
  ROLLBACK
ENDIF

In this example, I’ve added records to two different buffered tables and then begun a transaction. If the update succeeds in the first table, I’ll attempt to update the second table. The transaction is committed only if both updates succeed. If only one update succeeds, or if both fail, then the entire transaction is rolled back. Now we’re getting somewhere. Protection to updates in multiple tables can only be provided with transactions.

Nesting transactions

Let’s go back to my First National Bank of Dad example. The goal is to create an account object that can manage deposits and withdrawals and a transfer object that can contain account objects for protected movement of money from one account to another. Managing a combination like this is made easier by FoxPro’s ability to nest transactions. VFP allows transactions to be nested up to five levels deep. Commits and rollbacks to all nested transactions are controlled by the outermost transactions (the very first BEGIN TRANSACTION).

USE mytable
?"Transaction level = " + STR(TXNLEVEL()) +
  "  Record count = " + STR(RECCOUNT("mytable"))

*-- Outer transaction
BEGIN TRANSACTION
?"Transaction level = " + STR(TXNLEVEL()) +
  "  Record count = " + STR(RECCOUNT("mytable"))

*-- Inner transaction
BEGIN TRANSACTION
INSERT INTO mytable (myfield) VALUES (6)
?"Transaction level = " + STR(TXNLEVEL()) +
  "  Record count = " + STR(RECCOUNT("mytable"))

*-- Commit inner transaction
END TRANSACTION
?"Transaction level = " + STR(TXNLEVEL()) +
  "  Record count = " + STR(RECCOUNT("mytable"))

*-- Roll back outer transaction
ROLLBACK
?"Transaction level = " + STR(TXNLEVEL()) +
  "  Record count = " + STR(RECCOUNT("mytable"))

This example nests two levels of transactions. At each step of the way, it displays the level of the transaction and the number of records in the table. The TXNLEVEL() function returns the level of transactions. After each BEGIN TRANSACTION command, TXNLEVEL() returns the next higher number. A record is inserted into the table in the inner transaction, and the transaction is committed with END TRANSACTION. Notice that TXNLEVEL() is decremented from two to one. Then the outermost transaction is rolled back. The record insertion performed in the inner transaction is rolled back by the outer transaction, and the table is returned to the condition it was in before any changes were made.

Summary

Nesting transactions is the key to solving the problem presented by the bank account transfer problem. The transfer object provides the outermost transaction, and the account objects reside in inner transaction levels. If both inner transactions succeed, then the outer transaction is committed. But if either of the inner transactions fails, the outer transaction is rolled back. Even if one of the inner transactions succeeds, thus committing its changes, the rollback of the outer transaction will undo any changes committed in the inner transactions.

In the next article in this series, I’ll demonstrate how to incorporate transactions into an object-oriented design that effectively models the problems presented by the bank account transfer. I’ll also take a look at the issues involved in trying to achieve the same results with remote data.

Download 01DEWITA.ZIP at www.pinpub.com/foxtalk

Click here to open or copy the DEWITA sample object files

Gary DeWitt is senior software magician at Medsoft, Inc., a medical practice management software company in Tahoe City, CA. While Visual FoxPro is his favorite development tool, he also programs in C++, Java, and Visual Basic. Gary is a Microsoft Certified Professional and Microsoft MVP. 73512.57@compuserve.com, gdewitt@sierra.net.

To find out more about FoxTalk and Pinnacle Publishing, visit their website at
http://www.pinppub.com/foxtalk/ .

Note: This is not a Microsoft Corporation website.
Microsoft is not responsible for its content.

This article is reproduced from the January 1998 issue of FoxTalk. Copyright 1998, by Pinnacle Publishing, Inc., unless otherwise noted. All rights are reserved. FoxTalk is an independently produced publication of Pinnacle Publishing, Inc. No part of this article may be used or reproduced in any fashion (except in brief quotations used in critical articles and reviews) without prior consent of Pinnacle Publishing, Inc. To contact Pinnacle Publishing, Inc., please call (800)788-1900 or (206)251-1900.


User-Friendly Field Advance in VB 4.0

by Michael E. Burke

Users of data forms sometimes need strong visual reinforcement to confirm their actions. For example, users may need visual clues to indicate that they're entering data in the correct field. In addition, many users are accustomed to using the [Enter] key to move from field to field while entering data. In this article, we'll show you how to implement these form techniques in your projects. To illustrate, we'll build the form shown in Figure A.

Figure A

We'll write a routine to change the color of the active field in a form.

What not to do

Meeting the needs we've described seems simple enough: Just change the ForeColor and BackColor properties of the active field and capture the [Enter] key, making it act like a tab advance. Implementing a solution is a little tricky, however. For example, you might begin by capturing the [Enter] key and translating it to [Tab] by using the procedure shown here:

Private Sub txt_TextFields_KeyPress _
(KeyAscii As Integer)
   If KeyAscii = 13 Then
      KeyAscii = 0
      SendKeys "{TAB}"
   End If
End Sub

Simple, right? The problem is that when your focus is on a Command button, the program doesn't detect the keypress, and the button fires instead of advancing the focus to the next field on the form.

While it's possible to design some methods to make the above code work correctly, all the capture and retranslating presents a potential maintenance problem for anyone else who doesn't know what you're doing. Fortunately, the Visual Basic Programmer's Guide presents an alternative technique that uses the TabIndex property. The Programmer's Guide code is only a skeleton—not taking into account Data controls, Timer controls, and some third-party controls—but you can build on it to create the example we'll look at here.

Building the example

To keep things simple, we'll build our example around the Biblio.mdp database that comes with Visual Basic. Copy this database from your Visual Basic directory to your working directory, then use the Data Form Designer to build the form. Use the Publishers table for enough fields to make the test worthwhile, and name the form Data1.

We'll leave the form exactly as the Data Form Designer generated it, with two exceptions. First, replace the Data1.Caption text in the Data1_Reposition procedure with the following:

Data1.Caption = "Record: " & _
(Data1.Recordset.AbsolutePosition + 1) & _
" of " & (Data1.Recordset.RecordCount)

Doing so will let us see how many records are in the database.

The second change is to make the Close button's code read

Private Sub cmdClose_Click()
   GsbCloseMe
End Sub

This will ensure that the pointer returns to its normal state instead of remaining as an hourglass. Since the hourglass typically indicates that the system is still engaged, leaving it onscreen would detract from our goal of making the input process less confusing.

In order for the Data control to determine how many records are in the database, you must add the following code to the Form_Activate event:

If data1.Recordset.RecordCount > 0 Then
data1.Recordset.MoveLast
data1.Recordset.MoveFirst
End If

If there are no records, nothing happens. Otherwise, this routine moves the pointer to the end of the record set, thus counting the records, then moves it back to the first record.

Adding a module

The real work of this technique takes place in Module1, shown in Listing A. Create this module now, then we'll see what the code does.

Listing A: Module1 code

Option Explicit
Public gsDBName As String
Public gsTable As String
Public go_dbObject As Database
Public go_rsObject As Recordset

Public Sub gsbBGChangeFocus()
' This routine highlights the active control.
   If TypeOf Screen.ActiveControl Is TextBox
      Or TypeOf Screen.ActiveControl Is ComboBox
      Or TypeOf Screen.ActiveControl Is ListBox
      Or TypeOf Screen.ActiveControl Is MaskEdBox
      Or TypeOf Screen.ActiveControl Is DBCombo
   Then
      Screen.ActiveControl.BackColor = &HFFFFFF
      Screen.ActiveControl.ForeColor = &HFF0000
   End If
End Sub

Public Sub gsbTextClicked()
'This routine changes the highlight and sets the focus if the user clicks the control.

   Dim lcAControl As Control
   For Each lcAControl In Screen.ActiveForm.Controls
      If TypeOf lcAControl Is TextBox
   Or TypeOf lcAControl Is ComboBox
   Or TypeOf lcAControl Is ListBox
   Or TypeOf lcAControl Is MaskEdBox
   Or TypeOf lcAControl Is DBCombo
      Then
         lcAControl.BackColor = &HC0E0FF
         lcAControl.ForeColor = &H0
      End If
   Next lcAControl
   gsbBGChangeFocus
End Sub

Public Sub gsbCloseMe()
'Calling this routine makes sure that the form is unloaded and the mouse is reset to normal.
   Unload Screen.ActiveForm
   Screen.MousePointer = vbNormal
End Sub

Public Sub gsbFieldAdvance(KeyAscii As Integer)
'This routine moves the focus and the highlight to the next qualified field when the user presses
'the return key.

   Dim liNextTabIndex As Integer
   Dim liICount As Integer
   Dim liCycCount As Integer
   Dim lbDFlag As Boolean
   Dim lbMATCH As Boolean
   Dim lcAControl As Control
      If KeyAscii = 13 Then
         liICount = 0
         ' Checking to be sure that we don't set the counter beyond the number
         ' of controls on the form.
         If Screen.ActiveControl.TabIndex = Screen.ActiveForm.Controls().Count - 1 Then
            liNextTabIndex = 0
         Else
            liNextTabIndex = Screen.ActiveControl.TabIndex + 1
         End If
      Do Until lbDFlag = True
         lbDFlag = False
         If liNextTabIndex = Screen.ActiveForm.Controls().Count - 1 Then
            liNextTabIndex = 0
         End If
         For Each lcAControl In Screen.ActiveForm.Controls()
            lbMATCH = False
               If TypeOf lcAControl Is Data Or TypeOf lcAControl Is Timer Then
               'Catch controls without tabindexes here...See
               ‘Help for full list, but remember to check custom controls.
                  liICount = liICount
               Else
                  If TypeOf lcAControl Is MaskEdBox Or TypeOf lcAControl Is TextBox _
                     Or TypeOf lcAControl Is OptionButton _
                     Or TypeOf lcAControl Is CheckBox Then
                     'What controls to highlight
                        If lcAControl.TabIndex = liNextTabIndex Then
                        'if they are next in sequence
                           lbMATCH = True
                           lcAControl.SetFocus
                           lbDFlag = True
                           Exit For
                        End If
                  Else
                     If lcAControl.TabIndex = liNextTabIndex Then
                     'If the index matches but the control is not right, we move on
                        liNextTabIndex = liNextTabIndex + 1
                     End If
                  End If
               End If
               liICount = liICount + 1
               If liICount > 1000 Then
               'Insurance...Maybe the user won't want to CTL-Alt-DELETE....
                  If liCycCount > 5 Then
                  ' and this can be adjusted as much as needed to account for
                  ‘ numerous controls without tab indexes
                     lbDFlag = True
                     MsgBox "Exceeded search cycle limit. Call support."
                     Exit For
                  Else
                     liICount = 0
                     liNextTabIndex = liNextTabIndex +1
                     liCycCount = liCycCount + 1
                  End If
               End If
         Next lcAControl
      Loop
      KeyAscii = 0
   End If
End Sub

The first routine in Module1, gsbBGChangeFocus, changes the foreground and background colors whenever a field on the form receives focus. The next routine, gsbTextClicked, ensures that when the user clicks an entry field, that field changes to an appropriate color. We use this routine as a master so we only have to maintain one routine. The third routine, gsbCloseMe, simply ensures that the form unloads properly.

Most of the real work on the module occurs in gsbFieldAdvance, which handles the movement from one field to another. Let's look at exactly what this routine does.

gsbFieldAdvance

VB will crash the application if it tries to evaluate a control that doesn't exist. However, forms can be expected to have differing numbers of controls. So, after declaring variables, we have the routine count the number of controls to avoid setting the counter beyond the number of controls available. Then we initialize the counter liNextTabIndex to be either one more than the current control, or if we are at the last of our controls, we set it to be 0 (the first control).

The search order will be different for each form; it will also change on any specific form each time you modify any control. Maintaining precise indexing would mean extra work for a programmer each time the form was modified. On the other hand, letting VB keep track of the indexing means we have to be sure that, no matter how the form has changed, the routine will eventually cycle through all the legal controls and change the ForeColor and BackColor to indicate the focus.

The Dataform Designer usually numbers the controls in the order it generates them (which is top-left to bottom-right), but I admit that I actually adjust the TabIndex properties of each form after I make alterations so the user doesn't have to follow eye-popping color changes all over the form. Using a Do loop, however, will ensure that the user can hit [Enter] enough times to land on the right control even if the programmer isn't as picky as I am.

It would've been better to use a Case statement in this routine, but the Select Case statement in VB4 doesn't recognize the Typeof function. Using nested If-Then statements, we trap the controls that would crash the program by making it look for a TabIndex that doesn't exist.

There's a list of these in the online help file under TabIndex, but be sure to check the properties of any third-party controls for other potential problems. You should also examine the controls for which you would normally allow users to enter data and, finally, make provisions for anything that you might miss.

To make sure we get a graceful exit, we count the trips through the Do loop (liICount). The more controls you have on the form, the higher your “insurance” number must be. (One of my forms has 61 controls, and the Do loop executes a minimum of 12 times and a maximum of 684, which explains why my insurance number is 1,000.)

Notice that we use a second counter, liCycCount. If there's more than one control without a TabIndex property, this second variable will allow the Do loop to execute until the process succeeds. It's not unusual to have two or more Data controls and a Timer control. You can adjust the second counter to account for a variety of your forms.

Coding the form

Before we can try out our project, we must make a few changes to the form. Add the code in Listing B to the txtFields object.

Listing B: Code for txtFields object

Private Sub txtFields_Click(Index As Integer)
   gsbTextClicked
End Sub

Private Sub txtFields_GotFocus(Index As Integer)
   gsbTextClicked
End Sub

Private Sub txtFields_KeyPress(Index As _
   Integer, KeyAscii As Integer)
   If KeyAscii = 13 Then
      KeyAscii = 0
      Form_KeyPress (13)
   End If
End Sub

Finally, add the following code to the Form_KeyPress event:

Private Sub Form_KeyPress(KeyAscii As Integer)
   If KeyAscii = 13 Then
      KeyAscii = 0
      gsbFieldAdvance (13)
   End If
End Sub

Notice that the KeyPress event in the fields calls the KeyPress event of the form. This allows the form to trap other behaviors or entry errors, or simply pass the ASCII value to the routine that does the work.

Conclusion

One way to reduce the number of errors that creep into your data is to make forms simple to understand and easy to follow. Using color or contrast to mark the current location during data entry can greatly assist users. In this article, we've shown you how to highlight the active field on a form and how to let users press [Enter] to move among fields.

_____________________

Mike Burke has been programming computers since 1965, when he learned to do cryptology on an IBM 1401 using an assembly language called Autocoder. He's currently consulting on Visual Basic and Java, writing technical articles, and doing technical illustration. You can reach Mike at meburke@electrotex.com.

____________________

This article is reproduced from the February 1998 issue of Inside Visual Basic. Inside Visual Basic is an independently produced publication of The Cobb Group. No part of this article may be used or reproduced in any fashion (except in brief quotations used in critical articles and reviews) without prior consent of The Cobb Group. To contact The Cobb Group, please call (800) 223-8720 or (502) 493-3200.

Copyright © 1998 The Cobb Group, a division of Ziff-Davis Inc. The Cobb Group and The Cobb Group logo are trademarks of Ziff-Davis Inc. All rights reserved. Reproduction in whole or in part in any form or medium without express written permission of Ziff-Davis is prohibited.


Building External Objects with Visual Basic

by Michael C. Amundsen

In Inside Visual Basic's December 1997 articles “Classy Development” and “Come Fly the Friendly Skies of Visual Basic 5.0,” we discussed object-oriented development and how you can build standalone classes for use in your development work. This approach involves designing and building complete, compiled external objects and using those objects as the building blocks for your applications.

In this article, we’ll continue discussing class development. Specifically, we’ll show you how to create an object that writes data to text files. (The article “Using External Objects with Visual Basic” [Inside Visual Basic, January 1998] covers techniques for making use of the object you’ll design and build here.)

Creating compiled building blocks

Creating a set of reusable component objects is much more powerful than writing source code libraries for a couple of reasons. First, source code libraries are tied to a single language, such as VB, VC++, or J++. In fact, source code libraries are sometimes tied to a particular version of the language. Compiled components don’t suffer a single-language limit. You can use the external objects you build with VB in several development environments, including VC++, J++, Access, Excel, and other Office 97 applications.

Passing out source code also creates management problems. It’s all too easy for programmers to alter the source code to fit specific needs—and even unintentional changes can render the object useless for another programmer. By passing out only the compiled component, others can’t alter the object in any way, either accidentally or on purpose.

Although the process of designing, coding, and testing your building blocks takes time, the real advantage of creating compiled building blocks comes when it’s time to build a complete application. Once you’ve built a set of objects, creating a custom solution for customers (internal or external) is much quicker and less error-prone. The time invested in building these objects can pay back in big ways after just a few uses of the component in a production application.

Object, please

Let’s say you’ve been asked to build a set of routines that allow programmers to easily create log file entries in a disk file. This set of routines should be simple to use and very portable. It should meet a number of general needs:

You can use it to get basic user input and write it to a disk file.

You can use it to log user actions in a secure application (log in/out, etc.)

You can use it as an error log for applications.

You can use it to create a log of unattended operations for applications.

In addition, programmers should easily be able to add these routines to existing programs or access them from programs without the need to incorporate source code directly into their applications. Finally, the routines should be flexible enough that programmers can create their own dialogs and interfaces to the routines.

Designing the TextFile object

The first step in designing an object is to clearly identify the actions needed to complete the task. Then, you must identify all the materials needed to perform those actions. Armed with this information, you can design an object with all the necessary methods and properties.

To start, let’s call our object the TextFile object. Programmers will use this object to write data to a disk file. Although we only want a method to write data to the disk file, we’ll need a few other methods in order to complete the task. Table A lists the methods required for the TextFile object.

Table A: Methods for the TextFile object

Method | Public | Description
OpenFile | No | Opens the disk file for write access.
CloseFile | Yes | Closes the disk file after all writing is complete.
WriteLine | Yes | Writes a line of text to the open disk file.
CheckChar | No | Checks for invalid characters in filename.
MakeFullFileName | No | Ensures that a complete filename, including folder, body name, and file tail, is created.

You’ll notice that of the five methods in Table A, the only two marked Public are CloseFile and WriteLine. These are the only two methods available for others to use. The other three methods (OpenFile, CheckChar, and MakeFullFileName) are internal routines that won’t be published.

Next, you must define the materials needed to complete the actions—these are the properties of the TextFile object. Table B shows the suggested property list for our example.

Table B: Properties for the TextFile object

Property | Public | RW/RO | Description
FileFolder | Yes | RW | Contains computer, drive, and path of disk file (i.e., C:\MYFOLDER).
FileName | Yes | RW | Contains name of disk file (i.e., LOGFILE).
FileTail | Yes | RW | Contains file tail of the disk file (i.e., TXT).
FileMode | Yes | RW | Contains value to determine if file writes will overwrite or append to existing file.
FullFileName | Yes | RO | Contains complete name of target disk file (i.e., C:\MYFOLDER\LOGFILE.TXT).

Of the five properties defined in Table B, only one is read-only (RO). This read-only property can be seen by other users, but not altered. It’s common for objects to have one or more read-only properties.

Now that we’ve discussed all the actions and materials needed to complete the TextFile object task, it’s time to use Visual Basic to create the required object. (You can download our sample files from www.cobb.com/ivb, as part of the file jan98.zip. Just click the Source Code hyperlink.)

Coding the TextFile object

In order to get the most use out of the TextFile object, you must create a standalone DLL. You can do this using Visual Basic 5.0 or Visual Basic 4.0-32. If you’re using Visual Basic 4.0-16, you can still complete this project by creating an OLE EXE module. (The example in this article was built using Visual Basic 5.0.)

The first step in building the TextFile object is to create a new Visual Basic project. If you’re using Visual Basic 5.0, start Visual Basic and select ActiveX DLL as the project type. You’re now ready to begin adding code to the class module, so skip the next paragraph.

If you’re using Visual Basic 4.0 (32 or 16 bit), start Visual Basic, remove the default form, and add a BAS Module to the project by selecting Module from the Insert menu. Enter a single subroutine in this BAS module called Main, as follows:

Public Sub Main()

End Sub

Don’t add any code to this routine. It only exists to make sure Windows executes the DLL/EXE and registers its class object for use by others. Next, add a class module to the project (Select Insert | Class Module).

Now, set the Name property of the class module to TextFile. If you’re using Visual Basic 5.0, set the Instancing property to 5 - MultiUse. If you’re using Visual Basic 4.0, set the Public property to True and the Instancing property to 2 - Creatable MultiUse. This step guarantees that others can create their own copies of the TextFile object.

Next, set the name of the project. If you’re using Visual Basic 5.0, highlight the project in the Project Window, and set the Name property to prjTextFile. If you’re using Visual Basic 4.0, select Tools | Options | Project, and set the Project Name to prjTextFile.

Please note: It’s very important that you set the Class Name and Project Name properties correctly. All programmers who want to access your object methods and properties will use these two properties.

You’re now ready to add code to the project. First, you must create some local storage space to hold the values of the published properties of the object. To do so, add the code in section 1 of Listing A to the General Declarations section of the class module.

Listing A: TextFile external object

Section 1

Option Explicit

'
' define local storage for properties
Private mstrFileName As String
Private mstrFileFolder As String
Private mstrFileTail As String
Private mstrFullFileName As String
'
' for vb5 only
' create enumerated type
Enum tfFileMode
    overwrite = 0
    append = 1
End Enum
Private mtfMode As tfFileMode

' for vb4 only
'Private Const overwrite = 0
'Private Const append = 1
'Private mtfMode As Integer

'
' define private vars
Private mintFileChannel As Integer

Section 2

Private Sub Class_Initialize()
    '
    ' set initial values
    '
    mstrFileName = "logfile"
    mstrFileFolder = App.Path
    mstrFileTail = "txt"
    MakeFullFileName
    '
    mintFileChannel = -1
    mtfMode = append
    '
End Sub

Section 3

Private Sub MakeFullFileName()
    '
    ' build full filename
    '
    mstrFullFileName = mstrFileFolder & "\" & _
mstrFileName & "." & mstrFileTail
    '
End Sub

Section 4

Private Function CheckChar(strTemp As String,
strInvalid As String) As Boolean
    '
    ' check for invalid filechars
    '
    Dim intLoop As Integer
    Dim intLen As Integer
    Dim strChar As String
    '
    strTemp = Trim(strTemp)
    intLen = Len(strTemp)
    '
    For intLoop = 1 To intLen
        strChar = Mid(strTemp, intLoop, 1)
        If InStr(strInvalid, strChar) <> 0 Then
            CheckChar = True
            Exit Function
        End If
    Next
    '
    CheckChar = False
    '
End Function

Section 5

Private Sub OpenFile()
    '
    ' open file for output
    '
    On Error GoTo OpenFileErr
    '
    mintFileChannel = FreeFile
    '
    If mtfMode = append Then
        Open mstrFullFileName For Append As
 mintFileChannel
    End If
    '
    If mtfMode = overwrite Then
        Open mstrFullFileName For Output As
mintFileChannel
    End If
    '
    Exit Sub
    '
OpenFileErr:
    Err.Raise Err.Number, App.EXEName &
"_OpenFile", Err.Description
    '
End Sub

Section 6

Public Sub WriteLine(strLine As String)
    '
    ' write line to output file
    '
    On Error GoTo WriteLineErr
    '
    If mintFileChannel = -1 Then
        OpenFile
    End If
    '
    Print #mintFileChannel, strLine
    '
    Exit Sub
    '
WriteLineErr:
    Err.Raise Err.Number, App.EXEName &
"_WriteLine", Err.Description
    '
End Sub

Section 7

Public Sub CloseFile()
    '
    ' close open channel
    '
    On Error Resume Next
    '
    Close #mintFileChannel
    mintFileChannel = -1
    '
End Sub

Section 8

Public Property Get FileName() As String
    '
    FileName = mstrFileName
    '
End Property

Section 9

Public Property Let FileName(ByVal vNewValue As String)
    '
    vNewValue = Trim(vNewValue)
    If CheckChar(vNewValue, "`!@^*:;'<>,.?/\|")
Then
        Err.Raise 380 ' invalid property
    Else
        mstrFileName = vNewValue
        MakeFullFileName
    End If
    '
End Property

Section 10

Public Property Get FileTail() As String
    '
    FileTail = mstrFileTail
    '
End Property

Public Property Let FileTail(ByVal vNewValue As
String)
    '
    vNewValue = Trim(vNewValue)
    If CheckChar(vNewValue, "`!@^*:;'<>,.?/\|")
Then
        Err.Raise 380 ' invalid property
    Else
        mstrFileTail = vNewValue
        MakeFullFileName
    End If
    '
End Property

Section 11

Public Property Get FileFolder() As String
    '
    FileFolder = mstrFileFolder
    '
End Property

Public Property Let FileFolder(ByVal vNewValue As
String)
    '
    vNewValue = Trim(vNewValue)
    If CheckChar(vNewValue, "`!@^*;'<>,.?/|") Then
        Err.Raise 380 ' invalid property
    Else
        If Right(mstrFileFolder, 1) <> "\" Then
            mstrFileFolder = mstrFileFolder & "\"
        End If
        mstrFileFolder = vNewValue
        MakeFullFileName
    End If
    '
End Property

Section 12

Public Property Get FullFileName() As String
    '
    FullFileName = mstrFullFileName
    '
End Property

Public Property Let FullFileName(ByVal vNewValue
As String)
    '
    Err.Raise 383 ' read-only msg
    '
End Property

Section 13

Public Property Get FileMode() As tfFileMode
'Public Property Get FileMode() As Integer ' vb4
FileMode = mtfMode
    '
End Property

Public Property Let FileMode(ByVal vNewValue As
tfFileMode)
'Public Property Let FileMode(ByVal vNewValue As
Integer) ' vb4
    '
    If vNewValue = append Then
        mtfMode = append
        Exit Property
    End If
    '
    If vNewValue = overwrite Then
        mtfMode = overwrite
        Exit Property
    End If
    '
    Err.Raise 380 ' invalid property value
    '
End Property

Section 14

Private Sub Class_Terminate()
    '
    CloseFile
    '
End Sub

In the first section of code, you can see that, along with the four string variables, we used an Enum to create an enumerated data type. This is a custom data type that we’ll use to define the file mode (overwrite or append). The Enum is only available in Visual Basic 5.0. If you’re using Visual Basic 4.0, use the constants that are commented out. You should also note that we defined a private variable to use as the file channel pointer for the actual disk write operation (mintFileChannel).

Next, you need to add code to the Class_Initialize event to set the default values for your object properties. It’s always a good idea to set defaults for your properties. Add the code from section 2 to the Class_Initialize event.

You can see that the internal file pointer is set to -1. This is an invalid value for file pointers. You can use this value as a flag to let you know that the file pointer hasn’t yet been set.

Coding the TextFile support methods

The TextFile object has three support methods: CheckChar, MakeFullFileName, and OpenFile. These are internal routines that handle some of the behind-the-scenes work within the object. Although these routines are necessary, you probably don’t want to allow other users to access this code. For this reason, make all three of these routines Private rather than Public.

First, add the MakeFullFileName method to your class. If you’re using Visual Basic 5.0, select Tools | Add Procedure. If you’re using Visual Basic 4.0, select Insert | Procedure. Then, type MakeFullFileName in the Name box. Set the Type to Sub and the Scope to Private. Now, add the code from section 3 to the MakeFullFileName subroutine.

You can see this method just concatenates the stored file folder, filename, and file tail values into a single variable. This is the variable that we’ll use to open the disk file.

Next, add the CheckChar method to the class. This method takes two parameters: the string to check and a list of characters to look for. If at least one of those characters is in the string, the routine returns true. You’ll use this routine to check for invalid characters in the folder, file, and tail properties. Add the CheckChar function to your class and enter the code from section 4.

Finally, add the OpenFile method to the class. This routine will be called from within the public WriteLine method. Other users will never get access to this method. Add the OpenFile subroutine to your class and enter the code from section 5.

There are two points of interest in the OpenFile method. First, note the use of If statements to check the value of tfMode. This value determines whether the file will be overwritten each time or appended. Also notice the use of the Err.Raise method in the error trap. The Err.Raise method will send an error report back to the calling program instead of popping up a dialog box. It’s very important that all objects that have no interface return error reports, not dialogs.

Coding the TextFile public methods

Now you’re ready to add the two public methods, WriteLine and CloseFile, to the object. These two methods will be available to others to use. First, add the WriteLine method to the class object. Be sure to set the Scope to Public. Then add the code from section 6 to the WriteLine method. Please note that the WriteLine method first checks to see if the file has been opened. If not, it calls OpenFile.

Next, add the CloseFile method from section 7 to the object. This routine simply closes out the file when requested. As you can see, the file channel pointer is reset to -1 at this time in case the user tries to perform a WriteLine after executing the FileClose method. After adding the object methods, you’re ready to add the object properties.

Adding the object properties

There are five TextFile object properties defined in Table B. Properties are a way to give others controlled access to your local storage space. Two methods are actually used: The Let method allows users to send you data to store, and the Get method allows users to get a copy of your stored data. To build a complete property interface for your object, you must add a Let and a Get method for each property in your object.

Luckily, Visual Basic provides an easy way to do this. If you’re using Visual Basic 5, select Tools | Add Procedure. If you’re using Visual Basic 4.0, select Insert | Procedure. Then, enter FileName in the Name box. Be sure to select Property as the Type and Public as the Scope before you press the OK button. You’ll see that this creates both the Let and the Get methods for your property.

You can now add code to each method. Since the Get method lets users get a copy of your stored data, all you need to do is write code that copies your local memory variable back to the user. Section 8 shows how to do this. Notice that we changed the return type from Variant to String in order to match the local storage type.

Now you’re ready to code the Let method. Others will use this method to send you data to store. The real advantage of the Let method is that you can write code to check the values received before you store them. This gives you a chance to send an error message back to the user, if necessary. Section 9 shows the code for the FileName Let method.

Notice the error checking and reporting. After the CheckChar() method is called and if it returns true, the Err.Raise method is invoked using an existing error number. Visual Basic reports error number 380 when a property setting is invalid. It’s always a good idea to re-use existing Visual Basic errors for the same errors in your objects. Doing so will give users a familiar feedback and makes it easier for them to write error-handling code for your objects. Also, note that after the new property is accepted, the MakeFullFileName method is called to update the disk filename to use in all write actions.

The Property Let and Get statements for the other properties are almost identical. The Get statements simply echo the storage value and the Let statements perform the same error-checking before finally storing the received data in a local storage variable. Next, create the FileTail property and add the code from section 10 to the Property Let and Property Get methods.

The FileFolder property has a slight variance. Since the folder names can contain slashes and semicolons, the list of invalid chars is altered to fit folder names. Add the FileFolder property and enter the code from section 11.

The next property to build is the FullFileName property. This property is marked as read-only (RO) in Table B. You want to allow users to perform the Get operation, but not the Let operation. Create the FullFileName property and add the code from section 12.

The final property to build is the FileMode property. If you’re using Visual Basic 5.0, you’ll be creating a property of the type tfFileMode. If you’re using Visual Basic 4.0, make the property type Integer. Section 13 has the code for the Property Let and Get methods. That’s the end of the properties for the TextFile object.

One final touch

There’s one more method you must add to the TextFile object: the Class_Terminate event. This event fires each time the object is destroyed. This is a good place to add code to clean up any unfinished business, close open files, etc. Section 14 has the code for the Class_Terminate event.

You can see that the only thing that occurs here is a call to the CloseFile method. This will ensure that the data is properly flushed to the disk and that the file is safely closed.

Compiling the object

Now that you’ve completed the coding, you’re ready to compile the object into a usable module. If you’re using Visual Basic 5.0 or Visual Basic 4.0-32, you’ll create a DLL. If you’re using Visual Basic 4.0, you’ll create a special kind of EXE called an OLE Server EXE.

Usually, you should test your object before compiling for the final time. Testing the object is quite different with Visual Basic 4.0 or Visual Basic 5.0. For this reason, we’ll skip the details of testing right now. The article “Using External Objects with Visual Basic” [Inside Visual Basic, January 1998] shows you how to test your object.

If you’re using Visual Basic 5.0, select File | Make prjTextFile DLL… to build the DLL. If you’re using Visual Basic 4.0-32, select File | Make OLE DLL. If you’re using Visual Basic 4.0-16, select File | Make EXE. This will build the final product that other programmers can use. Remember that the Visual Basic 4.0-16 version will create an EXE, while the 32-bit versions of Visual Basic will create a DLL.

You can use this final compiled object not only within other Visual Basic applications, but also from Access, Excel, and other Office 97 applications.

Michael C. Amundsen keeps busy writing and working as an IS consultant specializing in systems analysis and design. He co-authored Teach Yourself Database Programming with Visual Basic in 21 Days with Curt Smith and was a contributing author for Visual Basic 4 Unleashed. When Mike isn’t traveling to client sites or writing, he spends time with his family in Kentucky. You can reach Mike via CompuServe at 102641,1267 or on the Internet at MikeAmundsen@msn.com.

Copyright © 1998 The Cobb Group, a division of Ziff-Davis Inc. The Cobb Group and The Cobb Group logo are trademarks of Ziff-Davis Inc. All rights reserved. Reproduction in whole or in part in any form or medium without express written permission of Ziff-Davis is prohibited.


Scriptlets

By Dino Esposito

With the introduction of Dynamic HTML in Microsoft® Internet Explorer 4.0, you can access the content of a Web page in an object-oriented fashion and make changes to it on-the-fly. Just as a macro language is the best way to enhance an application, scripting is the natural way to work with Dynamic HTML (DHTML). Originally, script languages were little more than glue between the various elements of an HTML page. Now, Internet Explorer 4.0 and DHTML give script writers the ability to modify the contents of a page programmatically without going through the server. Since the concept of a page now includes both the content and the code used to change it, scripting evolves from a simple way to automate individual components into the more active role of a content producer. DHTML becomes a real development platform for all HTML-based code, including an enhanced Web-based Windows® shell through the Active Desktop™. As with any newly introduced rich object model, coding starts out a bit more difficult. And as usual, when programming becomes more complex, code reusability becomes a central issue.

Scriptlets are a simple, elegant way to wed DHTML scripting with reusability. They allow you to create HTML-based objects that can be inserted in both HTML pages and ActiveX™ host applications. I’ll explore scriptlets and how they work in the Internet Explorer 4.0 environment. I’ll also discuss how to write them, and illustrate my explanations with a couple of samples. The source code I’ll provide even includes a wizard for the Developer Studio™ environment that gives you a bare-bones scriptlet in a snap (see Figure 1).

Figure 1: Scriptlet Wizard in Action

What is a Scriptlet Anyway?

Another name for scriptlets might have been ActiveX script controls—ActiveX components consisting of script code instead of binary code. Of course, there are a number of fundamental differences between a standard ActiveX control and a scriptlet, but from the user’s standpoint they are much the same. A scriptlet is nothing more than an HTML file with a body and some script code. It’s identified by name, not with an unintelligible 128-bit CLSID. A scriptlet requires neither compilation nor registration and is distributable as is. To use scriptlets, you need the release version of Internet Explorer 4.0 (available at http://www.microsoft.com/ie/ie40) or a late release candidate. Builds as late as Platform Preview 2 include neither support for scriptlets nor documentation for them.

What do standard ActiveX controls and scriptlets have in common? First of all, the interface appears the same to a programmer. Both controls and scriptlets expose properties, methods, and events and may be driven via automation. Furthermore, both can be hosted in an HTML page and any other ActiveX document container.

ActiveX controls can boast an edge in performance because they’re not interpreted, but scriptlets are far easier and quicker to write, as well as being considerably smaller and self-contained. Scriptlets, though, are made of pure HTML text and subject to undesired inspection as well as inadvertent changes. On the other hand, scriptlets are immediately ready for all OSs that host Internet Explorer 4.0.

Why Scriptlets?

Scriptlets are an easy way to facilitate scripting code reuse, and DHTML allows you to use the content of a Web page as the ordinary user interface of a desktop app. This means you can implement animation effects and functions like drag-and-drop text with relatively simple code. For example, to select and highlight an image when the mouse is over its client site, you just have to change the src attribute of the corresponding <IMG> tag. Changing the src attribute is easy with DHTML, but a code fragment like this can make the task even simpler and can be reused on any page for any image:

Sub DoChangeImage(i, sImage)
  Set coll = document.images
  coll.item(i).setAttribute "src", sImage
End Sub

The next step is quite straightforward. Why not write an extended version of the image element that can change a bitmap when the mouse moves over it? This would be a self-contained object made of pure HTML code and pieces of VBScript or JScript™ code: a scriptlet.

If you’re thinking that this is just another marketing gimmick, consider that the advantages of having reusable script code become more and more evident when you design and code large-scale Web apps. You can write a scriptlet in a few minutes, and the full edit-compile-debug cycle requires only that you save changes and press F5 to refresh Internet Explorer 4.0. Furthermore, since a scriptlet’s interface is nearly identical to that of an ActiveX control, you can port it from VBScript to Visual Basic 5.0 (or C++) and create an ActiveX control at your leisure.

How Scriptlets Work

A scriptlet is an HTML file that can be nested inside another HTML page, which is known as a container. In its host environment (the container), the scriptlet occupies a given site; this client area is indicated as a tag attribute at design time. The output produced by the scriptlet appears only inside that area. The parent page behaves like it includes a viewer for HTML documents. To the scriptlet, the site appears to be the traditional window object provided by the Internet Explorer scripting object model.

To include a scriptlet in a page, you use an <OBJECT> tag as if you were working with standard ActiveX controls. The only difference is that you must indicate a DATA or URL
attribute instead of CLASSID, and you must specify a particular MIME type: text/x-scriptlet. On the Windows platform, Internet Explorer runs scriptlets through a special container object, which exposes its own object model to the scriptlet. In other words, when Internet Explorer detects a scriptlet object in an HTML page (by encountering the scriptlet MIME type), it creates an instance of a specialized container control that accepts a URL and displays its content. This viewer, of course, provides full support for Dynamic HTML features.

Because of the sophisticated security built into Internet Explorer 4.0, scriptlets download to your computer only if the security level isn’t set to High for their server’s zone (a setting of Medium or less will work fine). If you use custom security settings, make sure that “Script ActiveX controls marked safe for scripting” is enabled. Also, “Initialize and script ActiveX controls not marked as safe” should be set to either Enable or Prompt (see Figure 2). Since a scriptlet may contain other scriptlets or controls from different zones, these settings ensure all will work fine.

Figure 2: Security Settings

Structure of a Scriptlet

The inner structure of a scriptlet follows the same layout as a standard ActiveX control. If you’ve used Visual Basic 5.0, this will be obvious. A Visual Basic 5.0 control is usually made up of constituent or child controls that you arrange and make interact to suit your needs. A scriptlet has an HTML body that may contain controls, images, applets, marquees, lines, or anything else that DHTML can handle. Visual Basic hosts and renders an ActiveX control inside a container of type UserControl. A scriptlet can refer to the DHTML object model as its host environment. Specifically, a scriptlet can call the window or document object to find out about its container or enumerate all its child elements.

A scriptlet is an HTML page where the body represents the user interface; the body can either be defined at design time or on-the-fly, and can be changed dynamically. The scriptlet source code must also include some scripting code. To write it, you can use and mix VBScript and JScript (or other scripting languages such as Perl, if you have the proper interpreter on your machine).

The script code implements the functionality you want the scriptlet to provide as well as some internal helper routines. Some of these procedures can be declared as public and made visible to external callers. There are two ways you can define the scriptlet’s public interface. The first relies on the JScript public_description object:

<script language="JScript">
public_description = new CreateScriptlet();

CreateScriptlet is a placeholder for the actual routine you’ll use. Its typical content resembles the following:

function CreateScriptlet () {
  this.put_Text = put_Text;
  this.get_Text = get_Text;
  this.Expand = DoExpand;
  this.event_OnExpand = "";
}

CreateScriptlet adds properties, methods, and events to the new object. To expose a property called Text you need to write two script procedures: get_Text and put_Text. They must be assigned to the corresponding elements in the public_description object you’ll refer with the this keyword. Put_Text and get_Text take care of reading and writing the property’s value. While the get side of the property usually requires that you only return the current content, the put method makes the changes effective and may affect the scriptlet’s output.

Methods don’t follow any specific convention. The name you assign to the public descriptor is the same one you can invoke outside the scriptlet. You need to provide the name of the script procedure that actually implements that method. In the above sample I’ve called it DoExpand.

Events don’t have to be explicitly declared, although I suggest you do it anyway for the sake of clarity. By convention, events have no server-side implementation. You declare their names just to let the object’s container know about them. As you’ll see, this is not necessary with scriptlets. You can remove the this.event_OnExpand line above and the event will still be raised and received properly.

A second way to define a scriptlet’s public interface is by using a special naming convention known as the default interface description, for all the properties and methods you want to expose. The prefix public is the magic word that makes a symbol available outside the scriptlet:

Function public_get_Text
Sub public_put_Text
Sub public_Expand
' no need to declare events

This approach allows you to use VBScript as well as JScript.

Although you can write scriptlets in any script language (even mixing them by procedure), I suggest that you use JScript at least for defining the object’s public interface. There are a couple of reasons I recommend the JScript public_description approach over the default interface description. The first is clarity. You have a single place in your code where the object’s public interface is fully described. Second, you get flexibility. You can have different public and private names for all methods and properties. Earlier I defined a method called Expand and implemented it via a DoExpand procedure. If you use the default interface description, public_Expand is at the same time both the declaration and the implementation of the method.

There are some performance problems you will encounter if you use more than one engine in a Web page. If you use VBScript to define a scriptlet, you shouldn’t bother defining a JScript public_description object. Doing so
will cause your code to jump from one script engine to the other just to get
your method.

Detecting When a Scriptlet is Running

Since a scriptlet is really nothing more than a Web page with extra interfaces, you should design it so that it’ll display properly through a browser. Note that this may result in a runtime script error as shown in Figure 3. The special container object that hosts scriptlets offers the ambient properties summarized in Figure 4. If you attempt to view the scriptlet as an ordinary Web page, make sure not to access those stock attributes since the main browser window doesn’t support them. A good way to tell where you’re running is by detecting whether the page is viewed through a browser or a scriptlet:

InScriptlet = (typeof(window.external.version)=="string");
if( InScriptlet ) {
  window.external.selectableContent = 1;
}

You might be wondering how it’s possible to fix the error in Figure 3 with the above code. The answer lies in the script language I used. The code that defines the variable InScriptlet is written in JScript while the procedure that originates the error is in VBScript. Compare the output of the following lines outside the scriptlet site:

// JScript
Alert( window.external.version );

' VBScript
Alert window.external.version

You’ll notice that the first displays a message box with the word “undefined,” while the second raises an exception like the one in Figure 3. A sample HTML file that reproduces this situation is ambient.htm, shown in Figure 5.

Figure 3: Runtime Script Error

Figure 4: Ambient Properties for Scriptlets

Property | Description
Scrollbar | Shows or hides the scrollbar for the scriptlet's site
SelectableContent | Enables or disables the ability to select the content and copy it to the clipboard
Version | Returns a string identifying the current version of the container object
Method | Description
BubbleEvent | Passes the current event down to the host environment, whether an HTML page or a Visual Basic form
RaiseEvent | Fires a custom event for the scriptlet
SetContextMenu | Allows you to associate a popup menu with the scriptlet

Figure 5: ambient.htm

<HTML><BODY>
<SCRIPT language="JScript" for="window" event="onload">
alert("JScript 'window.external.version' returned: \n" + window.external.version);
</SCRIPT>
<SCRIPT language="VBScript" for="window" event="onload">
MsgBox "VBScript 'window.external.version' returned: " + window.external.version
</SCRIPT>
</BODY></HTML>

The Container Object Model

The ambient properties listed in Figure 4 allow you to turn a client site scrollbar on and off and toggle whether or not the scriptlet’s content is selectable. You can also add a context menu that will display with a right-click. BubbleEvent is a method you can use to pass standard DHTML events such as onclick, onmouseover, and onmouseout to the scriptlet’s host. RaiseEvent fires custom events like the OnExpand seen previously. To make calls into this object model you need to access a new property, external, that’s exposed by the DHTML window object. The following code snippet notifies a document that a click has occurred inside the scriptlet’s client area:

<script language="VBscript"
  for="image1" event="onclick">
  if InScriptlet then
    window.external.bubbleEvent
  end if
</script>

I recommend that you always make sure you’re inside a scriptlet viewer before trying to access the window.external object.

Writing Your First Scriptlet

I don’t know how you feel about the feature in Visual Studio™ and Windows 95 setup programs where the bitmaps are highlighted when the mouse passes over them. But I like it very much and think it would be nice to add a similar effect to the buttons on my homepage. DHTML allows you to do this seamlessly. Scriptlets, by extension, let you write this code once and reuse it forever. Figure 6 shows my HotImage scriptlet in action, and Figure 7 illustrates its source code.

Figure 6: HotImage Scriptlet in action

Figure 7: HotImage

<html id=MyPage>
<head>
<title>HotImage Scriptlet</title>
</head>
<body>
<script language="VBscript" for="window" event="onload">
    InitHotImage
</script>

<script language="VBscript" for="image" event="onmouseover">
    if mEnabled <> 0 then
      DoSetImage mHotImageName
      window.external.bubbleEvent
    end if
</script>

<script language="VBscript" for="image" event="onmouseout">
    if mEnabled <> 0 then
      DoSetImage mImageName
      window.external.bubbleEvent
    end if
</script>

<script language="VBscript" for="image" event="onclick">
    window.external.bubbleEvent
</script>

<script language="VBscript">

' Initialize the scriptlet
' -------------------------------------------------------
Sub InitHotImage
  document.bgColor = mBkgndColor
End Sub

' Change the image
' The scriptlet document is made of a single
' <img> tag which refers the actual GIF file.
' -------------------------------------------------------
Sub DoSetImage( sImage )
  Set coll = document.images
  coll.item(0).setAttribute "src", sImage
  coll.item(0).setAttribute "alt", mDescription
End Sub

' Set a descriptive text for the image
' -------------------------------------------------------
Sub DoSetText( sDescription )
  Set coll = document.images
  coll.item(0).setAttribute "alt", sDescription
End Sub
</script>

<script language="JScript">

// declare the object interface
public_description = new CreateHotImage;
var InScriptlet = (typeof(window.external.version) == "string")

// Vars that match properties
/*---------------------------------*/
mImageName = "";
mHotImageName = "";
mEnabled = 1;
mBkgndColor = "#C0C0C0";
mDescription = "";

/*---------------------------------*/
function CreateHotImage() {

// This function defines the scriptlet interface in terms of
// properties, methods, and events.

  this.put_Image = put_Image;
  this.get_Image = get_Image;
  this.put_BackgroundColor = put_BackgroundColor;
  this.get_BackgroundColor = get_BackgroundColor;
  this.put_Description = put_Description;
  this.get_Description = get_Description;
  this.put_HotImage = put_HotImage;
  this.get_HotImage = get_HotImage;
  this.put_Enabled = put_Enabled;
  this.get_Enabled = get_Enabled;
}

// This scriptlet has no methods and limits to bubble
// standard events. The following JScript functions
// implement the get/set interface for the properties.
// Note that you can call VBScript code from within a
// JScript routine.

/*---------------------------------------------------
// ENABLED: This property enables the effect
----------------------------------------------------*/
function put_Enabled( bYes ) {
   mEnabled = bYes;
   return 1;
}
function get_Enabled() {
   return mEnabled;
}

/*----------------------------------------------------
// DESCRIPTION: Set the tooltip text for the image
-----------------------------------------------------*/
function put_Description( sText ) {
   mDescription = sText;
   DoSetText( mDescription );
   return 1;
}
function get_Description() {
   return mDescription;
}

/*----------------------------------------------------
// BACKGROUNDCOLOR: Set the document bgColor property
-----------------------------------------------------*/
function put_BackgroundColor( color ) {
   mBkgndColor = color;
   document.bgColor = mBkgndColor;
   return 1;
}
function get_BackgroundColor() {
   return mBkgndColor;
}

/*----------------------------------------------------
// IMAGE: Set the image for the Normal state
-----------------------------------------------------*/
function put_Image( sImageName ) {
   mImageName = sImageName;
   DoSetImage(mImageName);
   return 1;
}
function get_Image() {
   return mImageName;
}

/*----------------------------------------------------
// HOTIMAGE: Set the image for the Hot state
-----------------------------------------------------*/
function put_HotImage( sImageName ) {
   mHotImageName = sImageName;
   return 1;
}
function get_HotImage() {
   return mHotImageName;
}

// Note the body of the scriptlet document: a <img> tag
// with no image or alternate text specified. This means that
// you need to initialize it to see something!

</script>
<img id="image" src="" alt="">
</body>
</html>

Once you declare the public interface of the scriptlet (HotImage only supports properties), there is one step you need to take: the one-time initialization of the object. Initialization is usually accomplished during the window.onload event:

<script language="VBscript" for="window" event="onload">
    InitHotImage
</script>

Sub InitHotImage
  document.bgColor = mBkgndColor
End Sub

Like a Visual Basic-based ActiveX control, each exposed property needs an internal counterpart variable to hold its actual value. For example, mBkgndColor is used to store the current value of the Background color property (see the code in Figure 7).

The HotImage scriptlet standard body is an empty image tag that looks like Figure 8. This skeleton changes dynamically, becoming similar to what you see in Figure 6 once logos are implemented via two HotImage scriptlets. To be aware of mouse movements on the page, I need to listen for two DHTML-fired events: onmouseover and onmouseout. These events would make any programmer using Win32® happy if they were available as Windows messages. Fortunately, Dynamic HTML provides them for free:

<script language="VBscript" for="image"
    event="onmouseover">
  if mEnabled <> 0 then
    DoSetImage mHotImageName
    window.external.bubbleEvent
  end if
</script>

If you look at the HotImage listing (see Figure 7) you’ll notice that the code for onmouseover and onmouseout is nearly the same. The only difference is the name of the variable passed to DoSetImage, where the core of this scriptlet resides.

Sub DoSetImage( sImage )
  Set coll = document.images
  coll.item(0).setAttribute "src", sImage
  coll.item(0).setAttribute "alt", mDescription
End Sub

This procedure retrieves the image collection of the DHTML document and accesses the first element in the list. The scriptlet body contains just a single <IMG> tag (see Figure 8) so I can make sure that coll.item(0) refers to the right
element. All that remains is adapting the src and the alt attributes of this tag. Any other attribute can also be adjusted here if you want.

Figure 8: HotImage Skeleton

HotImage also bubbles the onclick event so that the image acts normally when clicked. From the container’s point of view it behaves just like an animated button:

<script language="VBScript” for=”HotImage1" event="onclick">
<!—
 MsgBox "You pick up" + HotImage1.Description—>
</script>

Hosting a Scriptlet

A scriptlet is inserted into an HTML page via an <OBJECT> tag. Here’s an example:

<object id="HotImage1"
  data="HotImage.htm" align="absbottom"
  border="0" width="300" height="100"
  type="text/x-scriptlet">
</object>

The data attribute specifies the file name or the URL where the scriptlet will be found. Type is set to the new MIME type text/x-scriptlet, telling Internet Explorer 4.0 that this object is a scriptlet. Figure 9 shows the script code for the HTML page that appeared in Figure 6.

Figure 9: HotImage Script Code

<script language="VBScript">
<!--
Sub window_onload()
  HotImage1.Image = "mind1.gif"
  HotImage1.HotImage = "mind2.gif"
  HotImage2.Image = "msj1.gif"
  HotImage2.HotImage = "msj2.gif"
  HotImage1.Description = "Microsoft Interactive Developer"
  HotImage2.Description = "Microsoft Systems Journal"
  Check1.Value = 1
end sub
--></script>

<script language="VBScript"
for="HotImage1" event="onclick">
<!--
  sCRLF = Chr(13) + Chr(10)
  s1 = "Congratulations for your choice!"
  s2 = "Your preference goes to "
  s = s1 + sCRLF + s2 + HotImage1.Description
  MsgBox  s, 0, "Choose Magazine"
--></script>

<script language="VBScript">
<!--
origColor1 = ""
origColor2 = ""
Sub BtnBkgnd_Click()
  if HotImage1.BackgroundColor <> "gainsboro" then
    origColor1 = HotImage1.BackgroundColor
    HotImage1.BackgroundColor = "gainsboro"
  else
    HotImage1.BackgroundColor = origColor1
  end if

  if HotImage2.BackgroundColor <> "gainsboro" then
    origColor2 = HotImage2.BackgroundColor
    HotImage2.BackgroundColor = "gainsboro"
  else
    HotImage2.BackgroundColor = origColor2
  end if
end sub
--></script>

<script language="VBScript">
<!--
bOrig = 1
Sub BtnInvert_Click()
  if bOrig = 1 then
    HotImage1.Image = "msj1.gif"
    HotImage1.HotImage = "msj2.gif"
    HotImage1.Description = "Microsoft Systems Journal"
    HotImage2.Image = "mind1.gif"
    HotImage2.HotImage = "mind2.gif"
    HotImage2.Description = "Microsoft Interactive Developer"
    bOrig = 0
  else
    HotImage1.Image = "mind1.gif"
    HotImage1.HotImage = "mind2.gif"
    HotImage1.Description = "Microsoft Interactive Developer"
    HotImage2.Image = "msj1.gif"
    HotImage2.HotImage = "msj2.gif"
    HotImage2.Description = "Microsoft Systems Journal"
    bOrig = 1
  end if
end sub
--></script>

<script language="VBScript">
<!--
Sub Check1_Change()
  HotImage1.Enabled = Check1.Value
  HotImage2.Enabled = Check1.Value
end sub
--></script>

Behind the scenes, Internet Explorer 4.0 uses an ActiveX control to host the scriptlet. Since it is installed with the release version of Internet Explorer 4.0, you can exploit it to build your own applications that support scriptlets. Figure 10 presents a calendar scriptlet that’s hosted within a Visual Basic form. The Microsoft Scriptlet control has a design-time property called URL that can be set to the actual name of the scriptlet file. Until updated development tools arrive, you’ll have to manually insert scriptlets into Web pages. FrontPad and FrontPage® recognize them as ActiveX controls but don’t allow you to edit their properties. The same holds true for scriptlet-related code, which must be entered without the help of ScriptWizard.

Figure 10: Calendar Scriptlet

A More Complex Sample

Let’s look at the more complex sample shown in Figure 11. What I’ve called ComboText is similar to a Windows-based combobox, an area of text that expands and collapses, showing more details. To modify the state (expanded or collapsed), you click on the scriptlet’s button or call the associated method. A ComboText object exposes a series of properties that let you customize the position of the button by how many pixels the control will expand. In addition, you can change font and color attributes, make the content selectable, and set the title (with Text) and the details (with Memo) to display.

Figure 11: ComboText Scriptlet

The body includes a <DIV> tag, with its ID set to “text,” that represents the title of the ComboText. When the user sets the Text property, the scriptlet formats a new string and assigns it with the outerHTML property of the element.

Set e = GetElemById( "DIV", "text" )
e.outerHTML = FormatText

The ComboText component expands and collapses via a mouse click, which is detected through the standard DHTML event model:

<script language="VBscript" for="document" event="onclick">
if window.event.srcElement.id = "push" then
  DoInsertText
else
  if window.event.srcElement.id = "pop" then
  DoRemoveText
  end if
end if
</script>

Push and pop are the IDs of the images used for the control’s button. When it’s time to expand the control, I’ll use this line to add new formatted text to the scriptlet document:

document.body.insertAdjacentHTML "BeforeEnd", FormatMemo

While formatting the string for the Text and Memo properties, I take into account the state of the control and the specified button position. This is also a good time to enable the scrollbar and resize the scriptlet to include the new content:

Set o = MyPage.style
o.pixelHeight = o.pixelHeight + mExpandBy
window.external.scrollbar = True

MyPage is the ID I’ve set within the HTML tag (see Figure 12), and is the means by which you can access the frame window of the scriptlet. I found that using the window element like this

window.ResizeTo w,h

resulted in changing the dimensions of the Internet Explorer 4.0 main window!

Adding Custom Events to Scriptlets

What makes this sample more complex is its full support for custom events. I defined two events specific to ComboText: OnExpand and OnCollapse. They’re fired at the times their names imply. They are declared in the public descriptor of the object, but if you turn them off, the code still mysteriously works. What’s that all about?

Figure 12 MyScriptlet Scriptlet

<html id=MyPage>
<head>
<title>MyScriptlet Scriptlet</title>
</head>
<body>

<script language="VBscript" for="window" event="onload">
    InitMyScriptlet
</script>

<script language="VBscript">

' Initialize the control
' -------------------------------------------------------
Sub InitMyScriptlet
  window.external.selectableContent = mSelectable
End Sub
</script>

<script language="JScript">

// declare the object interface
public_description = new MyScriptlet;
var InScriptlet = (typeof(window.external.version) == "string")

function CreateMyScriptlet() {
}

</body>
</html>

To fire an event you would use the window.external.raiseEvent method. The following is a typical call:

window.external.raiseEvent "OnExpand", window.document

RaiseEvent takes two parameters. The first is the name of the event and the second is the data you want to associate with the event. The syntax and semantics match the RaiseEvent call in Visual Basic 5.0. What changes is the behavior. In fact, the Visual Basic version of RaiseEvent links to a COM connection point where it expects to find an entry point with exactly the name you passed in. The DHTML RaiseEvent always connects to OnScriptletEvent regardless of the event name you fire, resulting in implementation code like this:

<script language="jscript" for="ComboText1" event="onscriptletevent(n,o)">
if( n="OnExpand" ) {
    Expand1.Caption = "Collapse1";
}
else {
    Expand1.Caption = "Expand1";
}
</script>

Since all the events a scriptlet raises are routed through OnScriptletEvent, you can define events at any time and call them with a string that matches their name. The host environment should implement a Select/Case or a multiple if statement to distinguish among the various events. The code above is excerpted from the page rendered in Figure 11, and causes the caption of the buttons to change from “Expand 1” to “Collapse 1,” according to the ComboText state.

On a personal note, I often write slides and give presentations. Most of the time I use PowerPoint®, but sometimes I like to do it with HTML pages that contain hidden paragraphs that open and close at my command. I want to accomplish this without runtime modules or ActiveX controls in the middle of the page. Dynamic HTML gives me an idea of what can be done, and scriptlets are an excellent way to put the idea into practice. Figure 13 shows a tour of a MIND presentation site that uses this technique.

Figure 13: Creative ComboText Use

Since the layout of a scriptlet is always the same, sooner or later you’ll start cutting-and-pasting code from a functional sample. Wizards always work well in situations like this, so I wrote one. The results are in Figure 1 (Figure 14 shows the wizard’s typical output). The Scriptlet Wizard is a two-step custom AppWizard for the Developer Studio environment that can create a couple of HTML files, though not a compilable project. Discussing its inner details is beyond the scope of this article. If you’re interested in this topic, check out the excellent piece by Steve Zimmerman, “Extend Developer Studio 97 with Your Own Add-ins, Macros and Wizards,” (MSJ, September 1997).

Figure 14: Scriptlet Wizard Output

The source code provided with this article includes the ComboText and HotImage script controls as well as the entire Visual C++®-based project for the Scriptlet Wizard. You can download the full source code of the wizard from http://www.microsoft.com/mind. Don’t forget to copy the binary file scrptlet.awx to the template subdirectory of Visual Studio 97, and feel free to modify and adapt it to your own needs. In addition, you’ll find a small Visual Basic 5.0-based project that shows how to host scriptlets in a desktop application. All the samples require at least the release candidate of Internet Explorer 4.0 to work properly. Have fun!

For related articles, see http://www.microsoft.com/sitebuilder/features/scriptlet.asp; http://www.microsoft.com/workshop/prog/ie4/scriptlet/faq-f.htm; http://www.microsoft.com/gallery/files/scriptlets/


Secure Logins

By Ken Spencer

Many organizations build Web applications that require users to log on, usually because they need to control access to sensitive information. But while security needs may vary, the overall challenge is the same: how do you force users to log on to your application appropriately?

The answer can take several forms. You might try to use built-in Windows NT® security features. However, if you force everyone to log on using Windows NT Password Authentication, then all the users accessing your site must be Windows NT users. This might be a problem if 10,000 users who are not in the Windows NT SAM database visit your site—that’s a lot of user accounts to set up and maintain. You might also try NTFS file security, but that requires lots of manual tweaking of file security, defining Windows NT user accounts, and so on, result-ing in many of the same problems.

I’m going to show you another approach, an approach that’s similar to what we used in the Registered Users area of our Web site at http://www.32x.com. This area allows anyone who has attended one of 32X Corporation’s seminars to access and download the sample files provided. When the user selects the Registered Users option, the resulting page requests the confirmation number given to the user when they attended the seminar. The confirmation number is looked up in a Microsoft Access database and, if the lookup is successful, a custom page is created and returned to the browser.

The example I created for this article uses a SQL Server™ database to hold user accounts that are defined explicitly for this application. The same account may or may not exist in the Windows NT SAM database, but this application does not check for a matching SAM entry. When a user tries to access the sample site, the Login.asp page requests their user name and password. When the user submits the page, CheckUser.asp looks up the user name and password in the SQL Server database. If the user name and password are found, the user is logged on and the groups the user belongs to are retrieved. If the user is not logged in, then Login.asp displays again. The home page each user sees depends on the group the user belongs to.

I will show you how to build this code for use in any application. I will also point out some of the nuances of using the login approach and what you may need to do to secure the app. Then I’ll walk you through the process of creating the database and stored procedures used to perform the data retrieval.

Getting Started

The first step in creating the login application was to define the requirements for users’ access to the pages in the application. Many people at the 32X Visual InterDev seminars have requested a way to force users to log in to the application and then to display only information that is related to the user’s role. This is the approach I took with my application. The requirements for the sample application are fairly basic:

  • All users accessing the application must be validated (no anonymous access).

  • After the user login occurs, the login function will return a list of all groups to which the user may be assigned.

  • Users should see only data related to their groups.

  • If a user logs in and is in the GeneralManager group, that user should get access to all data.

The database structure to support these requirements must hold user accounts, user groups, and groups of users. Then the application can base access to the data in the application on the groups a user is assigned to. Figure 1 shows the application’s home page (default.asp), which is displayed if the user logs on and is in the GeneralManager group.

Figure 2 shows the same home page as displayed for a user from the ManufacturingManager group. Notice the link to the Manufacturing data. Also notice that the Purchasing link shown in Figure 1 is missing. These are the only two links on the page, but I could easily add many more based on the group definitions of the users.

Figure 1: Home page

Figure 2: Home page for another group

Building the Database

The first thing I created was the database, which is easy if you understand the user requirements. This database must hold the user name, password, group names, and a link between group names and user names. Figure 3 shows the structure of the database as displayed in the Database Diagram window in Visual InterDev™.

Figure 3: Database Diagram Window

I used the New Database Wizard to create the SQL Server database. The database creation steps in this section will only work on SQL Server 6.5 with Service Pack 1 or higher. If you’re using Microsoft Access, Oracle, or another database, you must use the techniques required by your database to create the database structure.

First, I followed these steps to start the New Database Wizard:

  1. Select the File menu.

  2. Select the New option.

  3. Click the Projects tab.

  4. Click the radio button to select either a new workspace or to include the database connection in the current workspace.

  5. Double-click the New Database Wizard (see Figure 4). This will display Step 1 of the wizard.

Figure 4: New Project Screen

Next I entered the name of the database server—the computer hosting the SQL Server database—and the user account and password for the database administrator, or at least an account with the permissions to create a new database. The entries that I used are shown in Figure 5.

Figure 5: New Database Wizard

I completed the wizard by naming my database and selecting the device where the database and its log file will reside. This entire process should take about two to three minutes.

I used a separate database for the user accounts. This allows me to use the same database for more than one application and to easily share the login pages and data connections with other applications. Your requirements may differ.

To create the tables shown in Figure 3, I designed a new database diagram by right-clicking the Database Diagrams folder in DataView and selecting New Diagram. Then I right-clicked in the design window and selected New Table. I entered the name and column descriptions for each table, then saved the design, creating the tables.

The UserID field of the UserAccounts table, the GroupID of the UserGroups table, and the UserGroupID field of the UserGroup table all use the INT datatype. They are set to disallow nulls, and each is the primary key of its table. I also selected the Identity checkbox for these two fields, making each field a counter. The resulting UserID, GroupID, and UserGroupID fields will increment by one each time a new entry is added to the table.

Next I created a relationship from the UserAccounts table to the UserGroup table. I also created a relationship from the UserGroups table to the UserGroup table. The relationships were created by dragging the UserID field from the UserAccounts table to the UserID field of the UserGroup table. The second relationship was created by dragging the GroupID field from the UserGroups table to the GroupID field of the UserGroup table. If you build this project, your display should resemble the structure shown in Figure 3, except that it displays only the columns in the database.

Once you have created the database and the three tables, enter some sample data in each table. The UserAccounts table and UserGroups tables must have entries before they can be used in the UserGroup table. After you have entered several users or groups in the appropriate table, click the Run button on the Query toolbar to refresh the query—this will display the automatically generated ID field values.

Creating the Stored Procedures

Next I created two stored procedures. Logonuser checks the username and password fields and returns a UserID if the lookup is successful. GetUserGroups returns all the groups for a specific UserID. These two stored procedures retrieve all of the user information when a user logs in.

I used stored procedures for a couple of reasons. First, stored procedures may be 10 to 20 times faster than dynamic SQL (the type of SQL that is generated by Query Builder or that you enter directly into the CommandText property of ADO). Second, stored procedures work like objects in the database—once you create them, anyone can simply access them and take advantage of their features without knowing the details of how they work. These are all the reasons I need to move the code to a stored procedure. As an added bonus, the next time I need a UserID or the groups that the current user belongs to, I can get them from the database using the stored procedures created here.

The SQL code for Logonuser is:

CREATE PROC LogonUser @UserName char (20), @UserPassword char (20)AS SELECT  UserID FROM UserAccounts WHERE UserName = @UserName AND Password = @UserPassword

The SQL code for GetUserGroups is:

CREATE PROC GetUserGroups @UserID int AS SELECT UserGroup.GroupID, UserGroup.UserID, UserGroups.GroupName, UserGroups.Description FROM UserGroup INNER JOIN UserGroups ON UserGroup.GroupID = UserGroups.GroupID and UserGroup.UserID = @UserID

I created the SQL for each stored procedure in Query Builder. To open Query Builder, I double-clicked the UserAccounts table. This opened the results pane of Query Builder and returned all rows of the table. Next, I selected the first three buttons on the Query toolbar to display the other panes of the interface: Diagram, Grid, and SQL.

The following steps create the SQL for the Logonuser stored procedure:

  1. Select the UserID column from the field list.

  2. Deselect the entry (*) for all columns.

  3. Select the UserName and Password columns to add them to the current query.

  4. Uncheck the Output checkbox in the Criteria pane for both UserName and Password.

  5. Right-click a blank area in one of the panes and select Properties.

  6. Enter [ for the Prefix character and ] for the suffix character.

  7. Close the Properties dialog.

  8. Enter UserName in the criteria column for the UserName column.

  9. Enter Password in the criteria column for the Password column.

The SQL statement in the SQL pane should look like the first SQL statement shown above and in Figure 6. Click the Run button on the Query Builder toolbar to test the query. This should display the Parameters dialog in which you can enter a valid and invalid user name and password for the test. Try this with both valid and invalid user name/password combinations.

Figure 6: Query Builder

Now let’s turn the SQL statement into a stored procedure. Simply modify the SQL to look like the one in Figure 7, then click the Run button. Query Builder should display a dialog with this message: Query has executed successfully. Repeat these same steps for the second stored procedure that retrieves the groups a user belongs to. You can also enter the SQL directly into Query Builder and click the Run button to shortcut the process.

Figure 7: Modified SQL Statement

Developing the Login Code

Now I can create the login code and begin to build the application. First, I have to build the user interface. I used the FrontPage® 97 Editor to create this page. The page is shown in Figure 8, while the code is shown in Figure 9. The login page is displayed automatically by the code in Global.asa (see Figure 9).

Figure 8: Login Interface

Figure 9: Global.asa

<SCRIPT LANGUAGE=VBScript RUNAT=Server>
Sub Session_OnStart
    '==Visual InterDev Generated - DataConnection startspan==
    '--Project Data Connection
        Session("Customer_ConnectionString") = "DRIVER={SQL
            Server};SERVER=toshibaiis;UID=sa;PWD=;APP=Microsoft (R) Developer
            Studio;WSID=KLSTOSHIBA;DATABASE=Customer"
        Session("Customer_ConnectionTimeout") = 15
        Session("Customer_CommandTimeout") = 30
        Session("Customer_RuntimeUserName") = "sa"
        Session("Customer_RuntimePassword") = ""
    '--Project Data Connection
        Session("SecureAccess_ConnectionString") = "DRIVER={SQL
            Server};SERVER=toshibaiis;UID=sa;PWD=;APP=Microsoft (R) Developer
            Studio;WSID=KLSTOSHIBA;DATABASE=SecureAccess"
        Session("SecureAccess_ConnectionTimeout") = 15
        Session("SecureAccess_CommandTimeout") = 30
        Session("SecureAccess_RuntimeUserName") = "sa"
        Session("SecureAccess_RuntimePassword") = ""
    '==Visual InterDev Generated - DataConnection endspan==
    if Request("Name") = "" or  Not Request("ValidateLogin") = "99223322" then
        response.redirect "login.asp"
    end if
End Sub
</SCRIPT>

The following statement in the Global.asa Session_OnStart event code is used to redirect the user to the Login.asp page:

if Request("Name") = "" or  Not Request("ValidateLogin") = "99223322" then
    response.redirect "login.asp"
end if

This code will force users to the Login.asp page when they first enter the application. In fact, as long as the user does not have an active Session (with a valid SessionID) and has not filled out the UserName and Password fields on the Login page, the user will be sent to the Login.asp page. The If statement checks that the Name field isn’t blank and that ValidateLogin is not equal to 99223322. The ValidateLogin field is set in the Login.asp page. If the user guessed that I checked the Name field and tried to bypass that, they would still run up against the ValidateLogin field. It’s doubtful that they would ever guess I was checking for that crazy numeric value or the name of the field.

The Login page is simple enough, and at first glance seems to do the trick. It is asking the user for a user name and password. Simple, right? Well, not so fast. Almost every browser has a line where the user can enter a URL. So all the user has to do to bypass my login page is enter a URL into the URL line in the browser for a valid page in the application. This will immediately display the new page in the user’s browser, bypassing my login page.

Figure 10: Login.asp

<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
<html>

<head>
<meta http-equiv="Content-Type"
content="text/html; charset=iso-8859-1">
<meta name="GENERATOR"
content="Microsoft FrontPage (Visual InterDev Edition) 2.0">
<title>Document Title</title>
</head>

<body bgcolor="#FFFFFF">
<%
Session.Abandon
%>

<p>Today is <%=Now%> </p>

<p><font size="5"><strong>Welcome to the Animal Products Login
Page</strong></font></p>

<p><font size="6">Pleas enter your name and password</font></p>

<form action="CheckUser.asp" method="POST">
    <p><font size="4"><strong>User Name </strong></font><input
    type="text" size="20" name="Name"></p>
    <p><font size="4"><strong>User Name </strong></font><input
    type="password" size="20" name="Password"></p>
    <p><font size="3"><input type="submit" name="B1"
    value="Login Now"></font></p>
</form>
</body>
</html>

There are several ways to solve this problem. I chose one that my buddy David Hale at Compaq suggested. To use this approach, the Login page will immediately terminate a user’s session before they log in. This ensures that the user will be rerouted back to global.asa when they complete this page or if they try to leave the page by entering another URL or choosing a site from their Favorites list. The user’s session is terminated using this line of code from Figure 10:

<%
Session.Abandon
%>

The Login page still works because it’s running (displayed) in the user’s browser. The Login page is a standard page with two HTML form fields. The first field contains the UserName while the second field contains the user’s Password. The UserName is a standard Text field, while the Password is a Password field. The Action property of the HTML form is set to CheckUser.asp.

So, what happens when the user logs in? After the user enters a name and password and clicks the Login Now button, the Session_OnStart event in Global.asa is executed again because I terminated the user’s session with Session.Abandon in Login.asp when it loaded.

The code for CheckUser.asp is shown in Figure 11. The CheckUser page has an important job—it checks the user name and password in the SecureAccess database and, if a valid user is found, it retrieves a list of the groups the user is linked to. If the user name and password are not valid, then the user is redirected to Login.asp.

Figure 11: CheckUser.asp

<%@ LANGUAGE="VBSCRIPT" %>

<%
    UserName = Request("Name")
    Password = Request("Password")
%>

<!--METADATA TYPE="DesignerControl" startspan
    <OBJECT ID="DataCommand1" WIDTH=151 HEIGHT=24
     CLASSID="CLSID:7FAEED80-9D58-11CF-8F68-00AA006D27C2">
        <PARAM NAME="_Version" VALUE="65536">
        <PARAM NAME="_Version" VALUE="65536">
        <PARAM NAME="_ExtentX" VALUE="3986">
        <PARAM NAME="_ExtentY" VALUE="635">
        <PARAM NAME="_StockProps" VALUE="0">
        <PARAM NAME="DataConnection" VALUE="SecureAccess">
        <PARAM NAME="CommandText" VALUE="dbo.&quot;LogonUser&quot;">
        <PARAM NAME="Command Type" VALUE="1">
        <PARAM NAME="ParamCount" VALUE="3">
        <PARAM NAME="Param0" VALUE="Return Value,,4,4,4">
        <PARAM NAME="Param1" VALUE="@UserName,[UserName],1,1,20">
        <PARAM NAME="Param2" VALUE="@UserPassword,[Password],1,1,20">
    </OBJECT>
-->
<%
Set SecureAccess = Server.CreateObject("ADODB.Connection")
SecureAccess.ConnectionTimeout = Session("SecureAccess_ConnectionTimeout")
SecureAccess.CommandTimeout = Session("SecureAccess_CommandTimeout")
SecureAccess.Open Session("SecureAccess_ConnectionString"),
        Session("SecureAccess_RuntimeUserName"),
        Session("SecureAccess_RuntimePassword")
Set cmdTemp = Server.CreateObject("ADODB.Command")
Set DataCommand1 = Server.CreateObject("ADODB.Recordset")
cmdTemp.CommandText = "dbo.""LogonUser"""
cmdTemp.CommandType = 4
Set cmdTemp.ActiveConnection = SecureAccess
Set tmpParam = cmdTemp.CreateParameter("Return Value", 3, 4, 4)
cmdTemp.Parameters.Append tmpParam
Set tmpParam = cmdTemp.CreateParameter("@UserName", 129, 1, 20, "" & UserName & "")
cmdTemp.Parameters.Append tmpParam
Set tmpParam = cmdTemp.CreateParameter("@UserPassword", 129, 1, 20, "" & _
                                       Password & "")
cmdTemp.Parameters.Append tmpParam
DataCommand1.Open cmdTemp, , 0, 1
%>
<!--METADATA TYPE="DesignerControl" endspan-->

<%
if Not DataCommand1.BOF and Not DataCommand1.EOF then
    'response.write "Valid Logon " & DataCommand1("UserID")
    UserID = DataCommand1("UserID")
    Session("UserID") = UserID
    Session("UserName") = UserName
    Session("Password") = Password
Else
    response.redirect "Login.asp"
End If
%>

<!--METADATA TYPE="DesignerControl" startspan
    <OBJECT ID="DataCommand2" WIDTH=151 HEIGHT=24
     CLASSID="CLSID:7FAEED80-9D58-11CF-8F68-00AA006D27C2">
        <PARAM NAME="_Version" VALUE="65536">
        <PARAM NAME="_Version" VALUE="65536">
        <PARAM NAME="_ExtentX" VALUE="3986">
        <PARAM NAME="_ExtentY" VALUE="635">
        <PARAM NAME="_StockProps" VALUE="0">
        <PARAM NAME="DataConnection" VALUE="SecureAccess">
        <PARAM NAME="CommandText" VALUE="dbo.&quot;GetUserGroups&quot;">
        <PARAM NAME="CommandType" VALUE="1">
        <PARAM NAME="ParamCount" VALUE="2">
        <PARAM NAME="Param0" VALUE="Return Value,,4,4,4">
        <PARAM NAME="Param1" VALUE="@UserID,[UserID],4,1,4">
    </OBJECT>
-->
<%
Set SecureAccess = Server.CreateObject("ADODB.Connection")
SecureAccess.ConnectionTimeout = Session("SecureAccess_ConnectionTimeout")
SecureAccess.CommandTimeout = Session("SecureAccess_CommandTimeout")
SecureAccess.Open Session("SecureAccess_ConnectionString"), _
        Session("SecureAccess_RuntimeUserName"), _
        Session("SecureAccess_RuntimePassword")
Set cmdTemp = Server.CreateObject("ADODB.Command")
Set DataCommand2 = Server.CreateObject("ADODB.Recordset")
cmdTemp.CommandText = "dbo.""GetUserGroups"""
cmdTemp.CommandType = 4
Set cmdTemp.ActiveConnection = SecureAccess
Set tmpParam = cmdTemp.CreateParameter("Return Value", 3, 4, 4)
cmdTemp.Parameters.Append tmpParam
Set tmpParam = cmdTemp.CreateParameter("@UserID", 3, 1, 4, UserID)
cmdTemp.Parameters.Append tmpParam
DataCommand2.Open cmdTemp, , 0, 1
%>
<!--METADATA TYPE="DesignerControl" endspan-->

<%
dim i
dim GroupName
i = 1

do while Not DataCommand2.EOF
    GroupName = "Group" & Trim(i)

    Session(GroupName) = DataCommand2("GroupName")

    i = i + 1
    DataCommand2.MoveNext
Loop

response.Redirect "Default.asp"

%>

</BODY>
</HTML>

If the login is successful, then a number of session variables are created (see Figure 12). These session variables are the key to displaying the correct data for each user and for allowing the user to have the correct level of access throughout the application.

Figure 12: Session Variables

Session Variable Name | Description of Contents
UserID | UserID from the UserAccounts table.
UserName | Contains the username entered.
Password | Contains the password entered.
GroupX | A group of session variables that contain the groups the user is linked to. There can be any number of GroupX variables, where X starts at 1 and is incremented by 1 for each additional group a user is linked to.

I created CheckUser.asp using the File | New dialog and selecting the Active Server Page (ASP) entry. Next, I entered these two lines to retrieve the Name and Password fields from the Login page:

<%
    UserName = Request("Name")
    Password = Request("Password")
%>

Then I inserted a design-time Data Command control. Figure 13 shows the Data Command Control dialog. First, select the data connection that you will use. This data connection must point to your database that contains the user name and group tables. Next, select Stored Procedure from the Command Type list. Now you can select the LogonUser stored procedure you created earlier from the Command Text list.

Figure 13: Data Command Control

There is one more step: because this stored procedure contains parameters, you must set the parameters your application will use to hook up with it. This is done with the Parameters tab in the Data Command Control dialog. First, select the UserName parameter, then enter the name of the ASP variable you will relate to this parameter in the parameter area and click the green Check button to the left of your entry. This adds your variable name to the display. You must perform this step for any variable that you wish to link to a stored procedure parameter.

The next step involves adding the following ASP code to check the status of the login:

If Not DataCommand1.BOF and Not DataCommand1.EOF then
    'response.write "Valid Logon " &             DataCommand1("UserID")
    UserID = DataCommand1("UserID")
    Session("UserID") = UserID
    Session("UserName") = UserName
    Session("Password") = Password
Else
    response.redirect "Login.asp"
End If
%>

This code determines whether the recordset contains any records. If it does, the login is successful and the first part of the If statement executes. If the recordset is empty, then the login is unsuccessful and the user is returned to the Login.asp page.

You should now insert a second Data Command control and point it to the GetUserGroups stored procedure. The process is exactly the same as the first Data Command control, except this time you’re only using one parameter (UserID).

It is worth noting that you do not have to use two Data Command controls here. You can manually add the ADO code for the second control and use the references to the first ADO Command, Connection, and Recordset objects. This cuts down the overhead of recreating new references to the same component.

The last bit of code you need to add to this page comes right after the second Data Command control:

<%
dim i
dim GroupName
i = 1

do while Not DataCommand2.EOF
    GroupName = "Group" & Trim(i)

    Session(GroupName) = DataCommand2("GroupName")

    i = i + 1
    DataCommand2.MoveNext
Loop

response.Redirect "Default.asp"

%>

This code loops through the recordset containing the groups the user belongs to and creates a GroupX session variable for each. For instance, the name of the first group returned will be stored in Group1, the second in Group2, and so forth. This allows you to access the group information from another page in your application at any time.

You should consider using one variable to store the groups in an array. This makes it easier to loop through all the group names and determine which groups a user belongs to.

The last task this page performs is to redirect the user to Default.asp:

response.Redirect "Default.asp"

The entire listing for CheckUser.asp is in Figure 11. At this point, my application has a valid login process that validates the user and displays Default.asp (shown in Figure 14). What does Default.asp show? That depends upon the groups the user is linked to.

Figure 14: Default.asp

<%@ LANGUAGE="VBSCRIPT" %>
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
<html>

<head>
<meta http-equiv="Content-Type"
content="text/html; charset=iso-8859-1">
<meta name="GENERATOR"
content="Microsoft FrontPage (Visual InterDev Edition) 2.0">
<title>Home</title>
</head>

<body bgcolor="#FFFFFF">

<p align="center"><font size="7"><em><strong>Advanced Animal
Products Division</strong></em></font></p>

<p align="center"><font size="4"><strong>Home Page</strong></font>
</p>

<p align="center">&nbsp;</p>

<h1>Division Status Reports</h1>
<%
Dim Status

    if trim(Session("Group1")) = "GeneralManager" then
        Status = "All"
    Else
        Status = Session("Group1")
    End if
    Response.write "Security Status: " & Status & "<BR>" & "<BR>"

    Response.write "<ul>"
    If Status = "ManufacturingManager" or Status = "All" then
        Response.write "<li><a
            href=""ManufacturingStatus.asp"">Manufacturing</a></li>"
    End If

    If Status = "PurchasingManager" or Status = "All" then
        Response.write "<li><a href=""PurchasingAccounts.asp"">Purchasing</a></li>"
    End If
    Response.write "</ul>"

%>
<p>&nbsp;</p>

<p>&nbsp;</p>

<p>&nbsp;</p>
</body>
</html>

The page contains one block of script code that drives the content on the page. This code uses simple If…then syntax to check the value of the Group1 session variable and display the appropriate page for the user. I first check to see if the user is in the GeneralManager group:

If trim(Session("Group1")) = "GeneralManager" then
    Status = "All"
Else
    Status = Session("Group1")
End if

If the user isn’t in the GeneralManager group, then the value of Group1 is placed in the Status field. The next line displays the value of Status:

Response.write "Security Status: " & Status & "<BR>" & "<BR>"

The rest of the code checks the value of Status to determine which URLs to display in the unordered list. If Status is “All”, then every page is displayed:

Response.write "<ul>"
If Status = "ManufacturingManager" or Status = "All" then
    Response.write "<li><a href=""ManufacturingStatus.asp"">Manufacturing</a></li>"
End If

If Status = "PurchasingManager" or Status = "All" then
    Response.write "<li><a href=""PurchasingAccounts.asp"">Purchasing</a></li>"
End If
Response.write "</ul>"

You may want to allow your users to log off when they are finished using the application. This can be done quite easily using the code shown in Figure 15. This code uses Session.Abandon to terminate the user’s session just like Login.asp does. To add this logoff page to your application, all you need to do is create the file in Figure 15 and add a link to it from another page with the title Logoff or Logout.

Figure 15: Logoff.asp

<%@ LANGUAGE="VBSCRIPT" %>

<HTML>
<HEAD>
<META NAME="GENERATOR" Content="Microsoft Visual InterDev 1.0">
<META HTTP-EQUIV="Content-Type" content="text/html; charset=iso-8859-1">
<TITLE>Document Title</TITLE>
</HEAD>
<BODY>

You have been logged off.
<%
Session.abandon
%>

</BODY>
</HTML>

The Logoff page is also extremely useful when you are developing an application like this. When you are using Session variables, it’s useful to destroy them from time to time. All you need to do is display the Logoff.asp page.

What happens if the user is logged into the application and the PurchasingAccounts.asp is the only link they have? Keeps them from getting to other pages, right? Not necessarily. If the user is savvy, he or she may figure out the name of the ManufacturingStatus.asp file and try entering it in the browser’s URL field. This will display the file unless you take special precautions in the ManufacturingStatus.asp file. Figure 16 shows this file with only a few lines of ASP code that perform this check. This page uses part of the code from Default.asp. All I do is check to see if the user is linked to the GeneralManager or the ManufacturingManager groups. If the user is in one of these groups, they see this page. If not, they go back to Default.asp.

Figure 16: Manufacturing.asp

<%@ LANGUAGE="VBSCRIPT" %>
<%
Dim Status

    if trim(Session("Group1")) = "GeneralManager" then
        Status = "All"
    Else
        Status = Session("Group1")
    End if
    If not Status = "ManufacturingManager" and not Status = "All" then
        resonse.Redirect "Default.asp"
    End If

%>

<html>

<head>
<meta http-equiv="Content-Type"
content="text/html; charset=iso-8859-1">
<meta name="GENERATOR"
content="Microsoft FrontPage (Visual InterDev Edition) 2.0">
<title>Document Title</title>
</head>

<body bgcolor="#FFFFFF">
<font size="5"><strong>Manufacturing Status</strong></font>

<ul>
    <li><font size="5"><strong>Jones and Smith project is on
        Target for delivery</strong></font></li>
    <li><font size="5"><strong>Cat and Dog probject is delayed
        for weeks</strong></font></li>
    <li><font size="5"><strong>All other projects are likely to
        be really late</strong></font></li>
</ul>
</body>
</html>

Conclusion

You’ve seen how to build a login system using ASP and SQL Server. Here are a few tips I would consider using to extend this concept:

  • Use a session variable to store the groups in an array as I mentioned earlier.

  • Store the page names in a database table and pull out the ones the user should see based upon his or her group.

  • Store the contents of the pages in a database table and only build the contents on-the-fly for users.

  • Add the code to test for a user’s access to a page to a standard ASP function and place that function in an include file. Then it takes only two lines of code per page to check if a user should see that page.

For related articles, see http://www.microsoft.com/sitebuilder/features/asptips.asp, http://www.microsoft.com/workshop/server/asp/feature/security-f.htm, and http://www.microsoft.com/workshop/server/asp/bugs/tshoot-f.htm.


Using J/Direct to Call the Win32 API from Java

By Mike Pietraszak

While the pundits and purists debate the merits of lowest-common-denominator programming, many developers back on planet Earth are busy writing Windows®-based apps. And with the advent of J/Direct™, you can enjoy the liberating productivity benefits of the Java language without being weighed down by libraries that cater to even the least functional of the “pure” platforms. Now developers who choose to throw off the shackles of purity have a choice—and that choice includes the best of both the multiplatform and Windows-specific worlds.

J/Direct is a new feature of the Microsoft® Virtual Machine (VM) that allows developers to call the entire Win32® API directly. Before J/Direct, developers who wanted to access the rich functionality of Win32 had two options. They could either wrap the Win32 API calls in a custom DLL and call the wrapper using the Raw Native Interface (RNI), or use the COM features of the VM to access the subset of Win32 APIs exposed through COM interfaces. Now J/Direct provides a third, more direct approach to accessing DLL-based Win32 APIs in a way that automatically converts native pointers, structures, and types to their Java equivalents.

The simple program in Figure 1 demonstrates the J/Direct syntax. The program plays a WAV-format audio file (a format unsupported by the AWT applet.AudioClip class) using the sndPlaySound API found in the Windows Multimedia Library (WINMM.DLL). When the code is compiled by the Microsoft Compiler for Java (JVC.EXE) version 1.02.4213 or greater, the @dll.import directive is translated into special bytecode attributes in the HelloWindows class file. The VM for Java then decodes those attributes into an instruction to load the WINMM.DLL library. The sndPlaySound function can then be called like any other Java function in the HelloWindows class. So before you get started, you’ll need the VM and the Microsoft Compiler for Java, both of which are included in the Microsoft SDK for Java 2.0. The SDK can be downloaded from http://www.microsoft.com/java.

Figure 1: HelloWIndows

public class HelloWindows {

  /** @dll.import("winmm") */
  static native boolean sndPlaySound(String lpszSound, int fuSound);

  public static void main (String args[]) {
    boolean returnCode;
    int     flags     = 0;
    String  soundFile = System.getProperty("com.ms.windir") + "\\MEDIA\\The
                        Microsoft Sound.wav";

    System.out.println("Playing '" + soundFile + "'...");
    returnCode = sndPlaySound(soundFile,flags);
  }
}

Figure 2: HelloWindowsEx (MultiPlatform)

public class HelloWindowsEx {

  /** @dll.import("winmm",entrypoint="sndPlaySound") */
  static native boolean playWAV(String lpszSound, int fuSound);

  public static void main (String args[]) {

    if (System.getProperty("os.name").startsWith("Windows")) {

      // If the com.ms.util.SystemVersionManager class is not found,
      // catch the NoClassDefFoundError

      try {

        java.util.Properties vmProperties =
            com.ms.util.SystemVersionManager.getVMVersion();
        int majorVersion   = (new
            Integer(vmProperties.getProperty("MajorVersion"))).intValue();
        int minorVersion   = (new
            Integer(vmProperties.getProperty("MinorVersion"))).intValue();
        int buildIncrement = (new
            Integer(vmProperties.getProperty("BuildIncrement"))).intValue();

        if ((majorVersion >= 4) && (minorVersion >= 79) &&
            (buildIncrement >= 2164))
        {

          boolean returnCode;
          int     flags     = 0;
          String  soundFile = System.getProperty("com.ms.windir") + "\\MEDIA\\The
                              Microsoft Sound.wav";

          System.out.println("Playing '" + soundFile + "'...");
          returnCode = playWAV(soundFile,flags);
        } else {

          // Not the right version of the VM

          System.out.println("J/Direct requires VM Version 4.79.2252 or higher.");
        }
      } catch (NoClassDefFoundError e) {

        System.out.println("Class " + e.getMessage() + " not found.");

        // If com/ms/util/SystemVersionManager is not on the system,
        // then this is probably not the VM.

        if (e.getMessage().equals("com/ms/util/SystemVersionManager")) {
          System.out.println("J/Direct requires VM Version 4.79.2252 or higher.");
        }

      }
    } else {

        // This isn't Windows.

        System.out.println("J/Direct requires a Windows system.");
    }
  }
}

The Best of Both Worlds

The HelloWindows program in Figure 1 will run great, provided that the program uses the Microsoft VM version 4.79.2252 or greater that comes with Microsoft Internet Explorer 4.0 or the SDK 2.0. But in a Web-based world, developers may not have control over the VM their clients will be using to run Java programs. So it’s possible that the program will be run on a VM that doesn’t handle J/Direct calls appropriately. Developers can still use J/Direct in a way that permits Java-based programs to run successfully in both Windows-specific and heterogeneous client environments.

Figure 2 is a modified listing for the HelloWindows program in Figure 1. HelloWindowsEx performs several checks to determine the scenario under which it is being run: the environment is Windows and J/Direct is available; the environment is Windows, but the VM-specific class com.ms.util.SystemVersionManager can’t be loaded (so the VM probably isn’t the Microsoft VM); or the environment is Windows and the virtual machine is the Microsoft VM, but it’s not the correct version so there’s J/Direct support. By writing code that recognizes these gradations, you can take advantage of Windows-specific features and still deploy to platforms that do not provide J/Direct access to Win32 APIs.

Figure 3: Parameter and Return Value

BOOL | boolean
BOOL[] | boolean[]
BYTE | byte
BYTE* | byte[]
CHAR | byte
CLSID* | com.ms.com._Guid
double | double
double* | double[]
DWORD | int
DWORD* | int[]
float | float
float* | float[]
GUID* | com.ms.com._Guid
IID* | com.ms.com._Guid
INT | int
LONG | int
LPCSTR | String

Note: LPCSTR/String may be used as parameters but not as return values. In OLE mode, however, String maps to LPWSTR and both are permitted as return values. The Microsoft VM frees the string by using CoTaskMemFree.

LPSTR | StringBuffer
SAFEARRAY* | com.ms.com.SafeArray

Note: May only be passed as parameter, not as return value.

SHORT | short
UINT | int
ULONG | int
VARIANT* | com.ms.com.Variant
VOID | void

Note: void/VOID may not be used as parameters and may only be used as return values.

WORD | short
WORD* | short[]
__int64 | long
__int64* | long[]
COM interface | interface

Note: Must use jactivex or similar tool to generate interface.

function pointer com.ms.dll.Callback

Note: May only be passed as parameter, not as return value.

pointer to struct Object

Note: An IUnknown* is used instead of a pointer to a struct in OLE mode.

So now you can use J/Direct to access APIs (and you can even rename them). How do you map the signature of the API you want to call (usually documented in C) to one that uses Java intrinsic types? Another good question. When J/Direct accesses a Windows DLL from Java, the Java parameters must be marshaled from Java types to native C types. For return values, the C types must be marshaled back to Java types. This conversion is handled automatically by the VM, according to the table shown in Figure 3. Note that some conversions are only done for parameters, like Strings and SafeArrays, and some are only done for return values, like void. But as for the actual @dll.import declaration in your code, you’ll have to do that C to Java (or Visual Basic® to Java) conversion yourself.

The C signature for the sndPlaySound function is:

BOOL sndPlaySound(
  LPCSTR lpszSound,
  UINT fuSound
);

So the parameter and return value translation to Java types is as follows:

C Type |  | Java Type
BOOL | becomes | boolean
LPCSTR | becomes | String
UINT | becomes | int

The Java declaration for the API import statement then becomes:

/** @dll.import("winmm") */
static native boolean sndPlaySound(String lpszSound, int fuSound);

Figure 4: Parameter and Return Value

Byte | byte
Double | double
Integer | short
Long | boolean
Long | int
Single | float
VARIANT | com.ms.com.Variant

Converting Basic to Java

If you are using a reference that lists APIs with Visual Basic types, they can also be converted easily to Java. Figure 4 shows a table with the necessary type mappings. The sndPlaySound API would be declared in Visual Basic as:

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

The parameter and return value translation to Java types from Visual Basic is as follows:

Visual Basic Type |  | Java Type
Long (return value) | becomes | int
String | becomes | String
Long | becomes | int

The return value for the C API reference was of type BOOL, and was converted to Java type boolean. For the Visual Basic API reference, the return value was type Long, which can be converted to either the Java type boolean or the Java type int. In this case, either type will work just fine. So the Java declaration derived from an API reference using Visual Basic types could be correctly translated as either

/** @dll.import("winmm") */
static native boolean sndPlaySound(String lpszSound, int fuSound);

or

/** @dll.import("winmm") */
static native int sndPlaySound(String lpszSound, int fuSound);

J/Direct also provides a flexible way for developers to map a somewhat cryptic Win32 API to a more Java-friendly name. The sndPlaySound function, for example, can be mapped (or “aliased”) to playWAV by modifying the following lines of code in the HelloWindows program:

/** @dll.import("winmm") */
 static native boolean sndPlaySound (String lpszSound, int fuSound);

/** @dll.import("winmm", entrypoint="sndPlaySound") */
 static native boolean playWAV (String lpszSound, int fuSound);

Because J/Direct calls native APIs, the code must be authenticated and approved with maximum trust in order to be run from a browser. For J/Direct code to be run from an applet, it must be digitally signed, indicating full trust and granting all permissions. Untrusted applet code cannot access trusted applet classes that use J/Direct. Even after the applet has been signed, the init, start, stay, and destroy methods of the applet must include a call to com.ms.security.PolicyEngine.assertPermission. Tools like CABARC.EXE and SIGNCODE.EXE in the Microsoft SDK for Java 2.0 provide a means for packaging and digitally signing applets with the high levels of trust needed to be run from the browser.

Applets with J/Direct

For security reasons, applets that make J/Direct calls must be packaged into a CAB and authenticated. Figure 5 shows an applet that makes J/Direct calls, and the following HTML hosts the applet:

<html>
  <applet code="HelloIE" width=300 height=50>
    <param name="cabbase" value="hello.cab">
  </applet>
</html>

Figure 6 is a .bat file that can be used to create the test certificate, shown in Figure 7, that will be displayed the first time the applet is loaded. For information on how to apply for an official certificate for commercial applications, visit http://www.microsoft.com/workshop/prog/security/authcode/codesign-f.htm.

You may find the edit-compile-debug cycle for J/Direct applets tricky because applet classes remain cached while Internet Explorer is running. This means that even if you recompile a class and redeploy it in a CAB, the old class will still be in the cache, so it will be run again and your changes will not be displayed. You can use Ctrl-F5 to force a reload of your classes, or you can restart Internet Explorer. Next, you must be sure to sign your code with low trust (signcode.exe -jp low) and request security permissions (with PolicyEngine.assertPermission). In this example, maximum permissions (SYSTEM) were requested. Finally, you’ll have to turn on test certificates by running the SETREG.EXE tool.

Figure 5: Applet Using J/Direct

import com.ms.security.*;

public class HelloIE extends com.ms.ui.UIApplet {
  /** @dll.import("winmm") */
  static native boolean sndPlaySound(String lpszSound, int fuSound);

  public String soundFile = new String();

  public void init() {
    PolicyEngine.assertPermission(PermissionID.SYSTEM);
    soundFile  = System.getProperty("com.ms.windir") + "\\MEDIA\\The Microsoft
                 Sound.wav";
    String  buttonText = "Play '" + soundFile + "'";

    setLayout(new com.ms.ui.UIBorderLayout(0, 0));
    add(new com.ms.ui.UIPushButton(buttonText), "Center");
  }

  public boolean action(java.awt.Event e, Object arg) {
    if ( arg instanceof com.ms.ui.UIPushButton ) {
      PolicyEngine.assertPermission(PermissionID.SYSTEM);
      boolean returnCode = sndPlaySound(soundFile,0);
    }
    return true;
  }
}

Figure 6: Batch File for Signing CAB

rem //-------------------------
rem // Enable test certificates
rem //-------------------------
setreg.exe 1 true
rem
rem //-------------------------
rem // Compile HelloIE
rem //-------------------------
jvc.exe HelloIE.java
rem
rem //-------------------------
rem // Turn .class into .cab
rem //-------------------------
cabarc.exe -s 6144 n hello.cab HelloIE.class
rem
rem //-------------------------
rem // Make a test certificate
rem //-------------------------
makecert.exe -sv hello.key -n "CN=My Publisher Name" hello.cer
cert2spc.exe hello.cer hello.spc
rem
rem //-------------------------
rem // Digitally sign .cab
rem //-------------------------
signcode.exe -j javasign.dll -jp low -spc hello.spc -v hello.key hello.cab
rem
rem //-------------------------
rem // Launch the web page
rem //-------------------------
start hello.htm

Figure 7: Test Certificate

What about COM and RNI?

J/Direct is a new way for users of the Microsoft VM for Java to take advantage of Win32 APIs in Windows 95, Windows NT®, and beyond. But what’s so new about Windows access? After all, VM users can already access COM through a host of tools like the Visual J++™ Type Library Wizard for Java (JAVATLB.EXE), the Microsoft ActiveX™ Control Importer for Java (JACTIVEX.EXE), the Visual J++ ActiveX Wizard for Java, and the Java/COM Registration Utility (JAVAREG.EXE).

But with J/Direct, the Windows APIs are accessed with DLL calls, not via COM. And although RNI provides a great way to access DLLs, the RNI technology assumes that you started with a Java class, generated a C header file using msjavah.exe, and then added functionality to the C DLL. But Win32 DLLs aren’t callable by RNI in this way. Because Win32 API names don’t conform to RNI naming conventions, and because RNI expects data types (like strings) to be Java-format types (the Win32 API uses C-format types), RNI cannot be used to access Win32 or third-party DLLs. In other words, a single API cannot follow both the RNI conventions and the Windows conventions required by existing Windows-based apps.

J/Direct makes it easy to call DLL functions by automatically marshaling parameters from Java to native C types. Crossing the Java to C boundary also means dealing with garbage collection issues. In C, memory must be explicitly allocated and deallocated in code. But in Java, memory allocation and deallocation is done automatically by the VM.
This difference can be problematic if a Java-allocated variable gets deallocated by the VM while still in use on the C side. J/Direct helps when crossing this boundary by not garbage collecting (deallocating) during an API call, but there are limitations. If you access a DLL via J/Direct, that DLL cannot access another DLL that is accessed via RNI. And DLL functions cannot modify standard Java objects directly—structures built using the @dll.struct directive must be used.

In my next article, I’ll cover the @dll.struct directive, OLE calling conventions, ANSI versus Unicode issues, callbacks, and the com.ms.dll package.

For related articles, see http://www.microsoft.com/java.


Agents Revealed: An Introduction to Microsoft Agents

By Shannon Lake

The day has finally arrived. Using Microsoft Agent, you can now literally “tell” your computer to run a report, read a letter, or play a song. This article will describe how to make use of Agent’s text-to-speech feature. You’ll learn how to extend VBA to include an ActiveX, and how to code an application that will call Agent to read your active Word document aloud.

Microsoft Agent 1.5 uses multiple characters — Genie, Merlin, and Robby, as of this writing (see FIGURE 1) — to offer a human/machine interface unlike any of its predecessors, including Microsoft Office Assistant. Each character has been three-dimensionally rendered, and has a unique set of animations.

FIGURE 1: The Microsoft Agent characters: Genie, Merlin, and Robby.

The Set Up

To get the most from Agent, however, you need to do a little prep work. You must download and install several components. They’re all available at http://www.microsoft.com/workshop/prog/agent. With each of these, download the compressed executable file and run it to perform the installation:

the Microsoft Agent Control

the Microsoft Command and Control speech engine

the Lernout & Hauspie TruVoice text-to-speech engine

Then download and place the following files in the \Program Files\Microsoft Agent\Characters directory:

genie.acs

merlin.acs

robby.acs

You should also download the Agent documentation. At a minimum, you’ll need Programming the Microsoft Agent Control, Programming the Microsoft Agent Server Interface, and the documentation for each character, e.g. Microsoft Agent Animations for Robby Character. You’ll also need a sound card, speakers, and a microphone to dictate commands.

The Agent’s First Mission: Just Show Up

In the first project, we’ll write a test procedure to verify that Agent and its components are installed correctly. We’re not asking for a lot the first time out; the Agent just needs to show up and say a few words. We’ll use Genie for the test run.

Begin by launching Microsoft Word 97 and activating the Visual Basic Editor by pressing AL! (or selecting Tools | Macro | Visual Basic Editor). Next, click on the Project Explorer, or press Cr. This will give you a list of all the projects you’ve opened. Select the Normal project. (For more on Normal, see the sidebar “The Normal Template” on page XX.)

Creating the form. Insert a form by selecting Insert | UserForm from the Project Explorer or menu. Modify the UserFform by changing the following properties in the Properties window. (It may be necessary to press 4 to display the Properties window.)

Caption: Agent Control Form

Width: 100

Height: 85

Place two CommandButtons on the form. Then change their Caption properties to Start Agent and Stop Agent so they appear as shown in FIGURE 2.

FIGURE 2: Building the first demonstration in the Visual Basic IDE: Place two buttons on a UserForm.

Controls and references. With every new project, it’s important to add references to the DLLs and OCXs that contain the procedures, methods, and other code you’re referencing. Select Tools | References to activate the References dialog box. Scroll down, and select Microsoft Agent Control 1.5.

Next, make sure the Toolbox is active by clicking it, or by choosing it from the View | Toolbox menu. Activate the Additional Controls dialog box by selecting Tools | Additional Controls; find the line that reads Microsoft Agent Control 1.5, and click its checkbox. When finished, you’ll see the Agent icon in the lower portion of the Toolbox (see FIGURE 3).

FIGURE 3: The Visual Basic IDE with the Toolbox displayed; the Agent icon has been added to the Toolbox.

Next, we’ll code the portion of the subroutine that will make the Genie appear and disappear. Start by double-clicking the top button, labeled Start Agent. You’ll see that the Visual Basic IDE has already started the CommandButton1_Click subroutine. Scroll to the top, and enter the following statements:

Const DATAPATH = _
  "C:\Program Files\Microsoft Agent\Characters\genie.acs"

Dim Genie As IAgentCtlCharacter
Dim SpeakString As String

The full code listing appears in FIGURE 4. These statements declare the necessary variables and constant. Microsoft suggests that you install the Agent character files (.ACS files) in the directory listed, but you can put them anywhere — as long as you enter the correct path for the DATAPATH constant value.

Option Explicit

Const DATAPATH = _
  "C:\Program Files\Microsoft Agent\Characters\genie.acs"

Dim Genie As IAgentCtlCharacter

Private Sub UserForm_Activate()
  Agent1.Characters.Load "Genie", DATAPATH
  Set Genie = Agent1.Characters("Genie")
End Sub

Private Sub CommandButton1_Click()
  Genie.Show
  Genie.MoveTo 100, 100
  Genie.Speak "My name is Genie. Your wish is my command."
End Sub

Private Sub CommandButton2_Click()
  Genie.MoveTo 350, 250
  Genie.Hide
End Sub

Private Sub UserForm_Deactivate()
  Set Genie = Nothing
End Sub

FIGURE 4: This code demonstrates basic Genie control.

Create the code that will be executed when the UserForm is activated:

Private Sub UserForm_Activate()

Now we tell the Agent object to load the Genie character into its Characters collection, so we can access it:

Agent1.Characters.Load "Genie", DATAPATH

Next, the Set statement is used, to simplify subsequent code:

Set Genie = Agent1.Characters("Genie")

It’s not required, but later statements would need to be more elaborate. For example, this statement would be required to show the Agent:

Agent1.Characters("Genie").Show

Rather than the more straightforward:

Genie.Show

The next line gives the SpeakString variable a value. In this case, we’re telling Microsoft Agent to introduce itself, then prepare for speech-recognition input. (We won’t code that portion in this example.)

SpeakString = "My name is Genie. Your wish is my command."

Let’s complete the code by calling the Genie and having it speak the value of SpeakString. We’ll program the CommandButton1_Click subroutine to accomplish that task. Show is the command used to make the Genie visible to the user, and Speak triggers the Microsoft text-to-speech engine to speak the text it precedes:

Private Sub CommandButton1_Click()
  Genie.Show
  Genie.Speak SpeakString
End Sub

The CommandButton2_Click subroutine will hide the Genie from the user:

Private Sub CommandButton2_Click()
  Genie.Hide
End Sub

Finally, we must destroy the Agent object when we’re done with it, by using the following statement:

Private Sub UserForm_Deactivate()
  Set Genie = Nothing
End Sub

You’re now ready to start the Agent. Test to see if your machine was set up correctly by hitting the Run button, or by pressing 5. This will activate the form. The Agent server will initialize when the form is loaded, and be destroyed when the user exits the form. When the Start Agent button is pressed, the Genie will appear and say, “My name is Genie. Your wish is my command.” (as shown in FIGURE 5). It will disappear when the Stop Agent button is pressed.

FIGURE 5: The demonstration application at run time: the Genie appears.

Because we’re operating the Agent from a modal dialog box, this implementation isn’t very practical, although it’s okay for demonstrations or development. Next, we’ll do something a lot more useful; we’ll incorporate an Agent into Word 97 and have Robby read to us.

The Agent’s Second Mission: Read to Me

The goal is to place a button on the Word 97 toolbar, that when pressed, invokes an Agent who will read the active document aloud — a great way to get another perspective on your writing.

Let’s start with the declarations, beginning with the character-path constant:

Const ROBBYPATH = _
  "C:\Program Files\Microsoft Agent\Characters\robby.acs"

and the variables for the Agent and character:

Dim MSAgent As Agent
Dim Char As Object

As before, we then instantiate an Agent object using the New keyword:

Set MSAgent = New Agent

After the Agent object is created, we must start the Agent ActiveX server. To accomplish this, we set the Agent object’s Connected property to True:

MSAgent.Connected = True

Then we load the character and assign it to the Object variable:

MSAgent.Characters.Load "Robby", ROBBYPATH
Set Char = MSAgent.Characters("Robby")

The preliminary work is done. Now it’s time for our Agent to make an appearance and do something.

He Walks...

Controlling an Agent’s on-screen behavior is straightforward. Most action is accomplished using the Character object’s Play function. Play takes one parameter — a string consisting of an Animation string. For example, this statement will cause Robby to wave:

Char.Play "Wave"

while this will cause him to appear to be moving to the right (Robby uses a jet pack to move):

Char.Play "MoveRight"

FIGURE 6 lists a small subset of Animation strings for Robby, while FIGURES 7 through 11 illustrate a few. As you can see, there’s a lot Robby can do. The available Animation strings vary from character, so you’ll need to get the documentation for each character you want to work with. Robby, for example, has 94 Animation strings described in the Microsoft Agent Animations for Robby Character document.

Animation | Description
Acknowledge | Nods head
Alert | Straightens and raises eyebrows
Announce | Prints output and reads
Blink | Blinks eyes
Declines | Raises hands and shakes head
DontRecognise | Holds hand to ear
GestureLeft | Gestures to the left
GestureRight | Gestures to the right
GetAttention | Raises and shakes arms
GlanceLeft | Looks left briefly
GlanceRight | Looks right briefly
Hear_1 | Turns head left
Idle2_1 | Crosses arms
Idle2_2 | Removes head and makes adjustment
LookLeft | Looks to the left
MoveUp | Flies up
Processing | Presses buttons in looping animation
Reading | Reads in looping animation
Sad | Makes sad expression
Searching | Looks through toolbox in looping animation
Think | Tilts head and scratches
Wave | Waves
Write | Takes out clipboard, writes and looks up

FIGURE 6: Selected subset of Animation strings for Robby character. There are 94 in all.

Using Play, however, Robby will remain in the same on-screen location. To actually move an Agent about the screen, you can use the Character object’s MoveTo procedure, which takes two arguments — the x and y screen coordinates. Here’s an example:

Char.MoveTo 200, 300

If you want the character to move while on screen, use MoveTo after Show has been called; if you want the character to appear in a specific location when first invoked, use MoveTo to position the Agent before calling Show. Now that we know how to make the Agent gesture and move around, let’s make Robby speak.

FIGURE 7: Animation “Wave”: Robby says hello.

FIGURE 8: Animation “MoveRight”: Robby flies to the right.

FIGURE 9: Animation “Idle2_2”: Robby makes an internal adjustment.

FIGURE 10: Animation “Searching”: Robby rummages through his toolbox.

FIGURE 11: Animation “Reading ”: Robby reads to us.

He Talks...

Having Robby speak is equally straightforward; you simply use the Character object’s Speak function. Speak takes one parameter — the text to read. The text can take several forms, including a literal string, a text file, or a URL. In our case, we want the Agent to read a Word document, so we’ll use the following statement:

Char.Speak ActiveDocument.Content

If you do any Word 97 VBA programming, you’ll recognize that ActiveDocument.Content refers to the Content property of the ActiveDocument property; in other words, the body text of the currently-selected Word document. This statement will cause Robby to read the document aloud while displaying the associated text in a word balloon. Slick!

The lone Speak statement will work, but we can do better. This sequence:

Char.Play "Read"
Char.Speak ActiveDocument.Content
Char.Play "Readreturn"

will cause Robby to print some output from his chest, tear the document free when it’s completed, and begin to read before actually speaking. The “ReadReturn” Animation will cause Robby to return to his neutral, resting position after reading. Many animations have associated “...Return” animations, so smooth transitions can be made between actions.

Now, let’s put what we’ve learned together and script a short sequence of actions for Robby to perform. We’ll place them in a script so we can call it from Word 97, as shown in FIGURE 12.

Option Explicit

Const ROBBYPATH =
  "C:\Program Files\Microsoft Agent\Characters\robby.acs"

Dim MSAgent As Agent
Dim Char As Object

Sub Speak()

  Set MSAgent = New Agent
  MSAgent.Connected = True
  MSAgent.Characters.Load "Robby", ROBBYPATH

  Set Char = MSAgent.Characters("Robby")
  With Char
    .Top = 125
    .Left = 185
    .Show
    .Play "Greet"
    .Play "GreetReturn"
    .MoveTo 600, 400
    .Play "Read"
    .Speak ActiveDocument.Content
    .Play "ReadReturn"
    .Play "Idle2_2"
    .Play "Wave"
    .Play "WaveReturn"
    .Hide
  End With

End Sub

FIGURE 12: This VBA code has Robby appear at specific coordinates on the screen, wave hello, fly to the lower-right corner of the screen, read the active Word 97 document, make an adjustment, wave good-bye, and vanish.

Next, we must do a little work in Word.

He Crawls on His Belly Like a Reptile

To associate the new macro with a custom toolbar, select View | Toolbars | Customize to display the Customize dialog box. Click the New button. Name the new toolbar “Agent”, and make it available to Normal.dot. The toolbar is now ready to be used. In the Customize dialog box, click the Commands page and select Macros. The Normal.NewMacros.Speak macro should be available; drag it to the toolbar, then release it when the I-beam appears. Right-click the button, change its name to “Read”, and give it an icon if you like.

Call the new macro from your menu by returning to the Customize dialog box and selecting the object again. This time, drag it to the Tools menu (but don’t let go). The menu will open and scroll to the bottom. When you see the I-beam in the position you want, release the button. You can change the properties of the menu item the same way you did before — by right-clicking.

Conclusion

We’ve covered the basics of Microsoft Agent, and listed the software and hardware you need to exploit Agent’s potential. Then we went on to take advantage of Agent’s speech capability to implement a useful Word macro that literally reads a document aloud.

It’s also important to remember that Agent is available as an ActiveX. This means it can be used to liven up a Web site. For an example, visit Argo Technologies’ Web site at http://www.argolink.com/agent/merlinscorner/druidxicon.html (see FIGURE 13). You can link to it and other Agent-enabled sites from Microsoft’s Agent page (see FIGURE 14) — a page you’ll become familiar with as you explore Microsoft Agents.

FIGURE 13: You can see Microsoft Agent in action on the Web at the Argo Technologies site.

FIGURE 14: Get to know this site; it’s loaded with valuable tools and information.

This article has only scratched the surface. Microsoft Agent is a major piece of technology — the Programming the Microsoft Agent Control document alone is 75 pages! — so you’ll need to invest some time before becoming adept. The good news is that you’ll have lots of fun along the way.

The file referenced in this article is available for download from the Informant Web site at http://www.informant.com/mod/modnewupl.htm. File name: MOD9801SL.ZIP.

Shannon M. Lake, Sr., CET, NCT, and MCP, is CEO of The OMNIFORCE Group, an international telecommunications and software-engineering consultancy focused on designing and reinforcing companies’ new or existing information, software, telecommunication, and/or data-communication systems. Shannon is currently developing a new operating system. He can be reached at lake@omniforce.com, http://www.omniforce.com/lake, or (800) 367-9946.


An Introduction to Two Approaches: Dynamic Publishing Over the Web

by Rick Dobson

In a recent article, "Publishing Access 97 Data on the Web" [Microsoft Office Developer's Journal, December 1997], we described how to share static views of Access data on a Web site. This technique suits any type of information that appears on a regular cycle—such as summer and winter catalogs. Other publishing or data-sharing scenarios can demand more timely updates, however. For example, an employee phone directory should instantly reflect extensions for new employees and changed extensions for existing employees. With dynamic publishing, site visitors can immediately see any changes to an underlying database.

In this article, we'll look at two techniques for dynamically publishing Access data over an intranet: HTX/IDC and ASP. We'll also show you how to modify the content and appearance of the raw published tables, as Figure A shows, and illustrate ways to dynamically publish Access parameter queries.

Figure A

This Web page pulls updated information from an Access database.

As we'll see, the Publish To The Web wizard can shield you from nearly all the gory details. However, there are some significant reasons to learn the basics about these details.

An introduction to two approaches

The HTX/IDC technology was the first Microsoft technique for dynamic database publishing. Let's walk through the six steps to database publishing with HTX/IDC.

First, a Web browser sends a URL to a Web server. This URL must reference an IDC file that sits on the server. The IDC file, which has a text format, references an Access database through a DSN (Data Source Name). It includes SQL for extracting a subset from the database.

Second, the Web server hands off the IDC file to the Internet Database Connector (IDC) for processing. The IDC is a program named httpodbc.dll that runs on the server. The IDC passes the DSN and SQL to an ODBC driver that talks directly to the Access database.

In the third and fourth steps, the ODBC driver executes the SQL in the IDC file against the Access database and generates a return set. It then passes the results back to the IDC.

Fifth, the IDC uses an HTX file to generate an HTML file that the browser can use to view the return set. The IDC populates the HTML file with data based on placeholders in the HTX file. The HTX file contains standard HTML as well as these special placeholders.

Finally, the Web server passes the HTML generated by the IDC back to the browser. The server can be either a Microsoft Internet Information Server (version 2 or later) or a Personal Web Server, as we'll use in our example.

Active Server Pages

Active Server Pages (ASP) represents an evolution in Microsoft's Web development technology. ASP lets developers store their HTML and scripting code in a single file and supports either VBScript or JavaScript. ASP technology has its own object model, and it's this object model, plus the fact that the HTML and script reside in the same file, that makes ASP more powerful than HTX/IDC. However, ASP technology is also more complicated because it requires a working knowledge of a scripting language in addition to HTML proficiency. Also, if you're going to hand edit either HTX/IDC or ASP code, you'll need some familiarity with SQL.

With the ASP approach, the browser references an ASP file on the server. It executes its scripts, processes any special HTML placeholders, and returns HTML to the browser. The Web server must be running Active Server Pages, and the ASP file must reside in a virtual directory that has Execute Scripts permission. You can run Active Server Pages on Microsoft Internet Information Server (version 3 or later), Personal Web Server for Windows 95, or Peer Web Server for Windows NT.

If you're dedicated to mastering Internet Information Server (IIS) technology on Windows NT, you'll want to invest in mastering ASP technology. On the other hand, if you're using Windows 95 and have only occasional use for dynamic publishing, IDC/HTX may meet your needs.

Setting up for either approach

The easiest way to use either approach is with the Access 97 Publish To The Web wizard. However, before invoking the wizard, you must create a DSN that points to the database that you want to publish. The wizard will prompt you for this DSN.

You can make a DSN manually from the Control Panel. Begin by double-clicking the 32bit ODBC icon to open the ODBC Data Source Administrator dialog box. Now, select the System DSN tab and click Add…. In the dialog that appears, double-click Microsoft Access ODBC. Type a name for the data source in the Data Source name text box; we called ours Northwind. Then, click Select… and browse to the Access database that you want the DSN to reference (e.g., Northwind). Click OK twice to continue.

Make sure your DSN points to an Access database on a locally connected LAN or that the Web server has an identically named DSN. This DSN is the route that browsers will use to get to the database on the Web server.

Running the wizard

To start the wizard, open your database in Access, then choose Save As HTML from the File menu. When the first wizard dialog appears, click Next. (You shouldn't choose a publication profile until you become thoroughly familiar with the process.)

On the second wizard page, select one or more database objects that you want to publish. When you're publishing dynamically, this object will often be a select query. You can select fields and filter rows in the query until it represents just the datasheet you want to publish.

The third page lets you choose a background template. However, since you can get such superior results by using an HTML editor like FrontPage or FrontPage Express, we recommend you avoid doing any editing with the wizard.

On the wizard's fourth page, select one of the two dynamic publishing formats. The fifth page then asks for information about the DSN, as Figure B shows. When you're dynamically publishing a datasheet, you only need to complete the Data Source Name text box. Type the name of the DSN pointing at the Access database that you want to reference; this is the name you entered when you created the system DSN.

Figure B

The fifth Publish To The Web wizard screen lets you enter a DSN that points to an Access database.

The sixth wizard screen lets you designate a source in which to save the dynamic publication files. Remember, there's a single ASP file for each datasheet, but both an IDC and an HTX file for each datasheet when using the HTX/IDC technology. You should deposit the IDC and HTX files in a folder that contains httpodbc.dll. This folder must have Execute Scripts permission in order for the files to work properly.

You can store the ASP file in any virtual directory of your Web server that has Execute Scripts permission. No special program needs to be in the same folder.

We recommend not selecting a home page on the seventh wizard screen. Instead, create an HTML page with a hyperlink that points to the IDC or ASP file. By placing on a single page a collection of hyperlinks that point to different IDC or ASP pages, you can offer users a menu of different datasheet publications. If you use the automatic home page generator, you'll have to recreate all your publications whenever you change—or add—even one.

The last page of the wizard lets you save your selections as a profile. You can then re-use all or some of your choices in future uses of the wizard. If you find yourself making multiple runs through the wizard, you'll want to experiment with this option.

Running dynamic Web pages

For our example, we created a select query in the Northwind database to sort the Employees table by Last Name and show employee names and extensions; the query's name is qryEmpExtensions. Then, we published the query dynamically using both HTX/IDC and ASP formats. Finally, we created with FrontPage Express an htm file with two hyperlinks. One link points to qryEmpExtensions_1.idc, the other to qryEmpExtensions_1.asp. (The wizard automatically names the files in this manner.)

Figure C shows the htm file with the two hyperlinks in the top panel. The lower left panel shows the IDC file selection. The lower right panel shows the ASP file selection.

Figure C

We created both IDC and ASP files to publish our data.

As you can see, the query we used includes an extra column—Employee ID—and sorts the records in descending order by last name. It filters out some employees—those whose extensions' first digit is less than 3.

We also added a new employee named Rick Dobson to the database. Notice that we changed the new employee's extension in the Access database in between taking the IDC and ASP screen shots.

Revising your pages

Once you've successfully published a database, you may want to change the IDC, HTX, or ASP files. Editing these files by hand can be faster than re-running the wizard. Besides, you lose any extra formatting you've added to the files if you re-run the wizard.

Let's look at how to perform two basic tasks that require light editing. We'll change the sort order from descending to ascending and remove the Employee ID column from the report. We could adjust other publication features, but these two modifications illustrate basic principles that relate to just about all light editing situations.

To change the sort order for the IDC file, open qryEmpExtensions_1.idc, as shown in Figure D. Notice the simple IDC file format. It contains a field name, such as Datasource, Template, or SQLStatement, followed by a colon and the value for the field. Although the SQLStatement field looks very much like the SQL view in Access, there's a difference: Lines after the first one start with a plus sign. To change the sort order, simply highlight DESC and type ASC.

Figure D

We'll change this IDC file's sort order from DESC (descending) to ASC (ascending).

Figure E shows the comparable code segment from the ASP file. Mixing the VBScript and the SQL in the ASP file makes for a more complex view of the SQL than you get with the IDC file. In all fairness, the ASP file prepares an ADO recordset based on the SQL, and the IDC file doesn't accomplish this or other tasks inherent in the ASP file. However, if you just want to change the sort order, it's easier to read and edit in the IDC file.

Figure E

The ASP file is somewhat more complex than the IDC file.

To remove the Employee ID column from the IDC report, you must revise its corresponding HTX file. Listing A shows an excerpt from qryEmpExtensions_1.htx. Delete two cells—one from the THEAD block and the other from the TBODY block. The heading cell to delete contains Employee ID; the body cell to delete contains %EmployeeID%. (EmployeeID is the database field name, and the leading and trailing %s tell the IDC to insert the current record's field value into the HTML for viewing by the browser. The IDC automatically loops through all the records in the underlying data source.)

Listing A: Excerpts from qryEmpExtensions_1.htx

<THEAD>
<TR>
<TH BGCOLOR=#c0c0c0 BORDERCOLOR=#000000 >
<FONT SIZE=2 FACE="Arial" COLOR=#000000>Employee ID</FONT></TH>
<TH BGCOLOR=#c0c0c0 BORDERCOLOR=#000000 >
<FONT SIZE=2 FACE="Arial" COLOR=#000000>Last Name</FONT></TH>
<TH BGCOLOR=#c0c0c0 BORDERCOLOR=#000000 >
<FONT SIZE=2 FACE="Arial" COLOR=#000000>First Name</FONT></TH>
<TH BGCOLOR=#c0c0c0 BORDERCOLOR=#000000 >
<FONT SIZE=2 FACE="Arial" COLOR=#000000>Extension</FONT></TH>

</TR>
</THEAD>
<TBODY>
<%BeginDetail%>
<TR VALIGN=TOP>
<TD BORDERCOLOR=#c0c0c0 ALIGN=RIGHT>
<FONT SIZE=2 FACE="Arial" COLOR=#000000><%EmployeeID%><BR></FONT></TD>
<TD BORDERCOLOR=#c0c0c0 ><FONT SIZE=2 FACE="Arial" COLOR=#000000><%LastName%><BR></FONT></TD>
<TD BORDERCOLOR=#c0c0c0 ><FONT SIZE=2 FACE="Arial" COLOR=#000000><%FirstName%><BR></FONT></TD>
<TD BORDERCOLOR=#c0c0c0 ><FONT SIZE=2 FACE="Arial" COLOR=#000000><%Extension%><BR></FONT></TD>

</TR>
<%EndDetail%>
</TBODY>

Listing B shows the comparable code from the ASP file. Again, you just have to remove two cells, one in the table heading and another in the table body. While the ASP excerpt has the same general layout, notice that it's longer. This extra length is partly because the code includes VBScript for looping through the recordset created in Figure D. Since the ASP file does reference a recordset, its code references the recordset and its fields by name. The file also performs HTML encoding with a call to a function named HTMLEncode.

Listing B: Excerpts from qryEmpExtensions_1.asp

<THEAD>
<TR>
<TH BGCOLOR=#c0c0c0 BORDERCOLOR=#000000 >
<FONT SIZE=2 FACE="Arial" COLOR=#000000>Employee ID</FONT></TH>
<TH BGCOLOR=#c0c0c0 BORDERCOLOR=#000000 >
<FONT SIZE=2 FACE="Arial" COLOR=#000000>Last Name</FONT></TH>
<TH BGCOLOR=#c0c0c0 BORDERCOLOR=#000000 >
<FONT SIZE=2 FACE="Arial" COLOR=#000000>First Name</FONT></TH>
<TH BGCOLOR=#c0c0c0 BORDERCOLOR=#000000 >
<FONT SIZE=2 FACE="Arial" COLOR=#000000>Extension</FONT></TH>

</TR>
</THEAD>
<TBODY>
<%
On Error Resume Next
rs.MoveFirst
do while Not rs.eof
 %>
<TR VALIGN=TOP>
<TD BORDERCOLOR=#c0c0c0 ALIGN=RIGHT>
<FONT SIZE=2 FACE="Arial" COLOR=#000000><%=Server.HTMLEncode(rs.Fields("EmployeeID").Value)%><BR></FONT></TD>
<TD BORDERCOLOR=#c0c0c0 ><FONT SIZE=2 FACE="Arial" COLOR=#000000><%=Server.HTMLEncode(rs.Fields("LastName").Value)%><BR></FONT></TD>
<TD BORDERCOLOR=#c0c0c0 ><FONT SIZE=2 FACE="Arial" COLOR=#000000><%=Server.HTMLEncode(rs.Fields("FirstName").Value)%><BR></FONT></TD>
<TD BORDERCOLOR=#c0c0c0 ><FONT SIZE=2 FACE="Arial" COLOR=#000000><%=Server.HTMLEncode(rs.Fields("Extension").Value)%><BR></FONT></TD>

</TR>
<%
rs.MoveNext
loop%>
</TBODY>

One reason for hand editing the files generated by the wizard is to preserve the formatting. Figure A on page 1 shows a modified version of the IDC file output that appears in the lower-left corner of Figure C. We've edited the IDC and HTX files as described above, and made formatting changes with FrontPage Express, the page editor that ships with Internet Explorer 4.0.

The FrontPage Express improvements include a more intelligible table title as well as a common background image for both the table and page. Fonts appear in larger sizes or bolder faces. The telephone extensions are right-justified. Finally, the page closes with a "designed by" credit.

Publishing parameter queries

Parameter queries are very popular with Access users and developers because they make it easy to look up information in a table. These database objects automatically generate a simple form, wait for the user to fill it in, and then generate a return set that matches the user's input. They're essentially dynamic reports, and the Publish To The Web wizard makes it easy to put them on the Web.

Figure F

We've set up this Access query to run over the Web.

Figure F shows a simple parameter query for the Northwind database in action. In the top panel, you see our custom parameter query that returns an employee's home phone number based on the employee's ID. The second panel shows the automatic prompt that Access creates. We typed in 2 to get the home phone for Andrew Fuller, whose ID number is 2. The bottom panel shows the return set.

To publish this kind of query, just choose it on the second page of the Publish To The Web wizard. In reply, the wizard generates two Web pages. The first prompts the user to input a search criterion; the second is a datasheet with records matching the criterion entered on the first page.

Figure G presents the two screens that a user sees when he runs the parameter query over the Web. The first page simply prompts for an employee ID. When the user clicks the Run Query button, the Web server replies with the matching record(s), as the lower part of Figure G shows.

Figure G

The query in Figure F generates these two Web pages.

Improving your queries

You can make this easy query capability a little or a lot more complicated in two ways. First, you can use a text criterion instead of a numeric criterion. Second, you can allow wildcard parameters in the search.

When you use a text field as the search criterion, you must set the parameter data type, a step that's not required when you use a numeric field for setting the search criterion or when you plan to run the parameter query exclusively from within Access.

To set the parameter data type, right-click in the top of the Query-by-Example grid to bring up the Query Parameters dialog. Then, copy the text in the criteria row to the Parameter column in the Query Parameters dialog. Second, select Text as the data type. If you fail to perform these extra steps before publishing your parameter query, it will fail when you attempt to run it over the Web.

Wildcards represent another kind of challenge to the wizard, which doesn't translate wildcard parameters correctly. This situation is unfortunate since wildcards are popular with parameter queries. Although wildcard searches are beyond the scope of this article, we'll mention two separate Microsoft Knowledge Base reports describing the problem and solution: Report Q163893 gives the IDC solution; the ASP solution appears in report Q162977.

Conclusion

Dynamic publishing technology can be just what you need for publishing data from tables to the Web. When you need immediate display of database revisions over the Web, dynamic publishing is the only way to go!

Although dynamic publishing delivers a more timely view of an underlying database than does static publishing, this improved timeliness brings additional requirements. In this article, we've shown you how to publish dynamic Access reports using IDC/HTX and ASP.

__________________

Rick Dobson, Ph.D., is president of CAB, Inc., a consultancy offering database, Internet, and Office development services. Rick is Microsoft certified to train developers in Access and Office 97 development. He's a contributing editor to Microsoft Interactive Developer and a frequent contributor to Byte. You can reach Rick online at RickD@cabinc.win.net or by fax at (502) 426-3743.

___________________

This article is reproduced from the February 1998 issue of Microsoft Office Developer's Journal. Microsoft Office Developer's Journal is an independently produced publication of The Cobb Group. No part of this article may be used or reproduced in any fashion (except in brief quotations used in critical articles and reviews) without prior consent of The Cobb Group. To contact The Cobb Group, please call (800) 223-8720 or (502) 493-3200.

Copyright © 1998 The Cobb Group, a division of Ziff-Davis Inc. The Cobb Group and The Cobb Group logo are trademarks of Ziff-Davis Inc. All rights reserved. Reproduction in whole or in part in any form or medium without express written permission of Ziff-Davis is prohibited.


Getting to Know Beans: JavaBeans in Visual J++

By Brian Johnson

For the past couple of months I’ve been playing with JavaBeans, the component architecture that lets programmers create reusable objects (beans) that can be used without internal modification in Java programs. When you start playing around with multiple component architectures such as ActiveX, Delphi controls, and JavaBeans, creating components becomes second nature.

Because JavaBeans are designed to be used in visual development tools like Borland’s JBuilder and IBM’s Visual Age for Java, many Visual J++ users are left wondering how to use beans in their own programs. In this article, I’ll tell you a little about how beans are created, and how you can use them from Visual J++.

Component Basics

Before we talk about anything else, I want to get generic and discuss what a component is without getting into the code. Once the concepts are understood, the code should come easily.

For any component architecture, there are three characteristics that make it useful to the end programmer: methods, properties, and events. By “end programmer,” I’m referring to the developer who’s never seen the internals of the component, and really doesn’t need to worry about how things work.

A method is any function in a component. In any object-oriented or object-based language where methods of a component are called, the terminology usually consists of the name of the component, followed by a period (.), followed by the name of the method. To make things easier on the end programmer, any arguments to a method in a component are usually set up as properties. This differentiates a component from something like a package or class library, for which the end programmer provides arguments to functions within the package.

A property is a value that’s set in a component. Properties help to insulate the component, because the value of the property can be checked before use. Properties are set and retrieved in components through “get” and “set” methods. A method that sets a property is called the “set” method; the value of a property is retrieved through a “get” method. Without getting into too much code at this point, a “get” method for a bean looks something like this:

public int getMyValue() {
  ...
  return myNumber;
}

In this example, myNumber is a private variable within the component. In JavaBeans, the “get” portion of the getMyValue method is a standard naming convention telling a visual tool that this is a method that gets the value of a property.

In JavaBeans, the value of a property is set in much the same way as it’s retrieved. The word “set” is used in the method, while the new value for the property is passed as a parameter:

public void setMyValue(int val) {
  myNumber = val;
}

Notice that the method takes the value that’s passed, and sets the value of a private internal variable, which is then used internally in the component.

The final element of a component is an event. Events are important when you want to be able to write code that’s executed when things happen. Common events include mouse clicks, property changes, and keystrokes. Events are important in that they allow an application that contains a component to receive notification when things happen to that component.

The BeanBox

Javasoft’s Bean Developers Kit provides all the information you need to start working with JavaBeans and the JavaBeans API. Along with documentation, the kit also includes a reference implementation of the BeanBox visual development tool. If your bean works in the BeanBox, it will most likely work in other visual development tools. You can get the Javasoft BDK on the Web at http://www.javasoft.com/beans.

Beans themselves are actually just classes and supporting files that have been packaged in a .JAR file, which is a .ZIP-type archive that can be used directly by other visual development tools. To create .JAR files, you’ll probably need to obtain a copy of the Javasoft JDK 1.1.x, which contains a .JAR utility. If you’ve upgraded to Microsoft’s SDK for Java 2.0 or higher, you’ll have no problems either writing or compiling bean classes. [For a complete discussion of .JAR and CAB files, see Sean Sullivan’s article “From CAB to JAR” in the January 1998 Microsoft Visual J++ Informant.]

Creating beans. Microsoft Visual J++ is a natural environment for building beans: Component development depends mostly on hand-written code; Visual J++ provides an integrated environment where you can code and debug your bean classes. If you’re new to beans, the following primer touches on coding bean properties, methods, and events. It barely scratches the surface in terms of how far you can go with beans, but it should provide enough information to get you started.

Bean properties. Properties are the values within a bean; e.g. the bean is blue, the dial is set to 70, and the width of the box is 25. One of the features of a component model like JavaBeans is that a property can be set at design time (when you create a program using the bean in a visual programming tool), at run time (when you run the program), or both.

In a visual development tool, access to a bean’s properties is usually provided through a properties dialog box of some sort. The list of properties attached to a particular bean is known as a property sheet. The look of a property window or dialog box depends on the visual tool vendor.

There are several types of properties you can set in your beans: simple, indexed, bound, and constrained. In this article, I’ll show you how to set up simple and bound properties. Simple properties are important because that’s probably how you’ll set up most of the properties in any bean you create; bound properties are important because they send messages when the value of the property changes.

Getting and setting simple properties. Simple properties are created using a specific syntax. You can set a property, and you can get a property. Getting and setting refer directly to the way the properties are created and accessed. For example, setting up a property in a bean is as simple as adding a “set” method to the name of the property:

public void setMyValue(int val);

To retrieve that value, an associated “get” method is used:

public int getMyValue();

Though it might not seem obvious at first, there’s something special going on with these “get” and “set” methods. When created properly, simple properties are added to the bean’s property sheet automatically, through a process known as introspection. This simply means a tool can figure out what a bean does and what its properties are, just by looking at it. You don’t have to do anything special to set up simple properties in your beans. Just use “get” and “set,” and away you go.

Take a look at the code in Figure 1. This is a simple, do-nothing bean. I’ve added the “get” and “set” methods for the myValue property.

import java.awt.* ;
import java.beans.* ;
import java.io.* ;

public class PropBean extends Canvas
  implements Serializable {

  private int myNumber;

  public PropBean() {
    setSize(60, 40);
  }

  public void paint(Graphics g) {
    g.fillRect(0, 0, 60, 40);
  }

  public void setMyValue(int val) {
    myNumber = val;
  }

  public int getMyValue() {
    return myNumber;
  }
}

Figure 1: A simple, do-nothing bean.

Bound Properties

If you’ve ever wanted to be notified immediately about something, then you’ll understand a bound property — a property that sends a message when it changes.

What’s so great about that? Another bean or the application that contains the bean can listen for a message broadcast when the value of the bound property changes. When one bean listens to another, it means the messages the first bean creates can be interpreted by the listener bean, allowing it to react to changes in the bean sending out the message.

This might seem confusing at first; because it’s so important, it’s worth a good, hard look. Messages are what make a graphical user interface (GUI) useful. Messages are generated based on events that take place in a program. Messages are being fired all the time in GUI applications and operating systems; the JVM (Java Virtual Machine) environment is no exception. The problem is that many messages are being generated. It wouldn’t be very efficient if a bean had to listen to all these messages just to find out the one thing it was interested in.

A bean sends a message when a bound property changes. A listener bean is notified when the bound property changes. For a bean to let other beans listen in on property changes, it must use the addPropertyChangeListener method. To get off the listener list, a listener bean must be able to de-register its interest. This is done through the removePropertyChangeListener method.

Once addPropertyChangeListener and removePropertyChangeListener are in place, the bean creates a PropertyChangeSupport object, and uses the firePropertyChangeEvent method to broadcast a message containing the details of the change.

The code in Figure 2 shows you how this works. This bean code sends a notification when its name property changes. To determine whether this really works, you’ll need to register a listener bean, then have it report the results of a property change. You can test this in the BeanBox by using the ChangeReporter bean, which consists of a text box that’s able to report changes to beans with which it’s a registered listener.

import java.awt.* ;
import java.beans.* ;
import java.io.* ;

public class BoundBean extends Canvas
  implements Serializable {

  // Set up two variables. myString holds the value of the
  // myBound property. changes is a property support object
  // we need to add the property-change support
  // functionality.
  private String myName = "BoundBean";
  private PropertyChangeSupport changes = new

  PropertyChangeSupport(this);

  public BoundBean() {
    resize(60, 40);
  }

  public void paint(Graphics g){
    g.fillRect(0, 0, 60, 40);
  }

  // This is where the action is. When the value of the
  // myBound property is changed, the FirePropertyChange
  // method broadcasts the name of the property, the old
  // value, and the new value.
  public void setName(String s) {
    String old = getName();
    myName = s;
    changes.firePropertyChange("name", old, myName);
  }

  public String getName() {
    return myName;
  }

  // addPropertChangeListener lets listeners
  // register with our bean.
  public void
    addPropertyChangeListener(PropertyChangeListener l) {
    changes.addPropertyChangeListener(l);
  }

  // removePropertyChangeListener lets a
  // listener de-register.
  public void
    removePropertyChangeListener(PropertyChangeListener l) {
    changes.removePropertyChangeListener(l);
  }
}

Figure 2: A bean that announces its property changes.

To test this bean, you need to compile it, package it, and put it in the BeanBox. Once the bean is prepared and installed, the following steps will hook the BoundBean to the ChangeReporter bean:

Click BoundBean in the BeanBox ToolBox, then click the BeanBox window to place an instance of the bean there.

Click ChangeReporter in the BeanBox ToolBox, and place an instance of it in the BeanBox.

Click BoundBean to select it. A hashed line will be drawn around the bean.

Select Edit | Events | propertyChange from the BeanBox menu. A red line will follow your mouse pointer in the BeanBox.

Click the ChangeReporter bean to bring up the EventTargetDialog list box.

Select reportChange from the EventTargetDialog list box, and click OK. You should see a message that reads, “Generating and compiling adaptor class.”

To test the BoundBean, simply select it and watch the ChangeReporter bean. You should see the name of the property being edited, and the new value for the property as it’s being changed. In Figure 3, the value of the ChangeReporter bean matches the name property in the Properties window.

Figure 3: The BoundBean being tested in the BeanBox.

Bean Methods

Methods are the functions that exist inside your bean. Beans generally won’t contain a lot of public methods, but you can create beans for other programmers just to make certain methods you write easy to use. The methods you create for your beans are no different than in any other Java class. What you need to think about is whether these methods should be available to the end user. Public methods will be available programmatically to the end user. Private methods will be available only within the bean itself. Most of the methods you create inside your beans will usually remain private. Rather than call a method directly, a user will usually plug in a value or two, then get the result from the bean visually or as a property.

Methods may be used in beans as parts of invisible beans, which are JavaBeans that have no run-time interface. Invisible beans come in handy because they let programmers plug in values by using the bean’s property sheet in a visual development tool. Invisible beans can make it easy for a programmer to use a class without having to refer to the class’ documentation. This makes it easy to take advantage of the beans’ properties.

Figure 4 shows you how an invisible bean might work. This bean simply takes two integer values, and figures out the average. I talked about the set and get methods in the last section. The method used to perform the calculation is private, and is called from the getFinal method. Because the property Final has no set method in this class, it’s considered a read-only property. When the property is accessed, the private method Average is called, and returns the average of the Value_1 and Value_2 properties.

public class InvisibleBean {

  private int val1, val2;

  public InvisibleBean() {
  }

  public void setValue_1(int v) {
    val1 = v;
  }

  public int getValue_1() {
    return val1;
  }

  public void setValue_2(int v) {
    val2 = v;
  }

  public int getValue_2() {
    return val2;
  }

  // Property that contains a result
  public int getFinal() {
    return average(val1, val2);
  }

  // Private method in class / bean
  private int average(int x, int y) {
    return (x + y)/2;
  }

  public void init() {
    val1 = 0;
    val2 = 0;
  }
}

Figure 4: An invisible bean that returns a calculation as a read-only property.

Bean Events

Bean events include things like mouse movements, clicks, keystrokes, and changes in the property of a bean. Bean events allow the end user to attach things that happen to the bean to methods, properties, and events — in other beans, and in the program where the bean is used.

Bean events are closely tied to communications between beans and the application where the bean is used. Three things need to happen when an event takes place in a bean:

An event takes place in a bean.

The bean sends out a message saying the event happened.

Another bean is waiting for a message from the first, and executes some code after it gets the message.

The bean where the event originates is called the event source. The bean waiting for the message that the event happened is called the event listener.

If you’re working with the canvas as the bean, then you don’t need to do anything special to use these events in visual development tools. Since these events are inherited, you need only override them when you want to add your own functionality.

When your bean becomes more complex, there’s a slight problem with simply wiring bean events. If your bean consists of one or more buttons, for example, it would be good to know which button was pressed by the user.

Think about it this way: Suppose you’re listening to your car radio. You clicked on the button that started your radio, and everything was great. You’re the audience, the click was the event, and the radio is the bean containing the event. If your radio bean played just one station, you’d be set. All the radio bean would have to do is fire an event for the simple act of clicking on it. But your radio has six buttons, each of which play a different station when pushed. How do we wire things so we can figure out what will happen when any given button is pressed?

The answer is to create an adaptor that implements an ActionListener interface for each of the buttons on the radio. The ActionListener stuff is inherited from java.util.EventObject, which is part of the JavaBeans API. You wire the adaptor classes to each of the events in the bean by using addActionListener.

To keep things as simple as possible, I’ll create a bean that contains just a couple of buttons (see Figure 5). This preliminary bean is shown in Figure 6. When pressed, each of the two buttons send a string to the console.

import java.awt.* ;
import java.awt.event.*;
import java.beans.* ;
import java.io.* ;

public class ButtonBean extends Panel {

  public ButtonBean() {
    Button btn1 = new Button("Button 1");
    Button btn2 = new Button("Button 2");
    setLayout(new FlowLayout());
    add(btn1);
    add(btn2);

    // Create listener objects for buttons.
    btn1.addActionListener(new btn1Adaptor());
    btn2.addActionListener(new btn2Adaptor());
  }

  public void paint(Graphics g){
  }

  // Adapter for Button 1
  class btn1Adaptor implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      System.err.println("Button 1 Pressed");
    }
  }

  // Adapter for Button 2.
  class btn2Adaptor implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      System.err.println("Button 2 Pressed");
    }
  }
}

Figure 5: The ButtonBean bean uses adaptors to determine which button was pushed.

Figure 6: The ButtonBean in the BeanBox.

If you play with the bean at this point, you’ll notice there’s no way to attach a btn1 click event to another bean. For that, we can add a bound property, which can then be changed from adaptor classes. The new source code could look something like that in Figure 7.

import java.awt.* ;
import java.awt.event.*;
import java.beans.* ;
import java.io.* ;

public class DualButtonBean extends Panel {

  private int channel = 0;
  private PropertyChangeSupport changes =
    new PropertyChangeSupport(this);

  public DualButtonBean() {
    Button btn1 = new Button("1");
    Button btn2 = new Button("2");
    setLayout(new FlowLayout());
    add(btn1);
    add(btn2);
    btn1.addActionListener(new btn1Adaptor());
    btn2.addActionListener(new btn2Adaptor());
  }

  public void setBeanChannel(int x) {
    int old = getBeanChannel();
    channel = x;

    // Convert int values to strings.
    String myStringOld = "" + (old);
    String myStringNew = "" + (channel);

    // Fire the property change event.
    changes.firePropertyChange("beanChannel",
                                myStringOld,
                                myStringNew);
  }

  public int getBeanChannel() {
    return channel;
  }

  public void paint(Graphics g){
  }

  // addPropertChangeListener lets listeners
  // register with our bean.
  public void
    addPropertyChangeListener(PropertyChangeListener l) {
    changes.addPropertyChangeListener(l);
  }

  // removePropertyChangeListener lets a
  // listener de-register.
  public void removePropertyChangeListener(
                PropertyChangeListener l) {
    changes.removePropertyChangeListener(l);
  }

  class btn1Adaptor implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      setBeanChannel(1);
    }
  }

  class btn2Adaptor implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      setBeanChannel(2);
    }
  }
}

Figure 7: The ButtonBean class revised. DualButtonBean uses bound properties.

There are lots of ways to do this. My solution was to create a PropertyChangeSupport object named “changes” to later fire a PropertyChangeEvent within the bean. When the propertyChange event is wired to the BDK’s ChangeReporter bean, the change made to the beanChannel property is displayed.

Using a Bean in Visual J++

To use a bean in a hand-written applet or program, you first need to unpack the .JAR file that contains the bean classes. The BeanInfo class is unimportant; the information available in this file is there to make the bean easier to use in a visual tool. To find out what methods, properties, and events are available in a bean, you’ll need to rely on the documentation the bean writer supplies; or you can take a look at the bean in the BeanBox. Once you know what the class has to offer, you can simply add the bean to any class you create. The listing in Figure 8 shows you how the DualButtonBean class might be added to a simple applet.

import java.applet.*;
import java.awt.*;
import java.beans.*;

public class ByHand extends Applet {

  PropertyChangeAdapter adapter = new
  PropertyChangeAdapter();
  int dualButtonChannel;
  String dualButtonString;
  DualButtonBean myDual = new DualButtonBean();

  public ByHand() {
  }

  public String getAppletInfo() {
    return "Name: ByHand\r\n" +
           "Author: Brian Johnson\r\n" +
           "Created with Microsoft Visual J++ Version 1.1";
  }

  public void init() {
    setLayout(new FlowLayout());
    add(myDual);
    myDual.addPropertyChangeListener(adapter);
    resize(320, 240);
  }

  public void writeValue() {
    dualButtonChannel = myDual.getBeanChannel();
    dualButtonString = "" + (dualButtonChannel);
    repaint();
  }

  public void paint(Graphics g) {
    g.drawString(dualButtonString, 10, 20);
  }

  class PropertyChangeAdapter
    implements PropertyChangeListener {

    public void propertyChange(PropertyChangeEvent e) {
      writeValue();
    }
  }
}

Figure 8: The ByHand class contains the DualButtonBean from Figure 5.

For this to work, the DualButtonBean classes need to be in the same directory as the ByHand class. It’s really rather simple. I just used the Visual J++ App Wizard to create a simple applet. I created an instance of DualButtonBean by inserting the line:

DualButtonBean myDual = new DualButtonBean();

I then added myDual to the applet using add(myDual). The tricky part comes when we want to receive notification of a bean event. I worked from the sample code for the BDK’s ChangeReporter bean to add a PropertyChangeAdaptor to the ByHand class. As suggested in the commented source for the ChangeReporter class, I created a PropertyChangeAdaptor named adaptor, and added a PropertyChangeListener to the myDual object. I then created a PropertyChangeAdaptor class to listen for and react to events.

Next the writeValue method is called, retrieving the getBeanChannel property. This property is then changed to a string and written to the applet; you can see the result in Figure 9. The number that’s drawn depends on the button that’s pushed. Again, the programmer using the bean isn’t concerned with the inner machinations of the bean — only with the result.

Figure 9: The ByHand class containing the DualButtonBean in Internet Explorer.

Conclusion

JavaBeans is a component architecture designed to be used with both visual development tools and with tools like Microsoft Visual J++. This article has introduced some component concepts, and some features of beans. As a Java editing tool, Visual J++ lends itself naturally to the creation of bean components. Finally, I showed you how you can take advantage of bean components in your own Visual J++ projects.

The files referenced in this article are available for download from the Informant Web site at http://www.informant.com/ji/jinewupl.htm. File name: JI9802BJ.ZIP.

Brian Johnson is a free-lance writer and programmer in Orlando, FL. He’s co-author of VBScript Unleashed [SAMS, 1996]. You can e-mail him at brianjay@gate.net.


Java SDK 2.0: A Two-Way Bridge between ActiveX and Java

By Trevor Harmon

In July of 1996, I was hired to write a book about Microsoft Visual J++. I studied up on the product and was amazed at its potential. It could provide what no other software could: a bridge between the distant worlds of ActiveX and Java. With Visual J++, I could now integrate legacy Windows code with the elegant new features of Java. My mouth watered at the thought of designing Java applets that could talk to the powerful ActiveX controls I had written.

When Microsoft sent me a beta version of Visual J++, I popped open the hood and poked around, checking out the user interface and discovering (in utter disbelief) that the compiler could churn through a million lines in less than a minute. I also took a look at the ActiveX integration features in Visual J++, but I was disappointed to learn that most of those features were not working in my beta version.

Puzzled, I contacted one of the program managers for Visual J++ and asked him why the product’s ActiveX support — a feature that had been so heavily hyped on Microsoft’s Web site — was seemingly non-existent. “Well,” he said, “that’s just marketing slime, I’m afraid.” He went on to explain how Microsoft’s marketing department had redefined the meaning of the term ActiveX control. Previously, an ActiveX control was a heavyweight software component that supported a variety of interfaces and required a container application in which to operate. Now, however, it was nothing more than a simple OLE object.

I suddenly realized what had happened. Facing increasing competition from Borland, Symantec, and other vendors of Java development tools, Microsoft was under pressure to release the final version of Visual J++ as quickly as possible. They had already implemented support for basic OLE objects, but integration of true ActiveX controls was still several months away. So, rather than ship Visual J++ without its most significant feature, Microsoft’s “marketeers” decided to save face by quietly changing the definition of ActiveX controls. The result was a product whose feature set matched the wording of its press releases perfectly, but only because the meaning of those words had completely changed.

Over the next several months, I waited patiently as Microsoft upgraded Visual J++ and released two versions of their Java SDK. None of the products offered the ActiveX integration that was originally announced. Today, more than a year and a half later, Microsoft has finally delivered: Java SDK 2.0 can handle two-way integration of ActiveX and Java. Available for free download at http://www.microsoft.com/java, it can put ActiveX components inside Java programs, and it can put Java components inside ActiveX programs. Thanks to these features, developers must no longer choose between the two architectures — they can simply choose whatever tool is right for the job.

The Video Player Components

In this article, we’ll learn how to harness the power of Microsoft’s Java SDK to combine an ActiveX control with a JavaBean, the standard form for Java components. All the code we’ll be looking at centers around two components: an ActiveX control that plays video files, and a JavaBean that displays a few 3D-style buttons for controlling multimedia streams.

The ActiveX control is called “Video Playback”; I created it using Microsoft Visual C++ 5.0. It exposes methods for opening, playing, and pausing video files. It also offers properties for obtaining the total number of frames, changing the current frame, and stretching the video to a window. Because of the unnecessary complexity of the Windows Multimedia API and the C++ language itself, the source code for the Video Playback control is far too long to present here. However, you can download the code from the Informant Web site; see end of article for details. Figure 1 shows the control in action.

Figure 1: The Video Playback ActiveX control exposes methods and properties for playing videos.

The JavaBean is called “Video Buttons”; I created it using Microsoft Visual J++ 1.1 and the Microsoft Java SDK 2.0. It displays four buttons: reverse, play, stop, and forward (see Figure 2). It is much simpler than the Video Playback control — partly because the bean’s purpose is less demanding, but mostly because the Java language makes just about everything easier. This component doesn’t expose any methods or properties, although it does implement an event that gets fired whenever the user clicks one of the four buttons. Listing One on page XX shows the complete source code for the buttons.

Figure 2: The Video Buttons JavaBean fires an event whenever the user clicks one of its buttons.

ActiveX Controls in Java

The virtual machine (VM) included with Microsoft’s Java SDK can recognize ActiveX controls and treat them as though they were 100 percent pure Java components. However, it can’t do its job alone. It needs the help of a utility program called “JActiveX”. Located in the Java SDK’s bin directory, JActiveX reads the type library in an ActiveX control and generates a Java source-code wrapper from the information it finds. It stores this wrapper in a directory descending from the trusted libraries directory that was created by the installation of Microsoft’s VM. For instance, to generate a Java wrapper for the Video Playback control, I issued the following command in a DOS box:

jactivex VideoPlayback.ocx

Moments later, JActiveX placed a Java source-code wrapper for my control in a directory called C:\Windows\Java\TrustLib\videoplayback. By browsing the source code in this directory, I was able to determine how to call the Video Playback control’s methods, as well as how to access its properties from Java.

You can use this same technique for integrating ActiveX controls in your Java applications. Simply run the JActiveX tool on your control as I did, then check your C:\Windows\Java\TrustLib directory. (Note that this directory may differ depending on where you installed Windows.) Look for a Java source-code file whose name is identical to the name of your control, but with an ‘I’ prepended to it. This file should contain the Java interface that gives you access to the control’s methods and properties. The Video Playback control, for example, has a wrapper file named IVideoPlayback (see Listing Two on page XX). Note: Listing Two is a simplified version of the output from JActiveX; the code is the same, but Listing Two doesn’t show the comments that the Microsoft Java compiler requires.

Notice how JActiveX automatically translated the ActiveX property types and method parameter types into their Java equivalents when it generated the interface file (i.e. BOOL became boolean and LPSTR* became String).

Note: To compile the interface IVideoPlayback from the Java SDK, users must employ the Microsoft Java compiler. The source code is standard Java, but Microsoft’s compiler interprets the comments in IVideoPlayback to generate the ActiveX-Java bridge.

Once you’ve studied a control’s Java interface wrapper, you can easily write an application that integrates with the control via that interface. Simply create a new instance of the control, then call the interface methods. For example, here’s how to open a video file in Java using the Video Playback control:

VideoPlayback vp = new VideoPlayback();
vp.Open("test.avi");

Note that we can’t simply create an instance of an interface; we can only create an instance of a class. This is why the previous code uses the VideoPlayback class instead of the IVideoPlayback interface. The VideoPlayback class provides an implementation of IVideoPlayback, and it was generated by JActiveX along with the other source-code wrapper files. See Listing Three, beginning on page XX, for a complete example program that uses this class. Figure 3 shows the results of running the Java program.

Figure 3: The results of running the Java program shown in Listing Three.

That’s about it. After you’ve learned to use JActiveX and the interfaces it generates, you can integrate just about any ActiveX control into your Java programs. There are, of course, some other issues you might run into, such as how to catch events fired by ActiveX controls and how to generate source-code wrappers for non-standard ActiveX data types. These topics are beyond the scope of this article, but you can find plenty of details on them in the Java SDK documentation.

Despite these extra complications, the Microsoft Java SDK 2.0 does a remarkably good job of shielding Java developers from the complexities of the ActiveX architecture. It finally provides them with the full power of ActiveX — including real ActiveX controls — while still allowing the elegance and ease of the Java language to shine through. In fact, as this article has shown, the Java SDK can help achieve what many thought was impossible: full-motion video playback in Java.

JavaBeans in ActiveX

So far, we’ve looked at how the Java SDK allows ActiveX controls to masquerade as JavaBeans. In this section, we’ll see how the SDK can make JavaBeans look like standard ActiveX controls. This feature lets any development tool that supports ActiveX, such as Visual Basic, Delphi, and PowerBuilder, import Java components into its native environment. The result is a two-fold advantage whenever developers build software in pure Java: Their components are usable not only on any platform, but also from any language. (Microsoft’s Java SDK 2.0 wasn’t the first product to offer this benefit. See the sidebar “Sun’s ActiveX Bridge” on page XX for details.)

The key to gaining this dual advantage is a utility program called JavaReg. JavaReg, which can be found in the Java SDK’s bin directory, saves information about a JavaBean in the Windows registry. The information is stored in the same format for standard ActiveX components. Consequently, all ActiveX-compatible development tools think that the JavaBean is just another ActiveX control. When they attempt to load the disguised control, they are not loading the JavaBean, but rather Microsoft’s VM. The VM acts as a proxy for the bean, translating ActiveX method calls into Java method calls and Java events into ActiveX events.

To learn how to use JavaReg, let’s run it on my Video Buttons JavaBean. First, we must supply JavaReg with a so-called Globally Unique Identifier, or GUID. A GUID is a 128-bit value that is always unique, no matter when or where it is generated. Every ActiveX control requires its own GUID to prevent naming and versioning conflicts. To generate a GUID for JavaReg, simply run the Create GUID program that ships with the Java SDK (see Figure 4).

Figure 4: The Create GUID program generates the 128-bit values required by JavaReg.

The other parameters needed by JavaReg are:

/register: Registers a JavaBean. (Use /unregister to unregister a bean.)

/class:javabean: Specifies the name of the bean to register.

/codebase:directory: Specifies the directory in which the bean is stored.

/typelib:filename: Specifies the name of the file in which to store the bean’s type library.

/control: Specifies that the bean should be treated as a true ActiveX control, not just an OLE object.

When we put these parameters together, here’s how the command line would look:

javareg /register /control /class:VideoButtons /codebase:. /clsid:{CD71F6B1-5280-11d1-A816-0020AFC746E8} /typelib:VideoButtons.tlb

Running this command line should produce the message “Successfully registered Java class.” Development tools will then display the registered JavaBean as if it were a standard ActiveX control (see Figure 5).

Figure 5: The JavaReg utility lets ActiveX development tools — such as Microsoft Visual C++ 5.0 shown here — use JavaBeans as if they were ActiveX controls.

To illustrate the benefits of JavaReg, I’ve put together a C++ application that integrates the Video Playback control with the Video Buttons JavaBean. Like any C++ application that uses ActiveX, its source code is ridiculously complex and is thus too long to include in this article (however, it is available for download; see end of article for details). Without the Microsoft Java SDK, however, the code would have been far more complex, because it would be forced to handle all the Java-to-ActiveX mappings itself. The fact that only a simple utility program was necessary for the mappings is a testament to the hidden power of Microsoft’s SDK.

Conclusion

Naturally, Microsoft is tight-lipped about their long-term plans for Visual J++, but you can be sure that a future version will include all the features found currently in the Java SDK. Newer versions will most likely provide enhancements to the SDK’s features, such as the ability to drag-and-drop ActiveX controls onto a Java dialog box. You will no longer have to shell to DOS and run an external utility every time you want to use an ActiveX component in a Java application.

Still, the present form of the Java SDK packs both power and simplicity. It provides a nearly transparent two-way bridge between the ActiveX architecture and the Java language. With the SDK, you finally have the freedom to choose whatever tool works best for you.

Begin Listing One — The Video Buttons JavaBean

import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;
import java.util.*;

public class VideoButtons extends Panel
                  implements java.io.Serializable {
  public static final int BACK    = 1;
  public static final int PLAY    = 2;
  public static final int STOP    = 3;
  public static final int FORWARD  = 4;

  Image imageButtons;
  Vector listeners = new Vector();

  public VideoButtons() {
   imageButtons = loadImage("buttons.gif");
   enableEvents(AWTEvent.MOUSE_EVENT_MASK);
  }

  public void paint(Graphics g) {
   g.drawImage(imageButtons, 0, 0, this);
  }

  protected void processMouseEvent(MouseEvent e) {

   if ( e.getID() == MouseEvent.MOUSE_PRESSED ) {
    if (e.getX() < 1 * 34) {
      fireButtonEvent(BACK);
    }
    else if (e.getX() < 2 * 34) {
      fireButtonEvent(PLAY);
    }
    else if (e.getX() < 3 * 34) {
      fireButtonEvent(STOP);
    }
    else {
      fireButtonEvent(FORWARD);
    }
   }

   super.processMouseEvent(e);
  }

  private void fireButtonEvent(int button) {
    Vector l;

    synchronized (this) {
      l = (Vector)listeners.clone();
    }

    for (int i = 0; i < l.size(); i++) {
      VideoButtonsListener listener =
        (VideoButtonsListener) l.elementAt(i);
      listener.buttonClicked(button);
    }
  }

  private java.awt.Image loadImage(String name) {
    try {
      java.net.URL url = getClass().getResource(name);
      return createImage((ImageProducer)url.getContent());
    }
    catch (Exception ex) {
      System.err.println("ERROR: Could not load " + name);
      return null;
    }
  }

  public void addVideoButtonsListener(
                VideoButtonsListener listener) {
    listeners.addElement(listener);
  }

  public void removeVideoButtonsListener(
                VideoButtonsListener listener) {
    listeners.removeElement(listener);
  }
}

interface VideoButtonsListener
  extends java.util.EventListener {
   public void buttonClicked(int button);
}

End Listing One

Begin Listing Two — The IVideoPlayback interface generated by the JActiveX tool

package videoplayback;

import com.ms.com.*;
import com.ms.com.IUnknown;
import com.ms.com.Variant;

public interface IVideoPlayback extends IUnknown {
  public int getTotalFrames();
  public void setTotalFrames(int newValue);

  public boolean getFitToWindow();
  public void setFitToWindow(boolean newValue);

  public int getCurrentFrame();
  public void setCurrentFrame(int newValue);

  public void Close();
  public boolean Open(String strFilename);
  public void Pause();
  public void Play(boolean bLoop);
  public void Stop();
}

End Listing Two

Begin Listing Three — VideoPlayer provides easy access to Video Playback

import java.awt.*;
import java.awt.event.*;
import videoplayback.*;

public class VideoPlayer extends Frame
  implements ActionListener, VideoButtonsListener {
  public static void main(String args[]) {
    VideoPlayer player = new VideoPlayer();
  }

  private VideoPlayback videoPlaybackControl;
  private VideoButtons videoButtonsBean;
  private Button loadVideoButton;

  VideoPlayer() {
    addWindowListener(new VideoPlayerWindowListener());
    setTitle("Video Player");
    setBackground(Color.lightGray);
    setVisible(true);
  }

  private class VideoPlayerWindowListener
    extends WindowAdapter {
    public void windowClosing(WindowEvent e) {
      System.exit(0);
    }

    public void windowOpened(WindowEvent e) {
      videoPlaybackControl = new VideoPlayback();
      videoButtonsBean = new VideoButtons();
      loadVideoButton = new Button("Load video...");

      videoButtonsBean.addVideoButtonsListener(
                         VideoPlayer.this);
      loadVideoButton.addActionListener(
                        VideoPlayer.this);

      setLayout(null);
      add(videoPlaybackControl);
      add(videoButtonsBean);
      add(loadVideoButton);

      Insets i = getInsets();
      setSize(i.left + 340 + i.right,
              i.top + 344 + i.bottom);
      videoPlaybackControl.setBounds(i.left + 10,
                                     i.top + 10, 320, 240);
      videoButtonsBean.setBounds(i.left + 102,
                                 i.top + 260, 136, 34);
      loadVideoButton.setBounds(i.left + 130,
                                i.top + 304, 80, 30);
    }
  }

  public void actionPerformed(ActionEvent e) {
    FileDialog dlg = new FileDialog(this, "Open Video",
                                    FileDialog.LOAD);

    dlg.show();

    if ( dlg.getFile() != null ) {
      videoPlaybackControl.Open(dlg.getFile());
    }
  }

  public void buttonClicked(int button) {

    switch (button) {

      case VideoButtons.BACK:
        videoPlaybackControl.setCurrentFrame(
           videoPlaybackControl.getCurrentFrame() - 1);
        break;

      case VideoButtons.PLAY:
        videoPlaybackControl.Play(false);
        break;

      case VideoButtons.STOP:
        videoPlaybackControl.Stop();
        break;

      case VideoButtons.FORWARD:
        videoPlaybackControl.setCurrentFrame(
           videoPlaybackControl.getCurrentFrame() + 1);
        break;
    }
  }
}

End Listing Three

The files referenced in this article are available for download from the Informant Web site at http://www.informant.com/ji/jinewupl.htm. File name: JI9802TH.ZIP.

Sun’s ActiveX Bridge

Microsoft’s Java SDK 2.0 isn’t the only game in town. Sun Microsystems, the company that created Java, offers a similar product called the ActiveX Bridge, available for free download from http://java.sun.com/beans. It can’t put ActiveX controls into Java programs, but it can bring JavaBeans into the ActiveX world. By running a bean through the ActiveX Packager (a utility program included with ActiveX Bridge), developers can perform the same tricks that they can perform with the Microsoft Java SDK. In other words, the ActiveX Packager stores information about a JavaBean in the Windows registry, allowing the ActiveX Bridge to serve as a proxy for the bean. As a result, Sun’s ActiveX Bridge can disguise a JavaBean as an ActiveX control just as well as Microsoft’s Java SDK can.

So which product do you choose? They’re both free, so the money issue is moot. To make the decision, you must ask yourself two questions. First, do you need independence from Microsoft’s VM? Sun’s offering can work with any virtual machine, so if you don’t like Microsoft’s VM for some reason, the ActiveX Bridge would be the better choice. Second, do you need a Java-to-ActiveX link for your Internet applications? If so, then the ActiveX Bridge should be your pick. Under the hood, it’s simply a standard ActiveX control, allowing for easy, automatic installation through any ActiveX-compatible Web browser. The Java SDK, on the other hand, requires a separate end-user installation of Microsoft’s VM, and works best only on Microsoft’s Web browser.

— Trevor Harmon

Trevor Harmon is president of Vocaro Technologies (http://vocaro.com), a development and consulting firm specializing in JavaBeans components. He is also the author of Web Developer’s Guide To Visual J++ And ActiveX, published by Coriolis Group Books (ISBN: 1-57610-062-6). Trevor will soon graduate from Washington University in St. Louis with a degree in Computer Engineering.


From CAB to JAR: A Utility for Converting Cabinet Files into Java Archives

By Sean C. Sullivan

This article introduces two new packages from Microsoft’s Java Virtual Machine (VM): com.ms.util.cab and java.util.zip. The com.ms.util.cab package provides facilities for reading and writing CAB files. Similarly, the java.util.zip package provides facilities for reading and writing JAR files. We demonstrate how to use both of these packages by building a utility that converts CAB files into JAR files.

CAB Files

A CAB (cabinet) file is a file with the suffix .cab that acts as a container for other files. A CAB file is conceptually similar to a ZIP file: it serves as a compressed archive for a group of files. CAB files can contain any kind of file. For example, you might store Java classes, DLL’s, images, or text files in a CAB file.

CAB files can be digitally signed. The digital signature is used to identify the creator of the file.

You create a CAB file with the CABARC program. CABARC is a command-line utility in the Microsoft CAB SDK. Typically, you pass a list of options, a CAB filename, and a list of files to CABARC:

cabarc n mycab.cab Foo.class Bar.class bananas.jpg

This command creates the file mycab.cab. In this example, mycab.cab contains three files: Foo.class, Bar.class, and bananas.jpg. For more information on CABARC, see the Microsoft CAB SDK.

You digitally sign a CAB file with the SIGNCODE program. A cabinet file can be signed at three different security levels: high, medium, or low. For example, this command will sign mycab.cab with the low security setting:

signcode -j javasign.dll -jp low -spc publish.spc -k MyKeyName mycab.cab

For more information on SIGNCODE, see the Microsoft CAB SDK.

What Are CAB Files Used For?

CAB files are typically used in two ways: packaging Java applets and packaging ActiveX controls. A Java applet’s class files and images files can be packaged into a single CAB file. This results in more efficient downloads because client PC’s will only need to fetch one file from the Web server. After packaging your applet in a CAB, you must set the CABBASE parameter in the <APPLET> tag. Microsoft Internet Explorer uses the CABBASE parameter to find the CAB file. For example:

<APPLET CODE   = MyApplet.class
        WIDTH  = 460
        HEIGHT = 160>
<PARAM NAME  = CABBASE
       VALUE = "myapplet.cab">
</APPLET>

Internet Explorer will read the CABBASE parameter and download myapplet.cab. Netscape Navigator and other Web browsers will ignore the CABBASE parameter.

ActiveX controls are also packaged in CAB files. When an ActiveX control is placed on a Web page, the <OBJECT> tag’s CODEBASE attribute specifies the CAB filename:

<OBJECT
  CLASSID  = "clsid:9AB5C123-1234-11d1-8516-00A0C921F7F3"
  CODEBASE = "mycontrol.cab"
  WIDTH    = 200
  HEIGHT   = 200
  ID       = mycontrol>
<PARAM NAME  = "param1"
       VALUE = "value1">
</OBJECT>

When Internet Explorer loads the Web page, it reads the CODEBASE attribute to determine the ActiveX control’s CAB filename.

JAR Files

Similar to CAB files, a JAR (Java Archive) file is a file format that compresses a set of files into a single file. JAR files have the suffix .jar. JAR files also provide compression and support for digital signatures. The optional digital signatures can be used to authenticate the JAR file’s origin. You can put any kind of file in a JAR file. For example, you may package a set of Java class files and image files into a single JAR file.

You create JAR files with Sun’s JAR utility. This command-line utility is part of Sun JDK 1.1.

You can package your Java applets in a single JAR file. As with CAB files, this results in more efficient downloads because only one file needs to be downloaded from the Web server. Additionally, if you sign the JAR file, your applet can get access to system resources outside of the Java security sandbox. After packaging the applet class files in a JAR, you must add the ARCHIVE attribute to the <APPLET> tag:

<APPLET CODE    = MyApplet.class
        ARCHIVE = "jars/MyApplet.jar"
        WIDTH   = 460
        HEIGHT  = 160>
<PARAM NAME  = foo
       VALUE = "bar">
</APPLET>

Both Microsoft Internet Explorer 4.0 and Netscape Navigator 4.0 will read the ARCHIVE attribute and download the specified JAR file. Microsoft Internet Explorer 3.0 will ignore the ARCHIVE attribute.

JAR files are also used to package JavaBeans. A bean’s Java class files, icon files, and text files can be bundled into a single JAR file.

The com.ms.util.cab Package

The CAB utility classes are located in a package called com.ms.util.cab. This package allows Java programmers to create CAB files as well as read the contents of an existing CAB file. The Sun and Netscape Java VMs do not provide this package; it’s included only with the Microsoft Java VM. In the Cab2Jar application, we import this package:

import com.ms.util.cab.*;

The Cab2Jar application will use these classes from com.ms.util.cab:

CabCorruptException

CabCreator

CabDecoder

CabEnumerator

CabException

CabFileEntry

CabFolderEntry

CabConstants

CabDecoderInterface

CabProgressInterface

The java.util.zip Package

JAR files can be manipulated using the classes in java.util.zip. The package provides classes for checksum calculations, reading/writing ZIP files, and reading/writing gzip files. In the Cab2Jar application, we will use java.util.zip.ZipOutputStream and java.util.zip.ZipEntry classes to create a JAR file.

Cab2Jar

Cab2Jar is a command-line utility that converts a CAB file into a JAR file. It’s a Java application that consists of one class, Cab2Jar.class.

Like any Java application, the main method is invoked first. In main, we examine the command-line parameters to get the name of the CAB file. Once we have the CAB filename, we determine the name of our output file. For example, if the CAB filename is myapplet.cab, then the output filename is myapplet.jar. Next, we use a FileOutputStream to create the ZipOutputStream. Any bytes written to the ZipOutputStream go to the JAR file.

A CabDecoder object is created to decode the CAB file. We call CabDecoder.extract to start extracting the files from the CAB file. For each file in the CAB, we create a ZipEntry object. The ZipEntry is a special entry in a JAR that specifies the name and size of the file. For each ZipEntry, we write the corresponding bytes to the ZipOutputStream.

After the contents of the CAB file have been processed, we close the ZipOutputStream. This closes the JAR file. Voilà; the JAR file has been created.

Compiling and Running Cab2Jar

To compile Cab2Jar.java (see Listing One beginning on page XX), you must have the Microsoft SDK for Java 2.0 installed on your system.

To compile, use this command:

C:\WORK> c:\sdk-java.20\bin\jvc.exe Cab2Jar.java

To run the application, use this command:

C:\WORK> c:\sdk-java.20\bin\jview.exe Cab2Jar mycab.cab

Conclusion

CAB files and JAR files are similar; they both serve as compressed file archives. Both file formats can also accommodate digital signatures. The com.ms.util.cab package provides facilities for reading and writing CAB files. The java.util.zip package provides facilities for reading and writing JAR files. The Cab2Jar application uses both of these packages to convert a CAB file into a JAR file.

Begin Listing One — Cab2Jar Class

/*
 * Copyright (C) 1997-1998 Intel Corporation
 * All Rights Reserved
 *
 * A royalty-free copyright license to copy, modify and
 * compile this source code and to distribute it in your
 * own products in source and binary forms is hereby
 * granted, so long as this copyright notice and this
 * copyright license appear in all source code copies. No
 * other rights or licenses are granted. The Intel name or
 * other trademarks may not be used to endorse or promote
 * products using this header file without Intel's prior
 * written permission.
 *
 * THIS CODE IS PROVIDED "AS IS" WITH NO WARRANTIES
 * WHATSOEVER, INCLUDING ANY WARRANTY OF MERCHANTABILITY,
 * NONINFRINGEMENT, OR FITNESS FOR ANY PARTICULAR PURPOSE.
 * Intel disclaims all liability, including liability for
 * infringement of any proprietary rights, relating to use
 * of this source code.
 *
 * * - Other brands and names are the property of their
 *     respective owners.
 */

import com.ms.util.cab.*;
import java.util.zip.*;
import java.io.*;

/**
  *   This Java application is a command-line utility
  *   that converts CAB files into JAR files.
  *
  *   This application will only run on the Microsoft
  *   Java* VM. You will need the MS SDK for Java 2.0
  *   to run it.
  *
  *   <PRE>
  *   Usage:
  *        jview Cab2Jar myfile.cab
  *   </PRE>
  *
  *   Limitations
  *   + Cab2Jar does not handle nested CAB files
  *   + Digitally signed CAB's are not converted into
  *     digitally signed JAR's. Instead, an unsigned
  *     JAR file is created.
  *
  *   @author Sean C Sullivan
  */
public final class Cab2Jar
  implements com.ms.util.cab.CabDecoderInterface {

  private transient ZipOutputStream jarOutStream = null;

  /**
    *  @param jos - The output stream for the JAR file
    */
  public Cab2Jar(ZipOutputStream jos) {
    this.jarOutStream = jos;
  }

  /**
    *  This method is part of the CabDecoderInterface.
    *
    *  @param disk_name    - The disk name of the cabinet
    *  @param cabinet_name - The cabinet's name
    *
    *  @return - Returns an InputStream for the cabinet.
    *            If the specified cabinet is not available,
    *            null is returned.
    */
  public InputStream openCabinet(String disk_name,
                                 String cabinet_name) {
    // just return null
    return null;
  }

  /**
    *  This method is part of the CabDecoderInterface
    *
    *  @param centry - The CabFileEntry that
    *                  will be extracted.
    *
    *  @return - Returns an OutputStream into which the
    *            file should be written or returns null if
    *            the file should not be extracted.
    */
  public OutputStream
    openOutputStream(CabFileEntry centry) {

    OutputStream outstream = null;

    String entryName = centry.getName();
    System.out.println(
      "openOutputStream called for " + entryName);

    ZipEntry zEntry = new ZipEntry(entryName);

    try {
      this.jarOutStream.putNextEntry(zEntry);
      outstream = this.jarOutStream;
    }
    catch (java.io.IOException ex) {
      System.out.println(ex);
      outstream = null;
    }

    return outstream;
  }

  /**
    *  This method is part of the CabDecoderInterface.
    *
    *  @param os      - The output stream for the file
    *                   extracted from the cab
    *  @param centry  - Contains information about the file
    *                   extracted from the cab
    *  @param success - Was file extracted successfully?
    *
    *  @return - Returns true if processing should continue
    *            for files in this cabinet, or false if
    *            processing should end immediately.
    */
  public boolean closeOutputStream(OutputStream os,
    CabFileEntry centry, boolean success) {

    // For simplicity, we will assume that success is true
    try {
      this.jarOutStream.closeEntry();
    }
    catch (java.io.IOException ex) {
      System.out.println(ex);
    }

    return true;
  }

  /**
    *  This method is part of the CabDecoderInterface.
    *
    *  @param type - Type of reserved area. (see
    *                CabConstants for possible values)
    *  @param reserved_data - Array containing the reserved
    *                         data. If this parameter is
    *                         null, then this is only a
    *                         message to indicate that the
    *                         reserved area exists.
    *  @param reserved_data_size - The size of the reserved
    *                              data area. If the size
    *                              is not known, this
    *                              parameter will be zero.
    *  @param other_data - Used only when type is
    *                      RESERVED_CFDATA
    *  @param other_data_size - Used only when type is
    *                           RESERVED_CFDATA
    *
    *  @return - Returns true if this type of reserved
    *            entry should be read and passed back to
    *            subsequent calls of this method. Otherwise
    *            false is returned to indicate that no more
    *            calls should be made for this type of
    *            reserved data.
    */
    public boolean reservedAreaData(int type,
      byte[] reserved_data, int reserved_data_size,
      byte[] other_data, int other_data_size) {

      // Return false to indicate that no more calls should
      // be made for this type of reserved data.
      return false;
    }

  /**
    *  This method is part of the CabDecoderInterface.
    *
    *  This method serves as a handler for any
    *  progress messages.
    *
    *  @param progress_type - The progress type (see
    *                         CabConstants for possible
    *                         values)
    *  @param val1 - A value related to the specific
    *                progress type
    *  @param val2 - A value related to the specific
    *                progress type
    *  @param progress_data - Progress data for the
    *                         specific progress type.
    *
    *  @return - Returns a code specific to the message
    *            type. Returns null for messages that are
    *            not handled.
    */
    public Object progress(int progress_type, long val1,
      long val2, Object[] progress_data) {

      // return null for all progress messages
      return null;
    }

  /**
    *  The main method for this Java application.
    *
    *  @param args  - the program's command line arguments
    *
    *  @return  none
    */
  public static void main (String args[]) {
    CabDecoder decoder = null;
    String jarFileName = null;
    String cabFileName = null;
    File cabFile = null;
    FileInputStream input = null;
    CabDecoderInterface cdi = null;

    if (args.length < 1) {
      System.out.println(
        "Need a CAB file as an argument.");
      return;
    }

    cabFileName = args[args.length-1];

    cabFile = new File(cabFileName);
    if (cabFile.canRead() != true) {
      System.out.println("Can't read " + cabFileName);
      return;
    }

    try {
      input = new FileInputStream(cabFile);
    }
    catch (FileNotFoundException ex) {
      System.out.println("File out found: " + cabFileName);
      return;
    }

    boolean bEndsWithDotCab =
      cabFileName.toLowerCase().endsWith(".cab");
    int cabFileNameLength = cabFileName.length();

    if (bEndsWithDotCab && (cabFileNameLength > 4)) {
      jarFileName = cabFileName.substring(
                                  0,cabFileNameLength - 4);
      jarFileName = jarFileName + ".jar";
    }
    else {
      jarFileName = cabFileName + ".jar";
    }

    FileOutputStream fos = null;
    ZipOutputStream jarOutStream = null;

    try {
      fos = new FileOutputStream(jarFileName);
      jarOutStream = new ZipOutputStream(fos);
    }
    catch (java.io.IOException ex) {
      System.out.println(ex);
    }

    Cab2Jar converter = new Cab2Jar(jarOutStream);

    try {
      decoder = new CabDecoder(input, converter);
      decoder.extract();
    }
    catch (CabException ex) {
      System.out.println("A CabException occurred: " + ex);
    }
    catch (IOException ex) {
      System.out.println("An IOException occurred: " + ex);
    }
    finally {
      try {
        input.close();
        jarOutStream.close();
      }
      catch (IOException ex) {
        // ignore
      }
    }

    converter = null;
    input = null;
    jarOutStream = null;

  }  // End of main.

}  // End of Cab2Jar.

End Listing One

The file referenced in this article is available for download from the Informant Web site at http://www.informant.com/ji/jinewupl.htm. File name: JI9801CJ.ZIP.

Additional Resources

The Microsoft SDK for Java 2.0 can be downloaded from http://www.microsoft.com/java/sdk/

The Microsoft CAB SDK contains utilities and libraries for creating and digitally signing CAB files. You can download it at http://www.microsoft.com/workshop/prog/cab/

The JAR file specification is online at http://www.javasoft.com/products/jdk/1.1/docs/guide/jar/

Sean C. Sullivan is a Software Engineer in Intel’s Architecture Lab. Prior to Intel, Sean worked at IBM and Image Systems Technology. He can be reached at sean_sullivan@ccm.jf.intel.com.


Creating an Expanding Menu

By Jeremy Broyles

Dynamic HTML is great for creating an interactive Web environment. The ability to change styles on the fly with DHTML is an important addition to the Web developer's bag of tricks. In this article, we'll show you how to use Dynamic HTML to create a menu that expands to reveal its submenus in response to a mouse click. We'll outline the technique by walking you through the code step by step. When you put it all together, you'll have a functioning expandable menu that you can customize to meet your needs.

Working with style

The first step in creating our menu is to define the style of the menu items. We accomplish this by using the <STYLE> tag according to the Cascading Style Sheets (CSS) specification. As you can see in the following code, we define styles for DIV.Menu1, DIV.Menu2, and A (the anchor tag):

<HTML>
<HEAD>
<TITLE>Web Builder Expanding Menu</TITLE>

<STYLE TYPE="text/css">
<!--
DIV.Menu1 {font-size: 10pt;
        font-family: "Arial";
        font-weight: bold;
        line-height: 20pt;
        margin-left: 5pt;
        color: gray;
        background-color: black;
        cursor: hand;}
DIV.Menu2 {font-size: 9pt;
        font-family: "Arial";
        line-height: 15pt;
        margin-left: 10pt;
        color: white;}
A    {font-size: 9pt;
        font-family: "Arial";
        text-decoration: none}
-->
</STYLE>

Be sure to include the style information within the <HEAD> tags of your document. The .Menu1 and .Menu2 variations of the DIV reference are classes. We'll talk more about them later. For the A tag, we set the text-decoration style to none. This causes anchor text to be displayed without the underline.

A JavaScript toggle switch

To make our menu expand, we're going to use the display style property, which is specific to DHTML and works only with Microsoft's Internet Explorer 4.0. If you can't guarantee that people will view your site only with IE4, it's best to handle the situation gracefully, when possible. Therefore, in order to keep other browsers (including previous versions of IE) from choking on the unrecognized style, we'll use JavaScript to identify the browser and handle the situation appropriately.

The display style property has two possible values: “none” and “”. When set to none, the object that the style is applied to is hidden. Otherwise, it's displayed normally. We can make text appear and disappear on the fly by changing the value in response to an event—in this case, a mouse click. Below, we've listed JavaScript that determines if the browser is IE4 and toggles the display property.

<SCRIPT LANGUAGE="JavaScript">
<!--
ie4 = ((navigator.appName == "Microsoft Internet Explorer") && (parseInt(navigator.appVersion) >= 4 ));

function toggle( myId ){
   if (ie4){
      thisId=document.all(myId);
      if (thisId.style.display ==
      "none"){
         thisId.style.display =
         "";}
      else {
         thisId.style.display =
         "none";
      }
   }
}
//-->
</SCRIPT>
</HEAD>

Note that we pass the myId parameter into the toggle() function. This parameter is the ID that we'll assign to each object that we want to show or hide. If the browser is IE4, the function checks the current value of the display style and switches it to the other value. If the browser isn't IE4, it bypasses the rest of the function without crashing.

Arranging tables

For formatting purposes, we're going to create our menu inside a table—actually a table centered within a table. In order to create a solid border around our menu, we'll use an outer table with the width set to 140 pixels and cellpadding set to 2. Then within that table we'll center another table to contain the menu items. Following is the code to create both tables:

<body bgcolor="white">

<!-- Page heading -->
<h1>Web Builder Expanding Menu</h1>

<!-- Table to provide background color and border -->
<table border="0" cellpadding="2" cellspacing="0" width="140" bgcolor="black">
<tr><td>

   <!-- Table to contain our menu
   -->
   <div align="center">
   <table border="0"
   cellpadding="0"
   cellspacing="0" WIDTH="100%"
   bgcolor="black">
   <tr>

Menus with class

Now, the moment you've been waiting for—we're going to add our first menu item. Remember when we created the styles at the beginning of the article, and we briefly mentioned classes? Well, this is where classes should begin to click.

We're going to enclose each menu item in its own <DIV> tag. The <DIV> tag simply stands for division and provides a convenient way to divide your document into pieces that you can format individually. Since we want to maintain a consistent style for each level of menu item, we created DIV style classes called Menu1 and Menu2. To apply the styles from these classes to the contents of a division, simply use the class= statement within the <DIV> tag, as shown below.

   <!-- First main menu item -->
   <td><DIV class="Menu1"
   onclick="toggle('wbmenu')">Web
   Builder</DIV>

By using the class=”Menu1” statement, we tell the browser to apply the formatting that we listed in the style section under DIV.Menu1. Since this is a top-level menu item, and we didn't set the display property to none, it will always be displayed. As you can see, we've also added an onclick event that calls the toggle function when the user clicks the first menu item, Web Builder. Notice that we passed ”wbmenu” as the function's myId parameter, which we'll discuss next.

Drilling down

Now it's time to add our level two menu items that fall under the Web Builder menu heading. Once again, these menu items are contained within their own <DIV> tag. This time, however, we apply the formatting from the Menu2 class, as shown below:

   <!-- submenu items 1 -->
   <div id="wbmenu" class="Menu2"
   style="display: 'none';">
   <a style="color: white;"
   href="dhtml.asp">Dynamic
   HTML</a><br>
   <a style="color: white;"
   href="scrplts.asp">Scriptlets
   </a><br>
   <a style="color: white;"
   href="ie4.asp">Internet
   Explorer</a><br>
   </div>
   </td>
   </tr>

In order to hide the submenu items when the page loads, we set the display style to none. We also included an id property with the value of “wbmenu” within the <DIV> tag. Therefore, when the Web Builder menu item passes “wbmenu” to the toggle function, the toggle function displays (or hides) the contents of the wbmenu DIV.

Each submenu item within the wbmenu DIV is a link; therefore, we enclosed them within anchor tags. In addition to the href property, we used the color style property to make the anchor text white, since the table backgrounds are black. To complete the first section of our menu, we closed our table cell and row tags, as well.

An aesthetic touch

To make our menu more defined, we want to separate the main menu items. To do this easily when using tables, you can create a separator cell with a contrasting background color. Place a one pixel by one pixel transparent image within the cell to create the effect of a one pixel colored line that spans the width of your menu. The code to accomplish this is listed below:

   <!-- separator cell -->
   <<tr><td bgcolor="blue"> <img
   src="images/clear.gif"
   height="1" width="1">
   </td>
   </tr>
   <tr>

Adding more menu items

Finally, create the rest of your menu following the steps for creating main and submenu items. When you're finished, the rest of your document should look like this:

   <!-- Second main menu item -->
   <td><DIV class="Menu1"
   onclick="toggle('aspmenu')">
   Active Server</DIV>

   <!-- submenu items 2 -->
   <div id="aspmenu"
   class="Menu2" style="display:
   'none';">
   <a style="color: white;"
   href="vid.asp">Visual
   InterDev</a><br>
   <a style="color: white;"
   href="iis4.asp">IIS
   4.0</a><br>
   </div></td></tr>

   <!-- separator cell -->
   <tr><td bgcolor="blue"><img
   src="images/clear.gif"
   height="1" width="1">
   </td>
   </tr>
   <tr>

   <!-- Third main menu item -->
   <td><DIV class="Menu1"
   onclick="toggle('fpmenu')">
   FrontPage</DIV>

   <!-- submenu items 3 -->
   <div id="fpmenu" class="Menu2"
   style="display: 'none';">
   <a style="color: white;"
   href="temp.asp">Templates</a>
   <br>
   <a style="color: white;"
   href="form.asp">Easy
   Formatting</a><br>
   </div></td></tr>
   </table></div>
</td></tr>
</table>
</div>

As your menu expands, text or graphics below will make room.

</body>
</html>

When you load the menu in IE4, you'll see the compressed version of the menu, as shown in Figure A. However, as you click each main menu topic, the submenus will expand into view, as you can see in Figure B. This technique is fairly browser-friendly, too. If you view the menu in a browser other than IE4, you'll simply see the entire expanded menu.

Figure A

When the menu loads in IE4, it only displays the main menu items.

Figure B

When the user clicks a main menu item, the menu expands to reveal its submenu.

Conclusion

Dynamic HTML provides Web authors with the tools to make truly interactive sites. Using these tools, we've shown you how to create a dynamic expanding menu.

_______________

Jeremy Broyles is Editor-in-Chief of Microsoft Web Builder.

_________________

This article is reproduced from the February 1998 issue of Microsoft Web Builder. Microsoft Web Builder is an independently produced publication of The Cobb Group. No part of this article may be used or reproduced in any fashion (except in brief quotations used in critical articles and reviews) without prior consent of The Cobb Group. To contact The Cobb Group, please call (800) 223-8720 or (502) 493-3200.

Copyright © 1998 The Cobb Group, a division of Ziff-Davis Inc. The Cobb Group and The Cobb Group logo are trademarks of Ziff-Davis Inc. All rights reserved. Reproduction in whole or in part in any form or medium without express written permission of Ziff-Davis is prohibited.


Scriptlets: A New Powerful Way to Web Computing

by Shamir Dasgupta

Scripting languages such as Visual Basic Script and JavaScript have transformed the Web into a dynamic environment that now supports sophisticated client/server applications. If you’re a developer who uses a lot of scripting on your Web site, you know that the best code you can write for your application is the code you won’t have to rewrite. The “scriplet” technology lets you reuse script that you’ve already written and add even more functionality to it—without altering the original code.

Internet Explorer 4.0 currently supports scriptlets on all platforms. In this article, we’ll show you how to write and implement scriptlets for your Web site. First, though, let’s explore the underlying technology behind scriptlets.

A natural evolution of the Web

In a nutshell, a scriptlet is a Web component that you can write using HTML and scripting, which takes full advantage of the capabilities of Dynamic HTML (DHTML). Scriptlets are based on Win32 Component Object Model (COM), so you can use them to interact with other applications that support COM. On any platform, a scriptlet is just a Web page created with HTML; however, on a Win32 platform, a scriptlet becomes a COM object as well.

Advantages

As a Web author, you’ll find some definite advantages to using scriptlets. In the past, for example, to reuse code you’d have to cut and paste your code and then customize it for each implementation. With scriptlets, you can create your code once and then reuse it or let others reuse it in multiple Web pages. And since scriptlets are just HTML and scripts, they’re lightweight and quick to download.

Scriptlet rules

A scriptlet exposes only the global variables, procedures, and functions you want exposed. To expose any variable or function, all you have to do is use a prefix of “public_” on it . Any global variable with the “public_” prefix becomes a readable and writeable property of the scriptlet.

When it comes to event-handling, a scriptlet has an onscriptletevent in addition to the normal Windows events. onscriptletevent also has two parameters: a string and an arbitrary object. An event handler can select how to respond based on the string being passed. You can include additional detail about the event itself in the object parameter.

You name your scriptlets with an HTM (normal HTML page) or an ASP (Active Server Page) extension and then implement the scriptlet on a page with an <OBJECT> tag just like you would an ActiveX Control. However, unlike ActiveX Controls, scriptlets don’t use a CLSID (Class ID), but instead rely on a simple MIME entry like this:

<OBJECT ID="MyScriptlet" type="text/x-scriptlet" data="MyScriptlet.htm">
</OBJECT>

Now that we’ve explained how they work, let’s create and implement a simple scriptlet.

Scrolling scriptlet

For our example, we’ll implement a simple marquee scriptlet within a sample Web page. First we’ll write the marquee scriptlet using simple HTML and JavaScript. Our scriptlet will display a scrolling block of text moving from left to right. Once we’ve completed the scriptlet, we’ll implement it on a sample Web page using the <OBJECT> tag.

When we’re finished, our sample page will look like the one in Figure A. Let’s start by creating the scriptlet.

Figure A

Our marquee scriptlet displays a scrolling message.

Creating the scriptlet

Using an HTML editor, begin a new HTML document. Start the JavaScript with the <SCRIPT> tag on the <HEAD> section as we have here:

<html> <head>
<title>Scrolling scriptlet</title>
<script language="JavaScript">
<!--

Then, declare the variables and the Public description as shown here:

var maxLeft = 700;
var currentLeft;
var startLeft = 200;

public_description = new MyScriptlet;

Notice that we’ve used a “public_” prefix to describe the function MyScriptlet() since we want its properties exposed. Next, create the MyScriptlet() function that defines the marquee style, with the lines:

function MyScriptlet(){
   this.put_foreColor = put_foreColor;
   this.put_font = put_font;
   this.put_fontSize = put_fontSize;
   this.put_fontStyle = put_fontStyle;
   this.put_text = put_text;
   this.put_farPoint = put_farPoint;
   this.put_startPoint = put_startPoint;
}

Now define the functions for each attribute in the MyScriptlet() function asfollows:

function put_foreColor(sCol){
   theDiv.style.color = sCol;
}
function put_font(sfont){
   theDiv.style.fontFamily = sfont;
}
function put_fontSize(ssize){
   theDiv.style.fontSize = ssize;
}
function put_fontStyle(sstyle){
   theDiv.style.fontStyle = sstyle;
}
function put_text(text){
   theDiv.innerText = text;
}
function put_farPoint(farLeft){
   maxLeft = farLeft;
}
function put_startPoint(startPoint){
   startLeft = startPoint;
}

At this point we need to create the main scrolling function with the lines

function scrollMessage()
{
   if (currentLeft >= maxLeft)
   {
       theDiv.style.left = -startLeft;
   }
   currentLeft = theDiv.style.posLeft;
   currentLeft += 1;
   theDiv.style.left = currentLeft;
   window.setTimeout('scrollMessage()', 10);
}

We’re now finished with the marquee function and are ready to define the container theDiv that will hold our text message. But first, close out the <SCRIPT> tag with the following lines of code:

// -->
</script>
</head>

To create the container, we’ll simply use the Dynamic HTML <DIV> tag and define the style attribute necessary in the MyScriptlet() function we created earlier. The following lines of code will do the trick:

<div id=theDiv style="position:absolute; font-color: green; size: 14; font-family: Trebuchet, Arial; font-weight: bold; width:100%">
Hello Microsoft Web Builder Visitors!
<br>
<a href="http://mswebbuilder.com">
Click here
</a> to go to our site!<br>
</div>

As you can see, we’ve created a fairly simple container with text and a text link. We have one more task left to do: Provide a way to load the scrolling function when the page loads. For this final step, simply modify the <BODY> tag, so it reads

<body onload="scrollMessage()" bgcolor="white">

That’s it! We’re finished with this document. You’ve just created your first scriptlet! Next, we’ll create a sample Web page and implement our scriptlet—but first save this document as MARQUEE.htm.

Implementing the scriptlet

Start a new HTML document and insert an <OBJECT > tag in the body section as we have here:

<html>
<head>
<title>Scriplet Testing</title>
<STYLE type="text/css">
<!--
BODY  {font-size: 10pt; color: #000000; font-family: Verdana, Arial, Helvetica;z-index:1;}
P {font-size: 10pt; color: #000000; font-family: Verdana, Arial, Helvetica;z-index:1;}
H1 {font-size: 20pt; color: #CC0000; font-family: Verdana, Arial, Helvetica;z-index:1;}
-->
</STYLE>
</head>
<body bgcolor="white">
<h1>Web Builder Scriptlet Test</h1>
<p>The following Marquee effect is created with a scriptlet</p>
<object></object>

Now add the scriptlet by modifying the <OBJECT> tag, like this:

<OBJECT ID="headlines" STYLE="position:absolute; width:80%; height:70; top:120; left:50; color: white;" type="text/x-scriptlet" data="marquee.htm">
</OBJECT>

At this point, you may add any additional graphics or text you feel necessary. Remember to adjust the TOP attribute within the STYLE parameter to fit your text properly. Save the document as MIT.htm and open it in Internet Explorer. The result should look something like Figure A: You should see the marquee scrolling across the page.

Conclusion

By incorporating scriptlets on your Web pages, you can significantly cut development time and let others reuse your code more efficiently. In this article, we’ve shown how scriptlets can help you take advantage of Dynamic HTML’s capabilities.

Shamir Dasgupta is technical editor of Microsoft Web Builder and Active Server Developer's Journal, both publications of The Cobb Group.

Copyright © 1998 The Cobb Group, a division of Ziff-Davis Inc. The Cobb Group and The Cobb Group logo are trademarks of Ziff-Davis Inc. All rights reserved. Reproduction in whole or in part in any form or medium without express written permission of Ziff-Davis is prohibited.


Contents

MSDN News
Volume 7, Number 2

Contents of the March/April 1998 Issue

Add Web Technologies to Your Windows-based Applications

Ask Dr. GUI #39

Backtalk

Building Dynamic Visual Basic Programs with DHTML

Developing with Microsoft English Query

An Extended Object Map of Internet Explorer 4.01

MFC for Microsoft Windows CE: Using the Object Store


Add Web Technologies to Your Windows-based Applications

John Swenson
MSDN Content Development Group

As Web development merges with the world of Windows-based development, Microsoft is working on new ways to further blend these two programming models into its tools. If you're a developer for Windows, there's no need to sit and wait for these new Web-enabled tools to arrive, however. Plenty of ways exist to use today's tools to add Web technologies to your Windows-based applications. In some cases, you can even save yourself work over more traditional Windows programming methods.

Chief among these new development methods is to use Dynamic HTML with Visual Basic 5.0. Visual Basic developers can save themselves a lot of time and effort—and turn out better applications—if they insert Dynamic HTML into the appropriate parts of their Visual Basic applications, says David Stutz, an architect on the Microsoft Visual Basic team.

"There are a bunch of things that are really easy to do in Dynamic HTML, but are hard to do with Win32," he says. Stutz rattles off a long list of items that are simpler to program in Dynamic HTML, such as styles, nested tables, automatic layout, and fancy typography.

A Richer Interface

Then there's the user interface—probably the greatest strength of Dynamic HTML. With Dynamic HTML, developers can put a richer, more interactive user interface on their Visual Basic–based applications. They can create forms that tightly integrate typography with database content, forms that dynamically change the controls that they contain, and even forms that exploit multimedia effects. (For a complete primer on what Dynamic HTML can do, see the Microsoft Site Builder Network Web site at http://microsoft.com/workshop/author/dhtml/.)

For programmers, the coolest and most far-reaching feature of Dynamic HTML is its tendency toward separating logic from an application's look and feel, and navigation from content, Stutz says. This simple four-way division is extremely powerful:

  • Dynamic HTML mechanisms take the place of resizing, UI feedback, and layout code.

  • Dynamic styles take the place of code for manipulating an application's look and feel.

  • Hyperlinks and URLs stand in for code that would have controlled navigation and application flow.

  • Text, images, sound, and other MIME types supply standard types for the application's content, often eliminating the need for media-type-specific code to store, display, or manipulate the content.

Taken together, these four capabilities of Dynamic HTML can significantly reduce the amount of code written.

Unfortunately, few programmers for Windows think of using a familiar programming tool such as Visual Basic to add Web technologies to their applications. That's too bad, Stutz says, since there are many benefits to using Dynamic HTML in addition to Visual Basic forms.

For example, a developer who wants to create a scroll bar in Visual Basic has to write a lot of code. But if the same developer created a hybrid application that mixed HTML pages with Visual Basic forms, their application would have built-in scrollbars. HTML also has the notion of hyperlinking built into the format, eliminating the need to write code for navigating between pages.

Create Hybrid Applications

When you blend Dynamic HTML and other Web technologies with Visual Basic, you end up with hybrid applications that look like they're part Web and part Windows. These hybrid applications take advantage of the strengths of both platforms, and are clearly the wave of the future, Stutz says.

"We anticipate that hybrid applications are actually going to become the norm," he predicts. Stutz mentions Microsoft Money 98 as a prime example of one of these hybrid applications, where it's hard to tell when you're in a Windows screen and when you're in an HTML page.

For a primer on when Visual Basic programmers should use Dynamic HTML as a complement to the Win32 API, read Stutz's article "Building Dynamic Visual Basic Programs with DHTML." Although Stutz's main job is to design future versions of Visual Basic, he's been spending time lately visiting conferences to spread the word about using Dynamic HTML and other Web technologies with Visual Basic. The article contains highlights from his presentations.


Ask Dr. GUI #39

Oddly enough, Dr. GUI doesn't have a lot to say this month besides questions and answers. Maybe it's because he speaks his piece every week in his Web column (http://microsoft.com/msdn/). Recent topics include performance, XML, COM, using DHTML as your user interface, and J/Direct. Check out the archive at http://microsoft.com/msdn/archive/bycolumn.htm.

By the way, the final versions of add-ins for Microsoft Visual C++ and Visual Basic that enable you to develop for Windows CE–based devices (including Handheld PC, Palm-size PC, and Auto PC) are now available. The add-in for Visual J++ is in beta as of this writing. For more information, visit http://microsoft.com/windowsce/developer/.

And now for your questions . . .

Separating Siamese twin threads

Dear Dr. GUI,

I used to think that I had a basic (no pun intended) understanding of threads. But, the more I read, the more confused I get. My understanding is that my main app has to be an ActiveX .exe. No problem. But, I have DLLs that I want to run in their own threads. But, I am now starting to understand that in Visual Basic, a DLL will only ever run in the thread that instantiated it. Is this correct?

What I am trying to do is:

I have a point of sale (POS) system that prints out products (say a lift ticket at a ski resort or amusement park). I want the POS to send a class containing all the pertinent data to the "PrintEngine" DLL, which SHOULD be running in its own thread!!! So, the POS is not held up waiting for the printing process. Note: the printing process does NOT return any acknowledgment back to the POS, so no synchronization is necessary.

BUT, from what I am reading, my PrintEngine.DLL will only execute in the thread that instantiated it.

Sample of PrintEngine instantiation:

dim oMyPrintEngine as object
set oMyPrintEngine = CreateObject("MyPrintEngine.Engine")

Question: How should I build my PrintEngine? Leave it as a DLL? Make it something else? Just how do I get my PrintEngine to live off in its own corner away from my POS?

Thanks!
Robert G. Shurtleff III

Dr. GUI replies:

It's been a long time since Dr. GUI has had to separate Siamese twins. As you well know, such operations are always difficult because you have to figure out how to deal with shared organs.

So it is with Siamese threads: it takes careful work to separate the work you're doing into tasks that can be done with separate threads.

A component provided by a Visual Basic dynamic-link library (DLL) server never runs in its own thread: in-process components will always run on a client thread. Which thread the DLL will run on depends on whether the DLL is multithreaded or not. If the DLL is not multithreaded all components within that server will run on the thread of the first process to call the DLL server. If the DLL server is multithreaded, the component will run on the thread that created the component. Note that in neither case will the DLL's components somehow run on their own thread.

In order for the PrintEngine to run in its own thread, you will need to make the PrintEngine an out-of-process ActiveX .exe. Components in an ActiveX .exe have the ability to run in their own threads. If you create PrintEngine as a multiuse, multithreaded component, then each component in the ActiveX .exe will get its own thread and will execute in its own little corner away from the POS.

A word of caution with objects created by an ActiveX .exe. If you use the New operator to create the object (that is, Set objWidget = New Widget1) it is possible for the object to share a thread with another object created by the ActiveX .exe. To guarantee that each and every object created by the ActiveX .exe runs in its own thread, use the CreateObject function.

Another possible solution is to write the PrintEngine in Visual C++, where you can access the full power of Windows and COM and start any threads you like. That sure beats trying to separate Siamese threads!

Ya gotta follow da rulz

Dear Dr. GUI,

I am writing some code to add rules to a user's inbox. I have followed the steps outlined in the MAPI docs for creating a rule exactly, but when I finally insert the rule I only get partial success.

If I read a rule and then reinsert it, it works fine, but if I change the ACTION structure to mine

(HrStringToAction(......,"forward \"Calladine,Graham (Taverns)\"..)

I get the partial success message.

Can you help?
Graham Calladine

Dr. GUI replies:

Well, if you want Microsoft Exchange to follow da rulz, ya gotta follow da rulz.

Believe it or not, the reason for the partial success is because the ACTION structure is incorrect for a forward action even though it is a valid ACTION structure. There are a couple of changes that need to be made in the code. First, we actually have to modify the Restriction by adding the FL_LOOSE option to the ulFuzzyLevel. For example:

lpRes->res.resContent.ulFuzzyLevel =
FL_SUBSTRING | FL_IGNORECASE  | FL_LOOSE;

After your call to HrStringToAction(), you need to modify the address list returned in your ACTION structure. You need to add the PR_RECIPIENT_TYPE to the properties of the address list. The cValues of the address list is incremented by one. Below you will find an example that makes the necessary modification:

lpActs->lpAction->lpadrlist->aEntries->ulReserved1 = 1L;
lpActs->lpAction->lpadrlist->aEntries->cValues += 1;
lpActs->lpAction->lpadrlist->aEntries->
   rgPropVals[0].Value.MVbin.lpbin
+=
            sizeof(SPropValue)/sizeof(SBinary);
memset(&(lpActs->lpAction->lpadrlist->aEntries->rgPropVals[
            lpActs->lpAction->lpadrlist->aEntries->cValues-1
            ]), 254, sizeof(SPropValue));
lpActs->lpAction->lpadrlist->aEntries->rgPropVals[
      lpActs->lpAction->lpadrlist->aEntries->cValues-1
      ].ulPropTag = PR_RECIPIENT_TYPE;
lpActs->lpAction->lpadrlist->aEntries->rgPropVals[
      lpActs->lpAction->lpadrlist->aEntries->cValues-1
      ].Value.ul = MAPI_TO;

So with a little work, it is indeed possible to make Exchange follow da rulz.

Who is that masked control?

Dear Dr. GUI,

I want to set my masked edit control to read-only just like I did to the Textbox control. How can this be done in Visual Basic 5.0?

Aaron T.

Dr. GUI replies:

What, you want masks and locks? This is starting to sound like a psychological problem, no? Well, Dr. GUI's no psychologist, but he can answer this.

Ideally, MaskEdBox would support a Locked property as does TextBox, and you'd just set this property to lock the control. Note that MaskEdBox does support the Enabled property, but disabling the control also disables the ability to tab to the control—so you can't copy text in the control to the clipboard. If you don't need to be able to tab to the control, just disable it—it's much simpler.

But if you really want the locked read-only behavior, it's gonna take a little work. But relax: it's a quick surgery and you'll be home in no time.

One approach would be to use a roving TextBox with its locked property set to true and move/resize it over the MaskEdBox you wish to lock. This works, but it's somewhat cumbersome to implement—especially so if you have a lot of masked edit controls you want to "lock."

At first glance it appeared the answer would be to use SendMessage to send an EM_SETREADONLY message to the MaskEdBox. This works only if no mask is set—but what's the point of using the control if you don't set a mask?

On the other hand, why do you need a mask if you're not going to change the control? Since the EM_SETREADONLY message has no effect when a mask is on, how about storing the mask value, setting the control to use no mask, and then sending an EM_SETREADONLY message?

When you try this, you'll run into a few problems—especially when your MaskEdBox is bound to a DataControl. Changing the text of the MaskEdBox to display the same characters as were displayed when the mask was active causes the DataChanged property to be set to true. This can be troublesome if any of the displayed fields are not updateable. Fixing this requires checking the DataChanged property before canceling the mask, then resetting it after you cancel the mask.

But if you do this, you'll end up with a routine that can lock and unlock any number of MaskEdControls on a form. The lock status can be changed at any time, even after some fields have been updated. You could even write this as a subroutine that accepts a form as a parameter, then move it out of the form and into a .BAS module where all of your forms could access the same code.

See? That didn't hurt, now, did it?

Who's doing what when?

Dear Dr. GUI,

I'm having a problem with programming Schedule+ objects.

What I need to achieve is a weekly printed schedule of what "everyone" in the company is doing in the current week.

My problem at the moment is being able to access everyone's schedule. I'm trying to use the ScheduleForUser function/method but am a bit stuck on the how to get all the parameters for all the users which I need to pass it. How do I find the UserEntryID for each user in the system?

I'm sure there is something I have to do MAPI, but what?

Nigel Kitcher

Dr. GUI replies:

You wanna know where everyone is, huh? Even Dr. GUI's not omniscient. But, with Collaboration Data Objects (or CDO, formerly Active Messaging) you can at least know what people say they're doing in their schedules.

I'm assuming that you have a list of user names somewhere. (If you don't, I'll give you a way to get such a list later.) Given this list, there are a bunch of steps involved—too many to list here. But the key steps are to retrieve the EntryID for each user name, then pass it to the ScheduleForUser method of the Schedule+ application object. For the exact list of steps to take and sample code, please see article Q177216 in the Microsoft Knowledge Base, "HOWTO: Open Another User's Schedule Programmatically" (http://support.microsoft.com/support/kb/articles/q177/2/16.asp).

It's a little more work if you want to get a list of users from the system. The exact steps depend on how your global address list is set up, but they go something like this. From the Session object, open the AddressLists collection. Find the "Global Address List" and iterate through its AddressLists collections and AddressEntries collections to gather a list of all users. Each user would be an AddressEntry in one of the AddressEntries collections.

Note Using this method, you are directly accessing a resolved AddressEntry, so you do not have to go through the steps of creating a message, adding a recipient and resolving the recipient. Once you get to the AddressEntry level when iterating through the lists, you have the ID property needed for the ScheduleForUser method.

For example (much left out):

Set AllAddressLists = Session.AddressLists
   For Each OneAddressList in AllAddressLists
   For Each OneAddressEntry in OneAddressList.AddressEntries
      ' Add code to process the user
      ' The properties required for ScheduleForUser are:
      '   OneAddressEntry.Type
      '   OneAddressEntry.Address
      '    OneAddressEntry.Name
      '   OneAddressEntry.ID
   Next
   Next

Have a MAPI day

Dear Dr. GUI,

I have a Windows for Workgroup application that uses simple MAPI to send fax via the built-in fax support, now I am working on the migration to Windows 95.

I want to use OLE-Messaging but I cannot figure out how to install the OLE-Messaging library. WIN.INI says OLEMessaging=1, MAPI=1, MAPIX=1, CMC=1, but the file MDISP.TLB is not on the disk so I can't use MAPI.Session, etc.

I have installed the MAPI 1.0 PDK for Windows NT on the 4.0 beta that was sent on the MSDN. After that, OLE-Messaging is present on the NT machine, but I want it on Windows 95 and I would like to know how to get it there.

Regards,
Lars Lundqvist

Dr. GUI replies:

Dr. GUI empathizes with you: it's important to get all of your nutrients, including your daily requirement of MAPI. Thankfully, your MAPI supplements are as close as the Web. The Active Messaging libraries (the new name for OLE Messaging) are available from the Microsoft Exchange "Application Farm" at http://backoffice.microsoft.com/downtrial/moreinfo/appfarm.asp. From this page, you can select download and the second paragraph of the resulting page contains a link to the Active Messaging Library download page. The file that you download not only contains the Active Messaging Library, but also a Help file containing information and examples for the object model and a readme.txt file with instructions for installation.

For more information on Active Messaging and the new Collaboration Data Objects, please see the following articles in the Microsoft Knowledge Base:

Q176916, "INFO: Active Messaging and Collaboration Data Objects (CDO)" (http://support.microsoft.com/support/kb/articles/q176/9/16.asp)

Q171440, "INFO: Where to Acquire the Active Messaging Libraries" (http://support.microsoft.com/support/kb/articles/q171/4/40.asp)

TURN THAT (*&Q(*&#!@#(*& SOUND DOWN!

Dear Dr. GUI,

I cannot find any DLL call or OCX for setting the general volume in windows like the system mixer play control does. I developed an application with ActiveMovie OCX and WebBrowser Control and both are playing sounds. I have to implement a general volume control (two buttons, one vol-up, one vol-down) which are controlling the whole sound system´s volume out of Visual Basic 5.0.

Hope that you can help me out of this frustration.

Any help appreciated!

Thanks in advance,
Albert Fischlmayr

Dr. GUI replies:

Dr. GUI is gratified that you're concerned for your users' hearing. It speaks volumes for your intelligence and compassion. There are few things more annoying than an application that's just too noisy. It's almost as bad as stealing focus from the foreground application.

Unfortunately, there is no simple API call to get or set the volume. It requires iterating through four layers of functions and structures to get or set the system volume.

First, call mixerGetNumDevs to locate the correct mixer device. Next, call mixerGetLineInfo to determine which line of the device controls the volume to the speakers. Then, call mixerGetLineControls to determine which control on that line lets you change the volume. Finally, call mixerGetControlDetails to obtain the structure containing the volume setting.

To actually change the volume you must work around Visual Basic's limited ability to handle pointers. (Or you could write a COM object in Visual C++ to do this.) The availability of the VarPtr() function in Visual Basic 5.0 smooths the way considerably. Note that you'll have to declare some of the structures and functions yourself because of bugs in the WIN32API.TXT file that shipped with Visual Basic.

By the way, it would probably be a good idea to put the volume back when your application exits—changing it permanently for the user is kinda rude.

Yo! App! Are you there?

Dear Dr. GUI,

Hi. I need to see if an application is running; if so I put it in the foreground, otherwise I run this program from disk. I can do this in Win16. How can I do the same thing in Win32?

Farioli Roberto

Dr. GUI replies:

Activating the window is easy—once you have a handle to it. It's finding the handle to it that's a bit tricky.

The most common and simplest approach is to use the FindWindow() and SetForegroundWindow() Win32 APIs. FindWindow() is used to determine if a particular application is running, and to get its top-level window handle. SetForegroundWindow() is used to activate the window. Note that this steals focus and activation from whatever application currently has it.

For example,

HWND hwnd = FindWindow("CalcWindowClassName", "Exact Title");
  if(hwnd) {
   // It was already running, so bring to foreground...
   SetForegroundWindow(hwnd);
  }
  else {
   // It wasn't running, so start it up...
   ShellExecute(NULL, "open", "calc.exe", NULL, "C:\\", 1);
  }

This will find the window created with the window class, CalcWindowClassName, which has the caption "Exact Title." If you wrote the application in C or C++, you can set the exact class name you want when you register your window class. If you didn't, you can use Spy++ to find the class name the author used. Or you can use NULL rather than a class name—which will find all top-level windows with that title regardless of class.

Potential problem: if there is more than one window with the class and caption you specify, you might not get the right one and might therefore activate the wrong window. That's why it's really nice if you can register your own window class with a unique name.

Another potential problem is that you don't know the exact title of the window you want to activate. If you have the class name and it's unique, you're ready to operate—see above. But, if the class name isn't unique, you can search through the top-level windows using the technique described in Microsoft Knowledge Base article Q147659 "HOWTO: Get a Window Handle Without Specifying an Exact Title" (http://support.microsoft.com/support/kb/articles/q147/6/59.asp). This article describes a FindWindowLike function that you can write.

A much harder and rarely used technique is to search for the process by its module name (See Microsoft Knowledge Base Q175030, "HOWTO: Enumerate Applications in Win32" http://support.microsoft.com/support/kb/articles/q175/0/30.asp). There is no portable way to implement this on both Windows 95 and Windows NT. You will have to write separate code for each platform. In Windows 95, you will need to use the Tool Help Library of functions, and in Windows NT, the PSAPI.DLL functions. After you get the process handle, you will then need to find a top-level window via EnumThreadWindows() to call SetForegroundWindow() on.

Help! I'm broken!

Dear Dr. GUI,

Why does MSDN break the Developer Studio Help? I installed Visual C++ 5.0 to have all the help I needed on my hard drive, so I did not need the CD. When I installed the MSDN, Developer Studio started to always demand the CD for any help! I could find no options to control this at all. Uninstalling and reinstalling Developer Studio did not help! How can I install Developer Studio to work off the local hard drive and only go to the CD when specific MSDN queries are called for?

Steve Rodkey

Dr. GUI replies:

What's going on here is that MSDN is replacing the help you installed when it installs. Usually, this is a good thing: it's handy to have all your help integrated in one place, and MSDN is a superset of the help that ships with Visual C++. However, in your case, this is a problem.

The root of this problem lies in the registry key: HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\InfoViewer\5.0. The InfoViewer is a tool used to view Developer Studio's Online help. This registry key contains all of the information that the InfoViewer needs to organize the table of contents, create an index, and find help topics. When you installed the MSDN Library, it contained an updated version of the topics already on your hard drive. This caused the registry to point to the MSDN Library CD rather than your hard drive. You may be able to reset this key to the old values. It wouldn't be hard to have a couple of files that switch this registry key back and forth.

At this time, there is no supported way to install some topics on your hard drive, leaving others on the CD. Fortunately, there is a way to place all of the MSDN topics on your local hard drive. During MSDN Library setup, you are provided a checkbox to install "Books Online." Note that this requires approximately one gigabyte of space on your hard drive! Keep in mind that the majority of the MSDN files are copied to your <DeveloperStudio Path>\MSDN directory, even if you specify a different location in setup. However, if you are able to find the room on your hard drive, you should never be asked for an MSDN CD again.

If you don't have space to place the entire MSDN Library on your hard drive, consider placing it on a network drive. Typically the MSDN Library consists of two CDs. Both CDs should be copied to the same directory on the network. From your computer, map this network directory to a letter drive on your machine. Then launch SETUP.EXE to begin the MSDN installation. This time, do not check "Books Online." By installing from a network location, all MSDN topics will be retrieved across the network. Although the InfoViewer speed may decrease, this method avoids using the MSDN CD and saves local drive space. This is actually the method that Dr. GUI uses here at Microsoft.

Thanks!

Dr. GUI thanks his team of specialists for this column: Robert Jacik, Catherine Granger, Dennis Hanson, Chris Jensen, Diane Shytle, John Eikanger, Joe Crump, and Jason Roth. And he gives special thanks to the specialist coordinator, Rick Caudle, without whom this column would not be possible.


Backtalk

James Van Eaton
Microsoft Developer Network

Hello and welcome to another edition of Backtalk, where your questions, concerns, gripes, and suggestions are addressed by the MSDN team. The MSDN inbox has been filling up with some great mail since the January 1998 quarterly release, so I won't bore you with any chitchat and cut right to the chase.

Release Madness

The MSDN program exists to give you the inside track on Windows development so we do our best to get you what you need as early as possible. One question that is often asked is: "I just heard about product x. Will MSDN subscribers receive it?" If it is released software, publicly available, and could be required by a developer to develop or test their own software, chances are it will be sent to MSDN subscribers. We include prereleases (betas) of strategic software when we can, and when we do you can rest assured that the final, retail version will also be included after its release.

The next question is, naturally, "When will I get it?" With Web releases becoming more frequent, interim releases are becoming scarcer. A Web release will always beat the fastest interim shipment—it takes some time to manufacture all those discs! So, if a piece of software has been posted to the Web (such as a service pack), odds are we won't schedule an interim release for it. But if it's hot new technology we'll get it out as soon as we can. Watch the MSDN newsgroup microsoft.public.msdn.general for announcements of upcoming shipments.

Speak My Language

And speaking of interim shipments . . . As you know, beta versions of U.S. English Windows 98 have been shipping to all Professional and Universal subscribers for some time, most recently in the January 1998 quarterly release with Beta 3. In an upcoming interim shipment MSDN is going to distribute many international versions! Let us know if you find these helpful by sending us feedback to msdn@microsoft.com.

What's in a Name

Mary Lelanie of Honolulu, HI, recently sent us rebuking e-mail for allegedly omitting Service Pack 2 for Windows 95 from her MSDN Professional subscription. Specifically, she needed to test a new Windows application programming interface (API) GetDiskFreeSpaceEx. How frustrating!

For the record, Windows 95 Service Pack 1 is the one and only service pack available for the Windows 95 operating system. It is a collection of add-ons and updates for the released version of Windows 95.

What she is really looking for is the updated version of Windows 95 that Microsoft released to PC manufacturers (OEMs) called Windows 95 OEM Service Release 2, or OSR2 for short. Unlike service packs, service releases cannot be used to upgrade the retail version. Don't feel bad Mary—the distinction between "service pack" and "service release" is often not made clear so this mistake is understood. Imagine the chaos if there is ever a service pack for a service release . . . ay carumba!

Both Windows 95 Service Pack 1 and Windows 95 OSR2 are included in the MSDN Professional and Universal subscriptions. Check out index.htm, on Disc 1 of the Development Platform—U.S. Pack, for their locations.

The Platform SDK documentation discusses important new features of OSR2—follow this navigation to the topic: Windows Base Services/Windows 95 Features/Windows 95 OEM Service Release 2/Elements of Interest to Win32 Programmers. The following Microsoft Knowledge Base articles (http://www.
microsoft.com/support/) may also be of interest:

Q142794: "Availability of Microsoft Windows 95 Service Pack 1"

Q155003: "Description of Windows 95 OEM Service Release 2"

Postgap

This concludes our broadcast day. If you have any questions or feedback about the MSDN program, please send them to msdn@microsoft.com. If we use your letter on the air, you'll receive a limited edition MSDN T-shirt.


Contents

MSDN News
Volume 7, Number 1

Contents of the January/February 1998 Issue

Ask Dr. GUI #38

Backtalk

The Harmonious HelpDesk

The HelpDesk Administration Explorer

Visual Basic Clients for the HelpDesk Sample

Viewing HelpDesk Activity: A DHTML System Monitor


Ask Dr. GUI #38

(Remember, he's not a real doctor.)

In his most recent column, "Ask Dr. GUI #37," Dr. GUI announced that he was going to split his personality—or at least his time.

The new personality gives prescriptions about programming technologies and techniques, helping you make it all make sense. This new weekly column is easy to get to on the Web: http://www.microsoft.com/msdn/. The good doctor's already covered optimizing ActiveX controls for Internet Explorer 4.0, XML, J/Direct, and more!

Meanwhile, the old personality continues on, with a page full of questions and answers in each MSDN News issue.

So, without further ado, here are your questions—and the good doctor's answers:

Can you follow those links?

Dear Dr. GUI,

I'm experiencing this strange link problem while building my MFC-based ActiveX control. I created the project in MS Developer Studio (Visual C++ 5.0) using the ActiveX control wizard and added properties, methods, etc. using the class wizard. All of a sudden after a making an innocuous change in a .cpp file (believe me, this is all I did), the linker fails with the "DLLMain multiply defined" error. I've tried everything I could to get around this problem without success. Both of the libraries that the linker complains about are from MFC. And most surprisingly, what causes the linker to complain all of a sudden (after several successful builds)? I'm stumped. Can you please help?

Ashok K. Srinivasan, Intergraph Corporation

Dr. GUI replies:

What's happening here is that there's more than one function called DLLMain in the combination of libraries and object files you're linking. In the C Runtime library, the symbols for new, delete, and DLLMain are weak externals (see Knowledge Base article Q72651 at http://premium.microsoft.com/support/kb/articles/Q72/6/51.asp) for more on these. The good doctor's guess is that somehow the C Runtime Library has gotten moved to first in the list of libraries to link. It has to be after the MFC library, as described in article Q148652 (http://premium.microsoft.com/support/kb/articles/q148/6/52.asp).

Why is this happening now? The good doctor's stumped. But Dr. GUI suspects it may be that you were linking using incremental link before and the change you made forced a full link. Or maybe the moon was full.

Finding your dependencies

Dear Dr. GUI,

I tried to use Dependency Walker for Win32 (MSSDK "depends.exe" by Steve P. Miller). It worked fine. It, however, does not show the currently running program's (dll, exe) status.

Is there any program to show the list of all of the currently running programs and its dlls, as well as the list of dlls that are accessed by currently running programs? If we could have this information in similar format to "depends.exe", it would be really nice. (Pview.exe is not handy and does not show a list of running dlls! Also pwalker.exe shows only the selected process!)

Thanks for your response,
Hoo Kwon, Merrill Lynch

Dr. GUI replies:

Dr. GUI's got two ideas here. First, PVIEW.EXE does show you the list of DLLs loaded into a running process. To see this list just select the process and press the Memory Detail button.

Second, you can use the "tlist.exe" program from the Win32® SDK or the Microsoft Windows NT® Resource Kit. First run tlist.exe to obtain the process ID of the process that you are interested in and then run tlist.exe passing the process ID as the command line parameter.

Combatting COM stale ROT

Dear Dr. GUI,

We have a problem with COM under Windows® 95. Working about two weeks on this problem hasn't led to any result. We have no further ideas about how to resolve this problem and hope that you have some hints for us.

We want to extend two of our existing applications to COM Automation applications. One application is working as a local server and another as an Automation client. It is important for us not only to create an instance of the local server, but also to connect to an existing server. Our development environment is Visual Studio Professional 5.0.

Under Windows NT (Version 4.0, Build 1381) both are working fine. Creating an instance via CoCreateInstance (or derived function such as _com_ptr_t::CreateInstance) and connecting (GetActiveObject or _com_ptr_t::GetActiveObject) under Windows 95 CoCreateInstance works, but GetActiveObject fails with HRESULT set to ME_E_UNAVAILABLE. The following happens:

  1. Before calling GetActiveObject, the associated Item Moniker is registered in the Running Object Table (due to RegisterActiveObject) monitored with irotview.exe.

  2. When calling GetActiveObject (or IRunningObjectTable::GetObject, when enumerating the ROT with own code) the Kernel throws two exceptions with code 0x000006ba and the Item Moniker is removed from the ROT.

  3. GetActiveObject returns with MK_E_UNAVAILABLE.

The programmatic logic of our COM server is the same as in the COM-Hello example, but the application object is much more complex. The application is using a dual interface. Changing the registration (for example, RegisterActiveObject with REGISTEROBJECT_STRONG or not using CoLockObjectexternal for a visible server) doesn't resolve the problem.

Do you have some hints or additional information (we haven't found additional info in Online Books, MSDN, Inside OLE 2, or Knowledge Base) about:

  1. What exception code 0x000006ba means?

  2. Why is our running object removed from the ROT?

With best regards,
Frank Wiedenroth, Ingenieurbuero Schoop

Dr. GUI replies:

This problem is most likely due to the fact that the ROT entry has become stale. (Isn't the concept of a stale ROT scary?) In other words, the object associated with the ROT entry is no longer in existence. Perhaps the server process is no longer running. Perhaps the object deleted itself.

When you call GetActiveObject(), COM will try to find the object associated with the item moniker and if it cannot find it for any reason it will remove the stale moniker from the ROT.

The exception code 0x000006ba means RPC_S_SERVER_UNAVAILABLE. This error most likely means that the server process has gone away.

Dr. GUI hopes this is helpful and that you'll be able to debug this.

Getting an active remote object

Dear Dr. GUI,

I have a question about remote OLE automation. I can get an IDispatch interface about a running COM class on local machine. The code lists as below:

        ICalcDlgPtr m_calc;
        HRESULT hr = m_calc.GetActiveObject(__uuidof(CCCalcDlg));
        if (FAILED(hr))
        {
         AfxMessageBox("The CCCalcDlg COM class can't be Get!");
            if (FAILED(hr))
                _com_issue_error(hr);
        }

But I want to get an IDispatch interface about a running COM class on a remote machine. I have configured the Mfccalc.calculator COM class on local and remote machines. At the same time, I have run an Automation manage program on a remote machine. I used the same method to get the IDispatch interface, but it failed. I don't know what's wrong! Please tell me!

(Note: I can createinstance a COM class on remote machine, but I wish to get, not create!)

Best wishes,
Su.j.c

Dr. GUI replies:

Unfortunately, Dr. GUI has noticed that GetActiveObject() does not work remotely. Currently the only mechanism to access the ROT (Running Object Table) of a remote machine is via file monikers. A sample that shows how this can be done is available in the Knowledge Base article Q171974, Q171974 "FILE: Bind to an Object on a Remote Machine Using ROT" (http://premium.microsoft.com/support/kb/articles/q171/9/74.asp).

Bridge over Visual Basic waters

Dear Dr. GUI,

Do you know if there is something in Visual Basic 5.0 that will give me the same functionality as C++'s CoCreateInstanceEx()?

I'm not a C++ programmer and I would love to get a VB-based solution. Thanks in advance for any help.

Bernard Berger

Dr. GUI replies:

There isn't any way to do this directly, but there's almost always a solution to these things—usually by using a little of the most powerful programming language, C++.

So a Visual Basic–COM "bridge" can be used to accomplish this. This bridge is a DLL written in C++. It calls CoCreateInstanceEx on behalf of the Visual Basic program. One such bridge is available at http://www.wam.umd.edu/~mikenel/dcom/dcomtool.htm.

COM again? Should he be Dr. GUID?

Dear Dr. GUI,

I read the article on GUIDs in Inside OLE 2 on the MSDN Library and found a reference to "DEC/HP Network Computing Architecture RPC Run TimeExt. Specs. Ver OSF TX1.0.11" by Steven Miller (1992, July 23), said to be part of OSF DCE documentation. I am interested in the UUID allocation algorithm in Chapter 10. Can you help me to reach this reference? Is it available anywhere online so that I could download it? Else, perhaps you may help me find out how to access PC network communication cards (of different types . . .) and read the card ID, which is also rather globally unique.

Hoping to get your help and thanking you in advance,
Dr. Ben Zion Barta, Bar-code Computers Ltd., Haifa, Israel

Dr. GUI replies:

Well, of course you can do what Dr. GUI (should Dr. GUI be called Dr. GUID in honor of COM?) does: just use GUIDGEN. But if you want to create your own program to create UUIDs, read the IETF Internet draft that fully documents the algorithm for generating UUIDs (a.k.a. GUIDs). It's available at ftp://ftp.isi.edu/internet-drafts/draft-leach-uuids-guids-00.txt. The draft even includes C source code to a fully compliant generator. Note that Internet drafts are "works in progress" and should not be used for final reference purposes.

By hook or by crook . . .

Dear Dr. GUI,

I'm looking for a trick to redirect all keyboard and mouse events to a one window (that is, a dialog box). A function like the SetCapture works only for windows of the same process, while I need a trick to capture events from all desktop windows.

Thanks,
Michele Mazzurco, C.T. Cad Tech srl, Italy

Dr. GUI replies:

Somehow, Dr. GUI thinks your question has a hidden hook. In fact, the way to do this is to install Windows hooks that capture all keyboard and mouse messages at a system level. This can be done with the Win32 API function SetWindowsHookEx. The following code snippet will set up the hooks, do some other piece of code, and reset the hooks.

   HHOOK hKeyboardHook;
   HHOOK hMouseHook;

   'Hook ALL keyboard and mouse messages.
   hKeyboardHook = ::SetWindowsHookEx(WH_KEYBOARD,
      pfnMyKeyboardMonitor, NULL, 0);
   hMouseHook = ::SetWindowsHookEx(WH_MOUSE,
      pfnMyMouseMonitor, NULL, 0);

   'Invoke other code.
   DoSomethingElse();

   ::UnhookWindowsHookEx(hKeyboardHook);
   ::UnhookWindowsHookEx(hMouseHook);

Doing this will accomplish what you want but with some degradation to the system performance. (That's why hooks aren't supported under Windows CE.) If performance isn't an issue, then this function works just fine. Be warned though, debugging this type of hook is extremely tricky, if not downright ugly! This is definitely one of those times you want to be able to do remote debugging so that you don't crash the debugger too often.

A button for your macros

Dear Dr. GUI,

I have a bunch of Developer Studio macros that I'd like to get the people on my team to use, but to do it I need to make it as easy as possible for them. So is there a way I can put the macros on the tool bar with icons? That way they don't have to bring up the macro window and search for the macro they want. They just click on the icon.

Thanks,
Jake Watkins, Toadhall Software Studio

Dr. GUI replies:

One-click shopping, huh? A good idea. Fortunately, creating a toolbar with buttons that fire macros is very easy to do. First, create a new toolbar:

  1. Click Customize on the Tools menu.

  2. Click the Toolbars tab, and then click the New button.

Now you can place your macros on the toolbar. Bring up the Toolbars tab again and click on the Commands tab. Once there, scroll down to the macros selection and you will see a list of your defined macros. Simply drag the ones you want to the newly created toolbar and select an appropriate button appearance. Repeat this for each macro and voila! a toolbar with buttons that fire off macros.

ACE, ACL, SID, E-I-E-I-O

Dear Dr. GUI,

I am developing an administration utility for Windows NT that allows you to administer user rights on NTFS volumes. I want to display the Access Control List just like File Manager/Explorer does when you lookup the security permissions for a file or directory. I am able to go through the ACL for a file/dir. and retrieve individual ACEs, their SIDs, etc. The problem is that some ACLs have multiple ACEs for a given user/group and yet File Manager displays just one entry for each user/group. Can you tell me how File Manager clubs together multiple ACEs belonging to a user/group to come up with a single entry for each user/group in the security permissions list box?

Thanks,
Vijay Lele

Dr. GUI replies:

You have at least two options here:

  1. Use the Win32 API GetEffectiveRightsFromAcl(). This API takes a trustee and returns a mask that defines the effective rights for that user on the object.

  2. Do the grunt work yourself. Keep a list of the trustees as you process the ACEs (pay attention to whether the ACE is access denied or access allowed!) and perform a bitwise OR using the duplicate trustees' access mask.

Checking passwords

Dear Dr. GUI,

I want to boldly go where I haven't been before: programming for Windows NT. I want to use the security in NT for validating my users. I have found the GetUserName function, and have no problem using it.

How do I check the password that the user supplies?

Kind regards,
Michael K. Hansen, Naerum, Denmark

Dr. GUI replies:

So you want to be able to get the user's password? Uh, no. That would really mess up Windows NT security. Even an API to let you check it would make for a mess.

But it's OK—one of the best things about Windows NT is that you don't have to check passwords! You see, the system has already validated the user for you. Windows NT has already collected all the relevant credentials of the user and generated a handy-dandy security token that contains all the information you will likely need about that user.

All this usually comes into play in a client/server environment. If some client connects to your server you can use Windows NT security to impersonate that user and find out just who they are. Windows NT determines who you are by the information in your security token, which includes a list of Security Identifiers (SIDs) for yourself and all the groups to which you belong. Let's talk APIs. If your transport method is named pipes, you can call ImpersonateNamedPipeClient. For a sockets connection, you will want to look in the Platform SDK for SSPI documentation and the sample in the SDK called SOCKAUTH. Distributed COM (DCOM) offers a nice high-level interface through CoImpersonateUser().

Once you are impersonating, you can get the token from the current thread (this is where impersonation tokens are found). Once you have the security token of the client, you have two options:

  1. Search the token for a particular SID in the groups listed, or

  2. Get a Security Descriptor for an object that you want to check the user's access to and call AccessCheck().

This is just a quick summation of a system that is very complex. If you are serious about doing security right, take the time to look at the security section in the SDK documentation. If reading the docs from beginning to end is just too exciting for you, try searching for some of the APIs mentioned above. There is a large amount of security sample code in the SDK samples and Knowledge Base articles—your solution may already be coded!

What do you think?

Should Dr. GUI reinvent himself as Dr. GUID in honor of COM and ActiveX? Should the good doctor replace the "U" with a "V" to get that stylish Roman look (as in "Dr. GVID")? Do you have any questions of burning interest? You know the schtick: send mail to drgui@microsoft.com. The good doctor's surgery schedule doesn't allow him to answer or even acknowledge every question, but if we use your question in a column, we'll send you a cool t-shirt!

And a big THANKS . . .

. . . to Dr. GUI's specialists this issue: Saji Abraham, David Mowers, and Robert Coleridge.


Backtalk

by James Van Eaton and others
Microsoft Developer Network

How delightfully refreshing!

The December 1997 interim shipment to all Professional and Universal subscribers included a beta refresh of Windows 98, which by now has started whining that it will soon expire. Never fear! Windows 98 beta 3 is on its way in the January 98 quarterly release. It not only comes chock-full of new features and bug fixes, but extends your desktop's life as well. And speaking of refreshing, remember that the January 98 quarterly release is a complete refresh of your MSDN subscription. That is to say, all discs of all packs that you currently receive at your subscription level are included in the January shipment, complete with nifty new '98 release valises (that's "binders" to you and me)!

Staying organized

Many of you have written to ask when you'll be receiving Microsoft Project 98 as part of your MSDN subscription. The good news: it's part of the January 98 release. Watch your mailbox.

Questions about your subscription?

The Member Services section on MSDN Online (http://www.microsoft.com/msdn/services/) allows you to get the information you need about your subscription. You'll find:

  • Subscription CD Contents Index

    CD contents listings for each level, ship dates, and detailed contents listing for the latest quarterly and interim shipments.

  • Release Notes

    The latest information regarding software delivered with your MSDN subscription, including FAQs and known issues.

  • The Subscription Services Web Site

    A quick, easy way to check on shipment information, subscription status, or to update contact information on your subscription record. Includes links to U.S. and Canadian services and European services.

Developer to developer

You’re sitting there in your office (or cubicle), it’s 3:00 a.m. and there are two things bothering you: the cold pizza you consumed an hour or so ago and that nagging development question. You may have to suffer alone with your digestive system, but there are ways you can find help or just someone to commiserate with on that code, no matter what the hour. For a listing of public newsgroups on Microsoft products and technologies, check out the Newsgroups page on MSDN Online (http://www.microsoft.com/msdn/newsgroups/) There you’ll find listings of public newsgroups on Visual Basic, Visual C++, Visual FoxPro, Visual InterDev, SQL Server, Office Development, Win32 Development, and Multimedia Development. There’s even one for MSDN! Point your newsgroup browser at the ones that interest you. If you prefer, you can also contact Microsoft Technical Support. Just click on the hot links and you’ll find yourself at the Support site, where you can ask your questions.

If you would welcome the chance to get out of the office and meet with other developers face-to-face, consider joining a user group near you. The MSDN User Group program provides specific information and support to the developer-centric user groups and special interest groups (SIGs).

We support more than 500 developer user groups around the globe. These groups are especially interested in Visual Tools products. You’ll find more information about the groups and whether there’s one near you at http://www.microsoft.com/msdn/usergroup/. Microsoft also offers the Mindshare Program (see http://www.microsoft.com/mindshare/), which is designed to support the broad spectrum of general PC user groups.

Several of our subscribers have also written to ask if there are plans for developer chat rooms or forums on MSDN Online. All we can say now is stay tuned. We’re looking at various ways of helping our developer community communicate with one another and with us. For now, though, visit those newsgroups (we read them, too) and send us e-mail at msdn@microsoft.com.


The Harmonious HelpDesk

Robert Coleridge
MSDN Content Development Group

Recently, one of our readers wrote to express concern that two of the articles in our Helpdesk series, "Designing the HelpDesk Transaction-Processing Object" and "Task Distribution in a Customer Service System," (published in the last issue of Developer Network News) offer conflicting data-access models. In fact, they are actually in complete harmony. It was a good question, though, so we decided to take the time to reply here, in case other readers had the same impression.

Fred Pace's article, "Designing the HelpDesk Transaction-Processing Object," which shows how the object model for the HelpDesk sample was designed, gives an excellent example of encapsulating data access through a defined API. Why would Fred do this? The answer is quite simple: In a distributed environment, all client/server designs should be based on an n-tier paradigm.

Fred goes to great lengths to explain the design model for the HelpDesk. There are several reasons why this design is advantageous: The application is enterprise scalable, the application is easier to maintain, the application's clients tend to be thinner, and last but not least—the actual data is not exposed to the user, only a working copy of it. By encapsulating access to the data with an API set, there is a safety net of validation, security, workflow, and business logic. All of this ensures a much more scalable and robust application.

Steve Kirk's "Task Distribution in a Customer Service System" is a great article on how to do load balancing with Transact-SQL (T-SQL). Through the power of T-SQL, Steve shows how the HelpDesk sample uses SQL tasks to automate the assignment process for incoming requests. In order to gain the highest performance-to-maintainability ratio, Steve wrote a "distribution optimizer" in T-SQL. By doing this he gains direct access to the data and encapsulates the data access to a few stored procedures.

Since Steve's optimizer is written in T-SQL and directly accesses the data, it might appear that he violates the n-tier paradigm that MSDN advocates. However, because the optimizer is a scheduled SQL task, it runs at the lowest level—in the same tier as SQL Server itself.

So, here we have two models: Fred's encapsulates data access through an API set, and Steve's directly manipulates the data via T-SQL. The question is, "Are they in conflict?"

My answer: The model that Fred espouses deliberately exposes the data to a user, but only through an API set, while Steve's model does not expose the data at all. While Fred's API is for external exposure, Steve's stored procedures are for tasks internal to SQL Server and would never be invoked by an end user. So they are not in conflict, merely fulfilling different tasks on different tiers.


The Basics of ActiveX

by Al Saganich

Java is a delight to program. Applications are quick to develop, robust, and expandable. But often, developers wish to use a host of controls and other gadgets that already exist. Perhaps you're a converted Visual Basic or Delphi programmer and want to use an old, familiar control in your hot new development environment. But Java is platform-independent, right? Well, yes, in its purest form—but you can use ActiveX controls in your Java application! And if you ever wanted to, here's your chance to learn how.

The hype about ActiveX and the Internet has been unrelenting—how ActiveX controls build excitement into your Web pages by providing fancy graphics or access to legacy code or some other wizzy feature. But what does ActiveX really give you, and how do you use it? Are there limitations as to which controls you can use in Java applications? And how does ActiveX impact Java from the points of security, interoperability, and portability?

In this article, we'll explore the basics of ActiveX, examining how to use ActiveX controls in Java applications. We'll investigate the nuts and bolts of ActiveX controls; we'll discuss which controls you can and can't use, and why; we'll detail how to call ActiveX methods and capture ActiveX events; and we'll touch on how Active X affects Java application security.

Next month, we'll continue our ActiveX journey by demonstrating how to load ActiveX controls directly into a Web page, then pass these controls into a Java applet. We'll also show you how to package and ship controls, how to write your own ActiveX controls, and how to expose your Java applications as ActiveX controls.

Why ActiveX?

Why use ActiveX in the first place? For many reasons. If you're like me, you're always looking for a simpler way to do something. I migrated from C to C++ because I liked classes and other aspects of C++, then on to Java because of its simple elegance. But along the way, I had to abandon a large body of code I'd written in C++. ActiveX lets me regain some of that investment.

Another compelling reason to use ActiveX is the wealth of ActiveX controls that already exist. Visual Basic programmers have been evangelizing about the wonders of OLE controls for ages. Now, you can use many of those controls from Java, as well. In addition, ActiveX lets you easily access database information. Not to mention the fact that you can control other Microsoft applications via ActiveX scripting—you can embed your favorite spreadsheet directly into your Web page!

The downside

ActiveX is a great technology, but it has its warts. First and foremost, Java is supposed to be portable—we've all heard about write once, run anywhere. Well, with ActiveX, that idea is gone. Currently, ActiveX controls bind you not only to a single platform—Windows—but also to a single browser—Internet Explorer. Your programs are no longer completely portable.

Actually, ActiveX isn't as proprietary as it seems. A plug-in for Netscape exists, so you can run ActiveX-based pages in Netscape, but you're still limited to Win32 platforms. However, even that situation is changing. In October 1996, Microsoft placed ActiveX into the public domain by giving over the base code and control to the Cambridge, Massachusetts-based Open Group (www.activex.org). In fact, several companies are actively involved in bringing ActiveX to other platforms: Macromedia and Metrowerks to the Mac, and Bristol Technologies to UNIX. While it remains to be seen whether ActiveX will make it to other platforms, it's certainly here to stay on Windows.

Tools of the trade

Three tools assist the Visual J++ developer in working with ActiveX controls: Regsvr32.exe, which registers a given control; JavaTLB, which generates .CLASS files and summary.txt from registered controls; and JActiveX.exe, which generates .CLASS files and java source code for a control. We'll discuss each of these tools as we encounter them in building an ActiveX-enabled Java application.

Preparing an ActiveX control

A Java application accesses an ActiveX control via a set of .CLASS files that normally reside in the ${windows}\java\trustlib directory tree. Preparing a control for use with Visual J++ is a two-step process. First, you register the control using Regsvr32.exe, which ships with Visual J++. Regsvr32 takes one argument—the DLL or control to register—and any of several command-line switches, the most notable of which is /u to uninstall a control. By default—that is, when run with only a dll or ocx—Regsvr32.exe registers the control.

Note: Download our code!

For the remainder of this article, we'll assume that the ActiveX control binary (OCX, EXE, or DLL) has “magically” arrived on your computer. Next month, we'll look more closely at packaging controls for distribution directly from a Web page. You can download this article's sample files, including the ActiveX control, from www.cobb.com/vjp.

If you've downloaded the source code for this month's example, open a DOS box and change to the directory containing prodcons.ocx. Then, type regsvr32 prodcons.ocx. Figure A shows the result of running Regsvr32.exe on the prodcons ocx.

Figure A

Regsvr32 displays output like this.

Once you've registered a control, it will appear in the JavaTLB (Java Type Library Wizard) tool. To run the JavaTLB, choose Tools | Java Type Library Wizard from Visual J++'s main menu; there's no keyboard shortcut. For example, in Figure B, JavaTLB displays all the currently registered controls.

Figure B

The JavaTLB displays the currently registered tools.

You can now manually generate the registered tool's required classes and package its files for use with Visual J++. For this example, select the ProdCons ActiveX Control Module control, as shown in Figure B, then click the OK button. Figure C shows the Java Type Library tab of the output window after we ran JavaTLB on the ProdCons control.

Figure C

JavaTLB produced this output for ProdCons.ocx.

Tip: Double-click the summary.txt line.

After running the JavaTLB tool, you can double-click on the summary.txt line for any of the processed controls to immediately open the summary.txt file. This file contains a definition of the objects and interfaces in the ActiveX control from a Java perspective.

Our first control

It's surprising how similar ActiveX controls are to everyday Java classes. From a programming perspective, they're just another class that Java can instantiate and use. Our ProdCon example is a simple ActiveX messaging control that works as a go-between for a producer and a consumer of strings. When an application sends a message, a NewMessage event is generated, signaling that a message can be retrieved. The control also supports getting the current count of available messages. Figure D shows the ProdCon example running. Let's look more closely at how other ActiveX controls work.

Figure D

Our ProdCon example looks like this while it's running.

An ActiveX control is really just reusable code in a binary form. Each control contains one or more interfaces that you can use to access it. For instance, our producer/consumer control could have had one interface for producers and another for consumers.

When you create an ActiveX control, you create an instance of the control, but cast it as an instance of the interface in which you're interested. Listing A shows the summary.txt file for the ProdCons ActiveX control. We've highlighted the interfaces in color.

Listing A: ProdCons ActiveX object description

public class prodcons/ProdCons extends
  java.lang.Object
{
}
public interface prodcons/IProdConsEvents
  extends com.ms.com.IUnknown
{
    public abstract void NewMessage();
}
public interface prodcons/IProdCons extends
  com.ms.com.IUnknown
{
    public abstract int GetMsgCount();
    public abstract int SendMsg(java.lang.String);
    public abstract java.lang.String GetMsg();

. . .

}

We could have chosen the IProdCons interface or the IprodConsEvents interface as the interface to access our instance of an IProdCons object. As you'll see, the IProdConsEvents interface captures events. So, we created our object, as shown in Listing B, using IProdCons on the ProdCons object. Looking closely at the code, you'll see that while we created an instance of a ProdCons object, it was assigned to an instance of the interface we wanted to use to access—in this case IProdCons.

Note: Understand the interface.

In order to use an ActiveX control, you must understand the interfaces to that control. Carefully consult the documentation that came with the control you're using, then choose the interface that best suits your needs.

Listing B: Creating the ActiveX control in Java

// ProdCon.java
// 19-September-1997
// Al Saganich for
// Visual J++ Developer's Journal
//
// Copyright 1997, Al Saganich,
// Visual J++ Developers Journal.
//
// This code is provided without warrantee
// of any kind and may or may not be fit
// for any purpose other than illustration.
// This code may be used, changed or
// distributed in any form,
// electronic or otherwise, as long as this
// notice is not removed.
//
import java.awt.*;
import java.awt.event.*;
import com.ms.com.*;
import prodcons.*;

//
// ActiveX sample
// Al Saganich for
// Visual J++ Developer's Journal
//
// This class uses the prodcons classes to send
// and receive messages from an ActiveX control
//
class ProdCon implements
              IProdConsEvents,
              // Event fired by the control.
              ActionListener
{
    IprodCons    iActiveX;
    ConnectionPointCookie
                eventConnector;
. . .
    public ProdCon()
    {
        try
    {
        iActiveX = (IProdCons) new ProdCons();
    }
    catch (com.ms.com.ComFailException e)
    {
        System.out.println(e);
        return;
    }
 . . .
}

Methods

ActiveX controls can have methods, just like Java classes. While the arguments are limited to ActiveX-supported types, most Java types are supported. Table A shows most of the ActiveX types and their Java equivalents. Of course, the same types apply to both method arguments and return values.

Table A: ActiveX types and Java equivalents

ActiveX | Java
short | short
long | int
float | float
double | double
CURRENCY | long
DATE | double
LPCSTR | String
BOOL | boolean
short * | short[]
long * | long[]

Calling ActiveX methods and functions from Java is straightforward. Listing C shows two of the methods that act on an ActiveX object. The two lines shown in color access data from and send data to the ActiveX object, respectively.

Listing C: Calling ActiveX methods

public void NewMessage()
{
    System.out.println(
      " New Message event fired\n");
    try
    {
        String msg = iActiveX.GetMsg();
        String old = receiveBuffer.getText();
        old += "\n" + msg;
        receiveBuffer.setText(old);
    }
    catch (com.ms.com.ComFailException e)
    {
        System.out.println(e);
    }
}

public void send(String msg)
{
    try
    {
        iActiveX.SendMsg(msg);
        int count = iActiveX.getMsgCount();
    }
    catch (com.ms.com.ComFailException e)
    {
        System.out.println(e);
    }
}

Errors and exceptions

To this point, we haven't discussed errors and exceptions. From Java's perspective, ActiveX objects and Java objects are one and the same: Both can return values and throw exceptions. However, unlike most Java classes, ActiveX objects throw a small number of exceptions and count on one of the exception's member variables to determine exactly what happened!

For the most part, ActiveX objects will throw com.ms.com.ComException—or exceptions derived from ComException, such as com.ms.com.ComFailException—when something goes wrong. To get more specific information, you need to use the getHResult() method to determine the type of exception.

Table B shows common exception values and their meanings. Listing D shows how a simple method captures and processes exceptions. Note that ActiveX objects may return values other than the ones listed in the table; see your control's documentation for the error values it may return.

Table B: Common exception values

Description Symbolic constant Value
Unexpected failure
E_UNEXPECTED
 0x8000FFFF
Not implemented
E_NOTIMPL
 0x80004001
Ran out of memory
E_OUTOFMEMORY
 0x8007000E
One or more arguments invalid
E_INVALIDARG
 0x80070057
No such interface supported
E_NOINTERFACE
 0x80004002
Invalid pointer
E_POINTER
 0x80004003
Invalid handle
E_HANDLE
 0x80070006
Operation aborted
E_ABORT
 0x80004004
Unspecified error
E_FAIL
 0x80004005
General access denied error
E_ACCESSDENIED
 0x80070005
Not implemented
E_NOTIMPL
 0x80000001
Unknown interface
DISP_E_UNKNOWNINTERFACE
 0x80020001
Member not found
DISP_E_MEMBERNOTFOUND
 0x80020003
Parameter not found
DISP_E_PARAMNOTFOUND
 0x80020004
Type mismatch
DISP_E_TYPEMISMATCH
 0x80020005
Unknown name
DISP_E_UNKNOWNNAME
 0x80020006
No named arguments
DISP_E_NONAMEDARGS
 0x80020007
Bad variable type
DISP_E_BADVARTYPE
 0x80020008
Exception occurred
DISP_E_EXCEPTION
 0x80020009
Out of present range
DISP_E_OVERFLOW
 0x8002000A
Invalid index
DISP_E_BADINDEX
 0x8002000B
Memory is locked
DISP_E_UNKNOWNLCID
 0x8002000C
Memory is locked
DISP_E_ARRAYISLOCKED
 0x8002000D
Invalid number of parameters
DISP_E_BADPARAMCOUNT
 0x8002000E
Parameter not optional
DISP_E_PARAMNOTOPTIONAL
 0x8002000F
Invalid callee
DISP_E_BADCALLEE
 0x80020010
Does not support a collection
DISP_E_NOTACOLLECTION
 0x80020011

Listing D: Further refining errors

Try
{
  SomeActiveXObject.Method();
}
Catch (com.ms.com.ComFailException ce)
{
  switch (ce.getHResult()
     {
  case E_UNEXPECTED:
  System.out.println(“Unexpected exception!”);
  break;
  case E_ E_NOTIMPL:
  System.out.println(“Method not implemented exception!”);
  break;
         // Other cases as required
         . . .
     }
}

Events

We've talked about creating objects, calling methods, and handling errors. But Java normally provides another feature: events.

Note: Standard Visual J++ 1.1 doesn't support ActiveX events.

As of this writing, Visual J++ 1.1 didn't support capturing and processing events. In order to correctly handle events, you need to download the Java SDK 2.0 (now in final release) from Microsoft's Web site at www.microsoft.com/java. The remainder of this article section assumes that you've downloaded and installed the Java SDK 2.0 final release.

Earlier, we discussed which interface to use when accessing an ActiveX object. We also noted that another interface exists to support events. Listing E shows the portion of the ProdCon object description that deals with events. Let's see how to link a Java application to an ActiveX control for the purpose of capturing events.

Listing E: ProdCons events

public interface prodcons/IProdConsEvents extends com.ms.com.IUnknown
{
    public abstract void NewMessage();
}

As we previously mentioned, the IProdConsEvents interface defines the events that the ProdCons object produces—sharp eyes will have noticed that the one method this interface defines is abstract. Capturing events from an ActiveX object is a two-step process. First, the class that's going to intercept the events must implement the event interface, as defined in the summary.txt file. Second, the application must be connected to the object that generates the events. Listing F shows these steps in action.

Listing F: Steps to handing events

import java.awt.*;
import com.ms.com.*;
import prodcons.*;

class ProdCon implements
        IProdConsEvents,
        // Step 1
        ActionListener
{
    IProdCons               iActiveX;
      // Used for step 2
    ConnectionPointCookie   eventconnector;
    . . .
    public void NewMessage()
    {
        // Do something when event fires
        . . .
    }

    public ProdCon()
    {
        . . .

        // Step 2 attach the objects together.
        try
        {
            eventconnector =
              new ConnectionPointCookie(
                 iActiveX,this,
                 Class.forName(
                 "prodcons.IProdConsEvents"));
        }
        catch (ClassNotFoundException e)
        {
            System.out.println(e);
            System.out.println(
              "Cannot catch prodcons events!");
            return;
        }

        . . .
    }
}

First we define the prodcon class as implementing the IProdConEvents interface. This interface requires that the NewMessage method be implemented. NewMessage events are fired whenever the SendMsg method is called.

Next, we hook the applet and the control together using a ConnectionPointCookie object. This object takes three arguments: the event source, the event sink, and the connection interface. The event source is the class responsible for generating events. The event sink is the class responsible for consuming generated events. The connection interface is the class responsible for connecting the source to the sink.

We create the first two arguments ourselves in the normal course of the application's development. However, we must force the third argument into existence. We can force a class to be loaded by using the forName static method of the java.lang.Class class. The forName static class method loads a class dynamically (and is often used to implement class loaders), then returns the loaded class or throws a ClassNotFoundException.

Properties

ActiveX controls also have properties, which basically are public values that a user can manipulate to read and write on an object. Visual J++ doesn't provide a way to access a control's properties directly, but rather wraps each property in a get and set method. The ProdCon object exposes a property that was intentionally omitted from the prior interface description. Interested readers might want to load the ProdCon ActiveX project and see if they can determine what that property is.

Control restrictions

It's important to understand the restrictions on using controls. Visual J++ has one major failing when it comes to using ActiveX controls: Any control created within a Java applet or application must be non-visual. In a future article, we'll discuss how to create simple non-visual controls with Visual C++. For now, suffice to say that you can't create visual controls within a Java application.

However, all is not lost! You still can use a visual control from Java—you simply can't create it from within a Java applet. Instead, you must create the control on your Web page, then pass it into your Java applet via one of the applet's exposed methods. The focus of this article is using controls, but next month we'll look at the mechanics of, among other things, creating controls on your Web page for use in Java applications.

Security (trusted vs. untrusted)

Finally, we must mention security. ActiveX uses the keys to the castle model of security, meaning that once you've installed a control on your machine, it can do what any application can do. Next month, we'll look more closely at security and how to create ActiveX controls, load them into cab files, then digitally sign the files for download.

Conclusion

ActiveX technology has its quirks. However, it expands Java's potential immensely, opening areas that were previously unavailable to developers. Among other things, you can now take advantage of a huge number of commercial controls, many of which you can embed and control from within your Java applets. As we'll discuss next month, creating your own ActiveX controls and packaging them for distribution, while not trivial, is easily accomplished. So dust off those controls you mothballed when you started developing in Java—they can ride again!

___________________________

Al Saganich is an independent software consultant, currently under contract to Digital Equipment Corporation. He's co-author of the best-selling Microsoft Visual J++ 1.1 SOURCEBOOK and is working on the next release of Java 1.2 for C/C++ Programmers, both published by J. Wiley and Sons. You can reach Al at asaganich@aol.com.

___________________________

This article is reproduced from the February 1998 issue of Visual J++ Developer's Journal. Visual J++ Developer's Journal is an independently produced publication of The Cobb Group. No part of this article may be used or reproduced in any fashion (except in brief quotations used in critical articles and reviews) without prior consent of The Cobb Group. To contact The Cobb Group, please call (800) 223-8720 or (502) 493-3200.

Copyright © 1998 The Cobb Group, a division of Ziff-Davis Inc. The Cobb Group and The Cobb Group logo are trademarks of Ziff-Davis Inc. All rights reserved. Reproduction in whole or in part in any form or medium without express written permission of Ziff-Davis is prohibited.


Taming the GridBagLayout Class

by Tom Mitchell

New Java programmers must adjust to the concept of using AWT layout managers. Developers coming from Visual Basic, Windows SDK, or other windowed backgrounds are often surprised to find that, with Java, the user interface is built dynamically by code at runtime. Many Java-integrated development environments have been introduced that assist programmers by generating the user-interface layout code for them. In the end, however, you’ll want to understand all the layout managers available in the Java Development Kit (JDK) and how to use them effectively.

The GridBagLayout class has the most flexibility and functionality of the layout managers included in the JDK. It’s also the most complex. Many programmers avoid it completely, choosing either to use a simpler layout manager or none at all. In this article, we’ll explain how GridBagLayout works and how to use it—when we’re finished, you should find it far less intimidating. We’re assuming you have a conceptual understanding of layout managers. If you need a refresher, refer to the JDK documentation that you can view from the InfoViewer.

The GridBagLayout class has had some enhancements in the JDK 1.1 release, but functionally it’s virtually unchanged. The content of this article is applicable to both JDK 1.x and 1.1, unless explicitly noted otherwise.

What’s the big deal?

So what’s the big deal about the GridBagLayout manager? Like all the other layout managers, it’s extended from the Object class and implements the LayoutManager2 interface. This is a change in JDK 1.1. In JDK 1.x, the layout managers implemented the LayoutManager interface. LayoutManager2 extends the LayoutManager interface, adding some new methods intended mainly for tool builders.

GridBagLayout differs from all the other layout managers in the complexity of the layout. While other layout managers allow simpler, predictable positioning and sizing of components into fairly uniform layouts, GridBagLayout has the flexibility to lay out components of varied sizes into an aligned layout, without requiring that all rows and heights be identical. That’s an intimidating description—but the individual concepts are fairly easy to understand.

The GridBagConstraints class

The key to understanding GridBagLayout is understanding the GridBagConstraints class. Every component added to a GridBagLayout will have an associated GridBagConstraints object. The GridBagConstraints class specifies the criteria that will be used to size and position the component.

The GridBagConstraints class defines 11 constraints, or rules, used by the GridBagLayout class to perform its job. Each constraint exists as a variable in the GridBagConstraint class and, along with a group of constants, can (and should) be used to set the variables in a readable manner.

Generally, you’ll make an instance of a GridBagConstraints object in your application or applet, set the object variables appropriately, then associate the object with a component using GridBagLayout’s setConstraints() method. The setConstraints method will make a copy of the GridBagConstraints object so you can change its contents after the setConstraints call without affecting the component layout. In fact, we usually reuse one GridBagConstraints object each time we add a component to the layout. There’s no need to make new instances of the GridBagConstraints class for each component.

Setting or changing the constraints will often affect the sizing and positioning of other components in the layout. For that reason, we recommend not using the positional add(Component, Position) method to add components to the layout. Its effect on other components and the overall layout will make your code more difficult to understand. Now, let’s take a look at the 11 GridBagConstraints variables you must set.

anchor

The anchor variable tells the layout manager how you’d like the component justified if its display area is larger than the component size. The variable has nine possible values, allowing the component to be aligned to any corner, any side, or the center. You can use the constant CENTER (the default), EAST, NORTH, NORTHEAST, NORTHWEST, SOUTH, SOUTHWEST, SOUTHEAST, or WEST to set the value. Other values will cause the GridBagLayout manager to throw an IllegalArgumentException.

fill

The fill variable indicates how the component should be resized if its display area is greater than its requested size. This variable has no effect if the weightx or weighty values are set to 0. You can use the constant NONE (no resizing), VERTICAL (resize the component to fill the display area vertically, but not horizontally), HORIZONTAL (resize the component to fill the display area horizontally, but not vertically), or BOTH (resize vertically and horizontally) to set the value. The default value is NONE; invalid values will result in no resizing.

gridheight

The gridheight variable indicates the requested height of the component (in grid cells). Its default value is 1, and its effect will vary depending on the constraints of other objects in the layout. If the number of rows in the layout is less than the gridheight value, the component will be equal to the container height.

This variable has two special values identified by constants in the GridBagConstraints class: REMAINDER indicates that the component should be the last one in its column (and receive any remaining space). RELATIVE indicates that the component should be the next-to-the-last component in its column.

gridwidth

The gridwidth variable indicates the requested width of the component. Its default value is 1, and it causes the same behavior in regard to width that the gridheight variable does for height.

The constant REMAINDER indicates that the component should be the last one in its row (and receive any remaining space), and RELATIVE indicates that the component should be the next-to-the-last component in its row.

gridx and gridy

The gridx and gridy variables indicate respectively the horizontal and vertical grid-cell placement of the component. The GridBagConstraints constant RELATIVE indicates that the component should be placed to the right of (for gridx) or below (for gridy) the previous component.

insets

The insets variable specifies the amount of space between the component and the edge of the display area on the top, bottom, left, and right sides. It defaults to 0 for all sides.

ipadx and ipady

The ipadx and ipady variables indicate, respectively, the horizontal and vertical padding of the component beyond its minimum size. The GridBagLayout will multiply each of these values by two and add the products to the minimum horizontal and vertical sizes when laying out the components.

weightx and weighty

The weightx and weighty values indicate how any extra vertical or horizontal space should be distributed. GridBagLayout will consider the weighty of a row to be the maximum weighty of the components in that row and the weightx of a column to be the maximum weightx of components in that column. Unless you specify a value for at least one component in a row and column, the components will all be positioned in the center of their containers.

Putting it all together

Because of the number of variables involved and the near-infinite number of ways they can interact with each other, it takes some time to get comfortable with GridBagLayout. Let’s work through a simple example that will illustrate a few of its behaviors.

Our example consists of four buttons placed in an applet, as shown in Figure A. The code for the applet is shown in Listing A.

Figure A

Our example shows a simple GridBagLayout.

Listing A: gridbag.java

import java.awt.*;
import java.applet.Applet;

public class GBDemo extends Applet
{
  Button button1 = new Button("Component 1");
  Button button2 = new Button("Component 2");
  Button button3 = new Button("Component 3");
  Button button4 = new Button("Component 4");

  public void init()
  {
      GridBagLayout gb = new GridBagLayout();
      GridBagConstraints  gbc  =  new
        GridBagConstraints();
      setLayout(gb); // set the applet layout

      gbc.fill = GridBagConstraints.BOTH;
      gbc.weightx = 1;
      gbc.weighty = 1;
      gbc.gridheight = 3;
      gb.setConstraints(button1, gbc);
      add(button1);

    // reuse gbc, only change what we need to
      gbc.gridheight = 1;
      gbc.gridwidth =
        GridBagConstraints.REMAINDER;
      gb.setConstraints(button2, gbc);
      add(button2);

      gb.setConstraints(button3, gbc);
      add(button3);

      gb.setConstraints(button4, gbc);
      add(button4);
    }
}

The code is very simple. In the applet’s init() method, we instantiate a GridBagConstraints object, gbc; set the variables to the values we want; associate the object with button1; then add button1 to the layout. Reusing the same gbc object, we modify a few of the constraints, associate it with button2, and add button2 to the layout. Finally, we use the unchanged gbc object to set the constraints of button3 and button4, then add them to the layout.

Take a look at the final layout in Figure A. Because the gridheight for the button1 constraints is 3, its height is equal to that of all three rows. Buttons 2, 3, and 4 all have a gridheight of 1 and a gridwidth of REMAINDER—this value causes each of those components to use all the remaining space in their rows and forces each of the next components onto a new row.

Now let’s make a few small changes and observe the effect. We changed the caption of button3 with this statement:

Button button3 = new
  Button(
    "Component 3 has a very long caption");

This change increases the width of the button3 component and, therefore, the width of the entire column. We could have accomplished the same effect by increasing the weightx variable of one of the components in column 2.

Next, we changed the value of button1’s gridheight constraint:

gbc.gridheight = 2;

The effect of this change is twofold. The height of button1 is decreased, as you probably expected—but notice that button4 now has a width equal to that of the entire applet. This is because the gridwidth value of REMAINDER causes it to use all the remaining row space, even if it’s the first component in the row. If we added another button with the same constraints, it would be placed on the next row and would also take up the whole row width.

Figure B shows the modified applet. Notice that button3 and button4 still have identical constraints, but they’re sized and positioned much differently.

Figure B

Our example applet looks like this after modifications.

One other note about this example: To keep it short, we set only the values of the variables we wanted to change. For maintenance purposes, it may be a better practice to always explicitly set the contents of each variable. Doing so would make the code clearer and would also let the code continue working if the default values of the parameters ever change.

Summary

In this article, we covered the basics of the GridBagLayout and GridBagConstraints classes and how they work together to lay out the components in a container. GridBagLayout is always full of surprises, however. To get comfortable with this class, you should spend a few hours playing with it. Simply modify constraints one at a time and observe the effect on the component, as well as all the other components in the layout. Once you’ve mastered GridBagLayout, you’ll find that it’s a valuable tool for laying out dynamic user interfaces that must look good on a variety of platforms.

Tom Mitchell is co-author of Professional Java Fundamentals, published by WROX Press. He’s a senior product developer at The Molloy Group. You can reach him at tjmitch@nac.net.

Copyright © 1998 The Cobb Group, a division of Ziff-Davis Inc. The Cobb Group and The Cobb Group logo are trademarks of Ziff-Davis Inc. All rights reserved. Reproduction in whole or in part in any form or medium without express written permission of Ziff-Davis is prohibited.


Coping with a Corrupt Registry

You've probably heard a thousand times that it's a good idea to back up your Registry before making changes to it. However, many people reach a point where they don't think that creating this backup is really necessary. If you're a seasoned Registry Editor veteran, you probably feel comfortable making changes to the Registry.

But what if the unthinkable ever did happen? Suppose you accidentally changed the wrong Registry key and disabled Windows 95? In a situation like this, your first instinct may be to panic. However, there's a way you can fix the problem without reinstalling Windows. In this article, we'll teach you how to deal with a catastrophic Registry failure.

Required resources

The process of rebuilding your computer's Registry will go much smoother if you have at your disposal a second computer running Windows 95. If you don't have this luxury, you can still rebuild your Registry. Either way, you'll need a few tools to do so.

The first and most important tool is a Windows 95 Startup disk. The entire repair process depends on your being able to boot your PC to a command prompt. Although you may be able to press [F5] during the boot process and use the Command Prompt Only option to access the command prompt, Windows 95 may be damaged so badly in some cases that you can't use this option. In such an instance, a boot disk is your only way of accessing the command prompt.

The next tool you'll need is a copy of the MS-DOS version of PKZIP. You can download a copy from www.pkware.com. You'll need this tool whether you're using a second PC or not. If you're using a second PC, you'll be dumping the Registry's contents to a text file. A text file created from a basic Windows 95 Registry can be as small as about 900KB. However, in real life, you've probably installed other programs and configured various Windows 95 options on the damaged PC. Such installations drastically increase the size of the Registry. In our tests, regularly used PCs generated Registry text files that were typically 5-6MB in size. Since a standard floppy disk holds only 1.44MB, you'll have to use PKZIP to compress the text file to a size that will fit on a floppy disk.

If you don't have a second PC available or if you can't compress the file enough to make it fit on a floppy disk, you'll need a DOS-based text editor that can edit files of unlimited sizes. The editor that comes with MS-DOS won't do the job. We suggest using Ntuitive Editor. You can obtain a shareware copy by downloading the file Ntuit310.zip from www.shareware.com. After downloading the file, you'll have to use PKUNZIP to decompress it.

Creating the text file

Once you've got all the necessary tools in place, you can create the Registry text file. To begin, use your startup disk to boot the damaged PC to the command prompt. Next, switch to your Windows 95 directory, type Regedit, and press [Enter]. When you do, you'll see the command-line switches for the Registry Editor, as shown in Figure A.

Figure A

The Registry Editor has command-line switches you can use to edit your Registry from DOS.

At this point, you'll need to use the various switches to dump the Registry's contents to a text file. To do this, you must enter the Regedit command followed by the switches and specifications for the System.dat, User.dat, and text files. For example, on our PC, Windows 95 is installed in C:\Windows. Therefore, the Registry files (System.dat and User.dat) are also located in the C:\Windows directory. As you can see in Figure B, we exported the Registry files by typing the command

Regedit /L:C:\Windows\System.dat /R:C:\Windows\User.dat /E Bad.reg

Although this command may seem complicated, we've simply used the REGEDIT command followed by the /L: switch and the location of System.dat; the /R: switch and the location of User.dat; and the /E switch and the name of the text file we're dumping the Registry to. When you've entered the appropriate command switches, press [Enter] to begin the dumping process. This process may take several minutes, depending on the size of your Registry.

Figure B

Use these switches to create a text file containing the Registry settings.

Editing the text file

When the dumping process is complete, you should have a text file with the name you specified and in the directory you executed the command from. For example, in Figure B, we called our text file Bad.reg. Since we executed the command from the Windows directory, we now have a file called Bad.reg in our Windows directory.

After you verify the existence of the text file, you should make a backup copy of it. Since Windows 95 is still unoperational at this point, the simplest way to back up the file is from the DOS prompt. Simply copy the file to another file. For example, if you called the file Bad.reg, you might create a backup called Bad.bak by typing Copy Bad.reg Bad.bak and pressing [Enter].

Once you've backed up your text file, you're ready to edit it. If you plan on using another machine to edit the file, compress the file with PKZIP and then copy the compressed file to a disk. Next, take the disk to the other PC, decompress the file, and open it in WordPad.

If you plan on working with the file locally, use your text editor to open it. The Registry text file will look something like the one shown in Figure C. As you can see, the file lists all the Registry keys and subkeys in order.

Figure C

When you load the Registry text file into your editor, you'll see all the Registry keys.

At this point, locate the area of the Registry containing the problem entry by using your editor's Find feature. When you find the invalid entry, correct it. Be careful to leave the spacing intact and avoid changing anything you don't absolutely have to. If you know the correct value for the problem entry, you should replace the existing value with the correct one; otherwise, you may have to delete the entire subkey. When you've finished making the necessary changes, save the file. Make absolutely sure to save the file in text (ASCII) format, or you won't be able to use the file to update your Registry. After you've saved the file, go back to the DOS prompt.

Restoring the Registry

Now that you've corrected the problem, you can overwrite your existing Registry with the Registry values contained in your text file. To do so, switch to your Windows directory and type Regedit followed by the appropriate switches. You should use the same switches you used to export the Registry, except for the last switch, which should be /C instead of /E. Figure D shows the command we typed to import our new Registry, Bad.reg, to the C:\Windows directory.

Figure D

Use the REGEDIT command to build a new Registry from your text file.

When you've finished, reboot your PC and try to launch Windows 95. Hopefully, Windows 95 will be error free. However, there may be circumstances in which using this technique returns only limited functionality. If that's the case, try using the Windows version of Regedit to make any additional changes to the Registry.

Conclusion

In this article, we've shown you how to recover from a catastrophic Registry error. Remember that you should use this technique only if Windows 95 won't boot.

______________________

This article is reproduced from the February 1998 issue of Windows 95 Professional. Windows 95 Professional is an independently produced publication of The Cobb Group. No part of this article may be used or reproduced in any fashion (except in brief quotations used in critical articles and reviews) without prior consent of The Cobb Group. To contact The Cobb Group, please call (800) 223-8720 or (502) 493-3200.

Copyright © 1998 The Cobb Group, a division of Ziff-Davis Inc. The Cobb Group and The Cobb Group logo are trademarks of Ziff-Davis Inc. All rights reserved. Reproduction in whole or in part in any form or medium without express written permission of Ziff-Davis is prohibited.


Freeing Hard Disk Space Automatically

In the article "Making the Most of Your Hard Disk Space, Part 2" [Windows 95 Professional, January 1998], we gave you several methods you could use to regain lost hard disk space. However, you must perform many of those methods on a regular basis to be effective. In this article, we'll discuss how to perform those procedures more conveniently.

The basics

One of the best ways to automate a task is to build a batch file containing instructions for performing the task. Although a batch file can easily handle some of the steps in the main article, other steps involve running Windows programs such as Defrag. Fortunately, unlike its predecessors, Windows 95 allows you to launch Windows programs from a batch file. Therefore, we've created a batch file, shown in Listing A, that automates virtually every step of the task.

A word of caution

It's important to manually test each command on your system before running the batch file. In addition, since our batch file deletes files, you should back up your system before running the batch file for the first time. You should also exercise extreme caution, ensuring that all the commands run correctly on your system. As you can see, we've added a comment before every command in the batch file to identify what the command does.

In our batch file, we've used the default directory names. If you've used different directory names, you'll need to modify the batch file to compensate for each name change. You should also remove any commands that you don't feel comfortable running on your system.

The batch file

To create the batch file shown in Listing A, open Notepad, type the batch file's commands, and save it in your Windows directory as Conserve.bat. Before you can run the batch file, you'll have to create in your Windows directory a text file called Y.txt that contains nothing more than the letter Y, as shown in Figure A. Our batch file uses Y.txt to avoid prompting the user for confirmation of various functions. The batch file contains only one point that requires user intervention. Since you can't run Defrag while ScanDisk is still running, we've included a Pause command that requires user input before the batch file will continue. The user must wait until ScanDisk is finished before pressing any key to instruct the batch file to start the Defrag utility.

Figure A

The Y.txt file eliminates the need for almost all user intervention.

After you've created the batch file and Y.txt, you must close all other applications. If an application is open, the batch file may erase a temporary file that's in use.

Listing A: Conserve.bat

REM The following commands remove all files with the extensions
REM .bak, .old, .log, and .tmp from the root directory and from your
REM Windows directory. You should omit the appropriate lines if
REM you're intentionally storing backups of your system configuration
REM information in a .bak or .old file or if you're compiling a LOG
REM file that you don't want to overwrite.

Erase C:\*.bak
Erase C:\Windows\*.bak
Erase C:\*.old
Erase C:\Windows\*.old
Erase C:\*.log
Erase C:\Windows\*.log
Erase C:\*.tmp
Erase C:\Windows\*.tmp

REM The following command erases the entire contents of your
REM Windows\Temp directory. Don't execute this command unless
REM all other applications are closed.

REM The <y.txt appended to the end of the command prevents
REM the command from asking for permission to delete the files.
REM You can make the command ask for permission to delete all the
REM files in the directory by removing the <y.txt. If you want the
REM command to ask for confirmation before deleting each individual
REM file, remove <y.txt and replace it with /P .

Erase C:\Windows\Temp\*.* <y.txt

REM The next set of commands are for Internet Explorer 3 users only.
REM These commands empty your cache and history directories. If you're
REM using Internet Explorer 4 or have information in these directories
REM that you want to preserve, you should omit these commands.

REM The <y.txt appended to the end of the following commands,
REM prevents them from asking for permission to delete
REM the files. You can make the command ask for permission
REM by removing the <y.txt from the end of the commands.
REM If you want the command to ask for confirmation
REM before deleting each individual file, remove <y.txt and
REM replace it with /P .

Erase C:\Windows\Tempor~1\Cache1\*.* <y.txt
Erase C:\Windows\Tempor~1\Cache2\*.* <y.txt
Erase C:\Windows\Tempor~1\Cache3\*.* <y.txt
Erase C:\Windows\Tempor~1\Cache4\*.* <y.txt
Erase C:\Windows\History\*.* <y.txt

REM This set of commands is for Internet Explorer 4 users only.
REM These commands empty your cache and history directories. If you're
REM using Internet Explorer 3 or have information in the directories
REM that you want to preserve, you should omit those commands.
REM You'll also have to customize the directory names, since they
REM are system-specific. For information on determining the appropriate
REM directory names, see the article "Making the Most of Your Hard Disk Space,
REM Part 2" [Windows 95 Professional, January 1998].

REM The <y.txt appended to the end of the following commands
REM prevents them from asking for permission to delete the
REM files. You can make the commands ask for permission to
REM delete all the files in the directory by removing
REM <y.txt from the end of the commands. If you want the
REM commands to ask for confirmation before deleting each
REM individual file, remove <y.txt and replace it with /P .

Erase C:\Windows\Tempor~1\Ljz3akip\*.* <y.txt
Erase C:\Windows\Tempor~1\Wca56jbi\*.* <y.txt
Erase C:\Windows\Tempor~1\Epiiyh7y\*.* <y.txt
Erase C:\Windows\Tempor~1\Ckzus002\*.* <y.txt
Erase C:\Windows\History\*.* <y.txt

REM The next command runs ScanDisk and repairs any damaged files on
REM your hard disk. There are several switches you can use with this
REM command. These switches include
REM DRIVE   Specifies the hard disk to check
REM /A      Scans all nonremovable hard disks
REM /N      Starts and closes ScanDisk automatically
REM /P      Prevents ScanDisk from correcting errors that it encounters

ScanDisk /A /N

REM The Pause command that follows forces the batch file to wait until the user
REM presses any key before continuing with the next command. Otherwise, the batch
REM file will attempt to start Defrag while ScanDisk is still running. Since ScanDisk
REM and Defrag can't run simultaneously, you must wait until ScanDisk
REM finishes before pressing a key to launch Defrag.

Pause

REM The following command tells Defrag to defragment your hard disk.
REM There are several switches you can use with this command. These
REM switches include
REM DRIVE  Specifies which hard disk to defragment
REM /ALL       Defragments all nonremovable hard disks
REM /F              Defragments files and free disk space
REM /U              Defragments files only
REM /Q              Defragments free space only
REM /CONCISE     Hides details during defragmentation
REM /DETAILED    Displays details during defragmentation
REM /NOPROMPT    Makes Defrag run automatically with no required user intervention.

Defrag /ALL /F /DETAILED / NOPROMPT

Conclusion

Instead of manually running through the procedures we outlined in "Making the Most of Your Hard Disk Space, Part 2, " you can easily create a batch file to do routine hard disk cleaning. In this article, we've shown you how to create such a batch file.

-----------------------------

This article is reproduced from the January 1998 issue of Windows 95 Professional. Windows 95 Professional is an independently produced publication of The Cobb Group. No part of this article may be used or reproduced in any fashion (except in brief quotations used in critical articles and reviews) without prior consent of The Cobb Group. To contact The Cobb Group, please call (800) 223-8720 or (502) 493-3200.

Copyright © 1998 The Cobb Group, a division of Ziff-Davis Inc. The Cobb Group and The Cobb Group logo are trademarks of Ziff-Davis Inc. All rights reserved. Reproduction in whole or in part in any form or medium without express written permission of Ziff-Davis is prohibited.


Topic Not Found

No matching topic was found. Some possible reasons and remedies are as follows:

  • The file (.chm) that contains the destination topic is not in the expected location.

    • Remedy: Rerun the MSDN Library Setup.

  • The file (.chm) that contains the current topic or destination topic is out-of-date and the link is no longer valid.

    • Remedy: Install the latest version of the MSDN Library.

  • The MSDN Library CD is not in the CD-ROM drive.

    • Remedy: Insert the MSDN Library CD into the CD-ROM drive.