Visual Basic/Idioms

From Wikibooks, the open-content textbooks collection

< Visual Basic
Jump to: navigation, search

Contents

[edit] Introduction

An idiom is a sort of template or generic method of expressing an idea. In the same way that idioms in a human language make life easier for the both speaker and listener good idioms in computer programming make life easier for the programmer. Naturally what is idiomatic in one language might not be in another.

It is not necessary that there exist a physical template, an idiom can exist in your head and be so familiar that when faced with a problem that can be solved by application of an idiom you begin to type the code almost without noticing what you are doing.

Another word often used for ideas like this is pattern. However the word pattern has become so formalised and so strongly associated with object orientation that I feel the more generic word idiom is better also it is the word generally associated with the first major example that I will show.

An idiom can be language specific, library specific, domain specific, or any combination of the three. For instance if you are writing a program that deals with lists of things you might borrow some of the list processing idioms of Lisp even though Visual Basic Classic has no native list processing functions at all. By creating functions that manipulate lists rather than inlining the code to do it you will make your code more meaningful to someone reading it.

It is a commonplace that programmers feel ever so slightly godlike because they are in total control of their programs. I think that programmers would be better off emulating Shakespeare instead: when the English language of his day wasn't up to saying what he wanted he had no hesitation in extending both its stylistic range and its vocabulary.

[edit] Resource Acquisition Is Initialization - RAII

The name of this idiom makes it sound like a very complicated idea but in fact it is very simple and fits Visual Basic Classic very well. This idiom is a solution to problems such as setting and clearing busy flags, controlling access to files, setting the mouse icon and so on.

The basic idea is to use the constructor of an object to obtain or query a lock on some resource and to use the destructor to release that lock. In a language like Visual Basic which has deterministic finalization this produces very succinct code.

Let's take the mouse as an example.

[edit] Example: Mouse Busy Icon

It is very common in Windows programs to set the mouse icon to indicate that the program is busy and the the user must wait. A problem arises when you have lots of different pieces of code that can take a long time and lots of different code paths that invoke them. If the caller doesn't know that the process will be time consuming it won't know that the icon must be changed so we put code in the long running routine to change the mouse pointer. This works fine until we try to combine two or more such routines: which one gets to cancel the busy icon?

One common solution is to regard the mouse as a resource and control access to it with a pair of routines that maintain a count. One routine increments the count when the routine sets the busy icon and the other decrements and clears the icon when the count goes to zero. This works well until an exception occurs, then a routine might exit early and the routine that decrements the counter won't be called. Of course you could put another call in the error handler but that causes extra maintenance.

A better solution is to make use of the automatic termination events built in to Visual Basic. The basic idea is still to use a count but instead of guarding the count with a routine you guard it with an object.

Each procedure that wants to indicate that it is time consuming by showing the mouse busy icon should wrap its code in a with statement that creates a cMouseBusy instance by calling NewMouseBusy. When the routine finishes the cMouseBusy instance will be disposed of automatically, its Terminate event handler will be executed and the reference count will be reduced by one.

Because the program might have many forms open we must take account of the form when we set the busy icon because the MousePointer property belongs to a form not the application

So that we know which form's mouse pointer to change we must keep a separate count for each different client

[edit] Exercises

  • Compile the example code and check that it works.
  • Extend the code to allow for more than one type of busy icon. For instance an icon with a pointer that indicates that the user interface is still live and one without a pointer that indicates that the user must wait.
  • Create a test program to demonstrate the new features.

[edit] Code

Just add this line as the first line of every function that should show the busy cursor:

 Dim oMousebusy As cMouseBusy: Set oMousebusy = NewMouseBusy(Client)
 

or enclose the busy section in:

 with NewMouseBusy(Client)
   ...
 End With

Client is anything that has a mouseicon property


[edit] cMouseBusy

Objects of this class acquire the mouse busy status when created by a constructor called NewMouseBusy. They release it when they are terminated.

The busy status is simply a counter that is incremented each time a new cMouseBusy object is created and decremented when such an object is destroyed. If the counter increments to one then the mouse busy icon is set on the client object, when the counter decrements to zero the normal icon is set.

 VERSION 1.0 CLASS
 BEGIN
   MultiUse = -1  'True
   Persistable = 0  'NotPersistable
   DataBindingBehavior = 0  'vbNone
   DataSourceBehavior  = 0  'vbNone
   MTSTransactionMode  = 0  'NotAnMTSObject
 END
 Attribute VB_Name = "cMouseBusy"
 Attribute VB_GlobalNameSpace = False
 Attribute VB_Creatable = True
 Attribute VB_PredeclaredId = False
 Attribute VB_Exposed = False

 Option Explicit
 
 Private moClient As Object  
 
 Friend Sub Initialize(ByRef roClient As Object)
   Set moClient = roClient
   If IncrefCount(roClient) = 1 Then
     roClient.MousePointer = vbHourglass
   End If
 End Sub
  
 Private Sub Class_Terminate()
   If DecRefCount(moClient) = 0 Then
     moClient.MousePointer = vbDefault
   End If
 End Sub

