Visual Basic

Using a ROOS

Another aid to reusability, first mentioned in Chapter 1, is the ROOS (Resource Only Object Server), pronounced "ruse." We've referred to object servers as object components for most of this chapter, but these are two different names for the same object. (To be politically correct, they should really be called ActiveX components, but ROAC is not as easy to pronounce!) A ROOS essentially stores string, bitmap, and other resources that are liable to be changed at some time. Another use for a ROOS is to store multilanguage strings. If you wrote an application to be sold in the United States and in France, using the normal Visual Basic method of setting display text in code would mean that you would have to create two versions of the application: one with English display text and captions and one with French. Obviously, this would create a significant maintenance overhead, because if you have to apply a bug fix to one of the versions, you also need to apply the change to the other. The ROOS is exactly the same in principle as a resource-only DLL as used by many C and C++ programmers. The difference between a ROOS and its DLL counterpart is that the ROOS is an object component and as such can be deployed anywhere on a network and used by any client components.

You can store many types of resources in a ROOS:

Accelerator table Group cursor
Bitmap resource Group icon
Cursor resource Icon resource
Dialog box Menu resource
Font directory resource String resource
Font Resource User-defined resource

A ROOS has two components. The first is the resource module, a special file created with an application such as Microsoft Visual C++. The resource module contains all the resources you want to store and retrieve. The second element of the ROOS is a method to retrieve a resource from the resource module. At TMS, we prefer to expand the functionality of the ROOS methods so that string values can be parsed with input parameters. The following example illustrates this.

Resource Entries

String ID:    400
  String value: "The operation completed with % errors"

Client Code

StringID = 400
  MyText = GetStringFromROOS(StringID, "no")


Public Function GetStringFromROOS(StringID As String, _
      Param) As String
      Dim sText  As String
      sText = GetString(StringID)
      sText = MergeString(sText, Param)
      GetStringFromROOS = sText
  End Function


MyText: "The operation completed with no errors"

Many projects store custom error messages or other display text in a database file. In an error handler, the custom error text is better in a ROOS because the execution speed is much faster, and many organizations restrict access to make database changes to the database administrator-no good if you're a programmer and have to wait two days to change the caption on a button! Another excellent use of a ROOS is to store icons and bitmaps. Imagine you're lucky enough to have an artist to create all your graphics. You can create a ROOS with dummy resources, and then the artist can add the real graphics to the ROOS as they become available without having to access any source code. (No more multiple access problems!)

Creating a resource module is easy if you have the right tools. You simply enter the resources you want. Each resource has an ID value, which is a long integer. To retrieve the resource from the resource module, you simply use the LoadResData, LoadResPicture, or LoadResString command specifying the resource's ID. Figure 14-12 shows a typical resource file in Microsoft Visual C++ 6. Once the resource module is created (it's actually an RC file), you simply compile it with the RC.EXE program (supplied on the Visual Basic CD-ROM) to create a RES file that you can add to your ROOS project. You can have only one RES file in a single Visual Basic project, but one is plenty! (If you don't have access to Visual C++ or any other tool for creating resource files, you can use an editor such as Notepad. Before attempting this, however, you should study an RC file and a RESOURCE.H file to become familiar with the format.)

Obviously, any client requesting data from the ROOS will need to know the ID value for each resource. In Visual Basic 4, you would need to include your ID constants in each client application, either as hard-coded constants or in a shared type library. With Visual Basic 5 and 6, you can declare all your IDs within the ROOS as enumerated constants, which makes them automatically available to client applications.

Listing 14-1 shows a slightly more advanced ROOS that retrieves string and bitmap resources. The ROOS allows you to merge an unlimited number of tokens into a string resource. To create a string resource with tokens, simply insert a % symbol in the string where the supplied parameter(s) will be substituted.

Figure 14-12 A resource file created in Microsoft Visual C++ 6

Listing 14-1 ROOS for retrieving string and bitmap resources

' The following Enums declare the resource ID of the bitmaps
  ' in our RES file. The include file "resource.h" generated
  ' by the resource editor defines the constants to match each
  ' bitmap. Checking this file shows the first bitmap resource
  ' ID to be 101; therefore, these Enums are declared to match
  ' this.
  Public Enum BITMAPS
  ' ***
  ' *** NOTE: Any new bitmaps added must be inserted between
  ' *** IDB_TOPVALUE and IDB_LASTVALUE because these constants are
  ' *** used to validate input parameters.
  ' ***
      idb_topvalue = 100
  End Enum
  Public Enum STRINGS
      ' VBP project file key ID words
      IDS_VBP_KEY_FORM = 500
      ' Procedure keywords
      IDS_PROCKEY_SUB1 = 600
      ' File filter strings
      IDS_FILTER_FRX = 700
      ' Displayed caption strings
      IDS_CAP_STEP1 = 800
      ' Message strings
      ' Err.Description strings
      IDS_ERR_GDI = 1000
  End Enum
  ' Resource ROOS error constants
  Public Enum RR_Errors
      RR_INVALID_BITMAP_ID = 2000 ' Invalid bitmap resource ID
      RR_INVALID_STRING_ID        ' Invalid string resource ID
  End Enum
  Public Sub PuGetBmp(ByVal ilBitmapID As Long, _
      ByVal ictl As Control)
      ' Check that the ID value passed is valid. This is an
      ' Assert type of message, but the class cannot be part
      ' of the design environment, so raise an error instead.
      If ilBitmapID <= idb_topvalue Or _
          ilBitmapID >= idb_lastvalue Then
          Err.Description = "An invalid bitmap ID value '" & _
              ilBitmapID & "' was passed."
          Err.Number = RR_INVALID_BITMAP_ID
          Err.Raise Err.Number
          Exit Sub
      End If
       ' Load the bitmap into the picture of the control passed.
      ictl.Picture = LoadResPicture(ilBitmapID, vbResBitmap)
  End Sub
  Public Function sPuGetStr(ByVal ilStringID As Long, _
      Optional ByVal ivArgs As Variant) As String
      Dim nIndex          As Integer
      Dim nPointer        As Integer
      Dim nTokenCount     As Integer
      Dim sResString      As String
      Dim vTempArg        As Variant
      Const ARG_TOKEN     As String = "%"
      sResString = LoadResString(ilStringID)
      If IsMissing(ivArgs) Then GoTo END_GETRESOURCESTRING
      If (VarType(ivArgs) And vbArray) <> vbArray Then
          ' Single argument passed. Store the value so that we can
          ' convert ivArgs to an array with this single
          ' value.
          vTempArg = ivArgs
          ivArgs = Empty
          ReDim ivArgs(0)
          ivArgs(0) = vTempArg
      End If
      nTokenCount = 0
      Do While nTokenCount < UBound(ivArgs) _
          = LBound(ivArgs) + 1
          nPointer = InStr(sResString, ARG_TOKEN)
          If nPointer = 0 Then
              ' There are more arguments than tokens in the RES
              ' string, so exit the loop.
              Exit Do
          End If
          Call sPiReplaceToken(sResString, ARG_TOKEN, _
              ivArgs(LBound(ivArgs) + nTokenCount))
          nTokenCount = nTokenCount + 1
      sPuGetStr = sResString
  End Function
  Private Function sPiReplaceToken(ByRef iosTokenStr As String, _
      ByVal isToken As String, ByVal ivArgs As Variant)
      Dim nPointer As Integer
      nPointer = InStr(iosTokenStr, isToken)
      If nPointer <> 0 Then
          iosTokenStr = Left$(iosTokenStr, nPointer - 1) & _
              ivArgs & Mid$(iosTokenStr, nPointer + 1)
      End If
  End Function