Jeff Webb, Mike McKelvy, Ronald Martinsen, Taylor Maxwell, Michael Regelski September 1995 Special Edition Using Visual Basic 4 - Chapter 14 1-56529-998-1 Computer Programming computer programming Visual Basic OLE database applications ODBC VB VBA API This book is an all-in-one reference that provides extensive coverage of every topic and technique for creating optimized and customized applications with Visual Basic.

Chapter 14

OLE Container Programming Techniques


Visual Basic provides some powerful OLE features, but those features alone aren't enough to create applications that fulfill the entire vision of OLE. This chapter tells you how to extend Visual Basic so that you can create OLE applications that are document-centric. In this chapter, you learn how to do the following:

Creating Document-Centric Applications

Most OLE container applications are document-centric. That is, the user uses the document to load the application. This approach becomes increasingly important in Windows 95, where documents are often displayed in the Program Manager. Figure 14.1 shows an example of how a user activates and uses a document-centric application.

Fig. 14.1

Document-centric applications let the user think about content rather than the process of using a computer.

Although document-centric applications make life easier for computer users, they complicate things for us programmers. In order to create an application that is document-centric, you must consider the following programming tasks:

The rest of this chapter explains each of these tasks in greater detail. Remember, you can use any of these techniques to create applications that aren't document-centric. But when you combine these techniques, you're probably working on the type of OLE application that Bill Gates envisions as the future of computing.

The Big Tradeoff: Memory versus Performance

An OLE container application can use some very big chunks of code. For instance, editing a single Excel worksheet requires the entire Excel application to be loaded in memory. If Excel isn't in memory already, the user must wait while it is loaded (see fig 14.2).

Fig. 14.2

Small user actions can have big effects! Activating an Excel worksheet loads the entire Excel application in memory.

You can reduce the delay by loading the Excel application when your application loads. However, your target system must be able to keep all the components you need in physical memory at once. Otherwise, performance will degrade as Windows uses virtual memory by swapping to disk (see fig. 14.3).

Fig. 14.3

When physical memory fills up, Windows starts using virtual memory on disk. Physical memory is much faster, since it doesn't require disk access.

Determining Hardware Requirements

To balance performance against memory requirements, you need to know the hardware configuration on which your application will be run. In a corporate environment, it's a good idea to set up a couple of test machines to help you tune your application. Table 14.1 provides some guidelines for hardware requirements based on the type of OLE applications you are using.

Table 14.1 Estimated Minimum Hardware Guidelines for OLE Container Applications Written in Visual Basic

Item Minimum Processor Minimum Physical Memory (M)
.OCX 486 8
Jet data objects 486 8
Small OLE object (Paint, MS Graph) 486 8
One large OLE object (Excel worksheet) 486 12
Two large OLE objects (Excel worksheet, Word document) 486 16
The kitchen sink Pentium 16

Table 14.1 shows minimum requirements based on very subjective criteria—performance as perceived by someone who's just downed his third cup of coffee. OLE applications will run in leaner configurations than those shown in table 14.1, but they won't run very fast.

Table 14.1 assumes that you are running your application under Windows 95. Windows 3.x has similar requirements, although you might be able to squeak by with less memory in a minimum configuration. If you are running under Windows NT, add 4–8M to each minimum physical memory guideline in table 14.1.

Checking Hardware Configurations at Run Time

Ideally, you should test your application on an average configuration and on a "worst-case" hardware configuration. Don't make all your decisions based on the worst-case scenario, however. Instead, create a fall-back strategy for machines that don't have adequate processors or memory. The following code sets a global flag if the user's hardware doesn't meet some minimum requirements:

' Global flags section.
Public gDisableAdvanced As Boolean
Private Sub Form_Load()
Dim System As Object
Set System = CreateObject("WinAPI.Information")
If System.MeetsCriteria(486, 8) = False Then
' Set flag to disable some features.
gbDisableAdvanced = True
' Notify user.
MsgBox ("Your computer does not meet the minimum " & _
"requirements of 8 megabytes memory and 486 or " & _
"later processor. Some features have been disabled.")
Else
gbDisableAdvanced = False
End If

End SubThe preceding code uses the method MeetsCriteria to test a hardware configuration at run time. The MeetsCriteria method in listing 14.1 uses Windows API functions to get system information. The MeetsCriteria method is part of the SYSTEM.VBP OLE server found on the companion CD. Chapter 21, "Advanced OLE Programming Techniques," describes SYSTEM.VBP more completely.

Listing 14.1 Testing Hardware Configuration (SYSTEM.VBP)

