Bristle Software VB Tips

This page is offered as a service of Bristle Software, Inc.  New tips are sent to an associated mailing list when they are posted here.  Please send comments, corrections, any tips you'd like to contribute, or requests to be added to the mailing list, to tips@bristle.com.

Table of Contents:

  1. Form/Control Tips
    1. Use Move rather than Left, Top, Width, and Height
    2. Controls with Align property are late to re-align
  2. Control Tips
    1. TextBox Tips
      1. Undo support for TextBox control
      2. Managing lines and text in a TextBox control
  3. Sub/Function Tips
    1. Beware IsMissing with non-Variants
    2. Passing Missing Parameters
    3. Beware Call Without Parentheses
  4. Error Handling Tips
    1. Installing an Error Handler
    2. Exiting an Error Handler
    3. Error Handler is a Slice of Time
    4. Err Object
    5. Multiple Error Handlers in One Routine
    6. One Error Handler For Multiple Errors
    7. Inline Error Handling
    8. VB.NET Error Handling
  5. Event Procedure Tips
    1. Triggering Event Procedures via Properties
    2. Handling Errors in Event Procedures
    3. Errors in Event Procedures Triggered via Properties
  6. Class Tips
    1. Add a self-referential Get to Global MultiUse objects
    2. Use Class_Terminate to clean up
    3. Use CallByName and Class_Terminate to clean up
  7. Data Access Tips
    1. Passing a long string to SQL Server via ADO
  8. Debugging Tips
    1. Entering the Debugger Midstream
    2. Step Over, Step Into, Step Out, Step To, Step From
    3. Watching Objects
    4. Modifying Variables During Debugging
    5. Testing From the Immediate Window
    6. Changing Program Flow
    7. Call Stack
    8. Debug.Assert
    9. Beware MsgBox in IDE
  9. Install Tips
    1. Getting Install to Run a .REG file
    2. Other Simple Install Customizations
    3. More Complex Install Customizations
  10. VB7 (VB.NET or VB.Not?)
    1. VB6 Features Dropped in VB.NET
    2. VB.NET New Language Features
    3. See Also
  11. Miscellaneous Tips
    1. Avoid Dim As New
    2. Dictionary vs Collection
    3. Print Without a Newline