[edit] modMouseBusy.bas

This module provide the constructor for cMouseBusy. This gives us the ability to pass arguments to the object when it is created.

 Attribute VB_Name = "modMouseBusy"
 
 Option Explicit
 
 Private moReferenceCount As Dictionary
 
 Public Function NewMouseBusy(ByRef roClient As Object) As cMouseBusy
   If moReferenceCount is Nothing then
     Set moReferenceCount = New Dictionary
   End If
   Set NewMouseBusy = New cMouseBusy
   NewMouseBusy.Initialize roClient

Remember that Visual Basic is single threaded and that the User Interface will not update until all code stops executing unless you call DoEvents so do this to make sure that the change to the mouse icon is visible.

   DoEvents
 End Function

This function increments the count for the given object and returns the new count.

 Public Function IncrefCount(ByRef roClient As Object) As Long
   Dim lRefCount As Long
   IncrefCount = 1 + moReferenceCount(roClient)
   moReferenceCount(roClient) = IncrefCount
 End Function

This function decrements the count and returns the new count. Note that if the count goes to zero then the entry is removed from the list; this must be done because the keys are objects and they will not terminate if we do not release the reference.

 Public Function DecRefCount(ByRef roClient As Object) As Long
   DecRefCount = moReferenceCount(roClient) - 1
   If DecRefCount = 0 Then
     moReferenceCount.Remove roClient
   Else
     moReferenceCount(roClient) = DecRefCount
   End If
 End Function
 

[edit] frmTestMouseBusy

This form lets you test the mouse busy class. Just click the buttons and watch the mouse cursor.

 VERSION 5.00
 Begin VB.Form frmTestMouseBusy 
    Caption         =   "frmTestMouseBusy"
    ClientHeight    =   2175
    ClientLeft      =   60
    ClientTop       =   360
    ClientWidth     =   4140
    LinkTopic       =   "Form1"
    ScaleHeight     =   2175
    ScaleWidth      =   4140
    StartUpPosition =   3  'Windows Default
    Begin VB.CommandButton Command2 
       Caption         =   "Command2"
       Height          =   735
       Left            =   2160
       TabIndex        =   1
       Top             =   480
       Width           =   1215
    End
    Begin VB.CommandButton Command1 
       Caption         =   "Command1"
       Height          =   735
       Left            =   480
       TabIndex        =   0
       Top             =   480
       Width           =   1215
    End
 End
 Attribute VB_Name = "frmtestMouseBusy"
 Attribute VB_GlobalNameSpace = False
 Attribute VB_Creatable = False
 Attribute VB_PredeclaredId = True
 Attribute VB_Exposed = False
 Option Explicit
 
 
 Private Sub xb1(ByRef delay As Long)
   With NewMouseBusy(Me)
     Dim t As Double
     t = Timer
     Do While t + delay > Timer
       DoEvents
     Loop
     xBug
   End With
   
 End Sub
 
 
 Private Sub xBug()
   With NewMouseBusy(Me)
     Dim t As Double
     t = Timer
     Do While t + 3 > Timer
       DoEvents
     Loop
     Debug.Print 1 / 0
   End With
   
 End Sub
 
 Private Sub Command1_Click()
   On Error Resume Next
   xb1 3
 End Sub
   
 Private Sub Command2_Click()
   On Error Resume Next
   xb1 5
 End Sub
     
 

[edit] prjTestMouseBusy.vbp

The project file for the test. Note that you need a reference to the Microsoft Scripting Runtime library to provide the Dictionary class.

 Type=Exe
 Reference=*\G{00020430-0000-0000-C000-000000000046}#2.0#0#..\..\..\..\..\..\WINNT\System32\stdole2.tlb#OLE Automation
 Reference=*\G{420B2830-E718-11CF-893D-00A0C9054228}#1.0#0#..\..\..\..\..\..\WINNT\System32\scrrun.dll#Microsoft Scripting Runtime
 Class=cMouseBusy; cMouseBusy.cls
 Module=modMouseBusy; modMouseBusy.bas
 Form=frmtestMouseBusy.frm
 Startup="frmTestMouseBusy"
 HelpFile=""
 Command32=""
 Name="prjTestMouseBusy"
 HelpContextID="0"
 CompatibleMode="0"
 MajorVer=1
 MinorVer=0
 RevisionVer=0
 AutoIncrementVer=0
 ServerSupportFiles=0
 VersionCompanyName="ABB"
 CompilationType=0
 OptimizationType=0
 FavorPentiumPro(tm)=0
 CodeViewDebugInfo=0
 NoAliasing=0
 BoundsCheck=0
 OverflowCheck=0
 FlPointCheck=0
 FDIVCheck=0
 UnroundedFP=0
 StartMode=0
 Unattended=0
 Retained=0
 ThreadPerObject=0
 MaxNumberOfThreads=1

[edit] References

Personal tools
Create a book
  • Add wiki page
  • Collections help