#If Win16 Then
Private Declare Function GlobalCompact Lib "Kernel" _
(ByVal dwMinFree As Long) As Long
Private Declare Function GetWinFlags Lib "Kernel" () As Long
Const WF_CPU286 = &H2
Const WF_CPU386 = &H4
Const WF_CPU486 = &H8
#Else
Private Declare Sub GetSystemInfo Lib "kernel32" _
(lpSystemInfo As SYSTEM_INFO)
Private Declare Sub GlobalMemoryStatus Lib "kernel32" _
(lpBuffer As MEMORYSTATUS)
' Type declaration for system information.
Private Type SYSTEM_INFO
dwOemId As Long
dwPageSize As Long
lpMinimumApplicationAddress As Long
lpMaximumApplicationAddress As Long
dwActiveProcessorMask As Long
dwNumberOfProcessors As Long
dwProcessorType As Long
dwAllocationGranularity As Long
dwReserved As Long
End Type
' Type declaration for system information.
Private Type MEMORYSTATUS
dwLength As Long ' sizeof(MEMORYSTATUS)
dwMemoryLoad As Long ' percent of memory in use
dwTotalPhys As Long ' bytes of physical memory
dwAvailPhys As Long ' free physical memory bytes
dwTotalPageFile As Long ' bytes of paging file
dwAvailPageFile As Long ' free bytes of paging file
dwTotalVirtual As Long ' user bytes of address space
dwAvailVirtual As Long ' free user bytes
End Type
#End If
' Checks if a system meets processor and memory
' hardware requirements.
' iProcessor is a three-digit number: 286, 386, or 486
' iMemory is the number of megabytes of physical memory required.
Public Function MeetsCriteria(iProcessor As Integer, _
iMemory As Integer) As Boolean
Dim iAvailableMemory As Integer, lWinFlags As Long
Dim bProcessor As Boolean
#If Win16 Then
lWinFlags = GetWinFlags()
#Else
Dim SysInfo As SYSTEM_INFO
GetSystemInfo SysInfo
lWinFlags = SysInfo.dwProcessorType
#End If
Select Case iProcessor
Case 286
' Windows 3.1 won't run on earlier machines, so True.
bProcessor = True
Case 386
' If meets critieria, set to True.
#If Win16 Then
If lWinFlags >= WF_CPU386 Then bProcessor = True
#Else
If lWinFlags >= 386 Then bProcessor = True
#End If
Case 486
#If Win16 Then
If lWinFlags And WF_CPU486 Then bProcessor = True
#Else
If lWinFlags >= 486 Then bProcessor = True
#End If
Case 586
#If Win16 Then
' There is no test for 586 under Win16,
' so test for 486 -- probably
' better than returning an error.
If lWinFlags And WF_CPU486 Then bProcessor = True
#Else
If lWinFlags >= 586 Then bProcessor = True
#End If
End Select
' Win16 and Win32 have different ways of getting
' available physical memory.
#If Win16 Then
' Get available physical memory.
iAvailableMemory = GlobalCompact(0) _
/ (1024000)
#Else
Dim MemStatus As MEMORYSTATUS
GlobalMemoryStatus MemStatus
iAvailableMemory = MemStatus.dwTotalPhys / (1024000)
#End If
' Combine results of two tests: True And True = True.
MeetsCriteria = bProcessor And iAvailableMemory >= iMemory
End Function

Loading Object Applications at Startup

To reduce the amount of time it takes to activate an OLE object for editing, load the object's application at startup. For example, the following code loads the Excel application if it is not already running:

Private Sub Form_Load()
Dim System As Object
Set System = CreateObject("WinAPI.Information")
If System.IsRunning("Microsoft Excel") = False Then
' Start Excel minimized.
Shell "c:\excel\excel.exe", 6
End If
End Sub

The preceding code uses the IsRunning method to determine whether Microsoft Excel is running. The IsRunning method in listing 14.2 uses Windows API functions to check whether the application is loaded.

Listing 14.2 Checking Whether an Application Is Running (SYSTEM.VBP)

