Tips and Tricks for Visual Basic Programming:

This file contains gems I discovered while learning Visual Basic 5.0.  
In just three months I went from zero to writing a number of ActiveX
controls for the project I was working on.  The QA team was convinced
that the application was written in C++ -- the app had dynamically 
resizable dialog boxes, a tabbed main window, a pair of Explorer like
tree controls and even an animated easter egg in the about box!

Syntax: =================================================================

  Alternative way of using object methods and properties:
     Usually you use object.property but you can use
     [object].property if for example you named object the
     same as a keyword i.e. Loop

  Control Arrays:
     Referencing an item in a control array.
        Controls("List1")  Controls!List1  Controls(3)
        For Each Control In Form1.Controls()
           Control.Top = 200
        Next Control
     Adding to a Control Array:
        Load cmdArray(5)    Add a new control to the array
        Unload cmdArray(5)  Remove a control from the array
        cmdArray.Count returns the total number of controls in the array
        For i = LBound(cmdArray,1) to UBound(cmdArray,1)
           cmdArray(i).Top = 200
        Next i

   Control Containers:
      Form           Holds all controls on a form
      Frame          Use to group some controls together with a label
      PictureBox     Use to group controls together
      ToolBar        Use to group toolbar controls together

      Moving a control to a different container:
         cmdButton.Container = picContainer

   Predefined Collections:
      Forms          An array of all loaded forms
      Controls       An array of all controls on a form
      Printers       An array of the printers in the system

   Predefined Globals:
      App            Used to get info about the Application
      Screen         Used to get info about the Windows Desktop
      Err            Used to hold info about the last error

   Generic Objects:
      as Form        Use to point to any type of form in the project
      as Control     Use to point to any type of control
      as MDIForm     Use to point to any type of MDI form
      as Object      Use to point to any type of object

   Default Indexing:
      Control Arrays      Base 0
      Strings             Base 1
      Point(x, y)         Base 0, 0
      ListBox             Base 0
      Choose(idx,...)     Base 1
      ImageList           Base 1
      SSTab               Base 0
      StatusBar Panels    Base 1
      Collections         Base 1
      MSFlexGrid Row,Col  Base 0

   Default Properties:
      TextBox     .Text

      You may use the object name to assign to a default property
         txt = "Hello" instead of txt.Text = "Hello"
      To set a default property: Tools/Procedure Attributes/Advanced
      Remove the current default - find the Name and set Procedure ID
      to None.  Then set a new one.
      Can also use F2 - the object browser and clear/set the default
      with context menu

   Using New:
      1. Dim x as New Object
      2. Dim x as Object
         Set x = New Object

      2. is faster, prefer it
      When finished with an object: Set x = Nothing to free the memory
      When finished with a Form, Unload it and free the Hidden Global Reference
         Unload Form1: Set Form1 = Nothing
      Debug.Assert (Not obj Is Nothing) ' verify that an object points to something

      Create and show a new form:
         Dim frm as Form1
         Set frm = New Form1
         frm.Show
         ...
         Unload frm        ' Unload the form
         Set frm = Nothing ' Frees the form's memory

   To click a button in code:
      cmd.Value = 1 not cmd_Click()

   Form Lifetime:
      1. Created but not loaded
      2. Loaded, but not shown
      3. Shown
      4. Unloaded and unreferenced while a control is still referenced
      5. Memory and resources completely reclaimed

      ' Stage 1 in lifetime
      ' This is the first form event that gets executed
      ' Form exists as an object but has no window.
      ' No controls exist in the form either.
      ' All forms pass through this state.
      ' You can call Me.Show here to load and show the form
      Form_Initialize()

      ' Create but don't Load or Show a Form:
      Dim frm As Form1
      Set frm = New Form1
      ' You can call any custom Sub, Function and Property Procedures
      ' without forcing a load of the form.  If you access any built in
      ' properties of the form or any of the form's controls, it will
      ' be loaded.

      ' Create, Load and Show a Form
      Form1.Show

      ' Stage 2 in lifetime
      ' The form now has a window but hasn't been shown
      ' Controls have been created and loaded
      ' Forms pass through this function only once
      ' no matter how many times they are shown and hidden
      ' But if the form is Unloaded and Loaded multiple times
      ' then this will be called multiple times for the same form.
      Form_Load()

      ' Form is about to unload.
      ' We can prevent unloading by setting the Cancel argument
      ' We can determine how we're being unloaded:
      '  User clicked Close
      '  Unload called
      '  App closed
      '  Windows shutdown
      Form_QueryUnload(Cancel)

      ' Stage 4 in lifetime
      Dim frm As New Form1
      Dim obj As Object
      frm.Show() vbModal
      ' When the modal form is dismissed, save a reference to
      ' one of its controls
      Set obj = frm.Command1
      Unload frm
      Set frm = Nothing
      ' The form is now unloaded and all references to it released
      ' but we still refer to a control on the form so it's still
      ' in memory
      obj.Caption = "Back to life"
      ' Form_Load() was just called again by the above statement

      ' Unload the form.  Note, after unload,
      ' The form still exists in memory until all references
      ' to it and it's controls are set to Nothing
      Form_Unload()

   Static Properties of a Class:
      Simulating Static Class Member Variables:
      class.bas
         ' or make it a property get
         Public Property Get Static() as Integer
            return ClassGetSetStatic(false)
         End Property
         ' or make it a property let
         Public Property Let Static(ByVal iValue as Integer)
            ClassGetSetStatic(True, iValue)
         End Property
      class_stat.bas
         ' You should never call this function, only class.bas
         ' needs to call this.
         Public Function ClassGetSetStatic(bSet as boolean, optional value as integer) as integer
            Static iValue as Integer
            if bSet then iValue = value
            return iValue
         end Function

   Variant Properties of a Class:
      Private mvntAnything As Variant
      Public Property Get Anything() As Variant
         If IsObject(mvntAnything) Then
            Set Anything = mvntAnything
         Else
            Let Anything = mvntAnything
         End If
      End Property
      Public Property Let Anything(ByVal NewValue as Variant)
         Let mvntAnything = NewValue
      End Property
      Public Property Set Anything(ByVal NewValue as Variant)
         Set mvntAnything = NewValue
      End Property

   Choosing whether to make something a Property or a Method:
      The Syntax Argument:
         Do you want users to be able to say
         Set Widgets.Item(4) = wdgWidget
         If yes then a property is the way to go
      The Property Window Argument:
         Can you imagine the member showing up in the Property
         window or on a property page? If that doesn't make sense
         don't implement it as a property
      The Perception Argument:
         Do you want the member to be percieved as a method or
         as a property?  If retrieving the member is slow, it
         might be best to make it a member.  Properties are
         assumed to be fast.
      The Sensible Error Argument:
         If a user forgets that a property is read only and trys
         to assign to it, a property gives a more intuitive
         error message than a method.
      The Argument of Last Resort:
         Flip a coin.  If none of the other arguments seem
         compelling, it probably doesn't make much difference

    Implementing Class Events (Callbacks):
       Widget.cls
          Option Explicit
          Public Event PercentDone(ByVal Percent As Single, ByRef Cancel as Boolean)
          Public Sub LongTask(ByVal Duration as Single, ByVal MinimumInterval as Single)
             Dim sngThreshold as Single
             Dim sngStart as Single
             Dim blnCancel as Boolean
             sngStart = Timer
             sngThreshold = MinimumInterval
             RaiseEvent PercentDone(0, blnCancel)
             Do While Timer < (sngStart + Duration)
                ' Do some work
                If Timer > (sngStart + sngThreshold) Then
                   RaiseEvent PercentDone(sngThreshold / Duration, blnCancel)
                   if blnCancel Then Exit Sub
                   sngThreshold = sngThreshold + MinimumInterval
                End If
             Loop
             RaiseEvent PercentDone(100, blnCancel)
          End Sub
       Form1.bas
          Option Explicit
          Private WithEvents mWidget as Widget
          Private mblnCancel as Boolean
          Private Sub Form_Load()
             Set mWidget = New Widget
          End Sub
          Private Sub mWidget_PercentDone(ByVal Percent as Single, Cancel as Boolean)
             Static bMutex as Boolean ' Prevent re-entry with a mutex boolean
             if (Not bMutex) then
                iMutex = True
                lblPercentDone.Caption = CInt(100 * Percent) & "%"
                lblPercentDone.Refresh
                DoEvents
                If mblnCancel Then Cancel = True
                iMutex = False
             end if
          End Sub
          Private Sub Command1_Click()
             mblnCancel = False
             Call mWidget.LongTask(14.4, 0.66)
          End Sub
          Private Sub Command2_Click()
             mblnCancel = True
          End Sub

   Implementing Custom Form Events:
      Class1.cls
         Private WithEvents mForm1 as Form1
         Public Property Get Form1() as Form1
            Set Form1 = mForm1
         End Property
         Public Property Set Form1(ByVal NewForm1 as Form1)
            Set mForm1 = NewForm1
         End Property
         Private Sub mForm1_Gong()
            MsgBox "Gong!"
         End Sub
      Form1.bas
         Public Event Gong
         Private mc1 as Class1
         Private Sub Form_Load()
            Set mc1 = New Class1
            Set mc1.Form1 = Me
         End Sub
         Private Sub Text1_Change()
            RaiseEvent Gong
         End Sub

   Abstract/Concrete Classes:
      Animal.cls (Abstract class)
         Public Sub Move(ByVal Distance as Double)
         End Sub
         Public Sub Bite(ByVal What as Object)
         End Sub
      Flea.cls (Concrete class implementing Animal)
         Option Explicit
         Implements Animal
         Private Sub Animal_Move(ByVal Distance as Double)
            Debug.Print "Flea Moved"
         End Sub
         Private Sub Animal_Bite(ByVal What as Object)
            Debug.Print "Flea bit a " & TypeName(What)
         End Sub

   Debugging Object Creation/Termination
      Keep track of all objects created in a global collection.
      Then as you destroy objects you can see which ones are left
      by iterating over the global collection.
      globcol.bas
         Public gcolDebug as New Collection
         Public Function DebugSerial() as Long
            Static lngSerial As Long
            lngSerial = lngSerial + 1
            DebugSerial = lngSerial
         End Function
      Add this code to all your class modules:
          Public mlngDebugID as Long
          Property Get DebugID() As Long
             DebugID - mlngDebugID
          End Property
          Private Sub Class_Initialize()
             ' Add string to global collection for this instance of the class
             mlngDebugID = DebugSerial
             gcolDebug.Add "MyClassName Initialize; DebugID=" & DebugID, CStr(DebugID)
          End Sub
          Private Sub Class_Terminate()
             ' Remove string from global collection for this instance of the class
             gcolDebug.Remove CStr(DebugID)
          End Sub

   Implementing a Collection Class:
      Employees.cls - the collection of employees
         Option Explicit
         Private mcolEmployees As New Collection
         Public Function Add(ByVal Name as String, ByVal Salary as Double) As Employee
            Dim empNew as New Employee
            Static intEmpNum as Integer
            With empNew
               intEmpNum = intEmpNum + 1
               .ID = "E" & Format$(intEmpNum, "00000")
               .Name = Name
               .Salary = Salary
               mcolEmployees.Add empNew, .ID
            End With
            Set Add = empNew
         End Function
         Public Function Count() As Long
            Count = mcolEmployees.Count
         End Function
         Public Sub Delete(ByVal Index as Variant)
            mcolEmployees.Remove Index
         End Sub
         ' Make this the default method of the class:
         ' Tools/Procedure Attributes/(Name=NewEnum)/Advanced/Procedure ID/(Default)
         ' This allows users to write Employees("E00001") instead of
         ' Employees.Item("E00001")
         Public Function Item(ByVal Index as Variant) As Employee
            Set Item = colEmployees.Item(Index)
         End Function
         ' This allows users to use For Each on the collection
         ' You need to hide this function in the type library:
         ' Tools/Procedure Attributes/(Name=NewEnum)/Advanced/Hide This Member/Procedure ID/-4
         Public Function NewEnum() as IUnknown
            Set NewEnum = mcolEmployees.[_NewEnum]
         End Function
      Employee.cls - The class to put in the employee collection
         Option Explicit
         Public Name as String
         Public Salary as Long
         Private mstrID as String
         Property Get ID() As String
            ID = mstrID
         End Property
         Property Let ID(strNew As String)
            Static blnAlreadySet as Boolean
            If Not blnAlreadySet Then
               blnAlreadySet = True
               mstrID = strNew
            End If
         End Property

