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")

ROOS Code

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

Result

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
      IDB_SELECTSOURCE
      IDB_SELECTDESTIN
      IDB_NUMBERSOURCE
      IDB_COMPLETED
      idb_lastvalue
  End Enum
  Public Enum STRINGS
      ' VBP project file key ID words
      IDS_VBP_KEY_FORM = 500
      IDS_VBP_KEY_CLASS
      IDS_VBP_KEY_MODULE
      IDS_VBP_SEP_FORM
      IDS_VBP_SEP_CLASS
      IDS_VBP_SEP_MODULE
      IDS_VBP_SEP_RESFILE
      IDS_VBP_KEY_RESOURCE16
      IDS_VBP_KEY_RESOURCE32
      ' Procedure keywords
      IDS_PROCKEY_SUB1 = 600
      IDS_PROCKEY_SUB2
      IDS_PROCKEY_SUB3
      IDS_PROCKEY_FUNC1
      IDS_PROCKEY_FUNC2
      IDS_PROCKEY_FUNC3
      IDS_PROCKEY_PROP1
      IDS_PROCKEY_PROP2
      IDS_PROCKEY_PROP3
      IDS_PROCKEY_END1
      IDS_PROCKEY_END2
      IDS_PROCKEY_END3
      IDS_PROCKEY_SELECT
      IDS_PROCKEY_CASE
      IDS_PROCKEY_COMMENT
      ' File filter strings
      IDS_FILTER_FRX = 700
      IDS_FILTER_PROJECT
      IDS_FILTER_CLASS
      IDS_FILTER_FORM
      IDS_FILTER_MODULE
      IDS_FILTER_CONFIG
      IDS_FILE_TEMP
      ' Displayed caption strings
      IDS_CAP_STEP1 = 800
      IDS_CAP_STEP2
      IDS_CAP_STEP3
      IDS_CAP_STEP4
      IDS_CAP_NUMBER
  IDS_CAP_UNNUMBER
      IDS_CAP_CANCEL
      IDS_CAP_FINISH
      IDS_CAP_CANCEL_ALLOWED
      ' Message strings
      IDS_MSG_NOT_TEMPLATE = 900
      IDS_MSG_COMPLETE_STATUS
      IDS_MSG_TEMPL_CORRUPT
      IDS_MSG_INVALID_CONFIG
      IDS_MSG_CREATE_TMPL_ERR
      IDS_MSG_NO_SOURCE
      IDS_MSG_INVALID_DESTIN
      IDS_MSG_SAME_SRC_DESTIN
      IDS_MSG_QUERY_EXIT
      IDS_MSG_ABORTED
      ' Err.Description strings
      IDS_ERR_GDI = 1000
      IDS_ERR_PROCESS_ERROR
  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
      Loop
  END_GETRESOURCESTRING:
      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