#If Win16 Then
Private Declare Function GetNextWindow Lib "User" _
(ByVal hWnd As Integer, ByVal wFlag As Integer) As Integer
Private Declare Function GetActiveWindow Lib "User" () As Integer
Private Declare Function GetWindowText Lib "User" _
(ByVal hWnd As Integer, ByVal lpString As String, _
ByVal aint As Integer) As Integer
Private Declare Function APIFindWindow Lib "User" _
Alias "FindWindow" _
(ByVal lpClassName As Any, ByVal lpWindowName _
As Any) As Integer
#Else
Private Declare Function GetNextWindow Lib "user32" _
Alias "GetNextQueueWindow" _
(ByVal hWnd As Long, ByVal wFlag As Integer) _
As Long
Private Declare Function GetActiveWindow Lib "user32" _
() As Long
Private Declare Function GetWindowText Lib "user32" _
Alias "GetWindowTextA" (ByVal hWnd As Long, _
ByVal lpString As String, ByVal cch As Long) _
As Long
Private Declare Function APIFindWindow Lib "user32" _
Alias "FindWindowA" (ByVal lpClassName As String, _
ByVal lpWindowName As String) As Long
#End If
Const GW_HWNDNEXT = 2
Const SW_SHOW = 5
Public Function IsRunning(strAppName) As Boolean
#If Win16 Then
Dim hWnd As Integer, hWndStop As Long, hWndNext _
As Integer, iLen As Integer
#Else
Dim hWnd As Long, hWndStop As Long, hWndNext _
As Long, iLen As Long
#End If
Dim strTitle As String * 80
' Get a handle to the active window (first in task list).
hWnd = GetActiveWindow()
hWndStop = hWnd
' Loop until you reach the end of the list.
Do
' Get the next window handle.
hWndNext = GetNextWindow(hWnd, GW_HWNDNEXT)
' Get the text from the window's caption.
iLen = GetWindowText(hWndNext, strTitle, Len(strTitle))
If iLen Then
' If found, return True.
If InStr(strTitle, strAppName) Then
IsRunning = True
Exit Function
End If
End If
hWnd = hWndNext
Loop Until hWnd = hWndStop
' Not found, so return False.
IsRunning = False
End Function

The OLE control's AppIsRunning property returns True only if the object's application is loaded and the object is active for editing. The IsRunning method in SYSTEM.VBP tells you whether or not an application is running, regardless of the OLE control's state.

Loading Object Applications on Demand

By default Visual Basic loads an OLE object's application when the object is activated for editing. The OLE control's AutoActivate property controls how the user activates the object. For information on the AutoActivate property, see "Controlling Object Activation" in Chapter 13.

If the object's application is already running, Visual Basic simply increments the Windows reference count to the running application. Visual Basic deincrements the Windows reference count for an object's application when the form containing the object is closed. The object's application continues running until the user closes the application manually, or until the reference count goes to zero. Figure 14.4 shows how OLE loads and unloads an OLE object's application used on a Visual Basic form.

Fig. 14.4

OLE controls when an object's application starts and quits.

Table 14.2 describes the steps shown in figure 14.4.

Table 14.2 How OLE Loads,Activates, and Deactivates OLE Object Applications

Step User Action OLE Action Object's Application Reference Count
1. Activates object on form. OLE loads object's application if it is not already running. References + 1
2. None. OLE object's application loads. References + 1 (if application is visible)
3. User deactivates object. Updates object, closes object. No change.
4. Form unloads. Decrement reference count. References – 1
5. None. Close object's application. References = 0

Whether or not an object's application quits when a form unloads is determined by the reference count of that application. The application will not quit if the reference count is not zero. There are three main reasons an object's application won't close after a form unloads:

Raiders of the Lost OLE Reference

In the movie Raiders of the Lost Ark, they never explained how the Ark got lost in the first place—the point of the movie was that they wanted it back. The same thing is true of OLE references. Applications may or may not track their references correctly. When they don't, you need to find them. Lost references to objects keep the object's application loaded in memory. This consumes precious Windows resources and can affect performance or require the user to reboot his or her system.

Excel is a good example of an application that doesn't always track its references. For example, the following steps cause a "lost reference" to the Excel application, leaving it loaded in memory with no easy way to shut it down:

  1. Start Excel using the CreateObject function. The following procedure starts the Excel application invisibly and returns a reference to the OLE object for the Excel application:

    Sub CreateReference()
    Dim objExcel As Object
    Set objExcel = CreateObject("excel.application")
    End Sub

  2. Run the CreateReference procedure. The CreateObject function starts Excel and returns a reference to the object. But when the object variable objExcel loses scope at the end of the procedure, Excel remains loaded in memory.
  3. Notice that Excel does not appear on the taskbar—there is no obvious way for you to activate the Excel application so that you can shut it down!

Since you know that you just started Excel, this situation is easy to fix. To find Excel after you've lost its reference, follow these steps:

  1. Use the GetObject function to obtain a reference to the running instance of Excel.
  2. Set the returned object's Visible property to True. The following procedure finds a lost reference to the Excel application object and makes the application visible:

    Sub FindLostReference()
    Dim objExcel As Object
    Set objExcel = GetObject(, "excel.application")
    objExcel.Visible = True
    End Sub

You can avoid this specific situation by using object variables that are global in scope and by being fastidious about closing instances of Excel that you've started in your application.