Quick Tricks: ===========================================================
   Load an entire file into a string quickly:
      Dim iFILE as Integer
      iFILE = FreeFile
      Open szFile For Input As #iFILE
      szString = Input(iFILE, LOF(iFILE)) ' LOF=Length of File
      Close #iFILE
   
   C++ Style Exception handling approximation:
   On Error GoTo catch   ' try {
      do_something
      do_something_else  ' }
      If (Err) Then      ' catch (...) { 
      catch:             
         ' Be careful of errors here - app will terminate
         recover_error
      End If             ' }  
   continue_other_stuff  

   Resuming error handling in the catch section
   On Error GoTo Catch
      do_something
      do_something_else
      If (Err) Then
   Catch:
         Resume Here
      Here:
         On Error GoTo Catch22
         recover_error
      End If
      If (Err) Then
   Catch22:
         ' Handling an error from Here: block
         recover_recover_error
      End If
   continue_other_stuff  

Optimizing Code: ========================================================
   Checklist for Optimizing Code for Speed:
      Avoid using Variant.
        Use Option Explicit to help avoiding variant.
        Be careful declaring multiple variables on the same line.
        Dim x,y, z as Long -- x,y are variants not long
      Use Long Integer variables and integer math particularly in loops.
         If ScaleHeight is always set to twips or pixels then you can use
         long integers for all size and position calculations and graphics
         Use integer division \ if you don't need a decimal result.
         Long=Fastest, Integer, Byte, Single, Double, Currency=Slowest
      Cache frequently used properties in variables.
         Variables are 10-20 times faster than properties.  Never get
         properties more than once in a procedure unless you know it
         has changed.  Store a property in a variable when used in a loop
      Cache function returns if used more than once.
      Replace procedure calls with inline code.
         Although this produces code bloat and maintenance trouble
         it may be necessary to speed up critical code.
      Use constants whenever possible.
         Use intrinsic constants from Object Browser when possible.
         Unused constants are removed from the final .exe
      Pass arguments ByVal instead of ByRef.
      Use typed optional arguments.
         Variants are 16 bytes -- slow to push and fill up stack space
         quickly.
      Take advantage of collections.
         Use For Each Next instead of For Next.  For Each may be optimized
            for a given collection.
         Avoid using the Before and After arguments with Add.
         Use keyed collections rather than arrays for groups of objects
            of the same type.  If you can't key the objects and have to
            traverse the array to find then a straight array is faster.
            Arrays are faster for small collections of less than about
            100 objects.
      Set ClipControls property of containers to False.
         Unless you are using graphics methods (Line, PSet, Circle, Print)
         set ClipControls on the form, frame and picture box controls
      Use AutoRedraw appropriately.
         Set to true if you draw complex graphics which don't change
         very frequently.  Use False if you draw graphics that change
         frequently.  You must then perform the drawing in the Paint
         event.
      Use image controls instead of picture box controls.
         Use image when you are only displaying pictures and reacting
         to click events and mouse actions on them.
      Hide controls when setting properties to avoid multiple repaints.
         Place a number of controls on a picturebox and hide it prior
         to updating the contained controls.  Also, use Move to move
         a control instead of setting Left and Top separately.  This
         causes two repaints instead of one.
      Use Line instead of a series of PSet methods.
      Keep forms hidden but loaded.
         This reduces memory but makes showing forms much faster.
      Preload data.
         Load multiple files at the same time instead of making the user
         wait later.
      Use timers to work in the background.
         Use a static or module variable to keep track of progress and
         do a little bit each time through the timer.
      Use progress indicators.
         Use the standard Windows 95 ProgressBar and DoEvents to keep
         it updated.  Display the wait cursor with the MousePointer
         property (vbHourglass)
      Speed the start of your application.
         Use Show in the Form_Load event.
            Place show at the start of Form_Load so it is visible
            while the rest of the startup code executes
            Me.Show: DoEvents: Load MainForm: Unload Me: MainForm.Show
         Simplify your startup form.
            Use a simple splash form to let the user know that the
            application has started.  Preload the most commonly used
            forms while the splash screen is up.  Use a progress bar.
         Don't load modules you don't need.
            Avoid calling procedures from other modules in your startup
            form.
         Run a small Visual Basic application at startup to preload
            the run-time DLLs.
            Create a small but useful application like a calendar and
            install that to the startup folder.  Then the VB DLLs will
            already be loaded.
      Use early binding.
         Dim X as New MyObject instead of Dim X as New Object.  Only
         declare as Object if you don't have the type library available
         or you need to be able to pass any kind of object as an argument.
      Minimize the dots.
         Especially when referring to other applications' objects.
         Use the default property where possible and keep a reference
         to objects deep in a chain instead of always referring to it
         with the full reference.  Take advantage of other methods available
         from the object to accomplish the same things
         i.e. Application.Workbooks.Item(1).Worksheets.Item("Sheet1").Cells.Item(1,1)
         becomes Application.Workbooks(1).Worksheets("Sheet1").Range("A1")
      Use Set and With ... End With.
         Set xlrange = Application.ActiveSheet.Cells(1,1)
         then do stuff with xlrange instead of repeating the long
         expression.
         or With Application.ActiveSheet.Cells(1,1)
            ...
         End With
      Minimize cross-process calls.
         Especially in a loop.  If possible build a macro loop in the
         object and call that.  Design your ActiveX components with
         loops so users can do the same.  Provide methods which take
         a number of parameters and set properties from the parameters
         so users don't have to call several property methods in a row.
   Checklist for Optimizing Code for Size:
      Reduce the number of loaded forms.
         Set form = Nothing after you've done an Unload and only load
         the form when needed.
      Reduce the number of controls on each form.
         Use control arrays instead of a large number of controls of
         the same type.
      Use labels instead of text boxes.
      Keep data in disk files or resources and load only when needed.
         This is particularly useful for large bitmaps and strings.
      Organize your modules.
         Modules are loaded on demand.  Group common functions
         together in small modules to reduce the amount of loading done.
      Consider alternatives to variant data types.
         Only use variants when you need a variable to change types
         and you can't do it easily with other data types.
      Use Dynamic Arrays and Erase to reclaim memory.
         Use Erase or ReDim Preserve to discard unneeded data in a
         dynamic array.
      Reclaim space used by strings or object variables.
         Reclaim string space from global and module level strings
         when you're through with them.  Also set Object references
         to nothing to reclaim memory.
      Eliminate dead code and unused variables.
         Simple Debug.Print statements are omitted from the final .exe
            but if any functions are called in the Debug.Print statement
            they are still executed.  Remove these dead calls in the final
            product or use an #if.
         Finding unused global/module variables.  Use Option Explicit
            in all modules and then comment out the variable declaration.
            If no errors then the variable is unused.
      Use the image control to display bitmaps.
      Load bitmaps from files as needed and share pictures.
         Store pictures in a resource file and use LoadResPicture to load
         them.  Share the same picture between controls if possible.
      Use the PaintPicture method.
         Use PaintPicture to display a bitmap on a form instead of
         an image control.  Especially if you want to tile the bitmap.
      Free the memory used by graphics.
         Set Picture properties to Nothing when they are not in use.
         If using AutoRedraw you can reclaim the memory used by the Image
         buffer: pic.AutoRedraw = True: pic.Cls: pic.AutoRedraw = False
      Use RLE-Format Bitmaps or Metafiles.
         Save images as RLE encoded so they will load faster and take
         up less memory.  Metafiles can save even more in some situations.
         Avoid scaling metafiles to a larger size.  JPG and GIF are also
         good compression sizes.

    Source: geocities.com/gurucoder/Tools

               ( geocities.com/gurucoder)