Subclassing Functions and Subroutines

To fully understand how subclassing works, you have to understand how Visual Basic resolves which version of a function or subroutine to execute. When Visual Basic encounters a statement such as Call MySub, it begins a search for the MySub definition. First it looks within the current module, form, or class module for a definition of MySub. If it fails to find one, it looks through the rest of the project for a public (or friend) definition of MySub. Failing that, it goes to the list of project references (available by choosing References from the Project menu) and resolves each reference in order until it finds the first reference that contains a public definition of MySub.

If you check the project references for any project, the first three items in the list you will see are Visual Basic For Applications, Visual Basic Runtime Objects And Procedures, and Visual Basic Objects And Procedures. Because these references contain the definitions for all the intrinsic Visual Basic subroutines, functions, and objects, you will see that, following the rules of the previous paragraph, it is theoretically possible to define local versions of any intrinsic Visual Basic subroutine, function, or object within the program. Unfortunately, this is not the case, as there are some functions that cannot be subclassed for various reasons that I will discuss later in the chapter.

What Are the Benefits of Subclassing?

Subclassing allows you to extend, expand, and even diminish, or degrade, the existing Visual Basic functions and subroutines—perhaps to add extra validation of parameters, perform additional processing to the original function, or even to change the purpose of the function or subroutine altogether. This last choice has obvious pitfalls for users who are not aware that a routine now performs a completely different function than what the manual says, and should be avoided for that reason. For the first two purposes, however, subclassing can add real value to the existing Visual Basic equivalents.

One useful application of subclassing that we have implemented is in replacing the standard Visual Basic MsgBox function. The code for our MsgBox function is shown here:

Public Function MsgBox(ByVal isText As String, _     Optional ByVal ivButtons As Variant, _     Optional ByVal ivTitle As Variant, _     Optional ByVal ivLogText As Variant, _     Optional ByVal ivBut1Text As Variant, _     Optional ByVal ivBut2Text As Variant, _     Optional ByVal ivBut3Text As Variant, _     Optional ByVal ivBut4Text As Variant) As Integer '================================================================== ' '    Module: Message_Box. Function: MsgBox. ' '    Object: General ' '    Author - TMS Programmer. TMS Ltd. '    Template fitted : Date - 01/07/95    Time - 14:27 ' '    Function's Purpose/Description In Brief ' '    Internal, replacement MsgBox function and statement used in  '    TMS Tools. Allows four buttons, configurable button caption text,  '    and logging of error text to a LOG file. See nInternalMsgBox  '    for more information. ' '    This function provides a simple wrapper around frmIMsgBox's  '    nMb2InternalMsgBox method, allowing that method to be called  '    without bothering with the class prefix. As such, its name is  '    not nMb1MsgBox but MsgBox so as to keep its name short and such  '    that it overrides VBA's function/statement of the same name. ' '    Revision History: ' '    BY            WHY & WHEN            AFFECTED '    TMS Programmer. TMS Ltd. - Original Code 01/07/95, 14:27 ' '    INPUTS - See nMb2InternalMsgBox for a description. ' ' '    OUTPUTS - Possibly nothing, possibly an integer; i.e., as  '              it's a VB function, its return value may be ignored.  '              See nMb2InternalMsgBox for a full description. ' '==================================================================     ' Set up general error handler.     On Error GoTo Error_In_General_MsgBox:     Const ERROR_ID = "Message_Box - General_MsgBox"          Call TrTMS2TraceIn(ERROR_ID)     ' ========== Body Code Starts.==========     Dim fp      As Form     Dim sTitle  As String     ' Create the message box form.     Set fp = New frmIMsgBox     Load fp          If IsMissing(ivTitle) Then         sTitle = App.MsgBoxTitle & App.Title     Else         sTitle = App.MsgBoxTitle & CStr(ivTitle)     End If     ' Create and show the message box.     MsgBox = fp.nMb2InternalMsgBox(isText, ivButtons, sTitle, _                                    ivLogText, ivBut1Text, _                                    ivBut2Text, ivBut3Text, ivBut4Text)              ' Destroy the message box form.     Unload fp     Set fp = Nothing     ' ========== Body Code Ends.  ==========     Call TrTMS2TraceOut(ERROR_ID)          Exit Function      ' Error handler Error_In_General_MsgBox:     ' Store error details on stack. Pass procedure name.     Err.Push ERROR_ID          ' Add rollback code here...     Err.Pop          ' Call TMS error handler.     Select Case frmErrorHandler.nEhTMSErrorHandler                  Case ERROR_ABORT: Resume Exit_General_MsgBox         Case ERROR_RETRY: Resume         Case ERROR_IGNORE: Resume Next         Case ERROR_END: Call GuDoEndApp         Case Else: End          End Select Exit_General_MsgBox: End Function 

This is an example of a routine where we have completely replaced the original Visual Basic functionality. However, it is also possible to use the existing functionality and extend it, as in the following example:

Public Property Get EXEName() As String     EXEName = UCase$(VB.App.EXEName & _         IIf(App.InDesign = True, ".VBP", ".EXE")) End Property 