However, you can't always account for the behavior of other applications or unanticipated interruptions of your own application. For these cases, you need to be able to find all hidden instances of applications running on a system; and to do that, you need to make some Windows API calls. The MakeVisible method in listing 14.3 makes all of the applications on your system visible using common window-manipulation functions from the Windows API. Listing 14.3 shows the declarations for the API functions used in the MakeVisible method.

Listing 14.3 Making Hidden Application Instances Visible (SYSTEM.VBP)

' Makes all applications visible.
Public Sub MakeVisible()
#If Win16 Then
Dim hWnd As Integer, hWndFirst As Integer, iTemp _
As Integer, iLen As Integer
#Else
Dim hWnd As Long, hWndFirst As Long, iTemp As Long, _
iLen As Long
#End If
Dim strTitle As String * 80
' Get a handle to the active window (first in task list).
hWnd = GetActiveWindow()
hWndFirst = hWnd
' Loop until you reach the end of the list.
Do
iLen = GetWindowText(hWnd, strTitle, Len(strTitle))
If iLen Then
iTemp = ShowWindow(hWnd, SW_SHOW)
End If
' Get the next window handle.
hWnd = GetNextWindow(hWnd, GW_HWNDNEXT)
Loop Until hWnd = hWndFirst
End Sub

Don't close an OLE object's application in code unless you are absolutely sure no other objects on the system have references to that application. Doing so can lose data and disrupt file integrity. It is much safer to allow the user to close an application.

Creating an OLE Storage System

By default, Visual Basic stores OLE objects in the form that contains them. For linked objects, Visual Basic stores the image of the object and the source of the object. For embedded objects, Visual Basic stores the image of the object and the object's data.

When you compile a Visual Basic form, the object's image, source, or data is written into your application's executable file. This has the affect of "freezing" the initial state of the object. Changes you make to embedded objects at run time are lost when the application closes. Changes to linked objects are preserved, but the startup image of the object doesn't reflect those changes.

As mentioned in Chapter 13, you save OLE objects to files using the SaveToFile method and read OLE files using the ReadFromFile method. The following code shows using a form's Load event procedure to update an OLE object when the form is displayed:

Option Explicit
Const OLE_FILE = "c:\foo.ole"
Dim miOLE_File As Integer
Private Sub Form_Load()
miOLE_File = FreeFile()
Open OLE_FILE For Binary As miOLE_File
oleObject.ReadFromFile miOLE_File
End Sub
This code shows using the Unload event to save changes to the OLE object in a file:
Private Sub Form_Unload(Cancel As Integer)
oleObject.SaveToFile miOLE_File
Close miOLE_File
End Sub

Since the file you use with ReadFromFile and SaveToFile is open for Binary access, you don't have to close the file between loading and saving the OLE object. Binary access files support read/write access.

Storing Multiple Objects in a Single File

The Load and Unload event procedures in the preceding section support access to only one OLE object per file. To store more than one object in a single file, repeat the SaveToFile method for each OLE object in your application. The following cmdSave event procedure shows how to save multiple OLE objects in a control array to a single file:

Private Sub cmdSave_Click()
' Get a file number.
iFileNum = FreeFile()
' Open the file for Binary access.
Open "NEW.OLE" For Binary As iFileNum
' For each object in the oleObjects control array
For Each oleControl In oleObjects
' Save the object to the open file.
oleControl.SaveToFile iFileNum
Next oleControl
' Close the file when done.
Close iFileNum
End Sub

The ReadFromFile and SaveToFile methods don't correctly reposition the file pointer, so the Loc function is useless when saving or loading OLE objects.

Loading Multiple Objects from a Single File

The process of loading OLE objects from an existing file is similar to that of saving them as described in the preceding section. Use the OLE object's ReadFromFile method, as shown in the following cmdLoad_Click event procedure:

Private Sub cmdLoad_Click()
' Get a file number.
iFileNum = FreeFile()
' Open the file for Binary access.
Open "NEW.OLE" For Binary As iFileNum
' For each object in the oleObjects control array
For Each oleControl In oleObjects
' Save the object to the open file.
oleControl.ReadFromFile iFileNum
Next oleControl
' Close the file when done.
Close iFileNum
End Sub

The preceding code is simple enough, but it makes one major assumption: that the number of OLE objects contained in the file matches the number of controls in the oleObjects control array. To load an unknown number objects from a file, you must add a loop that contains the ReadFromFile method and poll for error number 31037. Listing 14.4 shows how to load all the objects from a file.

Listing 14.4 Loading All the Objects from a File (STORE.VBP)