Details of Tips:

  1. Form/Control Tips

    1. Use Move rather than Left, Top, Width, and Height

      Last Updated: 12/16/1998
      Applies to:  VB3+

      To move and/or resize a Form or control, use the Move method rather than the Left, Top, Width, and Height properties.  It is more efficient and reduces flicker because all of the properties are updated at once.

      --Fred

    2. Controls with Align property are late to re-align

      Last Updated: 2/2/1999
      Applies to:  VB3+

      In the Resize event of a Form, you can write code to compute the positions of the controls on the form, growing them to fill the space as the Form grows, etc.  Most of the values you want to work with are already updated to the new size before the event fires.  Thus, for example, you can position the controls within the available ScaleHeight and ScaleWidth dimensions.   However, don't make the mistake of assuming that self-aligning controls (controls with an Align property) have already re-aligned themselves.  You cannot rely on the Top property of a bottom-aligned PictureBox or StatusBar being updated before the Resize event of the Form executes.   To position a control just above the status bar at the bottom of the form, use code like:

      	cmdOK.Top = frm.ScaleHeight - StatusBar1.Height - cmdOK.Height

      not:

      	cmdOK.Top = StatusBar1.Top - cmdOK.Height

      --Fred

  2. Control Tips

    1. TextBox Tips

      1. Undo support for TextBox control

        See: Windows Tips: Undo support for TextBox control

      2. Managing lines and text in a TextBox control

        See: Windows Tips: Managing lines and text in a TextBox control

  3. Sub/Function Tips

    1. Beware IsMissing with non-Variants

      Last Updated: 9/6/1999
      Applies to:  VB5+

      VB4 added the ability to make a Variant parameter optional and determine whether or not it was passed by the caller.  The syntax is pretty simple, using the Optional keyword and IsMissing function:

      	Public Sub Sub1(Optional varParam As Variant)
      	    If IsMissing(varParam) Then
      	        MsgBox "varParam missing"
      	    Else
      	        MsgBox "varParam found"
      	    End If
      	End Sub

      So far, so good.  VB5 extended this feature to allow non-Variant parameters to be optional.  So now you can do the following, right?

      	Public Sub Sub1(Optional strParam As String)
      	    If IsMissing(strParam) Then
      	        MsgBox "strParam missing"
      	    Else
      	        MsgBox "strParam found"
      	    End If
      	End Sub

      Wrong!  In VB5 and VB6, the IsMissing function always returns False if the data type of the missing parameter is not Variant.   Instead, you have to do something like:

      	Public Sub Sub1(Optional strParam As String = "")
      	    If strParam = "" Then
      	        MsgBox "strParam missing"
      	    Else
      	        MsgBox "strParam found"
      	    End If
      	End Sub

      But, be careful to use a default value that the caller is sure to never actually pass.

      --Fred

    2. Passing Missing Parameters

      Last Updated: 9/6/1999
      Applies to:  VB4+

      Given that you can test for whether an Optional parameter is missing (see the tip:  Beware IsMissing with non-Variants), it is sometimes useful to pass the "missing" parameter to another Sub or Function, and have it be considered "missing" there also.  Also, it is sometimes useful to pass an explicit value that was not passed by your caller, but which will be interpreted as a missing parameter by the Sub or Function that you call.

      Here's a routine with an optional parameter and a test to see if it is missing:

      	Public Sub Sub1(Optional varParam As Variant)
      	    If IsMissing(varParam) Then
      	        MsgBox "varParam missing"
      	    Else
      	        MsgBox "varParam found"
      	    End If
      	End Sub

      So far so good. Now, add a calling routine that passes along its own optional param:

      	Public Sub Sub2(Optional varParam As Variant)
      	    Sub1 varParam
      	End Sub

      If varParam was not passed to Sub2, Sub2 is still able to "not" pass it to Sub1, and have it act as you would hope.   (Don't try this with non-Variant params for which IsMissing always returns False.)

      This tells me that the "missing" parameter must actually be pushed on the stack by VB as a Variant of some special type.  So, I used the VarType function to see what kind:

      	Public Sub Sub1(Optional varParam As Variant)
      	    If IsMissing(varParam) Then
      	        MsgBox VarType(varParam) ' Added this line
      	        MsgBox "varParam missing"
      	    Else
      	        MsgBox "varParam found"
      	    End If
      	End Sub

      It turns out that a missing parameter is not Variant type vbEmpty (0), or vbNull (1), as you might expect, but vbError (10).

      So who really cares about this bizarre piece of esoterica? It comes in useful when you want to wrap a function like PopupMenu which has useful default values for its optional params that are not easy for your wrapper code to generate and pass explicitly.  If you want to make them optional to your callers, and don't want to write a bunch of nested Ifs, you have to find a way to trick the routine you call into thinking you omitted params when you actually just passed along what you got, and your caller omitted them.

      --Fred

    3. Beware Call Without Parentheses

      Last Updated: 3/5/2000
      Applies to:  VB3+

      There are 2 ways to call a subroutine (Sub) in VB:

      	Call Sub1(param1, param2, param3, ...)
      	Sub1 param1, param2, param3, ...

      Note that with the first format, the keyword Call and the parentheses are both required, and with the second format, they are both disallowed. It is an easy mistake to mix the two formats, as:

      	Subname (param1, param2, param3, ...)

      VB will generally catch this syntax error. However, if the Sub has only one parameter, it is no longer a syntax error because there is a valid interpretation for VB to apply. For example, the line:

      	Subname (param1)

      is interpreted by VB as equivalent to:

      	Call Subname ((param1))

      that is, a call to Sub1 with the expression:

      	(param1)

      (including the parentheses) as the single parameter. An expression containing a single variable name in parentheses is treated like any other expression in parentheses. The value of the part inside the parentheses is computed, stored in a temporary location, and then used. If the parameter is declared ByVal, or if Sub1 does not modify the value of the parameter, this is not a problem. However, if the parameter is declared ByRef (which is the default) and Sub1 modifies it, you get an unexpected result. Sub1 successfully modifies the value in the temporary location, but leaves the real Param1 variable unexpectedly untouched. Then the temporary is discarded. With no error message from VB, this is an easy mistake to make.

      --Fred

  4. Error Handling Tips

    1. Installing an Error Handler

      Last Updated: 4/16/2000
      Applies to:  VB4+

      In VB, when an error occurs, control is transferred to the currently installed error handler, if any.  If no error handler is installed for the current subroutine, control is transferred to the error handler for the calling routine, or to its caller, etc.  If no error handler is installed at any level, the program terminates.

      To install an error handler, use the On Error statement, which has the following forms:

      On Error GoTo label
      Errors cause a jump to the line in this routine with the specified label.

      On Error GoTo line_number
      Errors cause a jump to the line in this routine with the specified number.   Don't use this -- use labels instead of line numbers.

      On Error GoTo 0
      Uninstalls the error handler for this routine.  Errors will cause a jump to the caller.

      On Error Resume Next
      Errors will be ignored, continuing at the next line of code, as though no error occurred.

      --Fred

    2. Exiting an Error Handler

      Last Updated: 4/16/2000
      Applies to:  VB4+

      In a VB error handler, after dealing with the error, you can do any of the following:

      Resume Next
      Resumes execution at the line in this routine immediately after the one that raised the error.

      Resume
      Re-executes the same line in this routine that caused the error.   Presumably, you took some action to prevent the error from recurring first -- otherwise this is an infinite loop.

      Resume label
      Resumes execution at the line in this routine with the specified label.   Not usually a good idea.

      Resume line_number
      Resumes execution at the line in this routine with the specified line number.  Not usually a good idea.

      Exit Sub/Function/Property
      Exits the current subroutine, returning normally to the caller, as though no error had occurred.

      Err.Raise
      Raises an error to the calling routine, transferring control immediately to its error handler.  Via the Number parameter, you can re-raise the error that occurred in this routine or raise a different error.

      --Fred

    3. Error Handler is a Slice of Time

      Last Updated: 4/16/2000
      Applies to:  VB4+

      VB Error handlers are different from error handlers in other languages like C++, Java, and Ada.  In those languages, the error handler is a slice of the code.  You can point at any line of code and state whether it is a regular line or a line of an error handler.  In VB, the same line of code may sometimes be an error handling line and other times not.  It depends on how control was transferred to that line of code.  

      For example, consider the following code:

            Sub MySub
                On Error GoTo MyErrorHandler
                Call DoRiskyOperation()
                ' Exit Sub
            MyErrorHandler:
                Call DealWithError()
                Resume Next
            End Sub

      If an error occurs in DoRiskyOperation, control is transferred to the MyErrorHandler line, and the subsequent lines act as an error handler.  In such a case the Resume Next statement works as expected.  However, if DoRiskyOperation does not raise an error, the program continues executing normally.  Since the Exit Sub line is commented out, the program "falls through" to the error handler.  In that case, because no error has occurred, the Resume Next doesn't make sense and it raises an error of its own.

      For this reason, be sure to always put an Exit statement immediately before your error handler.

      This problem can also occur if you use the GoTo statement to jump to the label of an error handler.

      --Fred

    4. Err Object

      Last Updated: 4/16/2000
      Applies to:  VB4+

      The VB Err object can be used to get information about an error that has already occurred, to record information to be passed to an error handler when a future error occurs, and to raise an error.

      To get information about an error that has already occurred, query the properties: Number, Description, Source, HelpFile, HelpContext, etc.  The Number property is useful inside an error handler to determine which error occurred.  The Description and Source properties are useful for displaying error messages to the user or writing them to a log file.  The HelpFile and HelpContextId properties are useful for jumping to the relevant section of your application's help file when the user clicks the Help button you may display along with your error message.

      To pass information to an error handler, set the Source, HelpFile, and HelpContext properties before attempting an operation that may raise an error.  Once inside the error handler, you can also change the values of these properties, as well as others like Number, and Description.  

      To raise an error, call the Err.Raise method, specifying a Number parameter, and optionally Source, Description, HelpFile, and HelpContext.  Any parameters that you don't specify retain their previous values.

      --Fred

    5. Multiple Error Handlers in One Routine

      Last Updated: 4/16/2000
      Applies to:  VB4+

      You can put multiple error handlers in the same routine, but only one of them is installed at any point in time.  For example:

            Sub MySub()
                On Error GoTo ErrorHandler1
                Call DoRiskyOperation1()
                On Error GoTo ErrorHandler2
                Call DoRiskyOperation2()
                Exit Sub
            ErrorHandler1:
                Call DealWithError1()
                Resume Next
            ErrorHandler2:
                Call ReportError
                Exit Sub
            End Sub

      In this example, any error that occurs during DoRiskyOperation1 jumps to ErrorHandler1, and any error that occurs during DoRiskyOperation2 jumps to ErrorHandler2.

      --Fred

    6. One Error Handler For Multiple Errors

      Last Updated: 4/16/2000
      Applies to:  VB4+

      You can write a single error handler to deal differently with different errors.   For example:

            Sub MySub()
                On Error GoTo MyErrorHandler
                Call DoRiskyOperation()
                Exit Sub
            ErrorHandler:
                Select Case Err.Number
                    Case ERROR_FILE_NOT_FOUND
                        Call DealWithMissingFile()
                        Resume Next
                    Case ERROR_FILE_IN_USE
                        Call WaitForLockedFile()
                        Resume
                End Select
            End Sub

      --Fred

    7. Inline Error Handling

      Last Updated: 4/16/2000
      Applies to:  VB4+

      You can handle errors "inline", without jumping to an error handler at a different location in your subroutine.  For example:

            Sub MySub()
                On Error Resume Next
                Call DoRiskyOperation()
                Select Case Err.Number
                    Case ERROR_FILE_NOT_FOUND
                        Call DealWithMissingFile()
                        ' Can't use Resume Next
                    Case ERROR_FILE_IN_USE
                        Call WaitForLockedFile()
                        ' Can't use Resume
                End Select
            End Sub

      --Fred

    8. VB.NET Error Handling

      Last Updated: 4/16/2000
      Applies to:  VB4+

      According to Microsoft, error handling in VB.NET will be very different from previous versions.  It will be much more like Java error handling, complete with Try, Catch, and Finally keywords.  For more details, see:

                  http://msdn.microsoft.com/vbasic/

      --Fred

  5. Event Procedure Tips

    1. Triggering Event Procedures via Properties

      Last Updated: 4/16/2000
      Applies to:  VB4+

      For some controls, setting the value of a property triggers an event, as though the user had just interacted with the control.  For example:

      Control Property Event
      TextBox Text Change
      ListBox ListIndex Click
      FarPoint Spread/OCX MaxRows Change
      (lots of others, feel free to mail me the ones you know)    

      These events are usually fired synchronously, meaning that the event procedure executes completely before control returns to the line following the one that set the property value.  For you Windows API programmers out there, this means it does a SendMessage, not a PostMessage.

      This is sometimes useful, and sometimes not.  However, there is no easy VB way to avoid it.  Typical startup code for a form loads the list items into a ListBox and then sets the ListIndex property to specify the initially selected list item.  If you do this, but don't want the Click event procedure to behave as though the user had manually selected that item, you have 2 choices:

      1. Set a form-level flag called something like blnSkipClick before setting ListIndex, test it in the Click event to decide whether to skip the normal processing, and clear it after setting ListIndex.
               
      2. Use the Windows SendMessage API to set the ListIndex property without triggering a Click event with code like:
      	Const LB_SETCURSEL = &H186
              SendMessage lst.hWnd, LB_SETCURSEL, lngNewIndex, 0&

      --Fred

    2. Handling Errors in Event Procedures

      Last Updated: 4/16/2000
      Applies to:  VB4+

      Most languages that support error handling (Java, C++, Ada, etc.) offer a single highest-level routine where you can install an error handler that catches all unhandled errors.  Unfortunately, VB does not.  The Sub Main of VB does not catch unhandled errors that occur in event procedures of forms and controls.   Therefore, you must explicitly code an error handler in each event procedure of your entire application, unless you are sure that the event procedure cannot possibly trigger an error.  Otherwise, an unhandled error can crash your entire application.

      --Fred

    3. Errors in Event Procedures Triggered via Properties

      Last Updated: 4/16/2000
      Applies to:  VB4+

      In general, unhandled errors that occur in VB routines are raised to the calling routine, so that it can catch them with its error handler.  Only when an error is unhandled at each level all the way up the call chain does the application terminate.  

      However, there is one exception to this.  As described in Triggering Event Procedures via Properties, a line like:

              lstMyListBox.ListIndex = 1

      can trigger a synchronous "call" to lstMyListBox_Click.  In such a case, any unhandled error in lstMyListBox_Click is not raised to the routine that set the ListIndex property.  Instead, the entire application terminates, reporting an error to the user.

      This is consistent with the handling of errors in lstMyListBox_Click when triggered by an actual user click, as described in Handling Errors in Event Procedures.  Even in this case, you must handle all errors in the event procedure.  However, it is particularly inconvenient when you have coded your application to know whether an actual user click has occurred and want to raise an error to the caller when there is a caller.

      As a workaround in such a case, use the techniques described in Triggering Event Procedures via Properties to suppress the code in the Click event while setting ListIndex, but then call lstMyListBox_Click explicitly.  If an error occurs during the explicit call, it is raised to the caller as expected.  For example:

              On Error GoTo MyErrorHandler
              blnSkipClick = True
              lstMyListBox.ListIndex = 1
              blnSkipClick = False
              lstMyListBox_Click

      --Fred

  6. Class Tips

    1. Add a self-referential Get to Global MultiUse objects

      Last Updated: 12/1/1998
      Applies to:  VB4+

      When defining GlobalMultiUse objects (the kind that your users can just use without declaring an instance of them), add a property Get for a self-referential object, as:

      	Property Get Utils() As CUtils
      	    Set Utils = Me
      	End Property

      Your users can then use the VB AutoListMembers feature by referring to the methods as "Utils.Method1" instead of just "Method1". As soon as they type "Utils." the dropdown shows them the method and property names. The fully qualified name "Utils.Method1" also makes the code easier to read, so that a maintenance programmer is not left wondering where "Method1" is defined.

      --Fred

    2. Use Class_Terminate to clean up

      Last Updated: 12/5/1998
      Applies to:  VB4+

      Everyone has made the mistake of setting some global state, like the current MousePointer, and not resetting it later, right?  It is especially easy to do if you have a sequence of code like:

      	On Error Goto ErrorHandler
      	ctl.MousePointer = vbHourglass
      	DoSomeLongErrorProneOperation
      	ctl.MousePointer = vbDefault

      If an error occurs, you skip the line that would have reset the MousePointer.  If you forget to duplicate that line in your error handler, the hourglass cursor is left up.

      Ed Bacon suggested this solution to me:   Create a special class whose whole purpose is to reset the global value, and allocate an instance of it in the local scope where you set the variable.  In the Class_Terminate event, reset the value.  For example:

      	On Error Goto ErrorHandler
      	Dim objResetMousePointer as CResetMousePointer
      	Set objResetMousePointer = New CResetMousePointer
      	ctl.MousePointer = vbHourglass
      	DoSomeLongErrorProneOperation
      	Exit Sub

      At the Exit Sub, objResetMousePointer goes out of scope and is automatically deallocated by VB, causing its Class_Terminate event to fire.  If DoSomeLongErrorProneOperation raises an error, or anything else happens to cause your routine to end before the Exit Sub, objResetMousePointer still goes out of scope, and Class_Terminate still fires.

      This is similar to the concept to a "destructor" in C++ and is the closest thing VB offers to the Java "finally" clause.

      --Fred

    3. Use CallByName and Class_Terminate to clean up

      Last Updated: 12/5/1998
      Applies to:  VB6+

      Here's a VB6 improvement on the tip:  Use Class_Terminate to clean up

      The one problem with the original tip is that it requires you to dedicate an entire class to resetting one specific property.  You can have the class take an Object parameter and reset the MousePointer property of that object, so you don't need a different class for each object type that has a MousePointer property.  However, you cannot have it take another parameter specifying which property to reset.  Therefore, you dedicate the entire class to MousePointer.  What if you have a similar need to reset another property?

      For example, I have a data class that sends events to various "observers" whenever its data value changes.  However, I sometimes know that I am about to make a bunch of changes in a row, and want to disable the notification events until all changes are made, and then send them once.  Therefore, I gave that class a RaiseEvents property that I temporarily set False and later back to True.  Same problem as with MousePointer -- what if the reset code gets skipped?

      The solution was to use the new VB6 CallByName feature, which allows you to call a method, property, Sub or Function by specifying its character string name.   This made the property name into a String parameter.  I now have one class that does all resetting of all properties of all objects.  It is called CResetter, and contains 2 methods:

      	Public Sub TempSetProperty( _
      	                         ByVal objObject As Object, _
      	                         ByVal strProperty As String, _
      	                         ByVal varValue As Variant)
      	    Set m_objObject = objObject
      	    m_strProperty = strProperty
      	    m_varOldValue = CallByName(objObject, strProperty, VbGet)
      	    CallByName objObject, strProperty, VbLet, varValue
      	End Sub
      	Private Sub Class_Terminate()
      	    CallByName m_objObject, m_strProperty, VbLet, m_varOldValue
      	End Sub

      The client code now looks like:

      	On Error Goto ErrorHandler
      	Dim objResetter as CResetter
      	Set objResetter = New CResetter
      	objResetter.TempSetProperty ctl, "MousePointer", vbHourglass
      	DoSomeLongErrorProneOperation
      	Exit Sub

      --Fred

  7. Data Access Tips

    1. Passing a long string to SQL Server via ADO

      Last Updated: 1/10/1999
      Applies to:  VB6, ADO 1.5, SQL Server 6.5

      ADO (like RDO and DAO before it) makes it relatively easy to access data in a database.   The typical code to execute a stored procedure in VB using ADO is:

      	'-- Create the ADO command object to execute the stored
      	'-- procedure.
      	Dim objCmd As ADODB.Command
      	Set objCmd = New ADODB.Command
      	With objCmd
      	    Set .ActiveConnection = objMyADOConnection
      	    .CommandText = "MyStoredProcedure"
      	    .CommandType = adCmdStoredProc
      	End With
      
      	'-- Have ADO query the parameter info for the stored procedure
      	'-- and create the Parameters collection.
      	Call objCmd.Parameters.Refresh
      
      	'-- Fill in the parameter values.
      	With objCmd.Parameters("@MyParameter")
      	    .Value = strContent
      	    .Size = Len(.Value)
      	End With
      
      	'-- Execute the stored procedure, populating a recordset with the
      	'-- results, if any.
      	Dim objRS As ADODB.Recordset
      	Set objRS = objCmd.Execute

      This works fine for most data types.   However, if the stored procedure resides in SQL Server and the data type of the parameter is text, ADO sets the data type of the Parameter object to the wrong type.  It sets it to adVarChar, not adLongVarChar.   Therefore, if you pass a string longer than 255 chars, you get the error:

      	[Microsoft][ODBC SQL Server Driver]String data, right truncation	

      My workaround is to explicitly set the Type property of the Parameter object after calling Refresh, as:

      	'-- Create the ADO command object to execute the stored
      	'-- procedure.
      	Dim objCmd As ADODB.Command
      	Set objCmd = New ADODB.Command
      	With objCmd
      	    Set .ActiveConnection = objMyADOConnection
      	    .CommandText = "MyStoredProcedure"
      	    .CommandType = adCmdStoredProc
      	End With
      
      	'-- Have ADO query the parameter info for the stored procedure
      	'-- and create the Parameters collection.
      	Call objCmd.Parameters.Refresh
      
      	'-- Fill in the parameters.
      	With objCmd.Parameters("@MyParameter")
      	    .Type = adLongVarChar
      	    .Value = strContent
      	    .Size = Len(.Value)
      	End With
      
      	'-- Execute the stored procedure, populating a recordset with the
      	'-- results, if any.
      	Dim objRS As ADODB.Recordset
      	Set objRS = objCmd.Execute

      Then it works fine.  No error reported.   No data truncation, even with strings 100K chars or more long.

      This bug is not likely to be fixed soon.  MS Knowledgebase article Q174223 acknowledges the problem and describes another workaround (which I couldn't get to work in my real multi-parameter case), but classifies it as "This behavior is by design."

      --Fred

  8. Debugging Tips

    1. Entering the Debugger Midstream

      Last Updated: 7/18/1999
      Applies to: VB3+

      If you know which code you want to step through in VB, it is easy enough to set a breakpoint and hit F5 to get there.  However, what if you don't know the application well enough to know where to set the breakpoint?

      Run the application in the VB IDE, navigating through its forms until you get to the situation where your bug occurs.  Hit Ctrl-Break to pause the application, then F8 to single step, then perform the application interaction (mouse click, keystroke, drag, whatever) that triggers your bug.  You immediately step into the code at that point.  Step through to find the problem.

      For those who prefer the mouse: see the VB Debug and Run menus or the toolbars for equivalent operations.  However, that is not always an option.  First, moving back to the IDE may generate an event and affect program flow.   Second, you can't always get back to the IDE with the mouse.  For example, if your application is in a tight infinite loop, you can't get the IDE to respond to mouse clicks, but Ctrl-Break still works fine.

      --Fred

    2. Step Over, Step Into, Step Out, Step To, Step From

      Last Updated: 7/18/1999
      Applies to: VB5+

      You probably know that you can use F8 to step into a subroutine in the VB IDE, and Shift-F8 to step over a subroutine call as a single step, but what to do when you hit the wrong one?

      If you accidentally hit F8 and step into the routine, you can step through the entire routine, or you can scroll to the bottom of the routine, set a breakpoint, hit F5, and clear the breakpoint.  However, it is easier to just hit Ctrl-Shift-F8 to step out of the routine.  In one step, you are taken to the line following the call to the routine.

      On the other hand, if you accidentally hit Shift-F8 and step over the routine, you can still go back and step into it without restarting the application.  Move the cursor to the line where the routine is called and hit Ctrl-F9.   This resets the current execution point to the calling line.  Then hit F8 to step from that point into the routine.  Note:  This re-executes the same subroutine that was just executed, which may be undesirable if your routine has global side effects like incrementing a global variable, updating a database, or writing to a file.

      A related tip:  Hit Ctrl-F8 to step to the line containing the cursor from the current execution point (similar to setting a breakpoint, hitting F5, and clearing the breakpoint).

      For those who prefer the mouse: see the VB Debug menu or the toolbars or the right mouse menu for equivalent operations.

      --Fred

    3. Watching Objects

      Last Updated: 7/18/1999
      Applies to: VB5+

      One very useful feature of VB is the Watch window.  It can monitor not only simple variables, but also complex objects, collections, etc.  The easiest way to open the Watch window is by hitting Ctrl-W.

      For example, as you step through your RDO or ADO code, you may want to keep a watch window open on the RDO or ADO objects.  It can be very enlightening to watch the changes to the properties of the Connection, Command, Parameter, rdoQuery, RecordSet and rdoResultSet objects.  You can see the details of the connection string, the SQL query being executed, the names and types of parameters expected by stored procedures, columns returned, etc. This makes it easy to find RDO and ADO errors, which all too often corrupt memory and crash the VB IDE if you don't spot them early.

      You can use the Watch window to examine built-in objects and collections, like the App, Screen, and Err objects, controls, entire forms including their Controls collection, the entire Forms collection, etc.  You can examine your own objects or even 3rd party objects for which you have no source code.  In all cases, the names, types, and values of all properties are shown.

      --Fred

    4. Modifying Variables During Debugging

      Last Updated: 7/18/1999
      Applies to: VB3+

      As you step through your code, you can change the values of variables, object properties, etc. via the Immediate window (previously called the Debug window).  Hit Ctrl-G to bring up the immediate window, and type VB code into it. 

      You can examine variables or entire expressions by typing ?<variable_name>, though just hovering the mouse over the variable or selected expression in the source code window (in VB5 and greater) is often faster.  You can modify a variable by typing a regular assignment statement <variable_name> = <value>.

      You can't type into the Immediate window when VB is running flat out.  You have to be stepping through the code, so pause it with Ctrl-Break, then type, hit Enter, and resume execution with F5, F8, or whatever.

      To re-execute a statement in the Immediate window, move the cursor to it and hit Enter.   To insert a new line between 2 lines in the Immediate window, without executing anything, hit Ctrl-Enter.

      --Fred

    5. Testing From the Immediate Window

      Last Updated: 7/18/1999
      Applies to: VB3+

      You can use the VB Immediate window (previously called the Debug window) as an ad-hoc test harness.  After writing a VB routine (Sub or Function), you can test it directly from the Immediate window without going to the trouble of creating another routine to call it.  Hit Ctrl-G to bring up the Immediate window, and type in a call to the routine you want to test.  Set a breakpoint in the routine first if you want to step through the code.  There is no way to type the call into the Immediate window and then "step into" it with F8.   You just hit Enter, which acts like F5, not F8

      For more extensive testing, you can package test code with your real code, and call it from the Immediate window.  For example, when you code a VB component (an OLE automation server packaged as one or more classes in a DLL or EXE), add an extra BAS file containing a public Test routine.  The Test routine contains your test code, creating instances of the classes and calling the various properties and methods.  Call the test routine directly from the Immediate window.  No need to write a separate VB application or component to test it.

      One limitation of the Immediate window is that you can't declare variables there.   If you are using Option Explicit (as you should), you have to either do without local variables, or declare them in your Test routine.

      --Fred

    6. Changing Program Flow

      Last Updated: 7/18/1999
      Applies to: VB5+

      How to test all the code that "shouldn't ever happen"?  For example, if you write a robust FileSystem object with operations to open and write to files, you want to test the code that executes in response to a disk full error.  However, that code exists inside an If statement or VB error handler that never executes because the disk on your test machine is never actually full.

      One way is to use Ctrl-F9 (or the corresponding menu, toolbar button, or right mouse menu) to set the current execution point to a line inside the If statement or VB error handler.  Then hit F8 to step through the code.

      --Fred

    7. Call Stack

      Last Updated: 7/18/1999
      Applies to: VB5+

      VB allows you to examine and traverse the call stack while stepping through your code.

      Hit Ctrl-L to see a popup window showing the routine that is currently executing, the one that called it, the one that called that, etc.  You can move back to the calling routines and examine variables in their scopes, set breakpoints that will be hit after you return from the current routine, etc.

      This is also a useful way to determine what caused a routine to be called.  For example, this is a good way to see that setting the ListIndex property of a ListBox causes its Click event to fire.  On the call stack, you'll see something like:

      	Project1.Form1.List_Click
      	[<Non-Basic Code>]
      	Project1.Function1

      If you select Function1, you'll see the line of code where it set the ListIndex property.   The "Non-Basic Code" in this case is VB itself, which intercepted the Windows message generated by the ListBox when its ListIndex property was changed, and called the List_Click event procedure.  You'll also see "Non-Basic Code" entries whenever the call stack contains entries from compiled VB or non-VB components.

      --Fred

    8. Debug.Assert

      Last Updated: 7/18/1999
      Applies to: VB5+

      An "assertion" is a useful technique for writing robust code.  It allows you to specify certain situations that should never occur.  VB has limited support for assertions via the Assert method of the Debug object, similar to the "assert" statement in C and C++.

      When you call Debug.Assert, you pass a boolean expression.  If the expression is False, the IDE acts as though a breakpoint had been set on that line.  This is useful for detecting unexpected conditions in code that you are developing. 

      Debug.Assert is ignored if the VB application or component is not running in the IDE, so this is not a good way to detect errors that occur in production code.  For that, you should code a real error handler to log and/or report the error.  Such error checking is especially important for errors beyond the control of the VB application or component, like a disk running out of space, the user doing something unexpected, or another component misbehaving. 

      Other errors can be absolutely prevented by the local VB code and only occur when a   maintenance programmer changes the code.  For example, on a form with 3 option buttons and code in Form_Load to select one of them, there is no user interaction that will cause them to all be unselected.  The only way for the user to unselect one is by selecting another.  In such a case, you might write code like the following to determine which button is selected:

      	If optButton(0).Value = True Then
      		Deal_With_Option_Zero
      	ElseIf optButton(1).Value = True Then
      		Deal_With_Option_One
      	Else
      		Deal_With_Option_Two
      	End If

      However, this code makes the assumption that there are only 3 buttons and fails if that assumption is violated.  When a future maintenance programmer adds a 4th button, this code still acts as though the 3rd button were selected.  To make the code more robust, change it to:

      	If optButton(0).Value = True Then
      		Deal_With_Option_Zero
      	ElseIf optButton(1).Value = True Then
      		Deal_With_Option_One
      	ElseIf optButton(2).Value = True Then
      		Deal_With_Option_Two
      	Else
      		Debug.Print "Assertion failure: No option selected."
      		Debug.Assert False
      	End If

      This code explicitly "asserts" that there are only 3 buttons.  When the 4th button is selected, this code prints a message to the debug window and stops at this line, showing the code to the programmer.  This doesn't guarantee that the programmer will ever test the code in the IDE and notice the assertion failure, but it's a good start.

      A word of warning about Debug.Assert and Debug.Print: they are completely ignored outside of the IDE.  Therefore, you shouldn't use them to call any functions with important side effects.  For example, don't write code like:

      	Debug.Assert SomeFunction(param1, param2) = True

      where SomeFunction is a function that does something important and returns True or False to indicate its success.  In the IDE, when you are testing it, this will work fine.  Then you build your EXE or DLL and deliver your product, but in the product SomeFunction is never called because the entire line is ignored.  Major bug!  Instead, write the code as:

      	Dim blnSuccess As Boolean
      	blnSuccess = SomeFunction(param1, param2)
      	Debug.Assert blnSuccess = True

      --Fred

    9. Beware MsgBox in IDE

      Last Updated: 9/12/1999
      Applies to: VB5+

      Beware of using MsgBox while running in the VB IDE.  It actively discards all Windows messages as long as it is displayed, which causes VB events to not fire.  For example, while a MsgBox is displayed, Timer events are discarded, as are Paint events, incoming events from other OLE components, etc.  In many applications this doesn't matter, and it is only a problem when you run from the VB IDE (doesn't happen if you build an EXE or DLL and run that), but it can be very confusing when you add MsgBox statements to help you debug and the behavior of the application changes.

      The same problem does not exist with the Windows API call MessageBox, or with a regular modal form.  It is better to use the Windows API or use your own custom form for messages.

      Thanks to Bob Rodini for reminding me that this only happens in the IDE.

      --Fred

  9. Install Tips

    1. Getting Install to Run a .REG file

      Last Updated: 6/20/1998
      Applies to:  VB5

      To get a SETUP.EXE generated by the VB5 Setup Wizard to not only copy a .REG file to the target machine but also run it (to update the registry on the target machine), edit the SETUP.LST file generated by the Setup Wizard.

      The lines in the [Files] section might look like:

      	[Files]
      	...
      	File12=1,,ODBC_INI.re_,ODBC_INI.reg,$(AppPath),,,6/15/1999 10:17:36,707,,"","",""

      Change the 6th field to repeat the name of the .REG file, as:

      	[Files]
      	...
      	File12=1,,ODBC_INI.re_,ODBC_INI.reg,$(AppPath),ODBC_INI.reg,,6/15/1999 10:17:36,707,,"","",""

      This is unnecessary in VB6 where REG files are executed automatically.

      --Fred

    2. Other Simple Install Customizations

      Last Updated: 6/20/1998
      Applies to:  VB5+

      You can tweak much of the behavior of a SETUP.EXE generated by the VB5 Setup Wizard by editing the SETUP.LST file generated by the Setup Wizard.  For example, change the line:

      	Title=My Application

      to:

      	Title=My Application v1.0.0

      to display the version number during setup and in the Add/Remove Programs applet of Control Panel.

      In VB6, the Packaging and Deployment Wizard (the VB6 version of the VB5 Setup Wizard) explicitly prompts for such info, so you don't have to edit the generated file.

      --Fred

    3. More Complex Install Customizations

      Last Updated: 6/20/1998
      Applies to:  VB5+

      To make more complex changes to the behavior of the SETUP.EXE generated by the VB5 Setup Wizard, edit the VB source files for the Setup Wizard project.  Each time you run the Setup Wizard, it builds SETUP.EXE from these source files.  You can add additional setup steps or completely change the behavior and/or appearance of SETUP.EXE.   For more details, see the VB5 docs.

      --Fred

  10. VB7 (VB.NET or VB.Not?)

    1. VB6 Features Dropped in VB.NET

      Last Updated: 1/27/2001
      Applies to:  VB.NET+

      Here is a brief summary of some features of VB6 that are no longer supported in VB.NET:

      1. Variant data type
      2. Default properties of objects (unless the property has parameters)
      3. Arrays with non-zero lower bounds
      4. Option Base statement
      5. Fixed length Strings (use new VB6.FixedLengthString if necessary)
      6. IsMissing() function
      7. Currency data type
      8. GoSub and Return keywords
      9. DefBool, DefByte, DefInt, DefLng, DefCur, DefSng, DefDbl, DefDec, DefDate,
        DefStr, DefObj, and DefVar keywords
      10. "ActiveX Documents"
      11. "WebClasses"
      12. DAO data-bound controls
      13. RDO data-bound controls
      14. RDO "User Connection"
      15. Database-style "Null propagation" in expressions
      16. Computed GoTo (On x GoTo 100, 200, 300)
      17. VarPtr(), ObjPtr(), and StrPtr()
      18. Set and Let statement (leave off the word Set or Let from now on)
      19. LSet, RSet statements (use System.String.PadRight and .PadLeft instead)
      20. Declare ... As Any
      21. OLE Container control
      22. Shape control
      23. Line control
      24. Form methods Circle(), CLS(), PSet(), Line(), and Point().
      25. Form method PrintForm().
      26. Form/control support for DDE
      27. Runtime access to the Name property of forms and controls.  (Use System.Reflection classes)
      28. DoEvents (use System.Winforms.Application.DoEvents instead)
      29. "Wend" keyword (use End While instead)

      Also, be aware of the following changes:

      1. Default parameter mode is now ByVal, not ByRef.
      2. Multiple variables declared on the same line no longer end up being Variants.
      3. Integer is now 32-bit, not 16-bit.
      4. Long is now 64-bit, not 32-bit.
      5. Controls cannot be directly referenced from outside the form unless you explicitly declare them to be Public Shared.
      6. Due to the new garbage collection, memory deallocation no longer occurs synchronously when you drop the last reference to an object.
      7. The syntax for declaring property procedures is changed.  One result of this that you can't have a different scope (Public vs. Friend) for the Get and Set procedures.
      8. The "extensibility object model" (used when you write an Add-In) is changed.
      9. Caption property of Label control is renamed to Text.
      10. And and Or are now "short-circuit" operators as in C, C++, Java, etc.
      11. And, Or, and Not operators can no longer be used for bitwise operations.  Use VB6.And, VB6.Or, and VB6.Not; or BitAnd, BitOr, and BitNot.
      12. The internal representation of True is now 1, not -1.
      13. The following String functions can no longer return Null:   Chr(), Mid(), Command(), Oct(), CurDir(), Right(), Date(), RTrim(), Environ(), Space(), Error(), Str(), Hex(), Time(), LCase(), Trim(), LTrim(), UCase()
      14. You can no longer use ReDim without first using Dim.
      15. Setting the Interval property of a Timer to 0 no longer disables it.
      16. Regular menus can no longer be used as context menus.  (Use ContextMenu and MainMenu separately.)
      17. Drag/Drop support is changed.
      18. Clipboard object is changed.
      19. The default coordinate system is now "pixels", not "twips".
      20. Assigning x = y (without the word Set), used to copy the value of y to x.  If x and y were objects, it copied the value of the default property of y to the default property of x.  Now it copies an object reference for y to x (like the old Set statement).

      There is an "Upgrade Wizard" to help you edit all your source files, but it doesn't support all VB6 project types (for example, "DHTML Projects",and "Web Classes"), and doesn't handle many of the above changes.  For details from the author of the Upgrade Wizard himself about how many months or years it will take to upgrade your VB code, see:

          http://www.devx.com/free/hotlinks/2002/ednote022002/ednote022002.asp

      For more details, see:

          http://www.mvps.org/vb/index2.html?rants/vfred.htm
          http://www.vbpj.com/upload/free/features/vbpj/2000/12dec00/bh0012/default.asp

      For the official Microsoft "spin" on how lucky we are that they did this wonderful thing for us, see:

          http://msdn.microsoft.com/vstudio/techinfo/articles/upgrade/vbupgrade.asp
          http://msdn.microsoft.com/library/en-us/dnvb600/html/vb6tovbdotnet.asp

      --Fred

    2. VB.NET New Language Features

      Last Updated: 1/13/2001
      Applies to:  VB.NET+

      Here is a brief summary of some of the new language features planned for VB.NET.  You'll recognize most of them from languages like C++ and especially Java.

      1. Object-Oriented Features
        Implementation Inheritance
        "Inherits" keyword for classes
        "Overrides", "Overridable", and "MustOverride" keywords for methods
        "MyBase" keyword for calling parent version from an overridden method
        "Protected" keyword for methods
        "Overloads" keyword for methods
        Constructors
        Constructor is named "New" and takes parameters.
        First line of constructor must call parent class constructor via mybase.New
        More than one class per file using new "Class" keyword
        Nested classes
        Interface enhancements:
        New "Interface" keyword (like new Class keyword) to declare interfaces.
        Can use "Implements" at the method level, not just class level, to specify that a method of a class maps to a differently named method of an interface implemented by that class.  Can use this approach to use one method to implement multiple methods of multiple interfaces.
        Visual Inheritance of Forms
      2. Type Safety:  "Option Strict"
      3. Exception Handling
        Try ... Catch ... Finally ... End Try
      4. Free Threading
        Dim t as Thread
        Set t = New Thread (New ThreadStart (AddressOf objMyClass.Method1))
      5. Function Pointers can be passed to VB code, not just from it.
      6. "Shared" keyword for members
        Static data members.
        Instance-free class variables and methods
      7. Initializers:
        Dim intCount As Integer = 1
      8. "Namespace" and "Imports" keywords to manage namespaces.
      9. Cross-Language Support
        Can call to/from C++, C#, etc.
        Can inherit from classes in C++, C#, etc.
        Can step across language boundaries in debugger
      10. Distributed Application:
        Can debug remote components.
        Web Services
        "Webmethod" keyword
      11. Forms/Controls:
        "Control anchoring" (less need for explicit logic in Form_Resize())
      12. Memory Management
        Garbage Collection
      13. Deployment
        "File-copy based deployment" via "assemblies" containing "manifests" (no more registering of components)
      14. Data Types
        Short data type (16-bit, since Integer is now 32-bit)
        Decimal data type
        Char data type
      15. Operators
        BitAnd, BitOr, BitNot, and BitXor
      16. "ReadOnly" and "WriteOnly" keywords for properties

      For more details, see:

      http://www.inquiry.com/techtips/thevbpro/10_minute_solutions/10min0500mg.asp
      http://msdn.microsoft.com/library/default.asp?URL=/library/techart/vb6tovbdotnet.htm
      http://www.devx.com/free/press/2000/030100.asp

      --Fred

    3. See Also

      Last Updated: 1/13/2001
      Applies to:  VB.NET+

      For a good summary of the new features planned for VB.NET, including the IDE, language, etc., see:

      http://www.devx.com/free/press/2000/030100.asp

      --Fred

  11. Miscellaneous Tips

    1. Avoid Dim As New

      Last Updated: 12/19/1998
      Applies to:  VB4+

      VB allows you to declare a variable as:

      	Dim objClass1 As New CMyClass

      as a shorthand for:

      	Dim objClass1 As CMyClass
      	Set objClass1 = New CMyClass

      Do not use this shorthand. It has the undesired side effect of reallocating the object after you deallocate it, if you accidentally continue to refer to it.  This can cause memory leaks and hide programming errors.

      --Fred

    2. Dictionary vs Collection

      Last Updated: 12/27/1998
      Applies to:  VB6+

      VB6 includes the "Microsoft Scripting Runtime", which contains the Dictionary, a souped up version of the Collection you may have used in earlier versions of VB.  The Dictionary includes the same properties and methods as the Collection:

      Add Add an item to the collection or dictionary
      Count Get number of items in the collection or dictionary
      Item (Default) Get an item from the collection or dictionary
      Remove Remove an item from the collection or dictionary

      but also adds the following:

      CompareMode Controls how string comparison of keys is done (case-sensitive, case-insensitive, etc.)
      Exists Is the specified key in the dictionary?
      Item (Default) No longer read-only. You can set an item in the dictionary, rather than having to remove and re-add it.
      Items Get a Variant Array of the items in the dictionary
      Key Change the value of a key
      Keys Get a Variant Array of the keys in the dictionary
      RemoveAll Remove all items from the dictionary

      Beware of the following differences between a Collection and a Dictionary:

      1. Parameters of Add method (Key and Item) are in reverse order.
      2. Add method of Dictionary has no Before and After parameters.
      3. Item method of Dictionary takes only Key, not Index.
      4. Querying the Item method of a Collection with a bogus key raises an error; Querying the Item property of a Dictionary with a bogus key adds a null item with that key to the Dictionary.  (This must be a bug!)
      5. Iterating over a Collection with For Each returns the items; iterating over a Dictionary returns the keys.  (Another bug?  Or intentional because you are iterating over the "words" in a "dictionary", not over their "definitions"?  In any case, beware if you are used to Collections.)
      6. Others?

      To use the Dictionary, which at this point I do not recommend (too much unexpected behavior for me), you must first add the Microsoft Scripting Runtime to your VB project via the References dialog.   For more info, see the Jan 1999 issue of VBPJ.

      --Fred

    3. Print Without a Newline

      Last Updated: 5/12/2000
      Applies to:  VB5+ (perhaps earlier versions also)

      Looking for a way to print text to a file, form, printer, debug window, etc., without having VB automatically add a newline (vbCRLF) at the end of the printed text?

      Add a semicolon (;) to the end of the Print statement or method call, as:

      	Print #1, "This is a partial line";
      	frmMain.Print "This is a partial line";
      	Printer.Print "This is a partial line";
      	Debug.Print "This is a partial line";

      --Fred

    ©Copyright 1999-2014, Bristle Software, Inc.  All rights reserved.