Here we are simply taking the EXEName property of the App object and reformatting it to our own standard. Note that to access the original Visual Basic property we must fully qualify the reference. When this is done, Visual Basic ignores the rules above for resolving the location of the routine and instead resolves it directly using the explicit reference.

The potential for subclassing Visual Basic intrinsic functionality should not be underestimated. In the MsgBox example alone, the scope you have for customization is enormous. For example, the TMS MsgBox function allows you to log any message displayed with the vbCritical flag. It contains auto-reply functionality in which you can have the message box automatically choose a reply after a given period of time. You can also configure up to four buttons with custom captions. All this functionality from one Visual Basic method!

Problems with Subclassing

Some Visual Basic functions are not suitable for subclassing. Some functions simply won't subclass because the function names have been made reserved words. Print is such an example, as were CDate and CVDate in Visual Basic 5. Ultimately, there is little that can be done about such functions except create functions with similar—but different—names. This requires that the developers be instructed to use the renamed function instead of the intrinsic function, and thus leaves the door open to inconsistencies within the code.

Functions that have multiple definitions but return a different result type (for example, Format and Format$) are also not possible to subclass fully. The problem here is that two functions are needed to subclass the function properly, but Visual Basic sees Public Function Format(…) As Variant and Public Function Format$(…) As String as being the same function and raises a compile-time error of a duplicate function definition. Here, the only option is to subclass the more generic function (in this example, the more generic function is Format, as it returns a Variant) and issue instructions to developers not to use Format$.

Other functions break Visual Basic's own rules on parameters. An example of this is InStr, which takes four arguments, of which the first and fourth arguments are optional. As Visual Basic's rules state that optional arguments must be at the end of a list of arguments, clearly we cannot subclass this function to match exactly the internal definition of it. To get around this, we either have four arguments, with the last two optional, or simply use a ParamArray of Variants as a single argument. Both of these solutions require more work within the new version of the function in order to work out which of the four arguments have been supplied.

One "nice to have" feature would be to wrap the subclassed functions in an ActiveX DLL such that the source code was "hidden" from the developers. Unfortunately, this currently isn't possible in Visual Basic. In the beginning of this section, I described the method Visual Basic uses to determine where to find the definition of a function. Remember the three references I listed that always appear at the top of the project references list? Unfortunately, there is no way to prevent those three references from being at the top of the list. This means that the reference to your ActiveX server with the subclassed functions is always below them in the list and, as a result, your subclassed functions are never called. It's a pity that this approach isn't possible, as it would not only hide the details of what extra functionality the subclassed functions contain, but it would also remove the clutter of the extra source code from the application.

Subclassing Objects

Just as it is possible to replace the Visual Basic versions of functions and subroutines, it is also possible to replace the "free" objects that are provided for you during program execution. The App, Err, and Debug objects can all be replaced with objects of your own design, although this does require you to instantiate an instance of each object you are replacing.

Because you are creating new objects of your own, it is essential to include all the properties and methods of the original object and simply pass the calls through to the original object where there is no extra functionality required. An example of this is the hInstance property of the App object, shown below.

Public Property Get hInstance() As Long     hInstance = VB.App.hInstance End Property 

NOTE


Note that hInstance is a read-only property of the VB.App object so you would not code a Property Let for it.

The App object

The main uses we have found for subclassing the App object are to add new application-level properties and to reformat the output of some of the standard App object properties. Typical properties that we find useful to add are InDesign (a Boolean value that tells us if we are running within the Visual Basic IDE or as a compiled EXE or ActiveX component), date and numeric formats (from the Registry), the operating system version details, and various file locations (the application help file and MSINFO.EXE, among others). Other uses for subclassing the App object include providing a consistent version number format, and the ability to store Registry settings in different locations depending on the InDesign property above (we append either ".EXE" or ".VBP" to App.ExeName).

The Debug object

This is probably the most difficult and least useful of the system objects to subclass, mainly because an object cannot be created with the name "Debug" and the Print method cannot truly be subclassed, as "Print" is a reserved word. For these reasons, it is probably best to leave the Debug object alone and, if extra debug-like functions are required, create a completely unrelated object.

The Err object

This is probably the most useful object to subclass, as it enables you to apply more consistently your chosen error handling strategy. We add Push and Pop methods to the error object, and we also make the undocumented Erl function a property of the object. In addition, we add an ErrorStackList method that allows us to examine the current error stack that we maintain internally within the new App object. This allows us to determine, from any point in a program, the routine that raised the current error. For more information on error handling strategies, and an implementation of a replacement "Err," please refer to Chapter 1.

One note of caution with the Err object relates to the Raise method. Although you should probably add a Raise method to your subclassed Err object for completeness, you should add code to it to ensure it is never called. The reason for this becomes obvious when you stop to think about it. Your Err.Raise method would deal with the error by passing the required details to VBA.Err.Raise. This would then generate an error, with the source of the error being your Err object every time! When subclassing the Err object, you must ensure that VBA.Err.Raise statements generate all errors raised in the program.



Ltd Mandelbrot Set International Advanced Microsoft Visual Basics 6. 0
Advanced Microsoft Visual Basic (Mps)
ISBN: 1572318937
EAN: 2147483647
Year: 1997
Pages: 168

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net