Private Sub mnuOpen_Click()
' Get a file name.
dlgFile.ShowOpen
' If the file name if valid, open it.
If Len(Dir(dlgFile.filename)) Then
mstrFileName = dlgFile.filename
' Get a file number.
iFileNum = FreeFile()
' Open a file to read.
Open mstrFileName For Binary As iFileNum
' Update the form caption.
Me.Caption = mstrFileName
' Enable the Save menu.
mnuSave.Enabled = True
Else
' Do nothing.
Exit Sub
End If
' Clear the existing OLE control array.
For Each oleControl In oleObjects
' Keep the control with Index = 0.
If oleControl.Index Then
Unload oleControl
End If
Next oleControl
' Turn on error handling.
On Error GoTo cmdLoad_Err
' Repeat until an error indicating you've reached
' the last object in the file, which causes an error.
Do
' Load an object into the control.
oleObjects(Index).ReadFromFile iFileNum
' Increment the count.
Index = Index + 1
' Create another control in the control array.
Load oleObjects(Index)
' Make the object visible
oleObjects(Index).Visible = True
' Reposition each object.
oleObjects(Index).TOP = oleObjects(Index - 1).TOP _
+ oleObjects(Index - 1).Height
Loop
cmdLoad_Err:
If Err = 31037 Then
' Unload the last control since it's empty.
Unload oleObjects(Index)
' Update the display.
Me.Refresh
' Close the file
Close iFileNum
' Update the number of objects.
mIndex = Index
Exit Sub
Else
MsgBox "An unhandled error occured: " & _
Err & Error
End If
End Sub

Listing 14.4 uses a rather simple system for positioning OLE objects on a form—it simply lays them out end to end. Ideally, users should be able to drag objects to new positions and have the program record those positions in the file. This gets tricky, because you must store dissimilar data in the same file. It's even further complicated by the fact that ReadFromFile and SaveToFile don't reposition the file pointer. There is no easy way to tell where in a file OLE objects begin and end. The easiest solution to this problem is to store position information in a separate, random-access file.

Maintaining the System Registry

Applications use the Registry to store the following information:

You can edit the system registry directly using the Registry Editor (see fig. 14.5). To run the Registry Editor, use this command line:

regedit

Fig. 14.5

The Windows 95 Registry Editor displays a hierarchical list of the system registry.

When running the Registration Info Editor under Windows 3.x, you must use the /v switch if you want to see all levels of detail.

The Structure of the System Registry

The data in the system registry is hierarchical..Below HKEY_CLASSES_ROOT are all the entries for registered applications and file types. Below them are sub-entries for the types of actions each application supports. These entries and sub-entries are referred to as registration keys. Most keys have a setting, as shown by the system registry entries for the Paint accessory included with Windows 95 (see listing 14.5).

Listing 14.5 Registry Entries for Paint (PAINT.REG)

REGEDIT4
*****************************************************************
Register file types for the Paint application.
[HKEY_CLASSES_ROOT\.bmp]
@="Paint.Picture"
[HKEY_CLASSES_ROOT\.bmp\ShellNew]
"NullFile"=""
[HKEY_CLASSES_ROOT\.pcx]
@="Paint.Picture"
*****************************************************************
Register the command lines that Windows uses for opening and
printing Paint documents.
[HKEY_CLASSES_ROOT\Paint.Picture]
@="Bitmap Image"
[HKEY_CLASSES_ROOT\Paint.Picture\shell]
[HKEY_CLASSES_ROOT\Paint.Picture\shell\open]
[HKEY_CLASSES_ROOT\Paint.Picture\shell\open\command]
@="\"C:\\PROGRA~1\\ACCESS~1\\MSPAINT.EXE\" \"%1\""
[HKEY_CLASSES_ROOT\Paint.Picture\shell\print]
[HKEY_CLASSES_ROOT\Paint.Picture\shell\print\command]
@="\"C:\\PROGRA~1\\ACCESS~1\\MSPAINT.EXE\" /p \"%1\""
[HKEY_CLASSES_ROOT\Paint.Picture\shell\printto]
[HKEY_CLASSES_ROOT\Paint.Picture\shell\printto\command]
@="\"C:\\PROGRA~1\\ACCESS~1\\MSPAINT.EXE\" /pt \"%1\" \
"%2\" \"%3\" \"%4\""
[HKEY_CLASSES_ROOT\Paint.Picture\DefaultIcon]
@="C:\\Progra~1\\Access~1\\MSPAINT.EXE,1"
[HKEY_CLASSES_ROOT\Paint.Picture\Insertable]
@=""
[HKEY_CLASSES_ROOT\Paint.Picture\protocol]
[HKEY_CLASSES_ROOT\Paint.Picture\protocol\StdFileEditing]
[HKEY_CLASSES_ROOT\Paint.Picture\protocol\StdFileEditing\verb]
[HKEY_CLASSES_ROOT\Paint.Picture\protocol\StdFileEditing\verb\0]
@="&Edit"
[HKEY_CLASSES_ROOT\Paint.Picture\protocol\StdFileEditing\server]
@="C:\\PROGRA~1\\ACCESS~1\\MSPAINT.EXE"
[HKEY_CLASSES_ROOT\Paint.Picture\CLSID]
@="{D3E34B21-9D75-101A-8C3D-00AA001A1652}"
*****************************************************************
Register the class ID for the application.
[HKEY_CLASSES_ROOT\CLSID\{0003000A-0000-0000-C000-000000000046}]
@="Paintbrush Picture"
[HKEY_CLASSES_ROOT\CLSID\{0003000A-0000-0000-C000-000000000046}\
TreatAs]@="{D3E34B21-9D75-101A-8C3D-00AA001A1652}"
[HKEY_CLASSES_ROOT\CLSID\{0003000A-0000-0000-C000-000000000046}
\Ole1Class]@="PBrush"
[HKEY_CLASSES_ROOT\CLSID\{0003000A-0000-0000-C000-000000000046}
\ProgID]@="PBrush"
(other registration entries omitted here)

Because you can't create all types of OLE applications using Visual Basic, you don't need to worry about all the different keys that can appear for each application. In fact, listing 14.5 omits many lines that register features that are either handled automatically or are unavailable through Visual Basic. The keys that are of interest to Visual Basic programmers are shown in table 14.3.

Table 14.3 Registry Keys

Key Example and Description
ProgID [HKEY_CLASSES_ROOT\.bmp]
@="Paint.Picture"

Identifies the application within the system registry. The programmatic ID Paint.Picture is used in all the subsequent keys. The setting for ProgID is the text that appears in the standard OLE Insert Object dialog box.
FileType [HKEY_CLASSES_ROOT\.bmp]
@="Paint.Picture"

Associates a file type with the ProgID. For instance, double-clicking on a .BMP file in Windows loads the file in Paint.
CLSID [HKEY_CLASSES_ROOT\Paint.Picture\CLSID]
@="{D3E34B21-9D75-101A-8C3D-00AA001A1652}"

Identifies the application to OLE. OLE uses the class ID (the very long number in braces) internally when starting an OLE object's application.
protocol Identifies the OLE services that the application supports.
StdFileEditing Identifies the OLE file editing services that the application supports.
server [HKEY_CLASSES_ROOT\Paint.Picture\protocol\StdFileEditing\server]
@="C:\\PROGRA~1\\ACCESS~1\\MSPAINT.EXE"

Identifies the application's .EXE file to use as the server for OLE editing services.
verb [HKEY_CLASSES_ROOT\Paint.Picture\protocol\StdFileEditing\verb\0]
@="&Edit"

Identifies the OLE verbs the application supports. These are the verbs listed in the object's pop-up menu when the OLE object is right-clicked.
shell Lists the Windows shell services that the application supports. Shell services include the Windows Explorer and Desktop.
open\command [HKEY_CLASSES_ROOT\Paint.Picture\shell\open\command]
@="\"C:\\PROGRA~1\\ACCESS~1\\MSPAINT.EXE\" \"%1\""
print\command [HKEY_CLASSES_ROOT\Paint.Picture\shell\print\command]
@="\"C:\\PROGRA~1\\ACCESS~1\\MSPAINT.EXE\" /p \"%1\""

Provides the command line Windows uses when the Explorer prints a file with a file type associated with this application's programmatic identifier (see the FileType key in this table).

Changing Registry Entries and Seeing the Effects

You can edit system registry entries directly using the Registry Editor, then save those entries to a separate .REG file. This is useful when creating .REG files for your own applications.

To use the Registry Editor to change the value of an entry, follow these steps:

  1. Select an item to edit by clicking on it in the list box.
  2. Type the new key value in the Value text box.

To use the Registry Editor to save an entry to an .REG file, follow these steps:

  1. Select an item to save by clicking on it in the list box. The Registry Editor saves all the entries subordinate to the entry that you select. To save the entire database to a .REG file, select the root entry, \, as shown in figure 14.6.

Fig. 14.6

Select the root entry to save the entire Registry to a file.

  1. Choose Registry, Export Registration File. The Registry Editor displays the Export Registry File dialog box (see fig 14.7).

Fig. 14.7

Registration files use the .REG file type by convention.

  1. Type the name of the file to create and click OK. The Registry Editor saves the entry and all its subordinate entries in a text format file.

Deleting Registry Entries

You might want to delete entries manually from your Registry after you delete an OLE application from your system. The old entries don't do any direct harm, but they can clutter up OLE dialog boxes with invalid entries. To use the Registry Editor to delete a key, follow these steps:

  1. Select the item to delete from the Registration Info Editor list box.
  2. Choose Edit, Delete.

Registering Your Application

Under Windows 3.x, you ran most Visual Basic applications by double-clicking on the application's icon in the Windows Program Manager. You can run document-centric applications by double-clicking on an icon on the Windows 95 desktop or on the file name in the Windows Explorer (see fig. 14.8).

Fig. 14.8

Document-centric applications can start directly from the documents that they create.

You should register your application in the system registry if you want your users to be able to open application documents directly through the Windows Desktop or Explorer.

There are currently two system registration tasks that are of interest to Visual Basic programmers:

The following sections describe these tasks in greater detail.

Registering a File Type for Your Application

The Registry contains FileType keys that identify specific file extensions as belonging to specific applications. These keys enable the Windows shell functions to start to load the file when the user double-clicks on the file name in the Explorer or a shortcut for the file on the Windows Desktop.

To associate a file type with your application, follow these steps:

  1. Create a registration file for your application. The following file registers STORE.EXE and creates associations for loading and printing the file:

    REGEDIT4
    *****************************************************************
    Register file types for the STORE application.
    [HKEY_CLASSES_ROOT\.ole]
    @="Store.Application"
    [HKEY_CLASSES_ROOT\.cdoc\ShellNew]
    "NullFile"=""
    *****************************************************************
    Register the command lines that Windows uses for opening and
    printing STORE documents.
    [HKEY_CLASSES_ROOT\Store.Application]
    @="OLE Storage Demo"
    [HKEY_CLASSES_ROOT\Store.Application\shell]
    [HKEY_CLASSES_ROOT\Store.Application\shell\open]
    [HKEY_CLASSES_ROOT\Store.Application\shell\open\command]
    @="\"C:\\VBBOOK\\SAMPLES\\CH14\\STORE.EXE\" \"%1\""
    [HKEY_CLASSES_ROOT\Store.Application\shell\print]
    [HKEY_CLASSES_ROOT\Store.Application\shell\print\command]
    @="\"C:\\VBBOOK\\SAMPLES\\CH14\\STORE.EXE\" /p \"%1\""
    [HKEY_CLASSES_ROOT\Store.Application\shell\printto]
    [HKEY_CLASSES_ROOT\Store.Application\shell\printto\command]
    @="\"C:\\VBBOOK\\SAMPLES\\CH14\\STORE.EXE\" /pt
    \"%1\" \"%2\" \"%3\" \"%4\""
    [HKEY_CLASSES_ROOT\Store.Application\DefaultIcon]
    @="C:\\VBBOOK\\SAMPLES\\CH14\\STORE.EXE,1"

  2. Merge the registration file. The following command line merges the file STORE.REG with the system registry. The -s switch merges the changes silently—no confirmation dialog box is shown.

    regedit -s store.reg

  3. After merging the file, you can view the changes using the Registry Editor (see fig. 14.9).

Fig. 14.9

The Registry entries for STORE.REG.

Registering Document Icons

Registering a file type for your application causes Windows to use your application's default icon for program items created from files with the specified type (see fig. 14.10).

Fig. 14.10

When you add a program item with the file type .OLE, Windows 95 displays it with the icon from STORE.EXE.

Visual Basic applications have always used the first icon stored in the executable file as their document icon. You can't use other icons stored in the executable as document icons.

Check Registry Entries at Startup

If your application has an entry in the system registry, it should check its registered entries at startup to verify that the executable's file path has not changed since the application was last run. If the executable's current file path does not match the registered file path, the application should update its registration.

Visual Basic does not provide built-in support for checking system registry entries at startup, so you must use Windows API functions. Table 14.4 lists the functions Windows provides to help you maintain the system registry.

Table 14.4 Windows System Registry Functions

Function Use
RegCloseKey Closes a key after opening it with RegOpenKey.
RegCreateKeyA Creates a new key.
RegDeleteKey Deletes an existing key.
RegEnumKey Gets the next subkey of a specified key.
RegOpenKey Opens a key.
RegQueryValueA Retrieves the value setting of a key as a text string.
RegSetValueA Sets the value of a key.

There are many more registration API functions than are shown in table 14.4. The ones listed in the table are compatible with Windows 3.x and provide the capability to check and update Registry entries as discussed in this chapter.

To check a registered key in the system registry, follow these steps:

  1. Use RegOpenKeyA to open the registration key that contains path and file name information, such as store.application\shell\open.
  2. Use RegQueryValueA to return the value of the subentry containing path and file name information.
  3. Compare the returned value to the application's current path and file name. If the two values don't match, use RegSetValueA to change the registered value of the subentry.
  4. Use RegCloseKey to close the registration key opened in step 1.

The CheckRegistrationEntry procedure in listing 14.6 shows the declarations for the system registry Windows API functions, and demonstrates how to check the registry entry for the STORE.VBP application found on the companion CD.

Listing 14.6 Checking Registry Entries at Startup (STORE.VBP)

Option Explicit
' Registry APIs used to check entry.
#If Win16 Then
Declare Function RegOpenKey Lib "Shell" _
(ByVal HKeyIn As Long, _
ByVal LPCSTR As String, _
HKeyOut As Long) _
As Long
Declare Function RegCloseKey Lib "Shell" _
(ByVal HKeyIn As Long) _
As Long
Declare Function RegQueryValue Lib "Shell" _
(ByVal HKeyIn As Long, _
ByVal SubKey As String, _
ByVal KeyValue As String, _
KeyValueLen As Long) _
As Long
Declare Function RegSetValue Lib "Shell" _
(ByVal HKeyIn As Long, _
ByVal SubKey As String, _
ByVal lType As Long, _
ByVal strNewValue As String, _
ByVal lIngnored As Long) _
As Long
Declare Sub RegDeleteKey Lib "Shell" _
(ByVal HKeyIn As Long, _
ByVal SubKeyName As String)
#Else
Declare Function RegOpenKey Lib "advapi32" _
Alias "RegOpenKeyA" _
(ByVal HKeyIn As Long, _
ByVal LPCSTR As String, _
HKeyOut As Long) _
As Long
Declare Function RegOpenKeyEx Lib "advapi32" _
Alias "RegOpenKeyExA" _
(ByVal HKeyIn As Long, ByVal LPCSTR _
As String, ByVal dwRes _
As Long, ByVal dwAccess _
As Long, HKeyOut As _
Long) As Long _
Declare Function RegCloseKey Lib "advapi32" _
(ByVal HKeyIn As Long) _
As Long
Declare Function RegQueryValue Lib "advapi32" _
Alias "RegQueryValueA" _
(ByVal HKeyIn As Long, _
ByVal SubKey As String, _
ByVal KeyValue As String, _
KeyValueLen As Long) _
As Long
Declare Function RegSetValue Lib "advapi32" _
Alias "RegSetValueA" _
(ByVal HKeyIn As Long, _
ByVal SubKey As String, _
ByVal lType As Long, _
ByVal strNewValue As String, _
ByVal lIngnored As Long) _
As Long
Declare Function RegDeleteKey Lib "advapi32" _
Alias "RegDeleteKeyA" _
(ByVal HKeyIn As Long, _
ByVal SubKeyName As String) _
As Long
#End If
#If Win16 Then
Const HKEY_CLASSES_ROOT = &H1
#Else
Const HKEY_CLASSES_ROOT = &H80000000
Const HKEY_CURRENT_USER = &H80000001
Const HKEY_LOCAL_MACHINE = &H80000002
Const HKEY_USERS = &H80000003
Const HKEY_PERFORMANCE_DATA = &H80000004
#End If
Public Const ERROR_SUCCESS = 0
Sub CheckRegistrationEntry(strSearchKey As String)
Dim hkroot As Long, lError As Long, lLen As Long
Dim strKeyID As String, strKeyDesc As String
Dim strAppName As String
' Get current application path and file name.
strAppName = Chr(34) & App.Path & "\" & App.EXEName & _
".EXE" & _
Chr(34) & Chr(32) & Chr(34) & "%1" & Chr(34)
lLen = 255
' Specify subentry value to check.
strKeyID = "command"
' Initialize key description (value returned by RegQueryValue).
strKeyDesc = String(lLen, 0)
' Get the registry entry for the Open key.
lError = RegOpenKey(HKEY_CLASSES_ROOT, strSearchKey & _
"\shell\open", hkroot)
' Get the value of the entry.
lError = RegQueryValue(hkroot, strKeyID, strKeyDesc, lLen)
' If RegOpenKey or RegQueryValue return an error,
' display a message and end.
If lError Then
MsgBox "Couldn't find registry entry. Please reinstall" & _
"the application."
End
End If
' Check the value against the current installation.
If Left(strKeyDesc, lLen - 1) <> strAppName Then
' If it doesn't match, change the registered value.
lError = RegSetValue(hkroot, strKeyID, 1, strAppName, 0)
End If
' If RegOpenKey or RegQueryValue return an error,
' display a message and end.
If lError Then
MsgBox "Couldn't update registry entry."
End
End If
' Close the registration key.
lError = RegCloseKey(hkroot)
End Sub

From Here...

For more information on OLE container applications, see the following chapters:


© 1996, QUE Corporation, an imprint of Macmillan Publishing USA, a Simon and Schuster Company.