Home Page                                                                  x

Basically Visual Columns
Visual Basic Tips and Techniques


 

Visual Basic Programming Tips 3

Who is Peter G. Aitken...

...and why should you care what he has to say about Visual Basic programming?

Send me e-mail

Visual Basic Programming Tips  4

Basically Visual

You can read the full text of past Basically Visual columns. These were originally published in Visual Developer magazine. Unfortunately this excellent magazine stopped as of May 2000, so Column 24 was my last.

Column 24. (March/April 2000) Drag and Drop.
Column 23
. (January/February 2000) ActiveX Documents part 2.
Column 22
. (November/December 1999) ActiveX Documents part 1.
Column 21
. (September/October 1999) Visual Basic for Applications.
Column 20
. (July/August 1999) Data Sources, Data Sinks (part 2).
Column 19
. (May/June 1999) Data Sources, Data Sinks (part 1).
Column 18
. (March/April 1999) VBScript and Active Server Pages for Easy Web Database Programming.
Column 17
. (January/February 1999) Visual Basic and the Common Gateway Interface.
Column 16
. (November/December 1998) Object oriented file access.
Column 15
. (September/October 1998) Visual Basic 6.
Column 14
. (June/July/August 1998) The Windows Common Controls.
Column 13
(April/May 1998): Internet programming.
Column 12
(February/March 1998): Creating and using custom events.
Column 11
(December/January 1998): Screen capture the Visual Basic way.
Column 10
(October/November 1997): Creating bitmaps from arbitrary pixel data.
Column 9
(August/September 1997): Pointers and callbacks.
Column 8
(June/July 1997): ActiveX programming, part 2.
Column 7
(April/May 1997): ActiveX Programming, part 1.
Column 6
(February/March 1997): Visual Basic 5.
Column 5
(December/January 1997): Using the TreeView control.
Column 4
(October/November 1996): Taskbar tray icons.
Column 3
(August/September 1996): Using resource files with Visual Basic
Column 2
(June/July 1996): Variant speed penalties, uses for collections.
Column 1
(April/May 1996): Objects in Visual Basic.

Visual Basic Programming Tips  5

Visual Basic Tips and Techniques

Here I present solutions to a few problems that I have encountered in my programming. Perhaps you will find something that is useful to you. These tips are all relevant to pre-.NET versions of Visual Basic.

Web-related

Customizing the WebBrowser control
Avoid using the Internet Transfer control
Validating web links
Sending email from a Visual Basic program

Screen, forms, and controls

Clear all Text Boxes on a form
Create blinking text on a form
Display 3-D text on a form
Change Text Box margins
Aligning controls at a specific position
Don't forget the Tag property
Sizing a Form's interior
Getting colors from a PictureBox control
AutoRedraw and the Paint event
Capturing screens from a Visual Basic program
Filtering TextBox input
Getting and using screen information
Clipping the mouse cursor
Move the mouse cursor in code
Drawing "rubber-band" boxes
Undo for Text Box controls
Disable a form's Close button
Modify the System menu
Accept only uppercase letters in a Text Box
Keeping a form on top
Using custom mouse cursors
Ensuring that all forms unload
Remembering a form's size and position
Support formatted text with the Rich TextBox control
Toggling a form's title bar at run-time
Understanding the ComboBox control
Get to know the SysInfo control
Changing ComboBox height
Implement auto-find in a List Box 
Selecting all text when a Text Box gets the focus
Save time with control arrays
Adding controls to a form at runtime
Scrolling controls on a form
Understanding the KeyPreview property
Create a pane splitter
Changing text alignment for Forms and Picture Boxes
Create an "auto-OK" dialog box
Create a "Sticky" Button
Creating Graphical Command Buttons
Implementing mouse-over effects
Using the PictureClip control
Implement a MouseExit event
Fire a Command Button repeatedly

File/disk operations

Create a temporary file name
Verify that a path is writable
Determining the type of a drive
Using INI files for program settings
Deleting files to the recycle bin
Dragging files to your Visual Basic program
Display file properties
Display a select folder dialog

Data manipulation

Reverse character order in a string
Parsing strings
Is that on a weekend?
Encoding and decoding passwords
Implementing a stack
Counting strings
Converting numbers between decimal and binary
Use the Data Report Designer
Making the most of UDTs
Comparing dates in Visual Basic
Using the Collection object
Normalize spaces in a string

Input/output

Send raw data to the printer port
Creating synthetic keystrokes
Detecting the state of the "lock" keys
Using the standard input/output streams in Visual Basic
Playing WAV files
Playing the Windows system sounds
Keeping an application log file

Miscellaneous

Detecting a sound card
Reduce the size of Visual Basic distribution files
Easy creation of database connection strings
Do you DoEvents?
Maximizing and evaluating computational performance
Using command line arguments
Use environment variables
Prevent multiple program instances from running
Avoid the End statement
Creating formless Visual Basic applications
Improving the Shell function
Unlocking a program
Determine if the Visual Basic IDE is running
Using a resource file in a Visual Basic project
Control the CD-ROM door
Get data from Excel
Understanding VB's advanced compiler options
Don't forget the Class Builder utility
Take advantage of conditional compilation
Cautions when using the Setup and Deployment Wizard on Windows XP
Tools for working with XML
Save time with the API Text Viewer
Keeping track of program usage
Getting the current User Name
Working with project groups
Creating and using global properties
Creating and using DLLs
Understanding ByVal and ByRef
Avoid bugs with Option Explicit
Customize your code editor
Make use of Visual Basic's constants
Using objects as properties
Using the Friend keyword
Making use of polymorphism
Use the Collection object
Simplify programming with enumerations
Display the Windows search dialog
Validate credit card numbers


Visual Basic Programming Tips  6

Customize the WebBrowser Control

The WebBrowser control is a powerful tool useful in many Web-related projects. In effect, this control provides the browser functionality of Internet Explorer in a software component that you can incorporate in your projects. In some applications it is desirable to customize the WebBrowser control's behavior. While some customization is possible through the control's properties and events, this is sometimes not sufficient. Microsoft has made a tool available that permits certain WebBrowser customizations, specifically having to do with accelerator keys and context (right-click) menus. You can disable context menus, and can disable all or selected accelerator keys. The tool is called WBCustomizer and is available by downloading WBCustom.exe from http://support.microsoft.com/support/kb/articles/Q183/2/35.ASP and executing it to extract WBCustomizer.dll and a sample project.

To use the WBCustomizer object, you must register the DLL as follows using the Run command:

regsvr32 wbcustomizer.dll

Use the full path to the DLL file as required. In your Visual Basic project the component must be selected using the References dialog box in order to permit early binding. Then, using the object is relatively simple. First, declare a variable to reference the object and create an instance of it:

Dim CustomWB As WBCustomizer
Set CustomWB = New WBCustomizer

Then, set the object properties to specify the desired customization, and associate it with the WebBrowser control. This code assumes that the WebBrowser control is named WebBrowser1. It turns off all context menus and accelerator keys:

With CustomWB
  .EnableContextMenus = False
  .EnableAllAccelerators = False
  Set .WebBrowser = WebBrowser1
End With

The sample Visual Basic project that comes with the DLL provides examples of disabling specific accelerator keys.

Visual Basic Programming Tips  7

Avoid using the Internet Transfer Control

In theory the Internet Transfer Control is the best thing since sliced bread. In reality I consider it a real pain in the neck. Even though the latest version is vastly improved over the earlier releases, it is still buggy. Sometimes the OpenURL method does not retrieve an entire page. Also, I find that trying to use this control in a program that also uses automation to work with Office components often leads to weird and impossible to solve bugs. If you need to retrieve HTML pages, you can do so easily by calling some functions in the WinInet library. Without going into the details, here's how. First, put the following declarations in your project (in a code module):

Public Const INTERNET_OPEN_TYPE_PRECONFIG = 0
Public Const INTERNET_OPEN_TYPE_DIRECT = 1
Public Const INTERNET_OPEN_TYPE_PROXY = 3

Public Const scUserAgent = "VB OpenUrl"
Public Const INTERNET_FLAG_RELOAD = &H80000000

Public Declare Function InternetOpen Lib "wininet.dll" Alias "InternetOpenA" _
(ByVal sAgent As String, ByVal lAccessType As Long, ByVal sProxyName As String, _
ByVal sProxyBypass As String, ByVal lFlags As Long) As Long

Public Declare Function InternetOpenUrl Lib "wininet.dll" Alias "InternetOpenUrlA" _
(ByVal hOpen As Long, ByVal sUrl As String, ByVal sHeaders As String, _
ByVal lLength As Long, ByVal lFlags As Long, ByVal lContext As Long) As Long

Public Declare Function InternetReadFile Lib "wininet.dll" _
(ByVal hFile As Long, ByVal sBuffer As String, ByVal lNumBytesToRead As Long, _
lNumberOfBytesRead As Long) As Integer

Public Declare Function InternetCloseHandle Lib "wininet.dll" _
(ByVal hInet As Long) As Integer


Then, here's a function that returns the entire text of the specified URL:

Private Function GetHTMLFromURL(sUrl As String) As String

Dim s As String
Dim hOpen As Long
Dim hOpenUrl As Long
Dim bDoLoop As Boolean
Dim bRet As Boolean
Dim sReadBuffer As String * 2048
Dim lNumberOfBytesRead As Long

hOpen = InternetOpen(scUserAgent, INTERNET_OPEN_TYPE_PRECONFIG, vbNullString, vbNullString, 0)
hOpenUrl = InternetOpenUrl(hOpen, sUrl, vbNullString, 0, INTERNET_FLAG_RELOAD, 0)

bDoLoop = True
While bDoLoop
    sReadBuffer = vbNullString
    bRet = InternetReadFile(hOpenUrl, sReadBuffer, Len(sReadBuffer), lNumberOfBytesRead)
    s = s & Left$(sReadBuffer, lNumberOfBytesRead)
    If Not CBool(lNumberOfBytesRead) Then bDoLoop = False
Wend

If hOpenUrl <> 0 Then InternetCloseHandle (hOpenUrl)
If hOpen <> 0 Then InternetCloseHandle (hOpen)

GetHTMLFromUrl = s

End Function

Visual Basic Programming Tips  8

Send raw data to the printer port

While Visual Basic makes some things very easy, other things are ridiculously difficult. For example, sending raw data to the printer port seems to be impossible. When I say "raw," I mean really raw - I am not talking about unformatted text. I wanted to make the printer port's 8 data lines output a specific bit pattern, such as 00000101, under program control. My task was to use the computer to control a piece of laboratory equipment that was equipped with a parallel interface, but this technique might also be applicable for programming the many other types of parallel port devices, such as external mass storage, that are available today. I tried a variety of Windows API calls without success and finally realized that this was one of those few times when going outside of Visual Basic is unavoidable. The solution lay in Visual C++, which has the _outp() function to send a byte directly to a hardware port (like Quick Basic's old out() statement). I compiled the code into a DLL called PPORT.DLL, which you can download in ZIP format by clicking here. Put the DLL in the \Windows\System folder, then declare the function in your Visual Basic program as follows:

Declare Function SendByteToPort Lib "pport.dll" (p As Integer, b As Integer) As Integer

The argument p is the port number, which for LPT1: is &H378. The argument b is the value, in the range 0-255, to be sent. The function's return value can be ignored. Be careful when using this function, because by writing directly to the hardware it bypasses the Windows safety nets. You can get into all sorts of trouble if you are not careful! I have had reports that this does not work under Windows 2000.

Since I wrote the above programmer Kris Tilly has informed me that you can send raw data to the printer port without resorting to a DLL written in C++, but rather by calling Windows API functions from your Visual Basic program. I have not tested this, but Kris says it works fine.

Public Type DOCINFO
  pDocName As String
  pOutputFile As String
  pDatatype As String
End Type

Public Declare Function ClosePrinter Lib "winspool.drv" (ByVal _
  hPrinter As Long) As Long
Public Declare Function EndDocPrinter Lib "winspool.drv" (ByVal _
  hPrinter As Long) As Long
Public Declare Function EndPagePrinter Lib "winspool.drv" (ByVal _
  hPrinter As Long) As Long
Public Declare Function OpenPrinter Lib "winspool.drv" Alias _
  "OpenPrinterA" (ByVal pPrinterName As String, phPrinter As Long, _
  ByVal pDefault As Long) As Long
Public Declare Function StartDocPrinter Lib "winspool.drv" Alias _
  "StartDocPrinterA" (ByVal hPrinter As Long, ByVal Level As Long, _
  pDocInfo As DOCINFO) As Long
Public Declare Function StartPagePrinter Lib "winspool.drv" (ByVal _
  hPrinter As Long) As Long
Public Declare Function WritePrinter Lib "winspool.drv" (ByVal _
  hPrinter As Long, pBuf As Any, ByVal cdBuf As Long, _
  pcWritten As Long) As Long

Dim lhPrinter As Long
Dim lReturn As Long
Dim lpcWritten As Long
Dim lDoc As Long
Dim sWritteRdata As String
Dim MyDocInfo As DOCINFO

lReturn = OpenPrinter(Printer.DeviceName, lhPrinter, 0)
If lReturn = 0 Then
  MsgBox "The Printer Name you typed wasn't recognized."
  Exit Sub
End If
MyDocInfo.pDocName = "AAAAAA"
MyDocInfo.pOutputFile = vbNullString
MyDocInfo.pDatatype = vbNullString
lDoc = StartDocPrinter(lhPrinter, 1, MyDocInfo)
Call StartPagePrinter(lhPrinter)

For I = 1 To NumCopies
  sWritteRdata = ""
  lReturn = WritePrinter(lhPrinter, ByVal sWritteRdata, _
    Len(sWritteRdata), lpcWritten)
  lReturn = EndPagePrinter(lhPrinter)
Next I

lReturn = EndDocPrinter(lhPrinter)
lReturn = ClosePrinter(lhPrinter)

Visual Basic Programming Tips  9

Reduce the size of Visual Basic distribution files.

The Visual Basic Application Setup Wizard makes it easy to create a set of distribution files for your program. Given all of the support files that a Visual Basic program requires, however, distribution files tend to be rather large. This is a special concern when creating distribution files for download. It is important to ensure that your distribution files are as compact as possible. Yet, the Setup Wizard will sometimes include files in the distribution package that you are sure - or almost sure - that your program doesn't need. Why is this?

The problem is that the Setup Wizard is not smart enough to determine which software components your program actually needs, but can only detect those components that were available within the Visual Basic development environment at the time the program was compiled. For example, if the "SooperDooperGrid Control" was checked in the Components dialog box, its files will be included in the distribution files even though your program does not use it. Therefore, you need to remove all of the components that your program does not use from the Components dialog box before compiling your program. Fortunately, Visual Basic is pretty good at preventing you from removing components that are in use. I shrank a 2.5MB download to 1.6MB using this technique, so the gains can be significant.

Visual Basic Programming Tips  10

Reversing character order in a string

The function presented here is passed a string and returns the string with its character order reversed. "ABC" becomes "CBA" etc. Note that Visual Basic 6 has its own function StrReverse that does the same thing, so this is for users of earlier versions of Visual Basic.

Public Function Reverse(s As String) As String

' Returns a string with the characters
' in s in reverse order.

Dim buf As String, i As Long

If Len(s) < 2 Then
    Reverse = s
    Exit Function
End If
buf = ""
For i = Len(s) To 1 Step -1
    buf = buf & Mid(s, i, 1)
Next i

Reverse = buf

End Function
Visual Basic Programming Tips  11

Creating Synthetic Keystrokes

Visual Basic has a statement SendKeys that can be used to "generate" certain keystrokes just as if they had been pressed by the user. However this function is limited in that it cannot be used to simulate all possible keystroke combinations. I discovered this recently when trying to capture screens from within a Visual Basic program - you cannot send Alt+PrintScr using SendKeys. I was informed by reader Matt Hart that you can accomplish essentially the same thing with an API function, Keybd_Event which does not have the limitations of SendKeys. This function "synthesizes" a keystroke, and it does a pretty good job because this is the same function that is called by the keyboard’s hardware interrupt handler. As far as I can tell, the operating system has no way of knowing whether a keystroke came from someone's grubby little finger actually hitting the keyboard or was generated in software using Keybd_Event. Here’s the function declaration:

Private Declare Sub keybd_event Lib "user32" (ByVal bVk As Byte, ByVal _
bScan As Byte, ByVal dwFlags As Long, ByVal dwExtraInfo As Long)

Without going into all the details, here’s how you can capture screens to the clipboard. First declare the following constant:

Const VK_SNAPSHOT As Byte = &H2C

Then capture the active Visual Basic window to the clipboard with:

Call keybd_event(VK_SNAPSHOT, 0, 0, 0)

Or capture the entire screen (hiding the Visual Basic window first, if desired) with:

Call keybd_event(VK_SNAPSHOT, 1, 0, 0)

You’ll find a sample program that uses this code to capture AVI video images on Matt’s web page at http://www.blackbeltvb.com/

Visual Basic Programming Tips  12

Validating web links

You can be pretty sure that www.microsoft.com will always be a valid link, but many other web links tend to come and go as their authors move on, get bored, or whatever. If you maintain a web page with lots of external links, it is in your interest to ensure that the links are functioning. If lots of the links on your page are dead, your visitors will not be impressed! Many web page design tools, such as FrontPage, have a "validate links" command but I have found these to be fairly useless. They tell you which links are OK and which are not, but they do not permit you to do anything about the bad links (other than manually removing them from the pages). I needed a better solution.

I keep my links database in an Excel worksheet - one column for URL, one for Title, etc. I have written an Excel Basic macro that reads through the table and writes the HTML code for each of my links pages. I decided to write a Visual Basic program that would:

  1. Use DDE to read a URL from the links worksheet.
  2. Attempt to connect to the URL and note whether the URL is valid or not.
  3. Use DDE to put the label "OK" or "BAD" in the worksheet to indicate whether the URL was good or not.
  4. Continue for all the links in the worksheet.

Then, I would re-write the Excel Basic macro so that a link will be written to the HTML file only if the URL is marked as "OK." This approach has worked very well for me. Here's the code. Create a form with a Text Box, an Internet Transfer control, four Label controls, and a control array of two Command Buttons.  In the General section put the following code:

Option Explicit
Const FILENAME = "c:\documents\stamps\links.xls"
Dim XLObj As Excel.Application

Change the FILENAME constant to point to your worksheet file. Here is the code for the Form_Load and Command1_Click event procedures:

Private Sub Command1_Click(Index As Integer)

Select Case Index
Case 0 'Start
Command1(0).Enabled = False
Call CheckLinks
Command1(0).Enabled = True
Case 1 ' Quit
End
End Select

End Sub

Private Sub Form_Load()

' Create the Excel object and open the worksheet.
Set XLObj = CreateObject("Excel.Application")
XLObj.Workbooks.Open FILENAME
Inet1.Protocol = icHTTP

End Sub

The bulk of the action goes on in the CheckLinks() procedure.

Public Sub CheckLinks()

Dim row As Integer, url As String
Dim buf As String, msg As String, fnf As Integer
Dim snf As Integer, tout As Integer, ok As Integer

On Error Resume Next
' Make row equal to the Worksheet row where
' your data starts.
row = 4
tout = 0
fnf = 0
ok = 0
snf = 0

' Minimize the form.
Form1.WindowState = 1
Do
' I keep URLs in column 3 (C) of the worksheet.
url = XLObj.Cells(row, 3)
' If it's empty we are done.
If url = "" Then Exit Do
' Try to open the URL.
Text1.Text = Inet1.OpenURL(url)
DoEvents
' If the URL returned any text, put the
' first 50 characters in a buffer. Error
' messages will be found here.
If Len(Text1.Text) > 50 Then
buf = Left(Text1.Text, 50)
Else
buf = Text1.Text
End If
' Catch a time out error.
If Err = 35761 Then
msg = "Timed out"
tout = tout + 1
Err.Clear
' If nothing is returned it usually means
' that the server was not found.
ElseIf Text1.Text = "" Then
msg = "Server not found"
snf = snf + 1
' If error 404 is returned from the URL
' it means the server was found but
' the requested file was not present.
ElseIf InStr(1, buf, "404") Then
msg = "File not found"
fnf = fnf + 1
' Otherwise the link is OK.
Else
msg = "OK"
ok = ok + 1
End If
' Put the result in column 5 of the worksheet.
XLObj.Cells(row, 5) = msg
' Move to the next row.
row = row + 1
' Display current status on form.
Form1.Caption = ok + fnf + snf + tout
Label1.Caption = "OK: " & ok
Label2.Caption = "File not found: " & fnf
Label3.Caption = "Server not found: " & snf
Label4.Caption = "Timed out: " & tout
Loop While True

' When all links checked, restore the form.
Form1.WindowState = 0
' Close the worksheet.
XLObj.Workbooks.Close
' Delete the object.
Set XLObj = Nothing
' Display a summary of results.
buf = "OK: " & ok & vbCrLf
buf = buf & "Server not found: " & snf & vbCrLf
buf = buf + "File not found: " & fnf & vbCrLf
buf = buf & "Timed out: " & tout
MsgBox (buf)

End Sub

The program takes a while to run - a couple of hours to check a few hundred links - but because it is running in the background you can continue to use your system for other tasks. You can download a ZIP file containing the Visual Basic project by clicking here.

Note: Since originally creating this project I have made some modifications that increase its reliability and flexibility. First, the links information is kept in an Access database rather than in an Excel workbook. The Visual Basic program uses ADO to read and write the data table. Second, I have abandoned the Internet Transfer control in favor of using the WinINet library to implement the http protocol. Details of using WinINet are presented here.

Visual Basic Programming Tips  13

Easy Creation of  Database Connection Strings

For a Visual Basic program to connect to a data source, a connection string is often required. This string specifies the data source, user name, security information, and other aspects of the data connection. Here's an example of a fairly simple connection string:

Provider=Microsoft.Jet.OLEDB.3.51;Persist Security Info=False;User ID=AliceK;Data Source=C:\data\Northwind.mdb

Who can remember all the details required to write connections strings? I know I can't! Visual Basic can help you, as follows:

  1. Make sure the ADO Data Control is displayed in the Visual Basic toolbox. This is not the standard Data control, but is identified by a tooltip that says "ADODC". If it is not present you will have to add it by pressing Ctrl+T to open the Components dialog box then putting a checkmark next to "Microsoft ADO Data Control 6.0."
  2. Use the usual techniques to place an ADO Data Control on a form.
  3. In the Properties window, select the ConnectionString property then click the ellipses button (...) in the right-hand column.
  4. The control's property page displays, Select the Use Connection String option, then click the Build button.
  5. The next dialog box contains several tabs on which you can enter and select the options for the database connection, including the provider, the DSN (if you are using one), the user name and password, and the read/write permissions. You can also test the connection from this dialog box.
  6. When you are finished defining the connection, click OK. You will return to the control's Property Page. The generated connection string will be inserted in the relevant text box.
  7. Copy the generated connection string from the Property Page to your code.
  8. Delete the ADO Data Control from the form.

This method for creating connection strings is a lot faster and results in fewer errors than doing it manually. 

Visual Basic Programming Tips  14

Aligning Controls at a Specific Position

The Visual Basic form designer makes it easy to align a group of controls with one member of the group. Simply select the controls by holding down shift while clicking each control, being sure to click the target control (the one the others will be aligned to) last. Then, select Format|Align, then select Lefts for vertical alignment or Tops for horizontal alignment.

But what if you want to align controls at a specific position rather than to an existing control? For example, you might want to align several TextBox controls so their left edges are all exactly 50 twips from the left edge of the form. Here's how:

  1. Select all the controls as described above. It does not matter which control is selected last.
  2. In the Properties window, enter the desired distance from the left edge of the form in the Left property. Or, to align at a fixed distance from the top of the form, enter the distance in the Top property.

This technique can be extended to other design tasks. When more than one control is selected, the Properties window displays only those properties that are common to all the controls. Changing a property value is automatically applied to all the selected controls.

Visual Basic Programming Tips  15

Don't Forget the Tag Property

Most Visual Basic programmers never make use of the Tag property, available with most controls. After all, this property doesn't do anything so why bother with it? The truth is, it can be very useful in a variety of situations. You can store any information you want in a control's Tag property, and use that information any way you want.

One example is when you create a control array of CommandButton controls. Programmers usually use the Index argument that is passed to the Click event procedure to identify which button in the array was clicked. By assigning each button a descriptive Tag property you can use that instead and have more readable code. Here's the code for a simple example:

Private Sub Command1_Click(Index As Integer)

Select Case Command1(Index).Tag
    Case "Exit"
        ' Code to exit the program goes here.
    Case "Cancel"
        ' Code to cancel goes here.
End Select

End Sub

Another way to use the Tag property is when working with a TreeView control. A TreeView is made up of nodes, and it can be useful to have some data associated with each node in addition to the text it displays. This is easily accomplished by using the Tag property of each Node object.

Visual Basic Programming Tips  16

Do You DoEvents?

Lots of programmers don't even know about Visual Basic's DoEvents function. This is not surprising because few Visual Basic programs need it. DoEvents returns control to the operating system temporarily, allowing it to process other events that may have occurred. In my experience, the only time DoEvents is needed is when a program has code that takes a long time to execute, such as certain complex mathematical calculations. By calling DoEvents at strategic locations in your code you can improve program responsiveness.

To see what I mean, create a Standard EXE project in Visual Basic and place one CommandButton and one TextBox on the form. Then, put the following code in the Command Button's Click event procedure:

Private Sub Command1_Click() 

Dim i As Long, j As Long 

For i = 1 To 100
    Text1.Text = i
    For j = 1 To 100000
    Next
Next

Text1.Text = "Done"

End Sub

You can see that the code has one loop within another, loops that will take a few seconds to complete (You may want to adjust the value that the inner loop counts to depending on the speed of your system). Each time the outer loop iterates, the current value of i is displayed in the text box. When the loops are finished, "done" is displayed.

What actually happens when you run the program, however, is that the text box does not change until "done" is displayed. The problem is that the system was so busy executing the loops that the requests to display i in the text box got stalled in Windows queue. When the loops were finished, all these requests were processed, too quickly for you to see on the screen.

Now, place a call to DoEvents in the code, just after the Text1.Text = i statement. When you run the program you will see that the text box "counts up" the values of i, just as you would expect. Calling DoEvents frees up the system to process the request, then returns control to the Visual Basic program.

DoEvents is not without potential problems. For example, if you call DoEvents from a procedure you must be sure that the same procedure cannot be called again before execution returns from the first call - otherwise unpredictable results may occur. Likewise, DoEvents should be avoided if other applications could interact with the procedure in unforeseen ways. Use of a Timer control or isolation of long-running code in an ActiveX component are two other approaches to improving program responsiveness.

Visual Basic Programming Tips  17

Screen Capture in Visual Basic

Capturing images of the screen is useful in many situations, such as when documenting computer glitches or writing technical manuals and software documentation. Can you capture a screen image from a Visual Basic program? You bet. There's a complex way, involving Windows device contexts and the API, but it would be easier to use Window's built-in screen capture capability. Pressing the PrintScrn button copies the entire screen to the clipboard, while Alt+PrintScrn copies just the active window. All a Visual Basic program would need to do is to "press" this key.

Unfortunately, you cannot use Visual Basic's SendKeys for this purpose - it just won't work with PrintScrn. You must use the Keydb_Event API function. This function synthesizes a keystroke, and because it is the same function that is called by the keyboard’s hardware interrupt handler, it should do a good job of fooling an application - or Windows in this case - into thinking that the key was actually pressed. The function declaration is as follows:

Declare Sub keybd_event Lib "user32" (ByVal bVk As Byte, _
  ByVal bScan As Byte, ByVal dwFlags As Long, _
  ByVal dwExtraInfo As Long)

I can't go into all the details of this statement, but here’s how to capture screens to the clipboard. Start by declaring the following constant:

Const VK_SNAPSHOT As Byte = &H2C

Then you capture the active window to the clipboard with this code:

Call keybd_event(VK_SNAPSHOT, 0, 0, 0)

To capture the entire screen you use the following:

Call keybd_event(VK_SNAPSHOT, 1, 0, 0)

Once the screen image is on the clipboard, you will use the Clipboard object's GetData method to retrieve it as required by your application. The following code captures the screen and displays it in a PictureBox control, from where it can be saved to disk is desired.

Clipboard.Clear
Call keybd_event(VK_SNAPSHOT, 0, 0, 0)
DoEvents
Picture1.Picture = Clipboard.GetData(vbCFBitmap)

Please note two things about this code. First, you should call Clipboard.Clear before "pressing" PrintScrn to remove any old data from the clipboard. Second, you should call the Visual Basic DoEvents function before trying to retrieve the captured image, to allow Windows the time to process the keybd_event call.

Visual Basic Programming Tips  18

Determining Drive Type

 A disk drive can be one of several types. Sometimes a program needs to be aware of the type of the drive in order to know what it can and cannot do. For example, a program cannot write to a CD-ROM drive. Likewise, if a drive a removable a program cannot assume anything about what files are on the drive. You can use an API function, GetDriveType, to easily determine the type of a drive. This function has the following declaration, which must be included in your project:

Declare Function GetDriveType Lib "kernel32" Alias _
  "GetDriveTypeA" (ByVal nDrive As String) As Long

The function's argument is s string identifying the drive's root folder - for example, "c:\" or "f:\". The return value is a type Long, one of the following depending on the drive type. This list includes the constants that are usually defined for use in the program.

DRIVE_REMOVABLE = 2
DRIVE_FIXED = 3
DRIVE_REMOTE = 4
DRIVE_CDROM = 5
DRIVE_RAMDISK = 6

If the function returns any other value it means the drive was not recognized (that is, does not exist or is not available, as in a network drive).

The following Visual Basic procedure illustrates the use of this API function. The argument to this procedure must be in the form required by the GetDriveType function, that is the drive letter followed by ":\". Of course the API function declaration and the constant definitions must be included in any program that uses this procedure.

Private Sub DriveType(dr as string)

    Dim dr As String
    Dim drivetype as Long
    drivetype = GetDriveType(dr)
    Select Case drivetype
        Case DRIVE_REMOVABLE:
            MsgBox "Drive " & dr & " is a removable drive"
        Case DRIVE_FIXED:
            MsgBox "Drive " & dr & " is a fixed (hard) drive"
        Case DRIVE_REMOTE:
            MsgBox "Drive " & dr & " is a remote (network) drive"
        Case DRIVE_CDROM:
            MsgBox "Drive " & dr & " is a CD-ROM drive"
        Case DRIVE_RAMDISK:
            MsgBox "Drive " & dr & " is a RAM drive"
        Case Else:
            MsgBox "Drive " & dr & " was not recognized"
        End Select

End Sub

Visual Basic Programming Tips  19

Filtering TextBox Input

The TextBox control is commonly used to accept keyboard input from the user. In some situations you may want to restrict what the user can type to ensure that invalid data is not entered. For example:

- A TextBox for ZIP code could be restricted to accepting digits (and, if ZIP+4 is being used, the hyphen).
- A TextBox for entry of a person's first name could be restricted to letters.
- A TextBox for an email address could be restricted to letters, digits, and the period and @ characters.

To restrict entry into a TextBox you use its KeyPress event procedure:

Private Sub Text1_KeyPress(KeyAscii As Integer)

End Sub

The KeyAscii argument is a code identifying the key that was pressed as an ASCII value. Code in the KeyPress event procedure can examine the value of KeyAscii to see if a permitted character was entered. If so, do nothing and the character will be passed through to the TextBox. If the key is not permitted, set KeyAscii to 0 and the keypress is cancelled. KeyPress should always pass through the BackSpace key (ASCII value = 8) to permit the user to delete erroneous characters.

The following KeyPress event procedure lets only the digits 0-9 through, plus Backspace. The ASCII values for the characters 1 though 9 are 48-57.

Private Sub Text1_KeyPress(KeyAscii As Integer)

If KeyAscii = 8 Then Exit Sub
If KeyAscii < 48 Or KeyAscii > 57 Then
    Beep
    KeyAscii = 0

End If

End Sub

Note that the KeyPress event procedure will not intercept editing keys such as Del or the arrow keys, so you do not need to make provision to pass them through in your code.

Visual Basic Programming Tips VB6 20

Counting Strings

Visual Basic makes it easy to find one string within another using the InStr function. But what about counting the number of times one string occurs within another? One use for this is when performing Web searches. The more times a term of interest appears on a Web page, the more likely the page is to be relevant.

It's not too difficult to program string counting yourself. You can use the InStr function, calling it repeatedly until it returns 0. With each call after the first one, update the Start position to the location of the last "hit" plus one so that InStr will find the next occurrence of the target. Keep track of the hit count, and when InStr returns 0 you are done.

The code shown here shows how. The function is named CountSubstrings. You pass it the target string to be searched, the template you are looking for, and a binary value indicating whether you want text searches to be case-sensitive. Note that the code takes care of three "error" conditions that might occur:

- The target string is blank.
- The template string is blank.
- The template is longer than the target.

In any of these situations the function returns -1.

Public Function CountSubstrings(target As String, _
  template As String, CaseSensitive As Boolean) _
  As Integer

' Returns the number of times template occurs in target.
' Returns -1 if either string is blank or if template
' is longer than target. If CaseSensitive is true, performs
' a case-sensitive comparison for text. If false, the
' comparison is case-insensitive.

Dim pos1 As Integer, pos2 As Integer, count As Integer

If Len(target) = 0 Or Len(template) = 0 Or _
  Len(template) > Len(target) Then
    CountSubstrings = -1
    Exit Function
End If

count = 0
pos2 = 1

Do
  If CaseSensitive Then
    pos1 = InStr(pos2, target, template, vbBinaryCompare)
  Else
    pos1 = InStr(pos2, target, template, vbTextCompare)
  End If

  If pos1 > 0 Then
    count = count + 1
    pos2 = pos1 + 1
  End If
Loop Until pos1 = 0

CountSubstrings = count

End Function

Visual Basic Programming Tips  21

Create a Temporary File Name

The only requirement for a temporary file name is that the file name not already be in use in the folder where the file will be created. The function presented here takes a path as its one argument - the path where you nee the temporary file - and returns a filename (with path) that is not present in the specified folder and can therefore be safely used for a temporary file. If a blank string is passed as the path argument, the application path (App.Path) is used. The function returns a blank string on error. The function does not verify that the path that is passed to it is valid.

The operation of the code is simple. It starts with the value 1 and makes a file name from it (1.tmp). It then checks to see if this file already exists in the specified folder using the Dir function. If not, the name is suitable and is returned by the function. If the file exists, the value is increased and the new file name (2.tmp, 3.tmp, etc.) is tried until an unused name is found.

Public Function MakeTempFileName(path As String)
 
' Returns a filename (with path) that is not
' already in use in the indicated path. Name
' has the form path\1.tmp, path\2.tmp, etc.
' If path is blank then App.Path is used.
' Does not verify that path is valid.
 
Dim x As Integer, s As String
 
If path = "" Then path = App.path
 
' Be sure path ends with \.
If (Right(path, 1) <> "\") Then path = path & "\"
 
x = 0
Do
  x = x + 1
  s = path & x & ".tmp"
Loop Until Dir(s) = ""
 
MakeTempFileName = path & x & ".tmp"
 
End Function
 

Visual Basic Programming Tips  22

Getting and Using Screen Information

 A Visual Basic program needs to be able to run on different systems. In particular, the video equipment is likely to differ from one system to another. Joe may have an older laptop with 800x600 resolution, while Alice is running a new 21 inch monitor at 1600x1200 pixels. For some programs these video differences may not be important, but in other cases you may want to make sure that your program makes the best possible use of the screen real estate and resolution. You can use the Screen object to obtain information about the display hardware.

Recall that Visual Basic uses twips for screen measurements, with 1 twip supposedly  equal to 1/1440 inch. You can determine the screen's size in twips from two of the Screen object's properties, Screen.Width and Screen.Height. In theory you could use this information to determine the actual screen size as follows:

ScreenWidthInInches = Screen.Width/1440
ScreenHeightInInches = Screen.Height/1440

I have found, however, that the actual size of a twip varies a lot from system to system. On mine, for example, a twip is a lot closer to 1/1000 inch, and the above calculations will report my 21 inch monitor as being about. 9x12 inches! The twip values are accurate in terms of positioning items on the screen, though, and you can use them to position forms on the screen without going off the edge. This code, for example, centers a form on the screen:

Form1.Move (Screen.Width - Form1.Width) / 2, _
   (Screen.Height - Form1.Height) / 2

Here's another example that positions the form at the lower right corner of the screen on any system (although some of the form may be covered by the Windows task bar):

Form1.Move Screen.Width - Form1.Width, Screen.Height - Form1.Height

Visual Basic Programming Tips  23

Maximizing and Evaluating Computational Performance

With today's multi-gigahertz computers, programmers are rarely concerned with the speed of computations. Code runs plenty fast, and the bottlenecks for program performance are much more likely to be related to network access and other factors that are beyond the programmer's control. At times, however, raw computation speed does become an issue. This is most likely to happen when you are performing complex recursive tasks such as certain mathematical calculations and text processing operations. In these situations, even minor improvements to your code speed are likely to have an impact on the program's usability. Shortening a computation from, say, 60 seconds to 40 seconds is quite noticeable to the user. Here are some coding tips to maximize the performance of long computations:

 While writing your program you can use the Timer function to test the speed of calculations and help you in fine-tuning the code for maximum performance. The Timer function returns the number of seconds since midnight, accurate to 1/100 of a second. Set one variable equal to Timer just before calculations start, and set another variable equal to Timer when they are complete. A simple subtraction will tell you how long the computations took. Be sure to run your tests on a compiler version of the program and not in the IDE.

Visual Basic Programming Tips  24

Create Blinking Text on a Form

Blinking text is great way to call attention to something on the screen. It's easy to implement blinking text using Visual Basic's Timer control. This example shows you how to make a Label control blink, but the same approach could be used for other controls as well.

When you place a Timer control on a form, it does not display when the program runs -  it's available "behind the scenes." The Timer control has two important properties. The Interval property determines how often the Timer "ticks." The value is in milliseconds, so a setting of 500 will give you a twice per second Timer which I think is a good rate for blinking text. The Enabled property determines whether the Timer is running (Enabled = True) or stopped (Enabled = False).

The actual blinking is done in the Timer event procedure, which fires each time the Timer "ticks."  One approach is to toggle the Label control's ForeColor property between black, which makes it visible, and the value of its BackColor property, which makes the text invisible. You must check the current value of the ForeColor property and then set it accordingly, as shown here:

Private Sub Timer1_Timer()

If Label1.ForeColor = Label1.BackColor Then
    Label1.ForeColor = vbBlack
Else
    Label1.ForeColor = Label1.BackColor
End If

End Sub

You could also implement a different type of blinking, for example having the text alternate between red and green:

Private Sub Timer1_Timer()

If Label1.ForeColor = vbRed Then
    Label1.ForeColor = vbGreen
Else
    Label1.ForeColor = vbRed
End If

End Sub

To turn the blinking on or off, set the Timer control's Enabled property to True or False. When you turn the blinking off, you want to make sure that the text is not left in the invisible state, or with an inappropriate color. To do so, include code to set the ForeColor property to the desired color at the same time you turn the Timer control off:

Timer1.Enabled = False
Label1.ForeColor = vbBlack

Visual Basic Programming Tips  25

Using Command Line Arguments

Command line arguments date from the days of DOS when programs were run by entering the program name at the command line. Many programs could accept arguments placed after the program name, as shown here:

C>ProgramName argument1 argument2

These command line arguments could be read by the program and used according to the program's design. For example, some programs accepted an argument specifying the data file to open, and others accepted arguments that set certain program options. With the Windows operating system, where programs are started with a click of the mouse, the command line may seem to be a relic of the past, but it's not. Your Visual Basic programs can still use command line argument.

But Windows has no command line, so how can you pass command line arguments to a program? There are two ways. In the Visual Basic environment, when writing and testing your program, you display the Project Properties dialog box (Select Project|Properties) and, on the Make tab, you enter the arguments in the Command Line Arguments field. After the program is compiled you must use a shortcut to use command line arguments:

  1. Create a shortcut for the compiled Visual Basic program.

  2. Right-click the shortcut and select Properties.

  3. In the Properties dialog box, select the Shortcut tab.

  4. The Target field will contain the command used to start the program. This command will include the path to the compiled program plus the program name, for example "c:\program files\myprogram.exe".

  5. Edit this text to include the command line argument(s) after the program name. Be sure to use a space between the name and the arguments, and put the arguments inside the quotes (if present). For example "c:\program files\myprogram.exe argument1 argument2".

  6. Click OK to close the Properties dialog box.

Now, whenever this shortcut is used to start the program, the specified arguments will be passed to it. By creating more than one shortcut to the same program, you can start the program with different arguments depending on the situation.

In a Visual Basic program, the Command$ function returns a string containing any command line arguments that were passed when the program was started. The arguments (if more than one) are not parsed in any way - they are returned together in a single string. This string is blank if no arguments were passed. It is totally up to your program what it does, if anything, with the arguments it receives. Command line arguments are not for every programming situation, but they can provide some additional flexibility in some circumstances.

Visual Basic Programming Tips  26

Use Environment Variables

Environment Variables are maintained by the Windows operating system. Each variable consists of a name and a value. You can see the current environment variables by opening a Command Prompt window and typing Set. These variables are system-wide and can be used by any program. Many of them have to with the operating system, but you can create environment variables for use by your Visual Basic programs. Because such variables are available to all programs, they provide a convenient approach when you need to set options or operating parameters for multiple programs.

There are two ways to create or change an environment variable. In a batch file, such as AutoExec.bat, you use the Set command as follows:

Set varname=value

You can also open System from the Windows Control Panel. On the Advanced tab, click Environment Variables and use the dialog box tools to add, edit, or delete variables. Note that there are User variables, which are set for and can be changed by the currently logged on user, and System variables, which can be added/changed only by someone with administrator privileges (Windows NT/2000/XP).

To access environment variables in your Visual Basic program, you use the Environ function. There are two ways to use this function. If you pass the name of  an environment variable the function returns the value of that variable, or a blank string if the variable is not defined. If you pass a number, the function returns the entire environment string (the variable name, equal sign, and value) of the variable at the specified position. If there is no variable at the specified position, the function returns a blank string. This code displays all environment variables in a Message Box:

Dim buf As String, msg As String, idx As Integer

idx = 1
Do
    buf = Environ(idx)
    msg = msg & buf & vbCrLf
    idx = idx + 1
Loop Until buf = ""

MsgBox msg

By using the Environ function, your Visual Basic program can retrieve the values of any relevant environment variables and use this information as needed.

Visual Basic Programming Tips  27

Prevent Multiple Program Instances From Running

Many Windows programs, including programs created with Visual Basic, permit more than one instance, or copy, of the program to run at the same time. You can, for example, have two or more copies of Notepad running at the same time, editing different text files. There are situations in which permitting multiple instances of a program is not a good idea. A program might monitor a specific folder and keep track of new files that are placed there; there is never a need to have two instances of such a program running. Likewise, a program that is a front-end to a database should be restricted to one instance because having two or more copies running at the same time could lead to data errors.

To prevent multiple copies of a program from running, use the App.PrevInstance property. This property returns True if there is another instance of the program running. You can check this property in a program's Load event procedure. If it returns True, you can display a message to the user and, if desired, activate the running copy before terminating the new, duplicate instance. Here's a code example that you would place in the Load event procedure:

If App.PrevInstance Then
    MsgBox "Another instance of this program is already open."
    AppActivate App.Title
    Unload Me
End If

Visual Basic Programming Tips  28

Avoid the End Statement

Visual Basic's End statement immediately terminates the program, and because it's so easy to remember a lot of programmers use it regularly for program termination. There's a problem with End, however, in that the program's forms are not unloaded properly. Specifically, each form's Unload event procedure is not triggered. If your program uses the Unload event procedure to save data, close files, and perform other cleanup tasks, then using End to terminate the program is clearly not a good idea.

The preferred way to terminate a program is to unload all of its forms. With a single form program this is not a problem - just execute

Unload Me

When a program has multiple forms, this can be more of a problem - particularly when you do not know which forms are open at the time the program is terminating. To address this task, you can use the Forms collection, which contains all of the program's currently loaded forms. The technique is to loop through the collection, unloading each form in turn until all have been unloaded. The result is that the program is terminated properly, with each form's Unload event rpocedure being triggered. Here's the code:

Public Sub TerminateProgram()

Static Unloading As Boolean
Dim idx As Integer

If Unloading Then Exit Sub
Unloading = True

For idx = Forms.Count - 1 to 0 Step -1
    Unload Forms(idx)
Next idx

Unloading = False

End Sub

Note two things about this code. First, the forms are unloaded in reverse order, starting with the last one in the Forms collection. If you started with the first form in the collection, the form numbers would be reassigned by the Forms collection to fill in for the removed form, and the required code would be more complex. Second, a flag is set to prevent the unloading loop from executing if it is already in progress (if, for example, the TerminateProgram procedure was inadvertently called twice).

Remember that a form can block its own unloading by setting the Cancel argument to a non-zero value in the QueryUnload event procedure. In this case the procedure shown above may not be able to close all forms and terminate the program, and the user may have to close one or more forms manually.

Visual Basic Programming Tips  29

Clear all Text Boxes on a Form

You have probably see Web pages that contain many text fields, such as a form for online ordering. Such pages often have a Clear or Reset button that erases the text in all the fields so you can start over. You can implement a similar feature for Visual Basic forms, clearing all the Text Box controls on the form for the entry of new data.

To do so you will use the form's Controls collection. This collection contains an entry for every control on the form. By looping through the collection you can determine the type of each control using the TypeOf keyword. If a control is a Text Box, erase its text; if a control is not a Text Box, ignore it. Here's a procedure to clear all text Box controls on the form:

Public Sub ClearTextBoxes()

Dim c As Control

For Each c in Controls
   If TypeOf c Is TextBox Then
      c.Text = ""
   End If
Next

End Sub

You can extend this technique to other controls. For example, this code clears (unchecks) all CheckBox controls on the form:

For Each c In Controls
  If TypeOf c Is CheckBox Then
    c.Value = False
  End If
Next

Visual Basic Programming Tips  30

Implementing a Stack

A stack stores data in a last-in, first-out manner, and can be very useful in many areas of programming. This tip shows you how to implement a stack in Visual Basic. Traditionally a stack has two operations associated with it: Push to put something on the stack and Pop to retrieve something from the stack. Push always puts a data item on the top of the stack, and Pop always retrieves and removes whatever it on the top of the stack. Suppose you did the following Push operations in this order:

Push A
Push B
Push C

If you then did three Pops, you would retrieve C with the first Pop, B with the second, and A with the first. Note that a stack needs some way to signal when it is empty. 

A stack is best implemented as a Class module in Visual Basic. Basically, a stack is an array that holds the data plus a pointer that indicates which position in the array is currently the "top" of the stack. In my Stack class I used a dynamic array that can expand as needed to hold new items that are pushed onto the stack. When items are removed by popping them from the stack, the array does not shrink in size to avoid the overhead of repeatedly calling ReDim.

The code for the Stack class is shown here. This implementation uses type Variant to enable the stack to hold both string and numeric data. You could also create stacks designed specifically for certain data types. Note that the Class_Initialize event procedure initializes the pointer variable to 0 and the data array to a size of 1. Also note that Pop returns Null when the stack is empty, and the program that is using a stack needs to check for this to verify that Pop has returned valid data.

Private data() As Variant
Private pointer As Long

Private Sub Class_Initialize() 

  pointer = 0
  ReDim data(1)

End Sub

Public Function Pop()

  If pointer > 0 Then
    Pop = data(pointer)
    pointer = pointer - 1
  Else
    Pop = Null
  End If

End Function

Public Sub Push(value)

  pointer = pointer + 1

  If UBound(data) < pointer Then
    ReDim Preserve data(pointer)

  End If

  data(pointer) = value

End Sub

To use the Stack class, create a instance of it using the usual Visual Basic syntax and then call Push and Pop as needed. The following code is from a demo program that you can use to verify how the Stack class operates. To run it, follow these steps:

1. Create a new Visual Basic Standard EXE project.
2. Place two Command Button controls on the form.
3. Create a new Class module and change its Name property to Stack.
4. Add the code listed above to this class module.
5. Add the code below to the form module. 

Dim st As Stack

Private Sub Form_Load()

  Set st = New Stack

End Sub

Private Sub Command1_Click()

  Dim t As Single

  t = Timer
  Debug.Print "Pushed ", t
  st.Push (t)

End Sub

 

Private Sub Command2_Click()

  Debug.Print "Popped ", st.Pop

End Sub

When you run the program, you can click the command buttons to push and pop values (provided by the Timer function) on and off the stack. In a real-world program you'll find numerous uses for the Stack class, whenever you need to temporarily store data on a last-in, first-out basis. 

Visual Basic Programming Tips  31

Converting Numbers Between Decimal and Binary

While computers may use binary numbers internally, programmers usually use decimal and sometimes hexadecimal notation in their programs. Even so, it can still be useful to see the binary representation of a number, particularly when you are working with logical operators or doing bitwise comparisons. Converting a binary representation to decimal reverses the process. This tip shows you how to convert a positive integer into a string containing its binary representation, and then back again.

Binary notation is based on powers of 2, and this fact makes the conversion process surprisingly simple. It works as follows (for decimal value X):

1. Calculate X Mod 2. The result will be either 0 or 1. This is the first (right-most) binary digit.
2. Divide X by 2, discarding any remainder (in other words, perform an integer division using the \ operator).
3. If X is 0 you are done. Otherwise return to step 1 to determine the next binary digit. 

Let's work this through for the decimal value 13:

1. 13 Mod 2 is 1 so the first binary digit is 1.
2. 13 \ 2 is 6.
3. 6 Mod 2 is 0 so the second binary digit is 0.
4. 6 \ 2 is 3.
5. 3 Mod 2 is 1 so the third binary digit is 1.
6. 3 \ 2 is 1.
7. 1 Mod 2 is 1 so the fourth binary digit is 1.
8. 1 \ 2 is 0 so you are done. The result is 1101, the binary representation of 13

The code is presented here as a Visual Basic function. In addition to the value to be converted, the function takes an argument specifying the minimum number of digits in the binary value. If the actual value has fewer digits that this value, leading zeros are added to make up the difference.

Public Function DecimalToBinary(DecimalValue As Long, _
    MinimumDigits As Integer) As String

' Returns a string containing the binary
' representation of a positive integer.

Dim result As String
Dim ExtraDigitsNeeded As Integer

' Make sure value is not negative.
DecimalValue = Abs(DecimalValue)

' Construct the binary value.

Do
    result = CStr(DecimalValue Mod 2) & result
    DecimalValue = DecimalValue \ 2
Loop While DecimalValue > 0

' Add leading zeros if needed.
ExtraDigitsNeeded = MinimumDigits - Len(result)
If ExtraDigitsNeeded > 0 Then
    result = String(ExtraDigitsNeeded, "0") & result
End If

DecimalToBinary = result 

End Function

Converting a binary representation to a decimal number is the reverse of the process in the above function. Each digit in a binary value represents a power of 2, starting with 2^0 for the first (right-most) binary digit, 2^1 for the second digit, and so on. Note that any number to the 0 power is by definition 1, and any number to the 1 power is the number itself. Using 1101 for another example, you have:

The first digit is 1, and 1 times 2^0 is 1.
The second digit is 0, and 0 times 2^1 is 0.
The third digit is 1, and 1 times 2^2 is 4.
The fourth digit is 1, and 1 times 2^3 is 8.
8 + 4 + 1 equals 13.

A function to perform the binary to decimal conversion is shown here. 

Public Function BinaryToDecimal(BinaryValue As String) As Long

 ' Returns the decimal equivalent of a binary number.

Dim idx As Integer
Dim tmp As String
Dim result As Long
Dim digits As Integer

digits = Len(BinaryValue)
For idx = digits To 1 Step -1
    tmp = Mid(BinaryValue, idx, 1)
    If tmp = "1" Then result = result + 2 ^ (digits - idx)
Next

BinaryToDecimal = result

End Function

This function treats any character other than "1" in the binary value as a "0". You might want to add error-schecking code to ensure that the binary value contains only "0" and "1" characters.

Visual Basic Programming Tips 31

AutoRedraw and the Paint Event

Understanding the AutoRedraw property and the Paint event is important when you are using graphics in your Visual Basic programs. They are related to Visual Basic's graphics methods that let you create output on a form or a Picture Box control. These methods are Circle, Line, PaintPicture, Print, and PSet. These methods may not always work the way you expect. To see what I mean, create a new Standard EXE project and put the following line of code in the Click event procedure for the form:

Me.Circle (1500, 1500), 600

When you run the program and click the form, the circle appears just as you would expect. But try minimizing and then restoring the form - no circle (unless you click the form again)! You get the same result if you switch to another program that covers the form. What's going on - why is the circle temporary?

The answer lies in something called persistence. The form itself and any controls on it are persistent, which means that Visual Basic keeps a copy in memory and uses this copy to redraw the form on the screen when it has been hidden and then redisplayed. In contrast, the output of the graphics methods are not persistent - they are drawn to the screen only and are not saved in memory and cannot be redrawn when the form is hidden then redisplayed.

There are two ways to make the output of graphics methods persistent. One is to set the form's AutoRedraw property to True. This results in any output from the graphics methods being saved in memory as well as being drawn to the screen. They are now persistent and will remain visible when the form is hidden then redisplayed.

The other technique is to place the calls to the methods in the form's Paint event procedure. This event is triggered when the form is first displayed on screen and then again each time it needs to be redrawn after having been covered or minimized. To see this in action, move the line of code that calls the Circle method from the Click event procedure to the form's Paint event procedure. Now, the circle is displayed when the program starts and remains visible when the form is minimized and restored.

When AutoRedraw is set to True the form's Paint event procedure is never called. Which of these techniques you should use depends on the details of your program. Setting AutoRedraw to True usually simplifies programming, but it consumes extra memory and, particularly for complex graphics, can slowe down screen display.

Remember that the question of persistence applies only to the output of the graphics methods. Controls, bitmaps, and metafiles are always persistent.

Visual Basic Programming Tips 32

Getting Colors from a PictureBox Control

A Picture Box control can display various kinds of graphics images. Using the technique presented here, you can determine the color at any point in the image. 

The Point method returns the color at a specified X,Y location in a Picture Box control. To get the color under the mouse pointer, put the code in the MouseMove event procedure for the control. Because the MouseMove procedure is passed the current X,Y coordinates of the pointer, this code is simple: 

Private Sub Picture1_MouseMove(Button As Integer, _
   Shift As Integer, X As Single, Y As Single)

  Dim rgb As Long
  rgb = Picture1.Point(X, Y)

End Sub

The value returned by the point method is a type Long that encodes the RGB value for the color. To be useful, this encoded value must be separated into the individual R, G, and B components, each of which is an integer in the range 0-255 decimal or 00-FF in hexadecimal. Expressed in hexadecimal notation, the value returned by Point is:

00BBGGRR

Hexadecimal notation makes it easy to perform the extraction. If rgb is the value returned by the Point method, this extraction is performed as follows:

red = rgb Mod &H100
green = (rgb \ &H100) Mod &H100
blue = (rgb \ &H10000) Mod &H100

The final MouseMove event procedure is shown here. To see this in action, create a Standard EXE project and place a Picture Box and a Text Box on the form. Load an image into the Picture Box then run the project. You'll see that when the mouse moves over the image the Text Box displays the RGB values of the underlying pixel.

Private Sub Picture1_MouseMove(Button As Integer, _
    Shift As Integer, X As Single, Y As Single)

  Dim rgb As Long
  Dim r As String, g As String, b As String

rgb = Picture1.Point(X, Y)
r = CStr(rgb Mod &H100)
g = CStr((rgb \ &H100) Mod &H100)
b = CStr((rgb \ &H10000) Mod &H100)
Text1.Text = "R " & r & ", G " & g & ", B " & b

End Sub

You could place the same code in the MouseDown event procedure if you want to see the RGB values for only pixels that you click.

Visual Basic Tips

Sizing a Form's Interior

It's easy enough to set a form's size in code using its Width and Height properties, but this sets the form's outer size including borders and title bar. What if you want to set the form's interior area to a specific size, for example to precisely fit a Picture Box control? This tip shows you how.

You may think that you can change the form's ScaleWidth and ScaleHeight properties but this does not work. Setting these properties does not change the form interior's size but rather changes the units of measure used for graphics operations on the form. You can, however, read these properties to determine the size of the form's interior. This permits use of the following strategy:

  1. Get the Form.Width value. This is the overall width of the form including borders.

  2. Get the Form.ScaleWidth value. This is the width of the interior of the form.

  3. Subtract the second value from the first. The result is the amount that the form's borders contribute to its overall width. Call this value deltaX.

  4. Take the desired internal width and add deltaX to it. This is the overall width that the form will need in order to have the desired internal width.

  5. Set the Form.Width property to the value obtained in step 4.

 The same process is used for the height. To see this technique in action:

  1. Start a new Standard EXE project.

  2. Place a Picture Box control on the form and set its AutoSize property to True. This causes the Picture Box to automatically resize itself to fit the picture loaded into it.

  3. Load an image into the Picture Box control.

  4. Place the following code in the Form's Load event procedure.

Private Sub Form_Load()

Dim deltaX As Single, deltaY As Single

Picture1.Move 0, 0
deltaX = Me.Width - Me.ScaleWidth
deltaY = Me.Height - Me.ScaleHeight
Me.Height = Picture1.Height + deltaY
Me.Width = Picture1.Width + deltaX

End Sub

When you run the project you will see that the image is moved to the top left corner of the form and the form is resized to precisely fit the Picture Box. You can verify this by using the mouse to expand the form, revealing that the borders of the form and the Picture Box coincide exactly.

This technique requires that the ScaleMode, ScaleWidth, and ScaleHeight property of the form not be changed from the default values. This is because the size calculations depend on both the ScaleWidth, and ScaleHeight properties being in the default unit of twips.

Visual Basic Tips 33

Encoding and Decoding Passwords

It's not uncommon for programs to require some form of password protection. When a password is stored, in the registry for example, you do not want it in readable form because many users have the technical know-how to access the registry. Likewise, passwords are sometimes sent as part of a Web GET request. Some form of simple encryption should be used, as explained here.

There are various complex encryption alorithms available, but sometimes simple is better. A simple ASCII-value transposition is sometimes all that is needed. For example, suppose the password is "ajax7." You could encode it as follows:

1. The ASCII value of "a" is 97. Add 1 to get 98, which is the ASCII value of "b"

2. The ASCII value of "j" is 106. Add 1 to get 107, which is the ASCII value of "k"

3. The ASCII value of "a" is 97. Add 1 to get 98, which is the ASCII value of "b"

4. The ASCII value of "x" is 97. Add 1 to get 98, which is the ASCII value of "y"

5. The ASCII value of "7" is 55. Add 1 to get 56, which is the ASCII value of "8"

The encoded password is therefore "bkby8". The process of decoding is simply the reverse, subtracting 1 from each ASCII value. Visual Basic procedures for encoding and decoding passwords using this algorithm are shown here. It is assumed that passwords are limited to letters and numbers.

Public Function EncodePassword(pw As String) As String 

' Passed a pw, returns the encoded pw. 

Dim codedPW As String
Dim i As Integer
Dim ch As String * 1

If Len(pw) = 0 Then
    EncodePassword = ""
    Exit Function
End If

codedPW = ""
pw = Trim(pw)

For i = 1 To Len(pw)
    ch = Mid(pw, i, 1)
    codedPW = codedPW & Chr(Asc(ch) + 1)
Next i 

EncodePassword = codedPW

End Function


Public Function DecodePassword(codedPW As String) As String

' Passed an encoded pw, returns the decoded pw.

Dim decodedPW As String
Dim i As Integer
Dim ch As String * 1

If Len(codedPW) = 0 Then
    DecodePassword = ""
    Exit Function
End If

decodedPW = ""
codedPW = Trim(codedPW)

For i = 1 To Len(codedPW)
    ch = Mid(codedPW, i, 1)
    decodedPW = decodedPW & Chr(Asc(ch) - 1)
Next i

DecodePassword = decodedPW

End Function

You could increase the complexity of the algorithm in various ways. For example, the amount added to each character's  ASCII could be the character's position in the string rather than a constant. These algorithms would not keep the FBI or CIA at bay for long, but they are more than adequate when all you need to do is hide plain-text passwords from casual observers.

Visual Basic Tips

Is that on a Weekend?

 In the workaday world, one of the most important questions about any specific date is whether it falls on a weekend. The function presented here can answer this question. 

Visual Basic provides several sophisticated functions for working with dates. One of them, the Weekday function, is the tool needed for this task. This function takes a date as an argument. This date can be a type Date or it can also be a Variant or string that can be interpreted as a date. The function's return value is an integer that identifies the day of the week that the specified date falls on: 1 is Sunday, 2 is Monday, and so on up to 7 for Saturday. It's an easy matter to determine whether the function returned 1 or 7, indicating a weekend, or another value. Here's the code:

Public Function IsWeekend(d As Date) As Boolean 

If Weekday(d) = 1 Or Weekday(d) = 7 Then
    IsWeekend = True
Else
    IsWeekend = False
End If

End Function

The problem with this function is that it assumes that d is a valid date - if not, an error occurs. Of course the calling program can use the IsDate function to make sure a date is valid before calling IsWeekend, but it is preferable to have the function self-contained. This can be accomplished by using the flexibility of the Variant data type, as shown in this rewritten function:

Public Function IsWeekend(d As Variant) As Variant

If Not IsDate(d) Then
    IsWeekend = Null
    Exit Function
End If

If Weekday(d) = 1 Or Weekday(d) = 7 Then
    IsWeekend = True
Else
    IsWeekend = False
End If

End Function

With this rewrite, the function returns True if the date is a weekend day, false if it is not, and Null if the date passed was not in fact a valid date.

Visual Basic Tips

Detecting a Sound Card

 While most of today's systems have a sound card built in, there are still millions of older computers being used. If your program depends on sounds you may want to check to see if a sound card is available and, if not, take alternate action. To detect a sound card you will use a function in the Windows API. This requires that the function be declared at the module level in your program, letting Visual Basic know about the function and its arguments and return type. The declaration is as follows: 

Declare Function waveOutGetNumDevs Lib "Winmm.dll" ()As Long

emember, simply being installed is not enough to make a sound card available - the proper drivers must be installed as well, and the card must be recognized by Windows. This API function returns the umber of sound cards available. That there may be 2 or more cards available is not of interest - as long as at least one card is available then our question has been answered. It's a simple matter to write a short Visual Basic function that returns True or False based on the return value of waveOutGetNumDevs:

Public Function SoundCardAvailable() As Boolean

If waveOutGetNumDevs() > 0 Then
    SoundCardAvailable = True
Else
    SoundCardAvailable = False
End If

End Function

When your program detects that a sound card is not available it could revert to using the Beep statement and the computer's built-in speaker to issue sound alerts. 

Visual Basic Tips

 Detecting the State of the "Lock" Keys

 The PC keyboard has three lock keys: Caps Lock, Num Lock, and Scroll Lock. You can read the state of these keys from your Visual Basic program using the technique presented here. This technique depends on a Windows API function. The declarations of this function, which must be included at the module level in a program that uses the function, is shown here:

GetKeyState Lib "user32" (ByVal nVirtKey As Long) As Integer

Getting the state of a key requires that you call GetKeyState and pass it the Visual Basic constant that identifies the key: vbKeyNumLock, vbKeyScrollLock, or vbKeyCapital. The function returns 0 if the key is off and 1 if it is on.

This function is not limited to use with the lock keys - it can also determine the state of the other keys on the keyboard. Passed the key code for a non-lock key, such as vbKeyX of vbKeyF6, the function returns 0 if the key is up and non-zero if it is down. This information is of limited usefullness, however, because the information reflects the state of the key only at the time the most recent input message for the key was processed. In other words, for the non-lock keys the information returned by GetKeyState is only a snapshot of the keyboard state at a given moment in time.

Visual Basic Tips

Parsing Strings

To parse a string means to extract subsections of the string based on their position or, more commonly, a delimiter character. Common needs for parsing strings include using command line arguments and reading CSV (comma separated value) files. This tip presents a Visual Basic function that should meet all your parsing needs.

On the surface, parsing a string based on a delimiter character seems simple enough. For example, the string "a,b,c" is easily parsed into the three substrings "a" "b" and "c" based on using the comma as the delimiter character. But things are not always so simple. What about the string "a,,b,c"? This should be parsed as four substrings because the pair of commas indicates a missing, or blank, second substring. The same is true of the string ",a,b,c" where the initial comma indicates a missing or blank first substring. Thus, any parsing function must be able to take such missing or blank values into account.

A second requirements is the use of multiple characters as a delimiter. In some applications this may be desirable so as to not preclude the use of any characters in the data. For example, using the \ character as a delimiter works fine but means that the data itself cannot contain this character. Using \\ will circumvent this limitation.

The function ParseString() meets all of these requirements. It is passed two arguments, the string to be parsed and the delimiter string. It returns an array containing the parsed substring. Under certain error conditions (explained in the comments in the code) it returns Null. The calling program can use the UBound function to determine the size of the returned array and then access the individual substrings as needed.

Public Function ParseString(StringToParse As String, Delim As String) As Variant

' Returns an array containing the results of parsing
' a string based on the specified delimiter.
' If StringToParse or Delim is empty returns Null
' If Len(Delim) >= Len(StringToParse returns Null.
' If StringToParse does not contain the delimiter, returns
' the entire string.

Dim a() As String
Dim s As String
Dim DelimPos As Integer
Dim count As Integer

If Len(StringToParse) = 0 Or Len(Delim) = 0 Then
    ParseString = Null
    Exit Function
End If

If Len(Delim) >= Len(StringToParse) Then
    ParseString = Null
    Exit Function
End If

DelimPos = InStr(1, StringToParse, Delim)
If DelimPos = 0 Then
    ReDim a(0)
    a(0) = StringToParse
    ParseString = a
    Exit Function
End If

s = StringToParse
count = 0

Do
    ReDim Preserve a(count)
    a(count) = Left(s, DelimPos - 1)
    s = Right(s, Len(s) - (DelimPos + Len(Delim) - 1))
    count = count + 1
    DelimPos = InStr(1, s, Delim)
    If DelimPos = 0 Then
        ReDim Preserve a(count)
        a(count) = s
        s = ""
    End If
Loop Until Len(s) = 0

ParseString = a

End Function

Visual Basic Tips

Creating Formless Visual Basic Applications

 Sometimes you want an application that just runs and does its job without interacting with a user at all, or at most in a minimal way. Such an application might be called by other applications or batch files to perform tasks such as file manipulations. Or, it might be a command line utility that runs without a window. This tip shows you how to create such applications in Visual Basic.

You might think that Visual Basic applications are inherently linked to a form, but that's not the case. Yes, a Visual Basic application has a form by default but there's no reason you cannot get rid of it. That one step in creating a formless Visual Basic application. Here's the entire process:

1. Create a new Standard EXE Visual Basic application.
2. Add a code module to the project.
3. Select the project's default form and delete it using the Project|Remove Form1 command.
4. Add a procedure named Main to the program's code module (this is where execution starts when the program is run).

The module of a formless application can contain other Sub And Function procedures as needed. The application can also use class modules, make API calls, and do just about anything that a regular Visual Basic application can that does not involve forms and controls. A   formless Visual Basic application can also use visual screen elements such as MsgBox and InputBox, but these calls are usually best avoided because they would defeat the main purpose of having a formless program. 

How can you get information to a formless application? The most common method is to use command line arguments. In the program, the Command$ function returns a string containing any command line arguments that were passed when the program was started. These arguments are included after the program name as shown here:

C:\>ProgramName argument1 argument2

The arguments could be typed by the user at a command prompt, included as part of a batch file command, or included in the properties of a shortcut to the program.

The code below presents an example of a formless program. It is passed the name of a text file as a command line parameter. The program reads the file, replaces each Tab character with two space characters, and writes the file back to disk. The code lacks the error checking and other safety features that a real-world program would need. To compile this program you need to have "Microsoft Scripting Runtime" selected in the References dialog box (select Project|References). After compiling the program to an EXE from Visual Basic you would run it from the command line as follows:

C:\TabsToSpaces myfile.txt

The ability to use the powerful Visual Basic programming language to create formless command line programs is an added attraction that you may find useful someday. It becomes even more flexible when your program can access the stdin and stdout streams, a topic that is covered here.

Sub Main()

Dim fs As New Scripting.FileSystemObject
Dim f As TextStream
Dim filename As String
Dim strin As String
Dim strout As String
Dim ch As String
Dim idx As Long

filename = Command$
Set f = fs.OpenTextFile(filename)
strin = f.ReadAll
f.Close

For idx = 1 To Len(strin)
    ch = Mid(strin, idx, 1)
    If ch = Chr(9) Then
        strout = strout & "  ""
    Else
        strout = strout & ch
    End If
Next idx

Set f = fs.CreateTextFile(filename, True)
f.Write (strout)
f.Close

End Sub

Visual Basic Tips

Using the standard input and output streams in Visual Basic

The standard input and output streams, referred to as stdin and stdout, originated back in the days before graphical user interfaces, when all computing was text based. They may seem archaic, but can still be very useful to a Visual Basic programmer.

Stdin is where a program got its input from, by default the keyboard. Likewise stdout is where a program sent its output, by default to the screen. Much of the power of these streams came from the fact that they could be redirected so that a program could, for example, send its output to a file instead of the screen. Today, there are two common uses for stdin and stdout. One is with formless Visual Basic programs (as was covered in a previous tip) that run from a batch file or the command prompt - by using stdout with redirection, a program's output can go to a file rather than being displayed at the command prompt. The other is for Web programming with CGI, the Common Gateway Interface. While it is not the "latest and greatest," this technology is still used on a lot of Web sites to provide executable functionality, and a CGI program must use stdin and stdout to operate. This tip is not about CGI program, but rather shows you how you can use stdin and stdout in your Visual Basic programs.

Using these streams requires that a program get a handle to the stream using the API function GetStdHandle. The declaration for this function is:

Public Declare Function GetStdHandle Lib "kernel32" (ByVal nStdHandle As Long) As Long

Pass the argument -11& to get the handle for stdout and -10& for stdin. To read data from stdin use this API function:

Private Declare Function ReadFile Lib "kernel32" _
     (ByVal hFile As Long, ByVal IpBuffer As Any, _
     ByVal nNumberOfBytesToRead As Long, _
     IpNumberOfBytesRead As Long, _
     ByVal IpOverlapped As Any) As Long

The arguments are:

This following code fragment shows how you would use these two functions to read from stdin. Note that the string variable used to receive the data is padded with spaces before being passed to the API function.

Dim buf as string, BytesRead As Long, hStdIn As Long
buf = Space(5000)
StdIn = GetStdHandle(-10&)
ReadFile hStdIn, buf, Len(buf) - 1, BytesRead, 0&

To send data to stdout use this API function:

Private Declare Function WriteFile Lib "kernel32" _
     (ByVal hFile As Long, ByVal IpBuffer As Any, _
     ByVal nNumberOfBytesToWrite As Long, _
     IpNumberOfBytesWritten As Long, _
     ByVal IpOverlapped As Any) As Long

Here is a code fragment showing how it is done. The variable temp holds the text to be written to stdout:

Dim hStdOut as Long, BytesWritten As Long
hStdOut = GetStdHandle(-11&)
Call WriteFile(hStdOut, (temp), Len(temp), BytesWritten, 0&)

Note the parentheses around the temp argument to disable Visual Basic's argument type checking. With the ability to use the standard stdin and stdout streams, you can use Visual Basic to create programs for tasks you never thought Visual Basic was suitable for.

Visual Basic Tips

Playing WAV Files

A program's user interface can be improved by using short sounds to signal certain events such as a download completing or a command being selected. With the right Windows Multimedia function, playing WAV files from a Visual Basic program is child's play. Of course, the system must be equipped with a sound card for this to work.

The API function you need is called, appropriately enough, PlaySound. Its declaration is shown here:

Public Declare Function PlaySound Lib "winmm.dll" _
  Alias "PlaySoundA" (ByVal lpszName As String, _
  ByVal hModule As Long, ByVal dwFlags As Long) As Long

The first argument is the name, including path, of the Wave file to play. The second argument is not used when playing sound files and you should pass a value of zero (this function can also play sounds that are associated with system events, but that is not covered here). The final argument consists of flags that control various aspects of how the function works. For the present purposes, two flags are used. They are (with the constants that are typically used for them:

Thus, the following code plays the sound in DingDong.wav:

PlaySound "dingdong.wav", CLng(0),SND_ASYNC Or SND_FILENAME

When playing sound files in a program there are two other considerations. First, you want to make sure that the specified Wave file exists or else an error will occur. This can be done with the simple function shown here:

Public Function FileExists(FullFileName) As Boolean

' Passed a filename (with path) returns
' True if the file exists, False if not. 

Dim s

s = dir(FullFileName)
If s = "" Then
    FileExists = False
Else
    FileExists = True
End If

End Function

Second, the program should have an option setting that lets users turn sounds off. This can be maintained as a global Boolean variable with a name such as gProgramSoundsEnabled. My approach is to deal with both of these considerations in a function as follows. This code assumes that the Wave files are stored in the application folder.

Private Sub PlaySoundX(filename As String)

' If sound is enabled and filename exists,
' play the specified sound.

filename = App.path & "\" & filename 

If FileExists(filename) And gProgramSoundsEnabled Then
    PlaySound filename, CLng(0), SND_ASYNC Or SND_FILENAME
End If

End Sub

With this function in place, the program can play any Wave file like this:

PlaySoundX "DingDong.wav"

Another way of playing sounds from a Visual Basic program involves including a multimedia control on a form. When you do not need the extra features of the control, however, the technique presented here is a lot easier.

Visual Basic Tips

Sending Email from a Visual Basic Program

Have you ever thought it would be great to be able to send email from a Visual Basic program? With Visual Basic's MAPI controls, it's a snap. These two controls let you send messages on any MAPI-compliant email system, such as Outlook and Exchange. To use the MAPI controls, you must select them in your project's Components dialog box. They are listed as "Microsoft MAPI Controls 6.0." The two controls will then appear in your toolbox; they are called MAPISession and MAPIMessages. Place one of each on a form - they are invisible at runtime.

The MAPISession control is used to establish a session, or connection, with whatever MAPI-compliant mail software is installed on the system. The control has UserName and Password properties for signing onto an email account. You can set these properties at design time or prompt the user for them in code. Then, call the control's SignOn method to establish the session. Once the session is established, the control's SessionID property returns a handle of the session.

The MAPIMessages control must be passed the handle of the MAPI session, obtained from the MAPISession control's SessionID property. Once this is done, the MAPIMessages control can be used for various tasks such as accessing messages in the InBox, saving, copying and deleting messages, and working with attachments. For this tip we are interested in creating and sending messages. This requires the following steps:

1. Call the Compose method to create a new message.
2. Put the recipient, the subject, and the body of the message in the corresponding control properties.
3. Call the Resolve method to verify the message recipient.
4. Call the Send method to send the message.

Sending a message with the MAPIMessages control does not literally send it, but puts it in the Outbox of the mail system. When the message is actually sent depends on the mail system settings. When your program is finished with mail-related activities, call the MAPISession control's SignOff method to terminate the session.

The following code demonstrates this. It assumes that the form containing the code contains TextBox controls for the various bits of information needed: user name, password, etc.

Private Sub SendMail_Click()

MAPISession1.UserName = tstUserName.Text
MAPISession1.Password = txtPassword.Text
MAPISession1.SignOn
MAPIMessages1.SessionID = MAPISession1.SessionID
MAPIMessages1.Compose
MAPIMessages1.RecipAddress = txtTo.Text
MAPIMessages1.MsgSubject = txtSubject.Text
MAPIMessages1.MsgNoteText = txtMessage.Text
MAPIMessages1.ResolveName
MAPIMessages1.Send
MAPISession1.SignOff

End Sub

An alternate way to use the Send method is to pass an argument with the value True (the default for this argument is False, that's why it is not included in the code above).

MAPIMessages1.Send True

In this case, the Send method displays a message dialog box in which the user can enter or edit the elements of the message and then send it by clicking the Send button.

When a Visual Basic program needs to send email messages, perhaps for support or licensing issues, it's a lot nicer to integrate email support in the program rather than requiring the user to switch to their own email software.

Visual Basic Tips

Using INI Files for Program Settings

In the old days, Windows programs and the operating system itself used INI (for Initialization) files to store program settings and other information. Since Windows 98 these tasks have been taken over by the Windows registry. Even so, there are times when storing a program's settings in an INI files is still preferable. This tip shows you how.

An INI files a text file that is organized into sections. Each section contains keys and values. Here's an example:

[section1]
key1 = value1
key2 = value2
[section2]
key1 = value1
key2 = value2

You can see that an individual entry is identified by both the section it is in and its key name. Your program can write and read INI files using these two API functions:

Private Declare Function GetPrivateProfileString _
  Lib "kernel32" Alias "GetPrivateProfileStringA" _
  (ByVal lpApplicationName As String, _
  ByVal lpKeyName As Any, ByVal lpDefault As String, _
  ByVal lpReturnedString As String, ByVal nSize As Long, _
  ByVal lpFileName As String) As Long

Private Declare Function WritePrivateProfileString _
  Lib "kernel32" Alias "WritePrivateProfileStringA" _
  (ByVal lpApplicationName As String, ByVal lpKeyName _
  As Any, ByVal lpString As Any, ByVal lpFileName As _
  String) As Long

Both functions return 1 on success and 0 on failure. A failure would occur if, for example, you try to write an INI file to a location where you do not have sufficient permissions. The arguments are as follows:

When you are writing to an INI file and the section and key exist, the current value is replaced with the new value. Otherwise the section and/or key are created. When reading you get either the value in the file or, if the section/key do not exist you get the default value passed to the GetPrivateProfileString argument. This default value is also returned if the INI file does not exist. Because an INI file is text only, storing a numeric value requires converting it to a string when saving it and then converting the string value back to a number when retrieving it.

The following code shows how to write a value to an INI file and then read it back. In the code for reading an INI file, note how the string variable that will receive the returned value is padded with strings before being passed to the function.

Private Sub cmdWriteINIFile_Click()

Dim retval As Long

retval = WritePrivateProfileString("MyApp", "Key1", _
    "Real value", App.Path & "\" & "Data.ini")
If retval = 0 Then MsgBox "Error writing INI file"

End Sub

Private Sub cmdReadINIFile_Click()

Dim retval As Long
Dim value As String

value = Space(128)
retval = GetPrivateProfileString("MyApp", "Key1", _
  "Default value", value, Len(value), _
   App.Path & "\" & "Data.ini")

If retval = 0 Then
    MsgBox "Error reading INI file"
Else
    MsgBox "The value read is: " & value
End If

End Sub

Why use INI files in preference to the registry? One reason is that the data in an INI file is a lot easier to view and edit, using any text editor. If you use descriptive names for your sections and keys this becomes even easier. Most of us know what a bother it is to edit the registry. A second and perhaps more important reason is that a program's settings can easily go along with it when the program is moved to or installed on a new system, simply by copying its INI file. Of course there's no reason a program cannot use both the registry and an INI file.

Visual Basic Tips

Improving the Shell Function

You can use the Shell function to run another executable program from within a Visual Basic program. This tip shows you a simple way to improve on Shell's functionality.

Shell takes two arguments. The first is the name, including path, of the EXE file to run. The second argument specifies the window style of the program. This argument is optional - if omitted, the program is started minimized with the focus. You can refer to the Visual Basic help for more information on the window style argument because it is not relevant here. The techniques presented here work regardless of the program's window style and even if the program does not have a window.

Shell runs its target program asynchronously, meaning that execution can return to the Visual Basic program before the second program is finished executing. In many situations this is not a concern, but at other times it may be. One example is when your Visual Basic program is dependent in some way on the result of the shelled program completing its operation. In this case you need some way to pause the Visual Basic program until the other program is done.

The first step is to get the handle of the shelled program. The Shell function returns its program ID, or PID, and once you have that you can get the handle using the OpenProcess API function:

Public Declare Function OpenProcess Lib "kernel32" _
  (ByVal dwDesiredAccess As Long, _
  ByVal bInheritHandle As Long, _
  ByVal dwProcessId As Long) As Long

Pass the value &H100000 (usually represented by the constant SYNCHRONIZE) for the first argument and 0 (as a long) for the second. The third argument is the PID returned by the Shell function. The function's return value is a handle to the Windows process representing the shelled program. With this handle, you can use the WaitForSingleObject API function to pause the Visual Basic program until the shelled program has terminated. The declaration is:

Private Declare Function WaitForSingleObject Lib _
  "kernel32" (ByVal hHandle As Long, _
  ByVal dwMilliseconds As Long) As Long

The first argument is the handle of the program to wait for, obtained from the OpenProcess function. The second argument is the length of time to wait. If you pass a millisecond value, the function returns when the shelled program completes or when the specified interval is up, whichever occurs first. If you want the function to wait as long as necessary for the shelled program to complete, pass the value &HFFFF (often represented by the constant INFINITE).

Here is sample code that shows how to use these API functions to shell a program and wait for it to complete.

Const SYNCHRONIZE = &H100000
Const INFINITE = &HFFFF 

Private Sub ShellProgramAndWait(ProgramName As String)

Dim hHandle As Long, pid As Long

txtStatus.Text = "Processing"
txtStatus.Refresh
pid = Shell(ProgramName, vbNormalFocus)
If pid <> 0 Then
    hHandle = OpenProcess(SYNCHRONIZE, 0&, pid)
    WaitForSingleObject hHandle, INFINITE
    txtStatus.Text = "Finished"
Else
    txtStatus.Text = "Error shelling " & ProgramName
End If

End Sub

The WaitForSingleObject function effectively freezes your Visual Basic program, something you need to take into account so the user does not think something is wrong. In this code, for example, a message is displayed to the user in a Text Box control. Note the use of the Refresh method to make sure the message is actually displayed before the program enters its wait state. Then, when the shelled program terminates, a "finished" message is displayed. With the techniques presented here, Visual Basic's Shell function becomes an even more useful tool.

Visual Basic Tips

Clipping the Mouse Cursor

This tip shows you how to clip the mouse cursor, restricting its movement to your program's form. This is a good way to ensure that a user's attention stays focused on your program. It's not perfect, because there are ways to get around it, but it can still be handy in some situations. You use the API function ClipCursor to restrict the cursor to a specific area of the screen. Here's the declaration:

Declare Function ClipCursor Lib "user32" (lpRect As Any) As Long

The function's one argument is a RECT structure that defines the clipping area. The area is expressed in screen pixels, and sample code given below shows how to convert Visual Basic's twips to pixels. The RECT structure, or UDT in Visual Basic, is shown here:

Public Type RECT
    Left As Long
    Top As Long
    Right As Long
    Bottom As Long
End Type

To turn off clipping you can call ClipCursor again, passing a RECT structure that contains the coordinates of the entire screen. It's easier, however, to use the ClipCursorBuNum function that is intended specifically for turning clipping off:

Declare Function ClipCursorByNum Lib "user32" Alias _
  "ClipCursor" (ByVal lpRect As Long) As Long

Note that this function is actually an alias of ClipCursor that accepts a type Long argument. Pass the value zero to turn clipping off. The following two Click event procedures can be associated with buttons on a form to demonstrate this technique.

Private Sub cmdClip_Click()

Dim ClipArea As RECT

With ClipArea
    .Left = Me.Left / Screen.TwipsPerPixelX
    .Top = Me.Top / Screen.TwipsPerPixelY
    .Right = .Left + Me.Width / Screen.TwipsPerPixelX
    .Bottom = .Top + Me.Height / Screen.TwipsPerPixelY
End With

Call ClipCursor(ClipArea)

End Sub

Private Sub cmdRelease_Click()

Call ClipCursorBynum(0)

End Sub

There are several actions that disable clipping, including resizing or moving the Visual Basic form, activating another program (for example, with Alt+Tab), or pressing Ctrl+Alt+Del. You program can deal with the form actions by re-establishing clipping whenever the form is moved or resized. Note that clipping remains in effect after the program ends, so a program should always be sure to turn clipping off before it terminates.

Clipping the mouse cursor is not a foolproof way to restrict mouse input to your program, but in some cases, particularly with users who are not too computer-savvy, it can work.

Visual Basic Tips

Move the Mouse Cursor in Code

Have you ever wanted to move the mouse cursor under program control? This tip shows you how to do so using the API function SetCursorPos. The declaration for this function is:

Declare Function SetCursorPos Lib "user32" (ByVal x As Long, _
    ByVal y As Long) As Long

The two arguments give the cursor position. This must be given in screen pixels, so there's a twip-to-pixel conversion required when using the function in a Visual Basic program. The return value, which is often ignored, is 0 on failure and nonzero on success.

Why would you want to move the mouse cursor under program control? The main reason that I use this technique is to position the cursor at the location where the user is most likely to want it. For example, if your program displays a form for confirmation of program settings, and the user is most likely to simply accept the default settings by clicking the OK button, you could position the mouse cursor over the OK button when the form is displayed, saving the user the effort.

To use the SetCursorPos function you obviously must know the pixel coordinates of the location that you want to move the cursor to. Suppose, for example, you want the cursor positioned in the center of a Command Button. At first glance the following calculations seem to be what's needed (these are for the X position; the Y position follows the same logic):

(The form's Left property) plus (the control's Left property) plus (one-half the control's Width property)

This is on the right track, but it ignores the width of the form borders and, for the Y coordinate, the title bar. Remember, the Form.Left property gives the position of the outer corner of the form, while a control's Left property gives its position relative to the form's client area (the interior). To determine the width of the form's border you can subtract the form's ScaleWidth, the width of the interior area, from the Width (the width of the entire for including borders). By adding this value to the calculation above you will be right on target. Note that this technique requires that the form's ScaleMode be at the default Twips setting, otherwise it will not work.

Wait, we are not quite done - this calculation gives the desired cursor position in Twips while the SetCursorPos function needs pixels. This is easily remedied by dividing with the Screen.TwipsPerPixelX and Screen.TwipsPerPixelY properties to convert twips to pixels.

The sample code below shows how this is done. This is a form's Load event procedure. When the form is loaded, the mouse cursor is automatically positioned on a Command Button control named Command1.

Private Sub Form_Load()

Dim x As Long, y As Long

x = ((Me.Left + (Me.Width - Me.ScaleWidth) + _
  Command1.Left) + Command1.Width / 2) _
  / Screen.TwipsPerPixelX

y = ((Me.Top + (Me.Height - Me.ScaleHeight) + _
  Command1.Top) + Command1.Height / 2) / _
  Screen.TwipsPerPixelY

SetCursorPos x, y

End Sub

If you choose to add this feature to your program, it's advisable to make it an option that the user can turn on or off. Some users love it, others hate it, but this way you can please everyone!

Visual Basic Tips

Drawing "Rubber-Band" Boxes

I am sure that you have seen the rubber-band boxes, sometimes called selection boxes, that some programs use to select areas on the screen. Point with the mouse, hold down the mouse button, and move the mouse, and a rectangular box is drawn between the original and the current mouse positions. The term "rubber-band" comes from the way the box grows and shrinks as the mouse is moved. When you release the mouse button the box remains on-screen. This tip shows you how to create rubber-band boxes in Visual Basic.

Drawing a box on a form or a Picture Box control is easily done with the Line method. The problem is how to make the previous box disappear when the mouse is moved and a new box is drawn. The answer to this lies in the DrawMode property. The default setting draws a solid line, but this won’t work for our purposes. Rather we will use the setting vbInvert which means that drawing operations such as the Line method draw using the inverse of the color that is already there on the form or Picture Box control. Thus, if the background is white, vbInvert draws a black line, and if the background is black a white line is drawn. Colors have their inverses too. This solves two problems:

The approach, then, is as follows:

  1. When the user presses the mouse button, record the current mouse coordinates. This will be the fixed corner of the box. At the same time, set a "drawing" flag to True.

  2. When the mouse is moved, erase the previous box by drawing it again. This step is not required the first time a box is drawn but it required all subsequent times.

  3. Also when the mouse is moved, draw a box between the starting coordinates and the new mouse coordinates.

  4. When the mouse button is released, set the drawing flag to False.

This is demonstrated in the following code snippet. To try this out, create a Standard EXE project and place a Picture Box control on the form. Load the Picture Box with an image of your choice, and set its DrawMode property to 6 - Invert and its AutoRedraw property to True. Next, put the following variable declarations at the module-level in the form:

' True if a box is being drawn.
Dim drawing As Boolean

' True if the box has just started.
Dim first As Boolean

' Previous box's mouse coordinates.
Dim oldX As Single, oldY As Single

' The box's starting coordinates.
Dim startX As Single, startY As Single

Finally, put the code shown here in the mouse-related event procedures for the Picture Box control:

Private Sub Picture1_MouseDown(Button As Integer, _
  Shift As Integer, X As Single, Y As Single)

  drawing = True
  first = True
  startX = X
  startY = Y

End Sub

Private Sub Picture1_MouseMove(Button As Integer, _
  Shift As Integer, X As Single, Y As Single)

  If Not drawing Then Exit Sub
  If Not first Then
      Picture1.Line (startX, startY)-(oldX, oldY), , B
  Else
      first = False
  End If

  Picture1.Line (startX, startY)-(X, Y), , B
  oldX = X
  oldY = Y

End Sub

Private Sub Picture1_MouseUp(Button As Integer, _
  Shift As Integer, X As Single, Y As Single)

  drawing = False

End Sub

When you run the program you will be able to draw rubber band boxes on the picture. Each box you draw remains in place, and you can draw essentially as many as you like. If you want an existing box to vanish when a new one is started, put code in the MouseUp event procedure to save the coordinates of the existing box. Then, in MouseDown, erase any existing box by drawing over it. You would want to save the coordinates of the box in any case so your program can make use of them, for example to perform processing on the selected region of the image.

Visual Basic Tips

Undo for Text Box Controls

Few Visual Basic programmers realize it, but the TextBox control has an undo feature built right into it. Here's how to use that feature from a Visual Basic program. Undo is a wonderful feature that is available in most commercial programs for editing text or graphics. Many programs offer multi-level undo so you can "roll back" a whole series of actions. Many also offer "redo" to cancel the last undo command. These sorts of sophisticated features require some complex programming, but a simple version of undo is readily implemented for text Box controls. The undo capability is already built into the control, so all that you need to do is send the control a message saying "undo yourself." This is done using the API SendMessage function. Its declaration is:

Declare Function SendMessage Lib "user32" Alias _
  "SendMessageA" (ByVal hWnd As Long, _
  ByVal wMsg As Long, ByVal wParam As Long, _
  lParam As Any) As Long

To see this technique in action, create a project with a form that contains one Text Box control and one Command Button control. Put this line of code in the Command Button's Click event procedure:

SendMessage Text1.hWnd, EM_UNDO, 0&, 0&

Add a module to the project and add the following code to it:

Public Const EM_UNDO = &HC7
Public Declare Function SendMessage Lib "user32" _
  Alias "SendMessageA" (ByVal hWnd As Long, _
  ByVal wMsg As Long, ByVal wParam As Long, _
  lParam As Any) As Long

When you run the project, enter and edit text in the Text Box control. You'll see that the button undoes the latest action. If there's nothing to undo, the message has no effect. You could implement undo as a menu command that affects the active Text Box as follows:

If TypeOf ActiveControl Is TextBox Then
    SendMessage ActiveControl.hWnd, EM_UNDO, 0&, 0&
End If

If a Text Box is not active, no action is taken.

An undo feature for the Text Box controls in your program can be an attractive user-friendly feature that is very little work to implement.

Visual Basic Tips

Modify the System Menu

A Visual Basic form's system menu normally displays a set of default commands for window-related commands, such as closing, minimizing, or maximizing the window. A program can add its own commands to this menu, as this tip explains. This is particularly useful because this menu can be displayed not only by clicking the icon at the left end of the title bar, but also by right-clicking the window's icon on the Windows task bar. This permits the user to send commands to the program while it remains minimized, a feature that can be useful in a variety of situations. As you might expect, you use Windows API functions to work with a form's system menu.

You start by getting a handle to the form's system menu using the GetSystemMenu API function. Once you have this handle you then use the AppendMenu API function to add the command to the end of the menu. Arguments to AppendMenu specify the menu item text and also assign a numeric ID that is used to identify when the command is selected by the user.

Once you have created the new menu command you must write the code that enables the program to respond to it. To do so, write a function with the proper arguments (as you'll see in the sample code). This function must be in a code module and not in a form. Code in this function will examine the data passed in the arguments to determine if your custom menu item was selected. If so, the function will take whatever actions are required. If not - that is, if one of the default system menu commands was selected - the command will be passed along for processing by the usual internal Windows mechanisms by calling the CallWindowProc API function.

The final step is to tell Windows to call your program's function when a system menu command is selected. This is accomplished by using the AddressOf operator to pass your function's address to the SetWindowLong API function.

You can try this out for yourself by creating a standard EXE project, adding a code module, and copying the code shown below into the code module. Then put the following line of code in the form's Load event procedure:

AddToSystemMenu Me.hWnd

The Windows API provides even more flexibility for customizing the system menu. You can, for example, add separator lines, enable and disable menu commands, insert a new command at any location in the menu and display checkmarks next to commands. These functions are beyond the scope of this tip, but you can find details in most Windows API reference books.

Private OriginalWindowProc As Long
Public Const MF_STRING = &H0&
Public Const MF_ENABLED = &H0&
Public Const IDM_MYMENUITEM = 2003
Public Const WM_SYSCOMMAND = &H112
Public Const GWL_WNDPROC = (-4)

Public Declare Function GetSystemMenu Lib "user32" _
  (ByVal hWnd As Long, ByVal bRevert As Long) As Long

Public Declare Function AppendMenu Lib "user32" _
  Alias "AppendMenuA" (ByVal hMenu As Long, _
  ByVal wflags As Long, ByVal wIDNewItem As Long, _
  ByVal lpNewItem As String) As Long

Public Declare Function SetWindowLong Lib "user32" _
  Alias "SetWindowLongA" (ByVal hWnd As Long, _
  ByVal nIndex As Long, ByVal dwNewLong As Long) As Long

Public Declare Function CallWindowProc Lib "user32" _
  Alias "CallWindowProcA" (ByVal lpPrevWndFunc As Long, _
  ByVal hWnd As Long, ByVal msg As Long, ByVal wParam As Long, _
  ByVal lParam As Long) As Long


Public Sub AddToSystemMenu(ByVal hWnd As Long)

Dim hSystemMenu As Long

' Get the system menu's handle.
hSystemMenu = GetSystemMenu(hWnd, False)

' Append a custom command to the menu.
AppendMenu hSystemMenu, MF_STRING + MF_ENABLED, _
IDM_MYMENUITEM, "My Menu Item"

' Tell Windows to call MyMenuProc when a system
' menu command is selected.
OriginalWindowProc = SetWindowLong(hWnd, GWL_WNDPROC, _
AddressOf MyMenuProc)

End Sub


Public Function MyMenuProc(ByVal hWnd As Long, ByVal msg As Long, _
  ByVal wParam As Long, ByVal lParam As Long) As Long

' If the custom menu item was selected display a message.
If msg = WM_SYSCOMMAND And wParam = IDM_MYMENUITEM Then
MsgBox "New menu item clicked!"
Exit Function
End If

' Otherwise pass the command on for normal processing.
MyMenuProc = CallWindowProc(OriginalWindowProc, hWnd, msg, _
  wParam, lParam)

End Function
 

Visual Basic Tips

Unlocking a Program

You may sometimes need to distribute two versions of the same program. One version is full-featured while the other, perhaps a demo or shareware version, is limited in some way. When a user registers or pays the shareware fee, you want them to be able to unlock the demo version to access the program's full functionality. This tip shows you how.

A program usually keeps information about whether it is locked or unlocked in the Windows registry. On program startup, code checks the registry and sets a global program variable to indicate the program status, locked or unlocked. Throughout the program, code checks this global variable as needed to enable or disable specific program features such as printing or "nag" screens. For example, put the following code in a program's code module and then call the CheckLockedStatus procedure from the main form's Load event procedure:

Public Unlocked As Boolean

Public Const APP_NAME = "MyApp"
Public Const SECTION_NAME = "Locking"
Public Const KEY_NAME = "Locked status"

Public Sub CheckLockedStatus()

Dim temp As String
temp = GetSetting(APP_NAME, SECTION_NAME, KEY_NAME, "")
If temp = "unlocked" Then
  Unlocked = True
Else
  Unlocked = False
End If

The registry section and key names used in this example are pretty obvious. If you want to prevent registry-savvy users from unlocking the program by editing the registry you should use non-obvious names for these items.

How would the user unlock the program? Most often, the user is given an unlocking key that is based on their user name. The program has an "unlock" command that prompts the user for this information. If the user name and the key match, the program is unlocked by writing the required information to the registry. This requires a method to generate a key from a user name. There are many ways you could approach this. My approach is to simply add up the ASCII values of all the individual characters in the user name. Because this might result in a key number that is too short, I multiply the generated value by a constant to create a larger number. The following function shows how to generate a key number from a user name using this technique.

Public Function GenerateKeyNumber(UserName As String) As Long

Dim i As Integer
Dim s As String * 1
Dim key As Long

key = 0
For i = 1 To Len(UserName)
s = Mid(UserName, i, 1)
key = key + Asc(s)
Next i

GenerateKeyNumber = Int(key * 12345.67)

End Function


When the user wants to unlock the program, he or she enters the user name and key that you provided to them. The program then checks to see if the key if valid for the user name. If so, the program will enter information in the registry that marks the program as unlocked. Otherwise, a message will be displayed to the user. The following code provides an example. It assumes that the program has prompted the user for his or her user name and key and that this information is passed in the procedure's arguments.

Public Sub UnlockProgram(Username As String, key As Long)

Dim CorrectKey As Long

CorrectKey = GenerateKeyNumber(Username)

If key = CorrectKey Then
  SaveSetting APP_NAME, SECTION_NAME, KEY_NAME, "unlocked"
  MsgBox "Program successfully unlocked"
  Unlocked = True
Else
  MsgBox "Invalid user name or key"
End If

End Sub

Visual Basic Tips

Determine if the Visual Basic IDE is Running

In the typical Visual Basic development process, a program is created and tested within the Integrated Development Environment, or IDE. Only when it is nearing completion will the programmer compile and run an executable. It can be useful for a program to know whether it is running in the IDE or as a compiled program. For example, you may want to provide certain testing and debugging commands when running in the IDE, but hide these commands in a executable. This tip shows you how.

The technique relies on the Debug.Print statement. In the IDE, this statement displays its output in the Immediate window. In an executable, however, all Debug.Print statements are removed. If you include a Debug.Print statement that will cause a run-time error - by dividing by 0, for example - the error will occur only when the program is running in the IDE. Here's a function that returns True or False depending on the situation:

Public Function RunningInIDE() As Boolean

On Error Resume Next
Debug.Print 1/0
If Err.Number = 0 Then
  RunningInIDE = False
Else
  RunningInIDE = True
End If

End Function

Your program can use this function to set a global flag and then hide or display menus and other program features as needed.

Visual Basic Tips

Deleting Files to the Recycle Bin

Visual Basic's Kill statement deletes files from disk. You can also use the methods that are associated with the FileSystemObject class in the Windows Scripting Runtime. Both of these methods have the shortcoming that they delete a file permanently - in other words, they do not delete the file to the Windows recycle bin from where it can be restored if needed. This tip shows you how to delete files to the recycle bin.

To delete a file to the recycle bin, use the SHFileOperation API function. This function takes as its one argument a user-defined type (UDT) that contains information, such as the file name, about the operation to perform. Note that this function has many other uses besides deleting files, but these are beyond the scope of this tip. You can see the definition of the UDT in the sample code that follows. To delete a file, there are three pieces of information that are required in the UDT: the name of the file, a flag specifying a delete operation, and another flag specifying that the delete is to be undoable.

The code that follows shows how this is done. Place this code in your project, in a code module, and then call the DeleteFileToRecycleBin procedure, passing the name (including path) of the file to be deleted. If there is a problem, such as the file not being found, insufficient permissions, or the file being read-only, an error message will be displayed. Otherwise the file will be deleted. Depending on the system settings, a confirmation dialog box may or may not be displayed. By using this technique, you have the added safety of knowing that any files deleted by your program can be recovered if needed.

Private Type SHFILEOPTSTRUCT
  hWnd As Long
  wFunc As Long
  pFrom As String
  pTo As String
  fFlags As Integer
  fAnyOperationsAborted As Long
  hNameMappings As Long
  lpszProgressTitle As Long
End Type

Private Declare Function SHFileOperation Lib "Shell32.dll" _
  Alias "SHFileOperationA" (lpFileOp As SHFILEOPTSTRUCT) As Long

Private Const FO_DELETE = &H3
Private Const FOF_ALLOWUNDO = &H40

Public Sub DeleteFileToRecycleBin(Filename As String)

  Dim fop As SHFILEOPTSTRUCT

  With fop
    .wFunc = FO_DELETE
    .pFrom = Filename
    .fFlags = FOF_ALLOWUNDO
  End With

  SHFileOperation fop

End Sub

Visual Basic Tips

Keeping a Form On Top

When a Visual Basic program has two or more forms displayed at the same time, the default behavior is for the active form - the one with the focus - to display on top of all other forms. There are situations when you might want to keep a form on top even when it does not have the focus. For example, a form containing instructions could remain on top and visible even when the user is working in another of the program's forms. Or, a graphics program can display a toolbox that remains on top at all times. It's easy to do this in Visual Basic, and this tip shows you how.

The technique relies on the ability to establish a parent-child relationship between two forms. When one form is a child of another, it always remains on top even when the parent has the focus. To make one form the child of another, display the child by using the following code in the parent form (assuming the child form is named InformationForm):

Dim frmChild As New InformationForm
frmChild.Show vbModeless, Me

The second argument to the Show method is the keyword Me, which is a reference to the form the code is running in. Usually this argument is omitted from calls to the Show method and the resulting form is parentless, which is appropriate when you want the form to behave normally (not stay on top). The above syntax, however, makes the new form a child of the current form, exactly what we need.

Note the use of the vbModeless argument. This displays the child form as a modeless form, permitting the user to move the focus to the parent form without first closing the child form - something that is clearly required in this situation.

Visual Basic Tips

Using Custom Mouse Cursors

Most Visual Basic programmers know that you can use an object's (a control or form) MousePointer property to change the appearance of the mouse pointer when it is over that object. As useful as this can be, it limits you to selecting from Visual Basic's predefined pointers. It's true that this set of predefined pointers includes those that are needed most often, such as the hourglass, crosshairs, and I-beam. But what if you need a mouse pointer that is not provided in this set? This tip shows you how to use any icon as your mouse pointer.

Icons are small images that are saved in ICO files. They are typically 32x32 pixels in size. You can create your own icons using one of the many shareware icon editors that are available, or you can use any of the icons that are provided as part of the Visual Basic installation (by default in the \Program Files\Microsoft Visual Studio\Common\Graphics\Icons folder).

Once you know the icon you want to use, there are two steps required. The first is to assign the icon to the object's MouseIcon property, using the LoadPicture function to load the icon file. Then, set the object's Mouse Pointer property to the value 99. For example, assume that your project contains a picture box control named Picture1. Putting this code in the form's Load event procedure will cause the specified icon to be displayed when the mouse pointer is over the picture box. This code assumes that the icon file is located in the folder from which the program is running.

Picture1.MouseIcon = LoadPicture("arw03dn.ico")
Picture1.MousePointer = 99

You should be aware that each icon has a "hotspot" associated with it. This spot identifies the precise spot that is the mouse pointer's location in terms of clicking and other operations. For example, the hotspot of the standard arrow cursor is at the tip of the arrow. If you create your own cursors using an icon editor you can define the location of the hotspot as well.

Visual Basic Tips

Ensuring that all Forms Unload

When a Visual Basic program ends, all of its forms should be unloaded and removed from memory. Unfortunately Visual Basic does not take care of this automatically. Particularly if your program contains a lot of forms, it is possible for one or more forms to remain in memory even after the program terminates. Remember, calling a form's Hide method or setting its Visible property to False does not unload it. Even if you explicitly unload a form (using the Unload procedure) it can still take up resources unless the form's reference is set to Nothing. This tip shows you how to ensure that all of a program's forms are unloaded and their resources released upon program termination.

This technique makes use of the fact that a Visual Basic application has a global Forms collection whose elements represent all of the application's loaded forms. You could loop through this collection, unloading all forms as shown in this code snippet:

Dim f As Form
For Each f In Forms
Unload f
Set f = Nothing
Next f

There's a problem with this straightforward approach, however. If you execute this code from a procedure it works fine, but if you call it from the main form's Form_Unload event procedure it will try to unload the main form which is already in the process of unloading itself (otherwise the Form_Unload event procedure would not have fired). You can get around this potential problem as follows:

Public Sub UnloadAllForms(Optional FormToIgnore As String = "")

Dim f As Form
For Each f In Forms
If f.Name <> FormToIgnore Then
Unload f
Set f = Nothing
End If
Next f

End Sub

With this procedure in place you would call it like this from the main form's Form_Unload event procedure and it will unload all forms except the main form:

UnloadAllForms Me.Name

If calling it from a separate procedure, pass no argument and the procedure will unload all of the program's forms.

Visual Basic Tips

Remembering a Form's Size and Position

Visual Basic's default is for a program to start each time with the form at the size and position that were specified when the program designed. For some applications, it can be a nice user-friendly touch to have the program "remember" its size and position from the last time it was executed. In order to do this, the program must store its size and position when it terminates and then retrieve them the next time it starts up.

The Windows registry is the best place to store this information. The following procedure, called from the form's Unload event procedure, will save the form's position (its Left and Top properties) and its size (its Width and Height properties) in the registry:

Public Sub SaveFormPosition(f As Form)

SaveSetting App.EXEName, "Form Position", "Left", Str(f.Left)
SaveSetting App.EXEName, "Form Position", "Top", Str(f.Top)
SaveSetting App.EXEName, "Form Position", "Width", Str(f.Width)
SaveSetting App.EXEName, "Form Position", "Height", Str(f.Height)

End Sub

Restoring the program's size and position when it starts up is a little more involved. The first time a program runs, this information will not have been placed in the registry. Your code must take this into account. Here's a procedure that can be run from the form's Load event procedure to restore its position. If the information is not found in the registry, the GetSetting function will return a blank string. In this situation the procedure simply exits and the form will be displayed at its default size and position.

Public Sub RestoreFormPosition(f As Form)

Dim buf As String
Dim l As Long, t As Long, w As Long, h As Long

buf = GetSetting(App.EXEName, "Form Position", "Left")

' If buf = "" then the settings were not saved perhaps because
' this is the first time the program was run. Exit the sub and
' let the form display at its default size and position.
If buf = "" Then Exit Sub

l = CLng(buf)
buf = GetSetting(App.EXEName, "Form Position", "Top")
t = CLng(buf)
buf = GetSetting(App.EXEName, "Form Position", "Width")
w = CLng(buf)
buf = GetSetting(App.EXEName, "Form Position", "Height")
h = CLng(buf)
f.Move l, t, w, h

End Sub

Your program would call these two procedures in the form's Load and Unload event procedures as shown here:

Private Sub Form_Load()

RestoreFormPosition Me

End Sub

Private Sub Form_Unload(Cancel As Integer)

SaveFormPosition Me

End Sub

These procedures will work for only one of a program's forms. If you want to save the size and position for two or more forms you need some way to differentiate the saved settings for each form. You can do this by including the form's name in the registry key as shown here for saving the Left setting:

SaveSetting App.EXEName, "Form Position", f.Name & "Left", Str(f.Left)

And here for retrieving the Left setting:

buf = GetSetting(App.EXEName, f.Name & "Form Position", "Left")

Visual Basic Tips

Using A Resource File in Your Visual Basic Project

Many Visual Basic projects use data that is stored in external files. This can include bitmap images, icons, cursors, and WAV files. Including these elements as separate files in your installation has two drawbacks. First, the installation can become cluttered with a large number of files, and if any one of them is deleted or moved inadvertently by the user the program will not function properly. Second, the material in the files is freely accessible to the user and may be used in ways you did not want. You can avoid both of these problems by using a resource file. Another advantage of resource files is that you can create version-specific resource files to be included with installation in different locales.

A Visual Basic project can have only one resource file associated with it. This file can contain both binary and string items. Each item in the file has an identifier - you use this identifier to retrieve a specific resource when the program needs it. To create or modify a project's resource file use the Tools|Resource Editor command from within the Visual Basic IDE (you may need to use the Add-in Manager to load the Resource Editor first). Then, use the Resource Editor's commands to add bitmaps and other items to the file. Each resource is assigned an identifier at this time. You can also add an existing resource file to the project using the Project|Add New Resource File command.

In your program you use three functions to load data from the resource file: LoadResPicture for bitmap images, LoadResString for strings, and LoadResData for various kinds of data. Let's look at some examples. This line of code loads an icon resource with identifier 101 and assigns it as the icon for Form1:

Form1.Icon = LoadResPicture(101, vbResIcon)

Likewise, this code loads a bitmap image and displays it is a picture box control:

Picture1.Picture = LoadResPicture(102, vbResBitmap)

The following code loads a string from the resource file and displays it in a label control:

Label1.Caption = LoadResString(103)

The LoadResData function is used for arbitrary binary data such as WAV (sound) and AVI (video) files. Your program must contain the code to make proper use of such data. Use of the LoadResData function is beyond the scope of this tip but there is more information in the Visual Basic documentation.

Visual Basic Tips

Control the CD-ROM Door

When your program makes use of the CD-ROM drive, it can be a nice touch to open and close the door under program control. You can do so with the mciSendString function from the Windows API. This function provides a general purpose interface to Windows' multimedia capabilities. Its declaration is shown here:

Declare Function mciSendString Lib "winmm.dll" Alias "mciSendStringA" _
(ByVal lpCommandString As String, ByVal lpReturnString As String, _
ByVal uReturnLength As Long, ByVal hwndCallback As Long) As Long


Because the CD-ROM is considered to be a multimedia device, you can use this API function to control it. The first argument tells the device what you want to do. For example, pass the string "set CDAudio door open" to open the door. For example:

retval = mciSendString("set CDAudio door open", "", 0, 0)

You can see that the second through fourth arguments are not used in this case and are passed either a blank string or the value zero. Likewise the function's return value can be ignored. Along with the function declaration shown above you can put the following two procedures in a code module in your program to provide control of the CD-ROM door.

Public Sub OpenCDDoor()

Dim retval As Long
retval = mciSendString("set CDAudio door open", "", 0, 0)

End Sub

Public Sub CloseCDDoor()

Dim retval As Long
retval = mciSendString("set CDAudio door closed", "", 0, 0)

End Sub


Note that calling OpenCDDoor when the door is already open, or CloseCDDoor when it is closed, has no effect.

Visual Basic Tips

Get Data from Excel

Microsoft's Excel spreadsheet is a popular program for storing all kinds of data. What if your Visual Basic programs need a piece of data that is stored in an Excel workbook? This is not too hard to do, thanks to the technique of automation, and this tip shows you how. You'll use a techniaue called automation, which permits one program to manipulate another program. In this case, your Visual Basic program will use automation to do the following:

1) Start Excel.
2) Open a workbook.
3) Retrieve data from a specific cell in that workbook.
4) Close Excel.

In order to automate Excel from within a Visual Basic program, you must add a reference to the Excel object library to your project. To do so, select Project|References from the Visual Basic menu to display the References dialog box. Scroll down to find the entry that says "Microsoft Excel 10.0 Object Library" and put a checkmark next to it, then close the dialog box. The version number may be different - "10.0" is for Excel XP - but the programming is the same for all versions of Excel from Office 97 through Office 2003.

With this reference in place, the first step is to start Excel with the following code:

Dim xlApp As New Excel.Application

After this code executes you will use the variable xlApp to refer to Excel. Note that Excel will not be visible on-screen when started this way - it just runs in the background. You also need two variables to refer to the workbook and worksheet. They are declared as shown here:

Dim wb As Excel.Workbook
Dim ws As Excel.Worksheet

Next, open the desired workbook and get a reference to the desired worksheet within that workbook. This code assumes the data is in "Sheet1" in the workbook "c:\data\testdata.xls":

Set wb = xlApp.Workbooks.Open("c:\data\testdata.xls")
Set ws = wb.Sheets("Sheet1")

Finally, get the data from the desired cell. The following code puts the data in a Text Box but your program can do anything it wants with the data:

Text1.Text = ws.Cells(2, 3).Value

The Cells method takes arguments that identify the cell by row and column. In this example it references the cell in row 2 and column 3 - in other words, cell C2. Finally, destroy your references to close Excel:

Set wb = Nothing
Set ws = Nothing
Set xlApp = Nothing

In a real-world program you will have to include error handling to deal with various possibilities such as the workbook not being found at the specified path. This is best done using an On Error Resume Next statement and testing the Err object after each automation statement that could cause an error. Here's an example. This Click event procedure, when the associated Command Button is clicked, gets the data from cell A1 of the specified workbook/worksheet and displays it in a Text Box.

Private Sub Command1_Click()

Dim xlApp As New Excel.Application
Dim wb As Excel.Workbook
Dim ws As Excel.Worksheet

On Error Resume Next
Set wb = xlApp.Workbooks.Open(App.Path & "\testdata.xls")
If Err > 0 Then
  If Err = 1004 Then MsgBox "File not found"
Else
  MsgBox "Error " & Err.Number & " occurred."
End If
Exit Sub
End If

Set ws = wb.Sheets("Sheet1")
Text1.Text = ws.Cells(1, 1).Value

Set wb = Nothing
Set ws = Nothing
Set xlApp = Nothing

End Sub

There's a great deal more to automating Excel and other Office programs, and you can add a lot of power to your Visual Basic programs this way. You must be aware, however, that this technique requires that the person running your program have the required Office program installed on their system. You cannot legally distribute Office components with your Visual Basic program.

Visual Basic Tips

Support Formatted Text with the Rich TextBox Control

Rich Text Format, or RTF, is a widely accepted standard for storage and transmission of formatted text. RTF consists of plain ASCII text with special codes embedded to control formatting. Most word processors, including Microsoft Word, support RTF. RTF is a very sophisticated standard and can control all of the aspects of formatting that you normally work with, such as fonts, indentation, boldface, underline, bulleted lists, and tabs. With the Rich TextBox, or RTB, control you can add RTF capability to your Visual Basic programs. To use this control in a program you must add a reference to it by selecting Project|Components and then selecting Microsoft Rich Textbox Control from the list.

When placed on the form, the RTB control looks the same as a regular TextBox control - and in fact it behaves pretty much the same, too. Most of its special features must be accessed programmatically by means of the control's properties and methods. For example, the RTB control's SelBold property is used to boldface text. Set this property to True and selected (highlighted) text in the control, or text that is subsequently typed in, will be boldface. Set SelBold to false to remove boldface from text. Similar properties exist to control italics, underlining, and strikethrough.

An RTB control can display any font that is installed on the system. Of course a Text Box control can do the same thing, but a TextBox control must use the same font for all its text while an RTB control can use different fonts for different sections of text. For example, you could use this code in a form's Load event procedure to load a Combo Box with a list of all the available fonts:

Private Sub Form_Load()

Dim i As Integer
For i = 0 To Screen.FontCount - 1
  Combo1.AddItem Screen.Fonts(i)
Next

End Sub

Then, when the user selects a font from the Combo Box this code assigns it to the RTB control:

Private Sub Combo1_Click()
  RichTextBox1.SelFontName = Combo1.Text
End Sub

File operations are also handled by built-in methods. You use the LoadFile and SaveFile methods to directly load and save data. Or, if you prefer, your program can use the TextRTF property to access the control's data and then use Visual Basic's usual file-related statements to perform disk operations. One very useful feature is that the TextRTF property gets all of the control's text, including any RTF codes, but the Text property returns the text minus any codes.

The Rich Text Box control can be used for unformatted text as well. When used in this way the control can be though of as a replacement for the regular TextBox control without its 64 k length limit on text. In fact the contents of a RTB control are limited only by system memory.

There's quite a bit more to this powerful and flexible control, including support for printing and for embedded OLE objects, that you can look into on your own. When you need to add formatted text support to a Visual Basic application, the RTB control may be just what you need.

Visual Basic Tips

Toggling a Form's Title Bar at Run-time

Permitting the user to toggle a form's title bar on or off can be a great program feature. Unfortunately the relevant form property, BorderStyle, is read-only at run-time. THis means that you must turn to the Windows API for this functionality. This tip shows you how.

Every Windows form has a set of style bits that controls various aspects of its appearance and behavior. The "caption" bit controls display of the title bar, and by setting or clearing that bit you can turn the title bar display on or off. The strategy is as follows:

1) Retrieve the window's style bits.
2) Set or clear the caption bit as needed.
3) Write the modified style bits back to the window.
4) Redraw the window to display its new style.

You'll need to use three API functions for this task. Their declarations are as follows (place these in a code module in your project):

Public Declare Function GetWindowLong Lib "user32" Alias _
"GetWindowLongA" (ByVal hwnd As Long, _
ByVal nIndex As Long) As Long

Public Declare Function SetWindowLong Lib "user32" Alias _
"SetWindowLongA" (ByVal hwnd As Long, _
ByVal nIndex As Long, ByVal dwNewLong As Long) As Long

Public Declare Function SetWindowPos Lib "user32" _
(ByVal hwnd As Long, ByVal hWndInsertAfter As Long, _
ByVal x As Long, ByVal y As Long, ByVal cx As Long, _
ByVal cy As Long, ByVal wFlags As Long) As Long

You will use GetWindowLong to retrieve the window's style bits, SetWindowLong to write the modified bits back to the window, and SetWindowPos to redraw the window. You'll also need the following constant declarations which should be placed in the same code module:

Private Const GWL_STYLE = (-16)
Private Const WS_CAPTION = &HC00000
Private Const SWP_FRAMECHANGED = &H20
Private Const SWP_NOMOVE = &H2
Private Const SWP_NOZORDER = &H4
Private Const SWP_NOSIZE = &H1

The final element is the function shown here. You pass it a reference to the window and a flag indicating whether to display or hide the title bar.

Public Sub ToggleTitleBar(f As Form, ShowTitle As Boolean)

Dim style As Long

' Get window's current style bits.
style = GetWindowLong(f.hwnd, GWL_STYLE)

' Set the style bit for the title on or off.
If ShowTitle Then
  style = style Or WS_CAPTION
Else
  style = style And Not WS_CAPTION
End If

' Send the new style to the window.
SetWindowLong f.hwnd, GWL_STYLE, style

' Repaint the window.
SetWindowPos f.hwnd, 0, 0, 0, 0, 0, SWP_FRAMECHANGED Or _
SWP_NOMOVE Or SWP_NOZORDER Or SWP_NOSIZE

End Sub

With this code in place, a program can set the title bar display for any of its forms. For example, this code turns off a form's title bar when the Command Button is clicked:

Private Sub Command1_Click()

ToggleTitleBar Me, False

End Sub

Visual Basic Tips

Playing the Windows System Sounds

The Windows operating system has a set of system sounds that are associated with certain system events such as a warning dialog box being displayed. By using these sounds in your program you will have more variety than if you limited yourself to the standard Visual Basic Beep command. This tip shows you how.

It's important to understand that the system sounds are not fixed. When Windows is first installed, the default sound scheme assigns specific sounds to certain system event. For example, the sound in the file Chord.wav is by default assigned to the "Question" sound. However the user can change these assignments using the Sounds applet in the Control Panel, assigning a different WAV file to each system sound or even specifying that a sound be silent. This means that you cannot be sure what sound, if any, will be played when you use the techniques in this tip. This may sound like a disadvantage but it really is not because the sounds that will play on each user's system are the specific sounds the user chose himself.

To play Windows system sounds use the API function MessageBeep. Its declaration is as follows:

Declare Function MessageBeep Lib "user32" (ByVal wType As Long) As Long

The function's one argument identifies the sound to play. The sound is usually identified using the constants shown here:

Public Const MB_ICONASTERISK = &H40&
Public Const MB_ICONEXCLAMATION = &H30&
Public Const MB_ICONQUESTION = &H20&
Public Const MB_ICONHAND = &H10&

The first three of these sounds are the ones identified as Asterisk, Exclamation, and Question in the Control Panel. The fourth one, MB_ICONHAND, corresponds to the Critical Stop sound in the Control Panel. You can also play the standard Beep sound by passing the value &HFFFFFFFF. Here's an example of using this function in a program, playing the Question sound when asking the user to confirm a file delete operation.

MessageBeep (MB_ICONQUESTION)
reply = MsgBox("Delete file - are you sure?", vbYesNoCancel, "Delete file?")
 

Visual Basic Tips

Understanding Visual Basic's Advanced Compiler Options

Most programmers compile their Visual Basic programs using the default advanced compiler settings. If you know about these settings you may be able to change them to improve performance of your compiled application. Visual Basic's compiler settings are found on the Compiler tab of the Project Properties dialog box, which you display by selecting ProjectName Properties from the Project menu. Click on the Advanced Optimizations button to set these compiler options, described below.

Assume no Aliasing. An alias is when a program refers to the same variable – that is, the same memory location – by two different names. It can occur when you use global variables and pass procedure arguments ByRef.. Here's an example:

Dim var1 As Long

MySub var1

Sub MySub(var2 As Long)
  var2 = 1 ' Refers to the memory location of var1
           ' because var1 was passed to the
           ' procedure ByRef (the default).
  var1 = 2 ' Refers directly to the memory
           ' location of var1.
End Sub

If your program does not use aliases, select the Assume No Aliasing option to permit the compiler to apply optimizations that it could not use if aliases were present, speeding program execution.

Remove Array Bounds Checks. When your code accesses an array element, Visual Basic normally checks to make sure the array element exists and, if not, triggers a run-time error. For example:

Dim MyArray(100) As Integer
MyArray(200) = 25

If you are confident this error will not happen - for example if your code uses the UBound and LBound functions to ensure that only legal array elements are accessed – you can select this compile option for faster, smaller compiled code. Be careful – if this option is selected and your code does write to a non-existent array element, it can result in erratic program behavior or a crash.

Remove Integer Overflow Checks. By default, Visual Basic checks the results of every calculation involving integer data types (Byte, Integer, and Long) to ensure that the result does not exceed the range of the data type. Selecting this option disables these checks and speeds integer calculations. Be aware, however, that if an overflow does occur the calculation will produce incorrect results without a warning or run-time error.

Remove Floating Point Error Checks. Floating point error checks do the same thing for calculations involving the floating point data types (Single and Double). Select this option to disable these checks with the same benefits and potential problems as described above for integer overflow checks.

Allow Unrounded Floating Point Operations. When you select this option, Visual Basic can perform certain operations with floating point data more efficiently. The downside is that when you compare floating point values, they may be considered not equal when in fact you expect them to be equal.

Remove Safe Pentium FDIV Checks. Some older Pentium CPUs have a bug that produces slightly inaccurate results when performing floating point divisions. Visual Basic's default is to check for and correct this inaccuracy. By selecting this option you disable the check and your code will perform floating point divisions faster. These old CPUs are pretty scarce these days, so this option is usually a safe one to select.

The advanced compiler options described in this tip should be applied carefully and only if you understand the potential effects on your program.

Visual Basic Tips

Understanding the ComboBox Control

The ComboBox control is very useful in a variety of situations, but many programmers don't make the most of it because they do not understand how its Style property works. This tip explains the details.

By default a ComboBox control's Style property is set to vbComboDropDown (value = 0). This is the default style because it results in a ComboBox that works the way programmers usually want it to work. The control displays like a single-line text box with a down arrow at the right side. The user can type directly into the control or, by clicking the arrow, can select from the predefined items that have been loaded into the control. This style saves screen real estate because the list of items is hidden until the user clicks the arrow.

Setting the Style property to vbComboSimple (value = 1) results in a control that looks like a text box above a list box. As with the default style, the user can type in the text box or select from the list. The difference is that the list is always displayed with the advantage that the available choices are always visible to the user. The disadvantage is, of course, that the control takes up more room on the screen when this style is used.

The third and final ComboBox style is vbComboDrop-DownList (value = 2). When this style is selected, the ComboBox displays the same way as with the default style, a single-line text box with a down arrow at the right side. The difference is in the behavior – the user cannot type data into the control but must select from the list. In other words, a ComboBox with Style set to vbComboDrop-DownList looks like a ComboBox but acts like a ListBox.

With an understanding of how a ComboBox control's Style property affects its behavior and appearance, you can select the setting that is best for your application needs.

Visual Basic Tips

Don't Forget the Class Builder Utility

While Visual Basic is not a full-fledged object oriented language, it does provide some OO features that can be very useful. Most important, it lets you define classes for use in your programs. Each class in defined in a Visual Basic class module, and can contain properties, methods, and event handlers. While it is possible to write a class's code by hand, you'll save a lot of time a frustration if you use the Class Builder utility – particularly for large, complex classes. This utility can be used to work with collections as well.

The Class Builder utility is an add-in and is accessed from Visual Basic's Add-Ins menu. If it is not listed there, you will have to use the Add-In Manager to add it to the menu before you can access it. The utility displays any existing classes and collections in your project as well as their hierarchical arrangement. To give you an idea of how handy this utility is, let's take a look at the process of creating a new class:

  1. Click the Add New Class button.
  2. Enter the name for the class.
  3. Specify the existing class the new class is to be based on, or specify (new class) to create a new class not based on another class.
  4. Click OK.

The new class is empty, of course (except for any members it inherited from the base class if one was specified in step 3 above). Adding members to the class is just as easy. Here's how to add a property:

  1. With the class selected, click the Add New Property button.
  2. Enter the property name.
  3. Select the data type of the property.
  4. Specify whether the property is to be implemented as a public property, a friend property, or a public variable.
  5. Specify whether the property will be the class's default property.

When you are finished, the code for the new property is added to the class. For a public property, for example, this consists of:

Adding methods is equally easy – you specify the method name, argument names and types, and optionally return value of the method and the utility inserts the skeleton of the method code in the class. You must write the code within the method, of course, but the dull scut-work is taken care of for you.

There's a lot more to the Class Builder utility that you can explore on your own. Even if you use classes infrequently in your Visual Basic projects, this tool can save you time and hassles.

Visual Basic Tips

Take Advantage of Conditional Compilation

Conditional compilation is a Visual Basic feature that lets you specify that some source code be included and other source code be ignored when the program is compiled. It is useful for creating different distribution versions of your program from the same source code files –for example, a limited "demo" version and a full-featured version for a shareware program, or different program versions for different languages. I have also found it useful for defining debug and distribution versions of a program. Visual Basic provides two compilation directives for this purpose: #Const and #If...#End If.

#Const is used to define conditional compilation constants as follows:

#Const ConstantName = 0 ' to set the constant to False
#Const ConstantName = 1 ' to set the constant to True

A conditional compilation constant define in this way is valid throughout its module. To define a constant that is valid throughout the project, enter them in the Conditional Compilation Constants box on the Make tab of the Project Properties dialog box.

You use the #If...#End If directive to block out code that will be compiled or not depending on the value of one or more conditional compilation constants. The syntax is very similar to the regular If...End If statement:

#If constant1 Then
' Statements to be compiled if constant1 is True
#ElseIf constant2
' Statements to be compiled if constant2 is True
#Else
' Statements to be compiled if no previous constant is True.
#End If

The #ElseIf and #Else sections are optional. Note that you cannot use #If...#End If with regular Visual Basic constants defined with the Const keyword.

Here's an example of using conditional compilation. Suppose you have written a shareware program and want printing to be functional only in the registered version, not the free trial version. You could put this code in the project:

#If RegisteredVersion Then
' Code to print goes here.
#Else
MsgBox "Printing is supported only in the registered version."
#End If

When compiling the free trial version, define RegisteredVersion as 0 (False), and when compiling the registered version define it as 1 (True).

Visual Basic Tips

Get to Know the SysInfo Control

The SysInfo control is designed to respond to changes in the operating system that occur while a program is running. These changes trigger events in the control, and your Program can respond as needed. To use the SysInfo control in a project you must first select it in the Components dialog box so it will be available in the Toolbox.

What changes does the SysInfo control detect? It's a fairly long list; here are some of the most commonly used events:

One way I use this control is to write code to respond to the DisplayChanged event. If the user changes the screen resolution while the program is running, the program can adjust the size of its forms to accommodate the new screen settings.

The SysInfo control also has a set of properties that let your program obtain certain operating system information. Some of them are relevant to only when a program is running on a laptop. For example, the ACStatus property can be used to determine whether the laptop is running on AC power, and the BatteryLifeTime property returns the time remaining on the battery.

Other SysInfo control properties provide information about the Windows desktop. Specifically, there are four properties that provide information about the work area – that part of the desktop not occupied by the Windows task bar. These properties are WorkAreaHeight, WorkAreaWidth, WorkAreaTop, WorkAreaLeft. The values returned by these properties are in twips. For example, when the task bar is in its default position at the bottom of the screen, WorkAreaHeight returns the height of the entire screen minus the height of the taskbar. Likewise, when the taskbar has been moved to the left edge of the screen, WorkAreaWidth returns the width of the entire screen minus the width of the taskbar.

Here's an example of using these properties. This Form_Load procedure causes the form to fill the entire work area when the form is displayed:

Private Sub Form_Load()

With SysInfo1
Me.Move .WorkAreaLeft, .WorkAreaTop, .WorkAreaWidth, .WorkAreaHeight
End With

End Sub

Since the task bar can be hidden, moved to different edges of the screen, and resized, you may want to use the the SysInfo properties to ensure that your program makes the best use of the work area.

Visual Basic Tips

Dragging Files to your Visual Basic Program

Windows provides excellent support for using drag-and-drop to move or copy information from one location to another. For example, you can use this technique in Windows Explorer to move or copy files from one location to another. This tool is available in Visual Basic as well. This tip shows you how you can set up a Visual Basic program to accept file names dragged from Windows Explorer.

Data that is dragged and dropped from another program is registered in Visual Basic in the OLEDragDrop event procedure. This means that any control that has this event procedure can be the recipient of file names dragged from Explorer. This includes forms as well as the TextBox and most other Visual Basic controls. When the user drops the data on the control this event procedure is passed a DataObject that contains the data. Specifically, it includes a Files collection that contains the names of the file or files being dropped. Note that drag-and-drop can be used to transfer other kinds of data to a Visual Basic program, but this tip will be limited to file names.

To permit the dropping of file names in a Visual Basic program, set the OLEDropMode property of the recipient object (a form or control) to 1 – Manual. Then, put code in the object's OLEDragDrop event procedure to retrieve the file names. Here's an OLEDragDrop event procedure for a Text Box control that displays the names of the dropped files. In addition to setting the OLEDropMode property of the Text Box to 1 – Manual you must also set its MultiLine property to True.

Private Sub Text1_OLEDragDrop(Data As DataObject, Effect As Long, _
  Button As Integer, Shift As Integer, X As Single, Y As Single)

Dim i As Integer
For i = 1 To Data.Files.Count
Text1.Text = Text1.Text & Data.Files(i) & vbCrLf
Next

End Sub

To try this program, open Windows Explorer and drag one or more file names to the Text Box. You'll see that the transferred data includes the full path of the file. What your program does with this information is, of course, up to you.

Visual Basic Tips

Change Text Box Margins

A Text Box control normally has essentially no margins – text will run right up to the right and left edges. This tip shows you how to change the margins of a Text Box control. This is relevant only when the control's MultiLine property is True.

To change the margins to send a message to the control using the API SendMessage function. The declaration is as follows (put this in a code module in your project):

Public Declare Function SendMessage Lib "user32" Alias "SendMessageA" _
(ByVal hwnd As Long, ByVal wMsg As Long, ByVal wParam As Long, ByVal _
lParam As Long) As Long

You'll also need the following constant values:

Public Const EM_SETMARGINS = &HD3
Public Const EC_LEFTMARGIN = &H1
Public Const EC_RIGHTMARGIN = &H2

Margin values are expressed in pixels. To change the margins of a TextBox, send it a message as shown here. The desired values of the left and right margins are assumed to be in the variables left_margin and right_margin, and the variable margins is a type Long.

margins = right_margin * &H10000 + left_margin
SendMessage Text1.hwnd, EM_SETMARGINS, _
EC_LEFTMARGIN Or EC_RIGHTMARGIN, margins

In theory, the control is supposed to repaint itself to display the new margins after receiving this message (if the control already contains some text). In my experience, sometimes only the left margin goes into effect. To get the new right margin setting to "take" you must delete and then re-write the contents of the control after setting the margins:

s = Text1.Text
Text1.Text = ""
Text1.Text = s

Visual Basic Tips

Accept Only Uppercase Letters in a Text Box

Problems can arise in some situations because users enter text with different capitalization. This problem can be avoided by converting all text to uppercase as it is entered. This tip shows you how.

Letters and other characters are represented numerically in Visual Basic. The lowercase letters a-z are represented by the values 97-122 while the uppercase letters A-Z are coded as 65-90. Thus, each uppercase letter's value is 32 less that the corresponding lower case letter's value. To force input to uppercase all you need do is check each character that is input and, if its value is between 97 and 122, subtract 32 from it. You would do this in the Text Box control's KeyPress event procedure:

Private Sub Text1_KeyPress(KeyAscii As Integer)

If KeyAscii >= 97 And KeyAscii <= 122 Then
KeyAscii = KeyAscii - 32
End If

End Sub

Non-letter characters, such as digits and punctuation, are not affected. Note that this technique works only for text entered from the keyboard – it will not change text inserted into the Text Box by code.

Visual Basic Tips

Disable a Form's Close Button

By default a Visual Basic form displays a close button – an "X" – in the title bar that the user can click to close the window. A Close command is also available on the form's System menu. In some programs you may not want the user to be able to close the form in this way – you want to force them to use a program command to close the form to ensure that certain housekeeping tasks are performed first. Setting the form's ControlBox property to False does the trick but it also hides the minimize and maximize buttons, which you may not want. This tip shows you how to selectively disable the close button and remove the Close command from the system menu.

This technique relies on three API functions. You'll need to put their declarations in a code module in your project, along with a couple of constant declarations, as shown here:

Public Const SC_CLOSE = &HF060
Public Const MF_BYCOMMAND = &H0

Public Declare Function GetSystemMenu Lib "user32" _
(ByVal hwnd As Long, ByVal bRevert As Long) As Long

Public Declare Function DeleteMenu Lib "user32" _
(ByVal hMenu As Long, ByVal nPosition As Long, ByVal wFlags As Long) As Long

Public Declare Function DrawMenuBar Lib "user32" (ByVal hwnd As Long) As Long

Then, put the following code in the form's Load event procedure:

Dim hMenu As Long

hMenu = GetSystemMenu(Me.hwnd, 0&)
If hMenu Then
Call DeleteMenu(hMenu, SC_CLOSE, MF_BYCOMMAND)
DrawMenuBar (Me.hwnd)
End If

When you disable the close button on a form, be sure to give the user another way to close it, such as a Command Button that executes an Unload statement.

Visual Basic Tips

 

Display 3-D Text on a Form

Visual Basic has plenty of tools for displaying regular text, but what if you want special effects? This tip shows you how to display 3-D text in your program.

The Windows API has a function TextOut that displays text on the screen. The declaration is as follows (place this in a module in your program):

Public Declare Function TextOut Lib "gdi32" Alias _
"TextOutA" (ByVal hdc As Long, ByVal x As Long, _
ByVal y As Long, ByVal lpString As String, ByVal _
nCount As Long) As Long

The arguments are:

- hdc is the device context of the destination (more on this soon).
- x and y are the coordinates of the location to display the text.
- lpString is the text to display.
- nCount is the length of the string.

A device context is the way Windows represents certain display objects. In Visual Basic, only forms, Picture Box controls, and the printers have a device context, so that means this technique is limited to displaying 3-D text on those objects.

The technique used here to display 3-D text is to output the string repeatedly at slightly different, overlapping locations. By varying the color of the text with each iteration, a 3-D effect is obtained. Here's an example.

Private Sub Command1_Click()

Dim i As Integer
Dim s As String

With Form1
s = "Text to display"
For i = 0 To 127
.Font.Name = "Times New Roman"
.Font.Size = 36
.ForeColor = RGB(i * 2, i * 2, i * 2)
TextOut .hdc, i / 15, i / 15, s, Len(s)
Next
End With

End Sub

You can see that the text is output 128 times. Each time, the position is shifted slightly down and to the right, and the color is changed. In the entire process, the color changes from RGB(0, 0, 0), or black, to RGB(254, 254, 254), or essentially white. You could get a different but equally effective result by varying the color from white to black by changing a single line of code as follows:

.ForeColor = RGB(256 - i * 2, 256 - i * 2, 256 - i * 2)

This code varies the color from blue to red:

.ForeColor = RGB(i * 2, 0, 256 - i * 2)

There are lots of attractive effects available with this technique. I recommend that you experiment until you get just what you want.
 

Visual Basic Tips

 

Save Time with the API Text Viewer

Many powerful Visual Basic programming techniques involve calling functions in the Windows API. This requires you to place the function declarations in your program. In some cases you'll need constant and type declarations as well. Typing them in manually is time consuming and prone to errors. It's much better to use the API Viewer utility that is installed with Visual Basic. To start this program select Microsoft Visual Basic 6.0 Tools and then API Text Viewer from the Start menu.

The API data is supplied as a text file. The first time you use the Viewer you can convert this to a database file for faster access. Here's how:

  1. Select File|Load Text File from the Viewer's menu.
  2. Select Win32API.txt and click Open.
  3. Select File|Convert Text to Database.
  4. Accept the default name Win32API.mdb and click Save. If this file already exists it means the conversion has already been performed so you can cancel the operation.

Once the database conversion has been perform you will load the database each time you start the API Viewer by selecting File|Load Database File from the menu.

To use the Viewer, first select the type of item you are looking for in the API Type list: Constants, Declares, or Types. The available items will be listed in the Available items list. You can scroll to find the item you need or, if you know the name, start typing it in the Type box to scroll automatically. Once you have located the item, select it by clicking.

The next step is to specify whether you want the item declared as Public or Private by selecting the appropriate option. Then click Add to add the item to the Selected Items list.

Repeat these steps until all the items you need have been added to the list. Finally, click the Copy button to copy the items to the clipboard. Now you can switch back to the Visual Basic IDE and use the Edit|Paste command to insert the declarations in your source code. No typing, no errors – a real time-saver!

Visual Basic Tips

 

Changing Combo Box Height

 When the user clicks a Combo Box, it drops down a list from which the user can select. The height of the dropped-down list is set as follows:

 This tip shows you how to change the maximum height of a Combo Box. This can be useful when, for example, your list contains 9 or 10 items and you do not want the user to have to scroll to see the last few. It requires a Windows API function whose declaration must be put in a project module: 

Public Declare Function MoveWindow Lib "user32" (ByVal hwnd As Long, _
  ByVal x As Long, ByVal y As Long, ByVal nWidth As Long, _
  ByVal nHeight As Long, ByVal bRepaint As Long) As Long 

Note that the size and location values are in pixels so you must change the scale mode of the form as shown in the example. The code shown here demonstrates how to use this function. 

Private Sub Form_Load() 

Dim OldScaleMode As Integer
Dim cbx_left As Integer
Dim cbx_top As Integer
Dim cbx_width As Integer
Dim cbx_height As Integer 

Combo1.AddItem ("Item1")
Combo1.AddItem ("Item2")
Combo1.AddItem ("Item3")
Combo1.AddItem ("Item4")
Combo1.AddItem ("Item5")
Combo1.AddItem ("Item6")
Combo1.AddItem ("Item7")
Combo1.AddItem ("Item8")
Combo1.AddItem ("Item9")
Combo1.AddItem ("Item10")
Combo1.AddItem ("Item11")
Combo1.AddItem ("Item12")

' Save old scale mode.
OldScaleMode = Me.ScaleMode

' Set scale mode to pixels.
Me.ScaleMode = vbPixels

' Get the combo box current location and width.
cbx_left = Combo1.left
cbx_top = Combo1.top
cbx_width = Combo1.Width

' Calculate the height that would make the combo box
' drop down to near the bottom of the form.
cbx_height = Me.ScaleHeight - cbx_top

' Restore scale mode.
Me.ScaleMode = OldScaleMode

' Change combo box height.
MoveWindow Combo1.hwnd, cbx_left, cbx_top, _
  cbx_width, cbx_height, 1

End Sub

Note that this technique sets the maximum height of the Combo Box, not its actual height. This means that if the height you specify is more than required to display the entire list of items, the size will be automatically adjusted to fit the list. The size also adjusts automatically if items are later added to the Combo Box while the program is running. 

Visual Basic Tips

 

Implement Auto-find in a List Box 

When a List Box control has the focus, it will automatically scroll to the first item that begins with the letter you type. It will not, however, automatically select items based on more than the first letter. For example, if you type "ap" you may want the control to select the first item that begins with "ap". You can implement this behavior using an API function as explained in this tip. 

The function you need is SendMessage. It's declaration, which must be included in the program, is as follows:

Public Declare Function SendMessage Lib "user32" _
  Alias "SendMessageA" (ByVal hwnd As Long, _
  ByVal wMsg As Long, ByVal wParam As Long, _
  ByVal lParam As String) As Long

You'll also need the following constant which tells the List Box control to select the first item that begins with the specified prefix: 

Public Const LB_SELECTSTRING = &H18C 

In addition to the List Box, this technique requires a Text Box control. The user enters text in the Text Box and the List Box automatically selects the first matching item. The message is sent in the Text Box's Change event procedure: 

Private Sub Text1_Change() 

If Text1.Text <> "" Then
    SendMessage List1.hwnd, LB_SELECTSTRING, -1, Text1.Text
End If

End Sub

Letting the user select List Box items based on more than just the first letter can be particularly useful when the list contains many items. 

Visual Basic Tips

 

Understanding the KeyPreview Property

 A Visual Basic form has a KeyPreview property. Few programmers have ever heard of this property let alone knowing what it is for. It can be quite useful for some programming situations. 

When KeyPreview is at its default setting of False, keyboard input goes to the control that has the focus. The form is unaware of keyboard events, and this is fine for most programs. When KeyPreview is True, however, the form's keyboard event handlers (KeyDown, KeyUp, and KeyPress) are fired before the keystroke is passed to the control with the focus. This lets you handle keyboard input at two levels, intercepting keystrokes that have a global meaning at the form level.

 When would you want to do this? One good example is the function keys. You may want certain function keys to have program-wide uses, such as F1 for help, F2 to save data, F3 to open a new file, and so on. By setting KeyPreview to true you can catch these keys in the form's KeyDown event procedure:

Private Sub Form_KeyDown (KeyCode As Integer, Shift As Integer)
   Select Case KeyCode
      Case vbKeyF1:
        ' Code to display help.
      Case vbKeyF2:
        ' Code to save data.
      Case vbKeyF3:
        ' Code to open a file.
   End Select
End Sub

If you want to handle keyboard events only at the form level, not allowing controls to receive keyboard events, set KeyAscii to 0 in the form's KeyPress event and set KeyCode to 0 in the form's KeyDown event. You can block selected keys or all keys from reaching controls.

Remember that if a form has no controls on it, or if all of its controls are disabled, it will receive keyboard events regardless of the value of KeyPreview. You also need to be aware that some controls intercept specific keystrokes so that the form can't receive them. This includes the Enter key when focus is on a CommandButton control and the arrow keys when focus is on a ListBox control.

Visual Basic Tips

 

Selecting all Text when a Text Box gets the Focus

When the focus is moved to a Text Box that contains text, Visual Basic's default is to position the caret (editing cursor) at the start of the text. In some applications it might be more appropriate to have all of the control's text selected when it receives the focus. One advantage to this is that the existing text will be deleted if the user starts typing something new. This tip shows you how to implement this behavior.

A Text Box has two properties that determine what text, if any, is selected: 

 You can select all text in a Text Box by setting SelStart to 0 and SelLength to the length of the text. Here's a short procedure that does this: 

Sub SelectAllText(tb As TextBox) 
  tb.SelStart = 0
  tb.SelLength = Len(tb.Text)
End Sub

 Then, for each Text Box that you want to behave this way, call the procedure from the GotFocus event procedure, passing the name of the Text Box as the argument:

Private Sub Text1_GotFocus() 
  SelectAllText Text1 
End Sub

This works when the Text Box gets the focus either by tabbing or by a mouse click.

Visual Basic Tips

 

Cautions when using the Setup and Deployment Wizard on Windows XP

Visual Basic's Setup and Deployment Wizard is a handy tool for creating installation packages that you can distribute. Unfortunately it has at least one bug. This applies only if you are using the Windows XP operating system for your Visual Basic development and someone running Windows 2000 is trying to install a setup package that you created. I can't say it happens all the time but it certainly happens enough to be a serious problem.

Here's what happens: during the setup process the user is told that some files need to be undated and the system rebooted. When the user clicks OK the system reboots – so far so good. But then they are presented with another dialog box with the same message - files need to be undated and the system rebooted. They are in and endless loop of reboots and the only way out of it is to click Cancel instead of OK. The end result is that the application setup is not complete.

Microsoft has been aware of this problem for some time and has claimed that it was fixed as part of service pack 4 for Visual Basic. It has not, however, been fixed reliably – in other words some developers still have this problem despite installing the service pack. There are only two sure fixes that I know of. One is to maintain a Windows 2000 system to use only for generating setup packages. The other is to use a third party program, such as Wise InstallMaster, to create your setup packages.

Visual Basic Tips

 

Tools for Working With XML

XML is becoming a widely accepted standard for platform-independent data storage and exchange. While it is a text-based standard it can be very complex and Visual Basic's built-in text handling tools are not up to the job of doing any serious XML processing. Fortunately, Microsoft has developed software tools that you can download and use in your Visual Basic programs. They are available in the Microsoft XML Parser that you can download by going to msdn.microsoft.com/downloads and searching for "MSXML parser." The latest version is 4.0. You'll actually download the Microsoft XML Software Development Kit which includes the parser, online documentation, an XML reference document, and a tutorial.

Once the parser has been installed it will be listed in the Visual Basic References list as MSXML 4.0. When you select it, the parser components will be available for use in your program. The one you'll probably use most often is the DOMDocument class. This class implements the Document Object Model which is an accepted standard for programmatically accessing, validating, and modifying XML data. An XML document that has been loaded into a DOMDocument object is represented as a tree of nodes corresponding to the elements, attributes, data, and other components of the XML file. Using the concept of parent, sibling, and child nodes, the model makes the entire contents of the XML file available for reading or modification. You have essentially complete control – your program con move forward and backward in the tree, read data, modify data, add new elements, and so on. Another essential task that this class can perform is to validate an XML file – in other words, to verify that the logical structure and data types in the file are in agreement with the associated Document Type Definition or Schema. Finally, the DOMDocument object can apply an XSLT transform to XML data.

Another XML tool that is provided in the parser is an implementation of the Simple API for XML, or SAX. The SAX does not have nearly as many capabilities as the Document Objet Model – you cannot, for example, apply XSLT transforms or modify the document structure. Because of its limited capabilities the SAX is very fast and is easy on your memory resources. It is the tool of choice when you need to quickly look through an XML file for specific data.

With XML becoming more and more widespread it is a good idea for the Visual Basic programmer to have some familiarity with the available tools. With the XML Software Development Kit, Microsoft has provided a powerful set of tools for you to use – and for free, which is hard to beat!

Visual Basic Tips

 

Save Time with Control Arrays

By default, every control on a Visual Basic form is an independent entity with its own name and its own event procedures. In some situations your programming work will be simplified by using a control array. This permits two or more controls of the same type to have the same name and same event procedures but retain their own separate properties. This tip shows you how.

To create a control array you must first put a single control of the desired type on the form. Then, with the control selected, press Ctrl+C followed by Ctrl+V. Visual Basic will display the following prompt:

You already have a control named XXXXX. Do you want to create a control array?

Click yes to create the array. You'll now have two identical controls – Command Buttons, Check Boxes, or whatever – on the form. Repeat the steps to add more controls to the array – there are no limits to the size of a control array other than those imposed by system resources, although you'll rarely find arrays of more than 8-12 controls to be useful.

When you select a control in an array the Properties window displays its properties. Except for the Name, these properties are completely independent from other controls in the array. If all the controls have the same name how are they told apart? The answer lies in the Index property. The first control in an array has Index = 0, the second has Index = 1, and so on. Controls that are not part of an array have a blank Index property.

As I have mentioned, all the controls in an array share the same event procedures. The procedure is passed an argument indicating the Index property of the control that received the event. This is where the usefullness of control arrays is most evident. You can write a single event procedure that handles the events for multiple controls. For example, you could create an array of three Command Button controls and then write the Click event procedure as follows:

Private Sub Command1_Click(Index As Integer)

Select Case Index
  Case 0
  ' Code for first action here.
  Case 1
  ' Code for second action here.
  Case 2
  ' Code for third action here.
End Select

End Sub

Keeping code together like this rather than spreading it out over multiple event procedures makes it easier to write and debug.

Visual Basic Tips

 

Adding Controls to a Form at Runtime

In most Visual Basic projects the number of controls on a form are set at design time and do not change when the program runs. But what if you have a situation where the number of controls needed is not known until runtime? For example, the program could read data from a database and need to display one control for each record that was found. This tip shows you how to add controls to a form at runtime. This technique is applicable to essentially any Visual Basic control.

To add controls at runtime there must already be at least one instance of the control on the form, and it must be part of a control array. To create a control array containing only a single control, add the control to the form and then set its Index property to 0. This control does not have to be visible.

When the program runs, you add additional controls with the Load statement:

Load ControlName(Index)

Each control that you add in this way initially has the same properties as the original control. You'll have to change at least the Top and Left properties to prevent all the added controls from displaying at the same position on-screen.

Here's an example. The variable rs refers to a recordset that contains information about rooms in a dormitory. The code goes through the recordset and for each room creates a Label control (lblRoom) and a ListBox control (lstRoom) and positions them in a grid on the form. ROOM_LIST_HEIGHT and ROOM_LIST_WIDTH are constants defined elsewhere that specify the control size.

col = 0
row = 0
Do While Not rs.EOF
  recordnumber = rs.AbsolutePosition
  Load lblRoom(recordnumber)
  lblRoom(recordnumber).Caption = "Room " & rs.Fields("RoomNumber").Value
  lblRoom(recordnumber).Top = row * (ROOM_LIST_HEIGHT * 1.3) + 300
  lblRoom(recordnumber).Left = col * (ROOM_LIST_WIDTH * 1.2) + 500
  lblRoom(recordnumber).Visible = True
  Load lstRoom(recordnumber)
  lstRoom(recordnumber).Left = lblRoom(recordnumber).Left
  lstRoom(recordnumber).Top = lblRoom(recordnumber).Top + _
  lblRoom(recordnumber).Height + 10
  lstRoom(recordnumber).Visible = True
  rs.MoveNext
  col = col + 1
  If col = 3 Then
    col = 0
    row = row + 1
  End If
Loop

Adding controls to a form at runtime can give your program the flexibility needed to react to changing data and circumstances.

Visual Basic Tips


Scrolling Controls on a Form

If you need to place more controls on a form than can be displayed at one time, one option is to create a tabbed interface using the TabStrip control. But sometimes the functions of the controls do not lend themseves to being divided among two or more tabs. IN this situation you can create a scrolling form that lets the user scroll controls into view.

Start by placing a Picture Box control on the form and naming it pbOuter. Then place a second Picture Box within the first one and name it pbInner. This is important – the second Picture Box must be a child of the first one. It can be useful to change the background color of one of the Picture Box controls so it is easy to tell them apart.

Now you can start placing the form's controls. They all must be placed within the second Picture Box, pbInner. You can increase the Picture Box size, both height and width, as needed to accommodate all the controls. The size of pbOuter does not matter as it will be adjusted in code when the program runs. The size of pbInner should be as tall as needed to fit all your controls, but no wider than the form. Add a vertical scroll bar on the form itself. Set the scroll bar to the desired width, but the height does not matter at this time. Finally, add a horizontal scroll bar on the form – set its height as desired but the width doesn’t matter.

The remaining action takes place in code. First, put code in the Form_Resize event procedure that positions the scroll bars against the right and bottom edges of the form and sizes pbOuter to fill the remainder of the form:

VScroll1.Move Me.ScaleWidth - VScroll1.Width, 0, VScroll1.Width, Me.ScaleHeight
HScroll1.Move 0, Me.ScaleHeight - HScroll1.Height, Me.ScaleWidth - _
VScroll1.Width, HScroll1.Height
pbOuter.Move 0, 0, Me.ScaleWidth - VScroll1.Width, _
Me.ScaleHeight - HScroll1.Height

You also need code in Form_Resize to determine if pbOuter is large enough to display all of pbInner without scrolling. In this case you should hide one or both scroll bars. If it's not large enough you need to show the scroll bars and set the scrolling parameters for the scroll bars:

If pbInner.Height <= pbOuter.Height Then
  VScroll1.Visible = False
Else
  VScroll1.Visible = True
  VScroll1.Max = pbInner.Height - pbOuter.Height
  VScroll1.LargeChange = 2500
  VScroll1.SmallChange = 250
End If

If pbInner.Width <= pbOuter.Width Then
  HScroll1.Visible = False
Else
  HScroll1.Visible = True
  HScroll1.Max = pbInner.Width - pbOuter.Width
  HScroll1.LargeChange = 2500
  HScroll1.SmallChange = 250
End If

Finally, you must place code in the Change event procedures for the scroll bars to perform the scrolling:

Private Sub HScroll1_Change()
  pbInner.Left = -HScroll1.Value
End Sub

Private Sub VScroll1_Change()
  pbInner.Top = -VScroll1.Value
End Sub

Using this technique you can place as many controls as needed on a form and the user will be able to scroll them into view as needed.

Visual Basic Tips
Keeping Track of Program Usage

Have you ever wondered when the last time a specific program was run? This information can be useful for security purposes in some situations where multiple people have access to a computer. If a user comes to work on Monday and sees that a program was last run on Saturday, when he was at the beach, he will know that someone else has been using the program. This tip shows you how to implement this.

The technique uses the Windows registry to store information. You should define constants for the application name and section, used for locating information in the registry:

Const APP_NAME = "MyAppName"
Const SECTION_NAME = "Usage"

Then place the following code where it will be executed each time the program is run. This would usually be the main form's Load event procedure, but you could also use the Unload event procedure. This code converts the current date and time to a string and stores it in the registry under the "Last used" key:

SaveSetting APP_NAME, SECTION_NAME, "Last used", CStr(Now)

Finally, you need code to read the registry value and display the information to the user. Here's an example of how you could do this:

Dim LastUse As String
LastUse = GetSetting(APP_NAME, SECTION_NAME, "Last used")
If LastUse = "" Then
  MsgBox "This program has not been run before."
Else
  MsgBox "This program was last run " & LastUse
End If

If you put this code in the Form_Load event procedure, be sure it comes before the call to SaveSetting. With a little additional programming you could also use the registry to keep track of when the program was started and stopped and how much total time it was running.

Visual Basic Tips

Create a Pane Splitter

Many Windows programs use a window with panes separated by a splitter. You can drag the splitter to make one pane larger and the other smaller, all without changing the parent window size. While Visual Basic does not provide a control for this purpose, you can do it yourself as shown in this tip.

The technique makes use of the fact that you can use a Picture Box control as a container for other controls. When designing the form, place two Picture Box controls on the form, one for each pane. You can call them pbTop and pbBottom, for example, for the upper and lower panes. Then place the other controls for each pane on the corresponding Picture Box.

When the program runs, code arranges the two Picture Box controls - the two panes - to fill the window at predefined sizes. The sizes are computed so that a thin strip of the underlying form shows through between the Picture Boxes - this will serve as the splitter. Set the form's MousePointer property to "7 - Size NS" so that an up/down sizing cursor will display when the mouse is over the splitter. Set each Picture Box control's MousePointer property to "1 - Arrow." The program also needs constants for the splitter height and for the minimum permitted height of each pane.

Then here's what happens. When the user starts dragging the splitter, the new splitter position is determined from the position of the mouse cursor. This occurs in the MouseMove event procedure. A procedure named ChangePaneSizes is called; code in this procedure uses the new splitter position to change the sizes of the two panes accordingly. Neither pane is permitted to be smaller than the specified minimum.

Code to perform these actions is shown below. You can try this out by creating a Visual Basic program with a form that contains two Picture Box controls named pbTop and pbBottom, then paste this code into the form's code window. This example is for two panes separated by a horizontal splitter, but could be adapted for other pane arrangements as well.

Option Explicit

Const SPLITTER_HEIGHT = 40
Const MIN_PANE_HEIGHT = 400

' The percentage of the window height
' occupied by the top pane.
Dim TopPanePercent As Single
' True when the splitter is being dragged.
Private Dragging As Boolean

Private Sub ChangePaneSizes()

' Arrange the panes according to the new splitter position.
Dim TopHeight As Single
Dim BottomHeight As Single

' Do nothing if window is minimized.
If WindowState = vbMinimized Then Exit Sub

TopHeight = (ScaleHeight - SPLITTER_HEIGHT) * TopPanePercent
If TopHeight < MIN_PANE_HEIGHT Then TopHeight = MIN_PANE_HEIGHT
pbTop.Move 0, 0, ScaleWidth, TopHeight
BottomHeight = (ScaleHeight - SPLITTER_HEIGHT) - TopHeight
If BottomHeight >= MIN_PANE_HEIGHT Then
pbBottom.Move 0, TopHeight + SPLITTER_HEIGHT, ScaleWidth, BottomHeight
End If

End Sub

Private Sub Form_Load()
' Initially each pane gets half the window.
TopPanePercent = 0.5
End Sub

Private Sub Form_MouseDown(Button As Integer, Shift As Integer, X As Single, _
Y As Single)
' Start dragging the splitter.
Dragging = True
End Sub

Private Sub Form_MouseMove(Button As Integer, Shift As Integer, X As Single, _
Y As Single)
' As the splitter is dragged.
' Do nothing if we're not dragging.
If Not Dragging Then Exit Sub

TopPanePercent = Y / ScaleHeight
If TopPanePercent < 0 Then TopPanePercent = 0
If TopPanePercent > 1 Then TopPanePercent = 1
ChangePaneSizes
End Sub

Private Sub Form_MouseUp(Button As Integer, Shift As Integer, X As Single, _
Y As Single)
' End dragging the splitter.
Dragging = False
End Sub

Private Sub Form_Resize()
' Change pane sizes if the window is resized.
ChangePaneSizes
End Sub

Visual Basic Tips

Changing Text Alignment for Forms and Picture Boxes

A Visual Basic program can dislay text on a form or a Picture Box using the Print method. The location of the text is determined by the destination object's CurrentX and CurrentY properties. The text is always left-aligned at this position, starting at the location (CurrentX, CurrentY) and extending to the right. Visual Basic itself has no provision for changing this alignment setting, but this tip shows you how to do so with a Windows API call. You'll use the SetTextAlign API function whose declaration is:

Declare Sub SetTextAlign Lib "gdi32" (ByVal hDC As Long, ByVal wFlags As Long)

- hDC is the destination object's device context which you obtain from its hDC property.
- wFlags is a value that specifies the desired alignment.

You specify the horizontal alignment using one of the following constants (You must define these constants in your program): TA_LEFT (value = 0), TA_RIGHT (value = 2), or TA_CENTER (value = 6). You can also specify the vertical alignment with these constants: TA_TOP (value = 0), TA_BOTTOM (value = 8), or TA_BASELINE (value = 24). Top alignment is the default - in other words the top of the text is aligned with the CurrentY position. The other two options are

- Bottom: The lowest any character descends such as the descender on a "y" or "g".
- Baseline: The bottom of characters that don’t have descenders such as "a" and "w".

To apply both a horizontal and a vertical alignment, simply use the Or operator to combine the two flags. This code sets the alignment for Form1 to center, baseline:

Call SetTextAlign(Form1.hDC, TA_CENTER Or TA_BASELINE

Note that changing alignment does not affect text that is already on the form or Picture Box.

Visual Basic Tips

Getting the Current User Name

Computers are often shared among two or more users. The Windows operating system provides for different user accounts so that each user can log on and then have access to their files and settings. Perhaps you would like to keep track of which users are running a Visual Basic program - you'll need to determine the name of the current user and then store it in some manner. This tip shows you how.

The API function GetUserName is designed specifically for this purpose. Its declaration is:

Declare Function GetUserName Lib "advapi32.dll" Alias "GetUserNameA" _
(ByVal lpBuffer As String, nSize As Long) As Long

The first argument is a string that will be used to return the user name, and the second argument is a number giving the length of the string. The return value is non-zero (True) on success and 0 (False) on failure. I have never seen this function fail so I always omit checking the return value.

To use this function you must first create a string and fill it with spaces. The number of spaces will determine the length of the string and therefore the maximum length user name that can be returned. Since people never choose long user names, I have found that a length of 40 is always more than adequate. Here's the code.

Dim UserName As String
UserName = Space(40)

Then it's simply a matter of calling the GetUserName function like this:

GetUserName UserName, Len(UserName)

After this call the variable UserName will contain the name of the current user. Your program can use this as needed, for example saving it to a log file or the registry to keep track of who is using the program.

Visual Basic Tips

Swapping the Mouse Buttons

On the Windows operating system the mouse usually has two buttons, right and left. You may want to swap the buttons so that pressing the left button triggers a right button event, and vice versa. This might be useful for left-handed users, for example. While the Windows control panel provides this option, you can also do it from a Visual Basic program. This tip shows you how.

To swap the mouse buttons you will use the function whose declaration is shown here:

Declare Function SwapMouseButton Lib "user32" (ByVal bSwap As Long) As Long

Set the argument bSwap to a non-zero value to swap the mouse buttons. Set it to zero to return to the normal non-swapped state. The return value is True if the mouse buttons were already swapped when the function was called, False if not.

Because the mouse is a shared resource, the setting you make with this function affects all applications, not just your Visual Basic application.

Visual Basic Tips

 

Create Complex Reports with the Data Report Designer

One of the reasons that Visual Basic 6 has become such a popular programming tools is it's sophisticated support for database programming. Tools such as the Visual Data Manager and the User Connection component greatly simplify tasks that used to be very time consuming. Another powerful database tool that Visual Basic programmers have at their disposal is the Data Report Designer which les you create complex reports from almost any data source.

The Data Report Designer creates what are known as banded hierarchical reports. While the name may sound strange these are in fact the most common type of database report and you have probably seen many of them, with headings, subheadings, details, and summaries organized in a hierarchical manner.

A Data Report is similar to a Visual Basic form in that it has a visual designer and a code module. Using the visual designer, you can divide the report into two or more sections each with its own headings. Each section can contain controls to display the report details. Design of the details sections is simplified by drag-and-drop functionality. The available controls are distinct from Visual Basic controls but have similar functionality. In particular the Function control lets you easily perform calculations on field data (sum, average, minimum, and maximum) and display the results as the report is generated. Headers and footers can be defined for the report as a whole and for each page of the report.

At run-time, output options are quite impressive. A print preview mode shows what the report will look like when printed. Printing is a simple matter of calling the PrintReport method. Export to a file is supported in both HTML and text formats.

The Data Report Designer is a very impressive tool. It is not suitable for every type of report, but when it fits your needs it can save you a tremendous amount of time.
 

Visual Basic Tips
Create an "Auto-OK" Dialog Box

With some programs it may be desirable to let them run unattended. But what if you want to give the user the option of changing program defaults or letting the program run on its own using the defaults? One way to do this is to use what I call "auto-OK" dialog boxes. As the program runs the dialog pops up to let the user change options if desired. If the user does not respond within a certain time, the dialog box closes itself and the program continues uaing the default settings. This tip shows you how to create this kind of dialog box.

This technique is based on the Timer control. The approach is as follows:

1. Place a timer on the form with its Interval property set to 1000 (1 second).

2. In the form's Load event procedure, initialize a "Time Remaining" variable to the total number of seconds the dialog box should remain open. At the same time, set the Timer control's Enabled property to True so that it starts running.

3. In the Timer control's Timer event procedure, decrement the Time Remaining variable by 1. Optionally, you can display the time remaining on the dialog box.

4. When the Time Remaining variable has reached zero, call the Click event procedure for the form's OK button or equivalent, closing the form and continuing program execution.

Here's some sample code. At the form level, declare the following:

Const AUTOCLOSE = 5
Dim TimeLeft As Integer


Here's the form's Load event procedure:

Private Sub Form_Load()

TimeLeft = AUTOCLOSE
Timer1.Interval = 1000
Timer1.Enabled = True

End Sub


And finally, here's the Timer event procedure. In this example, the form's OK button is named OKButton. The code in the OK button's event procedure, which is not included here, will take whatever actions are required to close the dialog box and continue program execution.

Private Sub Timer1_Timer()

TimeLeft = TimeLeft - 1
lblTimeRemaining.Caption = TimeLeft
If TimeLeft = 0 Then
  Timer1.Enabled = False
  Call OKButton_Click
End If

End Sub

An optional feature would be to use the Change event procedure of any controls on the form to turn the timer off. This cancels the countdown if the user makes any changes to the options that are presented on the form.

Visual Basic Tips

Working With Project Groups

Many development tasks that are undertaken with Visual Basic can be accomplished within a single project. In other circumstances, however, you may need to work on more than one project at the same time because they are dependent on each other in some way. For example, you may be working on an ActiveX control project at the same time that you are working on a Standard EXE project that uses the control. In situations such as this you can have two or more projects open at the same time in what's called a group. When you save the group, information about all the contained projects is saved. You can later open the group and have all contained projects opened at the same time. Each project can also be opened independently regardless of whether it is part of a group.

To create a group, first open or create the first project in the group. Then you can add an existing or new project to the group by select Add Project from the File menu. Visual Basic will display the Add Project dialog box. Use the New tab to add a new project or use the Existing tab to add an existing project. When you have two or more projects open, each one is listed in the Project window as usual, with forms and other project components arranged hierarchically under the project name. You can remove a project from the group by right-clicking it in the Project window and selecting Remove Project from the popup menu. Removing a project from a group does not affect the project files on disk.

Visual Basic groups are saved as VBG files. To save a group select Save Project Group from the File menu. Saving the group also saves the individual projects.

When you are working with a group, Visual Basic needs to know which project is the startup project. To set this, right-click a project name in the Project window and select Set as Startup from the popup menu. By default, the first EXE project that was added to a group is the startup project. Of course, any group can have only one startup project.

Visual Basic Tips

Creating and Using Global Properties

Most programmers know what properties are - the primary method by which objects store data and make it available to the program. In Visual Basic, you add properties to a class definition using Property Get and Property Let procedures. Relatively few programmers know that you can also create global properties that are not associated with any particular object but are available throughout the entire application. This tip shows you how.

But why would you want global properties - why not just create a global variable? The answer lies in the way properties work. A global variable is accessed directly, like any other variable. In contrast, a property is accessed indirectly via its Property Let and Property Get procedures. This lets you write additional code that is executed whenever the property is set or read. This code can be used for data validation or any other task that the program may require. This is not possible if you are using a global variable.

You create a global property in a code module. There are three things required:

1) A Private variable to hold the property value.
2) A Property Let procedure for setting the property value.
3) A Property Get procedure for retrieving the property value.

The Get and Set procedures must have the same name. This is the property name that you will use in the program. Here is sample code for a global property that holds a string value:

Private name As String

Public Property Set gName(ByVal newvalue As String)
    name = newvalue
End Property

Public Property Get gName() As String
    gName = name
End Property

Here's how it works. Suppose that code elsewhere in the program sets the property as follows:

gName = "Alice"

The Property Let procedure is automatically called with the new value - "Alice" in this case - passed as its argument. The code in the Property Let procedure assigns this value to the associated Private variable which is called name in this example. When code reads the value of the property, for example:

MsgBox(gName)

the Property get procedure is called and returns the value of the Private variable name to the program (Clearly the Get procedure is really a function).

In this example there is no extra processing so the property behaves just like a global variable. You could however include value checking, for example to store a default name if the program tries to store a blank name. That would require a change to the Property Set procedure as follows:

Public Property Set gName(ByVal newvalue As String)
  If newvalue = "" Then
    name = "Default name"
  Else
    name = newvalue
  End If
End Property

The use of global properties provides the programmer with more control over global data storage than is possible with global variables.

Visual Basic Tips


Creating and Using DLLs

A DLL, or Dynamic Link Library, is a file that contains compiled code. In this sense it is like an executable program (EXE file), but there is one major difference: once a DLL has been installed and registered on a system, its code is available to any program that calls it. This can be an advantage when you are writing multiple programs that will usually be installed together. By placing shared code in a DLL you can decrease program size and simplify the task of upgrades, and also make the code easily reusable in other projects.

Another use for DLLs is in web programming. ASP (Active Server Page) technology lets you include code in your web pages to provide dynamic functionality and database access, but the ASP code is viewable by anyone who visits your site. If you want to hide your code so that others cannot "borrow" it for their own use, you can create a DLL and install it on the server. Your ASP page can then call the DLL as needed, so the full functionality is available without exposing your source code to others.

In Visual Basic, you create a DLL by selecting ActiveX DLL from the New Project dialog box. The new project will contain a single class module, which illustrates how Visual Basic DLLs work. A DLL that you create will contain one or more classes, which are essentially identical to the classes you can create in a standard Visual Basic EXE project. When a program calls a DLL, it will actually be making use of the DLL's classes and their methods and properties. An ActiveX DLL can contain forms and other components as well, but classes are at the heart of their functionality.

The Visual Basic online documentation contains a lot of background information as well as a tutorial on ActiveX DLLs. I recommend reviewing this material before deciding whether there's a place for DLLs in your Visual Basic projects.

Visual Basic Tips


Making the Most of UDTs

A user defined type, or UDT, is a Visual Basic technique for defining a data type that exactly meets the needs of your program. A UDT can contain two or more individual data items that can be of different types, such as String and Integer. A UDT can even contain other UDTs and arrays, and you can create arrays of a UDT.

To define a UDT, use the Type...End Type statement, which must appear in the declarations section of a code module. Within the statement you define the individual items, or members, that the UDT will contain. Here's an example of a UDT you might create for keeping track of employee data:

Public Type Employee
    FirstName As String
    LastName As String
    DateOfHire As Date
    Salary As Currency
    EmployeeNumber As Integer
End Type

Once the type is defined you can create instances of it just like any other data type:

Dim OneEmployee As Employee
Dim AllEmployees(500) As Employee

To access the members of a UDT you use the Name.Member notation. Here are some examples:

OneEmployee.FirstName = "Jack"
OneEmployee.LastName = "Sprat"
AllEmployees(1).FirstName = "Maria"
AllEmployees(1).LastName = "Sanchez"

Nesting UDTs can be useful in some situations. Here's an example:

Public Type Person
FirstName As String
LastName As String
End Type

Public Type Employee
Name As Person
DateOfHire As Date
Salary As Currency
EmployeeNumber As Integer
End Type

When UDTs are nested you use the same Name.Member notation but with an extra level:

Dim OneEmployee As Employee
OneEmployee.Name.FirstName = "Jack"

UDTs can also be used for arguments to functions and procedures and as the return type for functions. They can also be helpful when storing data on disk because Visual Basic's Random file type is specifically designed to work with UDTs. All in all, UDTs are a tool that every Visual Basic programmer should know about.

Visual Basic Tips
Understanding ByVal and ByRef

Procedures are an essential part of almost every Visual Basic program. When you define a procedure, whether a Function or a Sub procedure, you need to decide whether the procedure arguments are passed by reference or by value. What difference does it make?

Visual  Basic's default is to pass arguments by reference. You can include the ByRef keyword in an argument list if desired, but because this is the default it has no effect:

Sub Foo(ByRef Arg1 As Integer, ByRef Arg2 As String)

When an argument is passed by reference, the procedure is passed the address of the argument variable - in other words a reference to the variable:

Dim Total as Integer
Call MySub(Total)

In this example, MySub receives a reference to Total. The practical consequence of this is that code in MySub can change total. Here's an example. First the procedure:

Sub MySub(Total As Integer)
Total = 50
End Sub

Now the code that calls the procedure:

Dim Total As Integer
Total = 100
Call MySub(Total)

After this code executes, the variable Total equals 50 because the code in the procedure changed its value. To pass an argument by value, use the ByVal keyword:

Sub MySub(ByVal Total As Integer)

When you use ByVal, the procedure is passed a copy of the argument variable and not a reference to the argument variable itself. Code in the procedure cannot change the variable's value:

Sub MySub(ByVal Total As Integer)
    Total = 50
End Sub

Now the code that calls the procedure:

Dim Total As Integer
Total = 100
Call MySub(Total)

After this code executes, Total is still equal to 100.

Note that array arguments and user-defined type arguments cannot be passed ByVal. Also, using ByVal or ByRef does not have any effect when the argument is a literal constant, only when it is a variable.

For most procedures, the default ByRef argument passing is fine. You can use ByVal when you want to ensure that code in the procedure cannot change the variable that was passed as an argument. You also must use ByVal when declaring Windows API and other DLL functions for use in your programs.


Visual Basic 6

Avoid Bugs With Option Explicit

Most programming languages, such as C and Fortran, require that all variables be explicitly declared before being used. Visual Basic does not - you can simply use a variable in code without declaring it. Some Visual Basic programmers tout this as an advantage but in fact it is a serious shortcoming. Here's why.

If explicit variable declaration (with a Dim, Private, or Public statement) is not required, Visual Basic creates a new variable in memory every time a new variable name is encountered in code. If you misspell a name, Visual Basic cannot know that it's an error - it just happily creates a new variable and initializes it, a process called implicit variable declaration. This can cause serious and difficult to find bugs in a program when, for example, a calculation uses a misspelled variable that was initialized to zero rather than the correct variable that you intended.

Another shortcoming is that such implicitly declared variables are always of type Variant, and you are denied the program efficiencies that result from using the proper data type for your variables.

Fortunately there is a way around this. If you include the Option Explicit statement in a module, then Visual Basic will require explicit declarations for all variables in that module. Any misspelled variables will be flagged as syntax errors when you try to run the program, and you can make the needed corrections. Place the Option Explicit statement at the start of each module, before any procedures. You can make this automatic by selecting Tools|Options and, on the Editor tab, selecting the Require Variable Declaration option.

Visual Basic Programming

Customize Your Code Editor

The Visual Basic code editor has several handy features to make the programmer's task easier. One is the ability to display different code elements in different colors, making it easier for you read and interpret code. Many programmers do not know that this feature can be customized so you do not have to stay with the default code display scheme.

To change the settings, select Tools|Options and then click the Editor Format tab. The Code Colors list displays the various code categories that you can customize. They are as follows:

- Normal text: Variable names (if not changed under Identifier Text), operators, literals.
- Selection Text: Text that you have selected.
- Syntax Error Text: Code in which Visual Basic has found a syntax error.
- Execution Point Text: When the program is paused during debugging, the next line of code to be executed.
- Breakpoint Text: Lines of code where you have set a breakpoint.
- Comment Text: Comments in the code.
- Keywords: Visual Basic statements and data types such as Do, Dim, and Integer.
- Identifier Text: Variable names.
- Bookmark Text: Lines of code where you have defined a bookmark.

Below this list are three drop-down lists that you use to define the appearance of the selected code component:

- Foreground: The color of the text.
- Background: The color behind the text.
- Indicator: The color of the indicator displayed in the left margin.

For each of these you can select a color. You can also select Auto which means that Visual Basic does not change the specified display attribute from the default.

On the right side of this dialog box you can view a sample of how the selected settings will appear. You can also turn the margin indicator bar on and off and set the font and size. This latter setting affects all code.

Visual Basic Tips and Techniques

Make Use of Visual Basic's Constants

Visual Basic provides a set of predefined symbolic constants that you can use anywhere in your program. They are useful because the constant name, which always starts with the letters "vb," is descriptive of what the constant is. This tip takes a look at a few of the more useful ones.

The constant I use most often is vbCrLf, which is a combination of a carriage return and a line feed. In other words, it starts a new line when you are outputting text. For example, you can break Message Box text over two or more lines when you have a long message to display:

MessageBox "This is a long message that should be broken over " & vbCrLf & _
  "two lines to prevent the message box from being too wide."

If you have need for a carriage return or a line feed alone, you can use vbCr and vbLf.

The constant vbNullChar is the character with the value 0. It is equivalent to Chr(0). It is useful in various situations such as calling external procedures that use C calling conventions, in which strings must be terminated with a null character.

The constant vbNullString represents a string with the value 0. This is not the same as vbNullChar, and it is not the same as an empty string (""). It too is used for calling external procedures such as those in the Windows API.

vbObjectError represents the highest error code used by Visual Basic. When you are defining your own errors you should always use values greater than this, for example:

Err.Raise vbObjectError + 500

Finally, two text-related constants are vbTab for the tab character and vbBackspace for the backspace character.

Visual Basic Tips and Techniques

Keeping an Application Log

When you create an application that will be distributed to multiple users, it's a pretty sure bet that they will be calling you with problems. In order for you to diagnose the problem, it can be very useful to have a record of what exactly the program has been doing. The easiest way to do this is to maintain a log file of program activity. The user can send this file to you and by examining it you may well be able to locate the problem.

The first step is to add a procedure to your project that writes to the file. I like to write the date and time as well as whatever information is relevant at the time. Here's an example:

Public Sub WriteToLogFile(logentry As String)

  Dim fn As Integer
  fn = FreeFile
  Open App.Path & "\ProgramNameLog.txt" For Append As #fn
  Write #fn, Now & ": " & logentry
  Close #fn

End Sub

When called and passed a message, this procedure opens the specified file in the application directory, or creates it if it does not exist. It then writes the current date and time and the information to the file and closes the file.

The next step is to call the procedure at the appropriate locations in your code. Where this will be depends on your program, but some examples of information that may be useful in a log file include:

- Names of procedures that are called.
- SQL statements passed to a database.
- Names of files opened or created.
- Values of intermediate values in calculations.
- Data input by the user.

I like to have the log file as an option that the user can turn on or off, for example with a Checkbox in an Options dialog box. Set a global Boolean variable according to the option setting and then write to the log file only when the option is selected. For example:

If Logfile Then WriteToLogFile(sqlStatement)

It's a little more effort to program log file capability in a program, but it may save a lot of time and headaches down the road.

Visual Basic Tips and Techniques

Comparing Dates in Visual Basic

One of the places where Visual Basic really shines is in the handling of dates using the Date data type. You can display a date in any one of several standard date formats, but internally it is represented as a serial number specifying the number of days since December 31, 1899, with negative values used for dates before then. The decimal portion of the value can be used to represent a time of day, but that is beyond the scope of this tip.

The way dates are represented makes it really easy to compare dates, but some Visual Basic programmers don't realize how easy it is and will go to unnecessary complexity to perform a comparison. There's no need to extract the year, month, and day individually for comparison--rather you can simply compare dates directly using the standard comparison operators.

For example, the date June 30, 2004 is represented by the serial number 38168. Comparing it with another date to see if it is earlier, the same, or later is really just comparing the serial numbers to see if one is smaller, the same, or larger than the other. Here's an example that executes one block of statements if the date stored in the type Date variable MyDate is earlier than January 1, 2000 and another block if it is the same or later than that date.

If MyDate < #1/1/2000# Then
  ' Statements here are executed if MyDate is earlier.
Else
  ' Statements here are executed if MyDate is the same or later.
End If

This is just one example of the flexibility of Visual Basic's Date data type.


Using Objects as Properties

VB provides a limited degree of object-oriented programming. The objects you define can have properties which are, in essence, data storage locations within the object. Most properties are ordinary variables, and are usually defined using the Property Let and Property Get procedures within the class definition. Many programmers do not know that a property can also be an object reference. In other words, one object can contain a reference to another object as a property.

This approach can be very useful. For instance, suppose you are writing an application that creates financial charts. You might define classes for the X-axis, the Y-axis, the title, and so on. Then you could define a "Chart" class that encapsulates all of the individual parts such as the axes and titles. Clearly the Chart object will need a way to reference these other objects.

Properties that are object references are treated a little differently from other properties. This is because of the VB syntax requirement that object reference assignments use the Set keyword. Thus, you cannot write simply

obj = New SomeClass

But you have to write like this:

Set obj = New SomeClass

Because of this requirement, the procedure you use in the class to assign a value to the property is not a Property Let procedure but rather a Property Set procedure. To read the property value you still use Property Get. The Property Set procedure looks like this:

Property Set PropertyName(o as Type)
Set PropertyVar = o
End Property

In this example, Type is either the specific type of the property or the generic object type Object. PropertyVar is the internal class variable that is used to hold the property value.

Having your program's objects reference other objects can be a useful technique, and it's easy if you know how to use Property Set procedures.

Visual Basic Tips and Techniques

Using the Friend Keyword

When writing property procedures and methods for a VB class there are three keywords you can use to control access to the property or method. Two of these are straightforward and easy to understand:

The third keyword is Friend, and its use and meaning are not well understood. You use Friend when you want a property or method to be accessible to other objects in the project but not to the project as a whole. Or, within a component, objects can access each other's Friend members but the program that is using the component cannot.

Suppose you are writing a component as a DLL or ActiveX, and there are several objects in the component that need to communicate with each other. However you do not want this communication channel to be available to the program that is calling the component. This is perhaps the most common use of Friend properties and methods.

Friend properties and methods are not part of a class's interface. They do not appear in the type library of the class and they are not included when you implement an interface using the Implements keyword.
 

Visual Basic Tips and Techniques
Making Use of Polymorphism

The term polymorphism means "many forms" and in object-oriented programming it refers to the ability to define more than one class with the same interface. An interface is a class's methods and properties and there are many programming tasks that can benefit from the use of polymorphism. It can save programming time as well.

Suppose you are writing a program to handle vehicles and you know that you will have a Car class and a Truck class. Both of these classes will need a Drive() method and a Turn() method, so it's a good place to use polymorphism. You start by defining a more general class called Vehicle:

1. Add a new Class Module to your project.
2. Change its name to Vehicle.
3. Add the following code to the class module:

Public Sub Drive(Speed As Integer)

End Sub

Public Sub Turn(Direction As Single)

End Sub

This defines the interface--two methods named Drive and Turn with their specified arguments. The interface could include properties as well. They are empty, and you'll see why in a minute. The next step is to create a class that implements the Vehicle interface:

1. Add a new Class Module to your project.
2. Change its name to Car.
3. Add the following code to the class module:

Implements Vehicle

With the Car code window open, pull down the Object list at the top left of the window. You'll see that Vehicle is listed there. When you select Vehicle, the members of its interface--namely the methods Drive and Turn--become available in the Procedure list at the top right of the code window. Select one and it is entered in the Car class--still empty. Then, add the code to the method to implement it as needed for this specific class. You would do the same for the Truck class and for any other classes that need the same interface, such as Motorcycle and Bicycle.

So what's the big deal? Each of these classes is likely to implement the methods differently. Car for example will have a higher top speed than Bicycle, and Motorcycle will be able to turn more quickly than Truck. But because all these classes implement the same interface, you know they will have Drive and Turn methods with specified arguments. You can, for example, call the method

obj.Drive(25)

on any obj regardless of whether it is a Car, Truck, Motorcycle, or Bicycle.

You can add more members to an object's interface beyond the ones it implements from the base class. For example, Car might have a MilesPerGallon property while Bicycle would not. In any event, you should always implement the full interface, not just part of it. After all, implementing an interface can be seen as a contract that the class will in fact have all the members.

Visual Basic Tips and Techniques

Using the Collection Object

Visual Basic makes use of collections in many ways, but some of you may not be aware that it also provides a generic Collection object for use in your programs. A collection is an ordered set of items that can be referred to as a unit. What makes the Collection object so useful is that the items it contains can be essentially anything--variables, literal numbers or text, objects, and so on. What's more, they do not have to be the same data type.

You create a Collection object using the usual VB syntax:

Dim col As New Collection

You use the Add method to add items to the collection:

col.Add item, key, before, after

Obviously you would not use the before and after arguments at the same time. If both are omitted the new item is placed at the end of the collection. If you use one of these arguments it must refer to an existing member of the collection or else an error will occur.

To retrieve an item from a collection use the Item method:

col.Item(index)

The index argument is either a number specifying the position of the item in the collection or the key string that was specified when the item was added. If the index argument does not match an item in the collection, an error occurs. Note that the numerical index is one-based - in other words, it runs from 1 to the number of items in the collection. Since Item is the default method, you can use the shorthand syntax like this:

col(index)

To delete a item, use the Remove method. Its syntax is exactly the same as Item. If you remove an item that is not the last item, other items move up to fill in the space of the deleted item.

The Collection object has one property, Count. It returns the number of items in the collection.

Collections can be useful in various situations. Suppose your program lets the user create one or more child windows. You can use a collection to keep track of them and then destroy them as needed. First create the collection:

Dim col As New Collection

Then, when each window is created add it to the collection:

Dim f As New Form2
col.Add f
f.Show

When it's time to close all the windows, loop through the collection like this:

Dim i As Integer
Dim f As Form
For i = 1 To col.Count
Set f = col(i)
f.Visible = False
Set f = Nothing
Next
 

Visual Basic Tips and Techniques

 

Create a "Sticky" Button

VB's Command Buttons are widely used for registering user input such as mouse clicks. The normal behavior of these buttons is to appear "raised" normally, then to appear "down" during the click, and return to the "raised" appearance when the mouse button is released. For most uses this is just what you want. But how about a Command Button that can be toggled? Click it once and it stays in the "down" state; click it again to return it to the "raised" appearance. Such "sticky" buttons are useful for selecting on/off options as an alternative to a checkbox. This tip shows you how to create them

You need to send a message to the Command Button using the SendMessageBynum Windows API function. You must put the function declaration in your program, as well as a constant definition:

Public Const BM_SETSTATE = &HF3

Declare Function SendMessageBynum Lib "user32" Alias "SendMessageA" _
(ByVal hwnd As Long, ByVal wMsg As Long, ByVal wParam As Long, _ ByVal lParam As Long) As Long

Then it's a simple matter of toggling the button in its Click event procedure. The code shown here changes the button's caption as well.

Private Sub Command1_Click()

Static Downstate As Long

Downstate = Not Downstate
If Downstate Then
    Command1.Caption = "Option On"
Else
    Command1.Caption = "Option Off"
End If

Call SendMessageBynum(Command1.hwnd, BM_SETSTATE, Downstate, 0)

End Sub

If you make the Downstate variable global rather than local, code in other parts f the program will be able to check the button's state as needed.

Visual Basic Tips and Techniques

 

Simplify Programming with Enumerations

VB lets you create enumerations, programmer-defined data types that can take on a set of constant values. The use of enumerations can simplify certain programming tasks and make your program code easier to read. This tip shows you how to create and use enumerations.

You create an enumeration with the Enum...End Enum statement:

Public Enum EnumName ConstantName1 ConstantName2 ... ContantNameN

End Enum

EnumName is the name of the enumeration following VB's usual variable naming rules. Each constant name-you can have as many as you like-becomes a member of the enumeration. By default, the constants are automatically assigned numerical values in order, starting with 0. Here's an example:

Public Enum Flavor
  flVanilla
  flChocolate
  flCoffee
  flStrawberry
End Enum

This results in the constant flVanilla being equal to 0, flClocolate being equal to 1, and so on. Usually the actual numerical values of the constants in an enumeration do not matter, but if you want to assign specific values you can:

Public Enum Flavor
  flVanilla = 2
  flChocolate = 4
  flCoffee = 8
  flStrawberry = 16
End Enum

Note the use of a prefix, in this example "fl," as part pf each constant name. This helps to identify the constant, when used in code, as a member of an enumeration. It's not required but it can make code easier to read.

Once you have defined an enumeration, then what? It becomes available in your program as a data type along with VB's built-in data types. You can declare variables of this type as shown here:

Dim MyFavoriteFlavor As Flavor

You can use the type for procedure arguments:

Public Sub MakeCone(f As Flavor)
...
End Sub

Finally, you can use the type as a return value of a function:

Public Function Taste() As Flavor
 ...
End Function

When you are typing code, the VB editor will display a drop-down list of enumeration members for you to choose from - a real convenience. Also, if you try to use an incorrect value-one not in the enumeration-you'll receive an error message. In terms of saving coding time and preventing errors, enumerations have a place in every VB programmer's toolkit.

Visual Basic Tips and Techniques


Creating Graphical Command Buttons

The Command Button control is widely used in almost every VB program. Almost always, this control will display a short text caption that describes its function. For some program designs, however, you may want to get a bit more creative and use graphical buttons that use images instead of text to indicate their function. Many VB programmers do not know that the Command Button control has this capability built in. There are three relevant control properties:

- Picture: The image to display normally.

- DownPicture: The image to display when the button is clicked (in the down state).

- DisabledPicture: The image to display when the button is disabled (when the Enabled property is False).

Each of these properties can be set at design time by specifying a bitmap, icon, or metafile image to use. You can also set them at runtime by using the LoadPicture function:

Command1.Picture = LoadPicture(filename)

You must also change the button's style property to 1 - Graphical for the images to display. If the Caption property is not blank, the caption displays along with the graphic. If the graphic is smaller then the button, it will be centered. If it is larger, it will be centered and clipped.

Visual Basic Tips and Techniques


Implementing Mouse-Over Effects on VB Forms

If you spend much time browsing the web you have probably see mouse-over effects on some web pages (sometimes called a hover effect). These effects change the appearance of a screen element when the mouse cursor passes over them without clicking. The appearance changes back to the way it was originally when the mouse cursor exits the element. Changes in the element's color or its font size are the most commonly used effects. You can do the same thing in VB, as this tip explains.

The technique makes use of two events-or more precisely, the same event for two objects. It's the MouseMove event which fires whenever the mouse cursor moves over a screen element. Here's what to do:

I also like to save the control's default appearance when the form loads. This lets me change its default appearance in the VB form editor and not have to worry about changing my code.

Here's an example that implements a mouse-over effect for a command button. In the form, at the module level, declare two variables to hold the control's default color and font size:

Private OrigColor As Long
Private OrigSize As Integer

In the form's Load event procedure, load these variables with the control properties:

Private Sub Form_Load()
  OrigColor = Command1.BackColor
  OrigSize = Command1.Font.Size
End Sub

In the Command Button's MouseMove event procedure, change it's appearance to the "over" state:

Private Sub Command1_MouseMove(Button As Integer, Shift As Integer, X As Single, Y As Single)
  Command1.BackColor = vbRed
  Command1.Font.Size = 14
End Sub

Finally, in the form's MouseMove event procedure, change them back to the default values:

Private Sub Form_MouseMove(Button As Integer, Shift As Integer, X As Single, Y As Single)
  Command1.Font.Size = OrigSize
  Command1.BackColor = OrigColor
End Sub

Visual Basic Tips and Techniques


Understanding the PictureClip Control

The PictureClip control can simplify the organization and use of graphical resources in your program. It is most commonly used with small graphics that you have a lot of, such as icons and toolbar buttons. With the PictureClip control you can create a single graphical image that contains all the individual icons or buttons, then extract each one for display as needed. The PictureClip control itself is never visible on screen when the program is running.

To use this control you must select Microsoft PictureClip Control 6.0 in the Components dialog box. Place it on the form where it will be needed. You can load the composite image at design time or at run time:

PictureClip1.Picture = LoadPicture("c:\images\icons.bmp")

Once the image is loaded you can retrieve any arbitrary portion of it by setting the ClipX and ClipY properties to specify the top left corner of the region and the ClipHeight and ClipWidth properties to specify the size. Then the Clip property retrieves the region for use in, for example, a PictureBox control:

PicClip1.ClipX = 20
PicClip1.ClipY = 34
PicClip1.ClipWidth = 22
PicClip1.ClipHeight = 22
Picture1.Picture = PicClip1.Clip

You can also divide the composite image into a rectangular grid of same-size sections, and then retrieve a section based on its position. This technique is used when the composite image contains a collection of same-size sub-images such as you might use for a toolbar. You can set the number of rows and columns at design time or at run time via the Cols and Rows property. Then retrieve an individual subimage using the GraphicCell property:

Picture1.Picture = PicClip1.GraphicCell(2)

The top left cell is index 0 and this value increases from left to right and then top to bottom.

Visual Basic Tips and Techniques
Implement a "Mouse Exit" event for controls

You can detect when the mouse enters a control via the MouseMove event. What about then the mouse exits a control? VB has no built-in event for this but it is easy to create your own. First, put the control on a Frame control that is just slightly larger than the control on all sides. Set the caption of the Frame control to a blank string and set its BorderStyle property to 0-None.

Next, declare a form-level boolean variable that will be true when the mouse in over the control and false if not. For a button named Command1 this might be as follows. Booleans are false by default.

Dim MouseInCommand1 As Boolean

Now, write a sub that performs the actions that you want done when the mouse leaves the control:

Private Sub Command1_MouseExit()
 ...
End Sub

In the MouseMove event procedure for the control, set the Boolrean variable to true:

Private Sub Command1_MouseMove(Button As Integer, Shift As Integer, X As Single, Y As Single)
    MouseInCommand1 = True
End Sub

Finally, in the MouseMove event procedure for the Frame, check the Boolean variable. If it is true, set it to false and call the procedure that you defined before:

Private Sub Frame1_MouseMove(Button As Integer, Shift As Integer, X As Single, Y As Single)
    If MouseInCommand1 Then
         MouseInCommand1 = False
         Call Command1_MouseExit
    End If
End Sub

Visual Basic Tips and Techniques

 

Display the Windows Search dialog from a VB Program

The Windows Search dialog box is very handing for finding a variety of items including files, computers, and documents. It is usually displayed by pressing the Windows key + F. It can be useful to display this dialog box from within a VB program. The technique is simply to mimic the normal keypress, Windows+F. This is done with the keybd_event API function. It's definition and a few constants must be placed in a module:

Public Declare Sub keybd_event Lib "user32" _ (ByVal bVk As Byte, _ ByVal bScan As Byte, _ ByVal dwFlags As Long, _ ByVal dwExtraInfo As Long)

Public Const VK_LWIN = &H5B
Public Const KEYEVENTF_KEYUP = &H2
Public Const VK_APPS = &H5D

Then send the required keystrokes from the program as follows:

Private Sub Command1_Click()
    Call keybd_event(VK_LWIN, 0, 0, 0)
    Call keybd_event(&H46, 0, 0, 0)
    Call keybd_event(VK_LWIN, 0, KEYEVENTF_KEYUP, 0)
End Sub

While you cannot transfer information from the Search dialog back to your program, it can still be very useful to locate a file in this way while running a VB program.

Visual Basic Tips and Techniques


Displaying File Properties from VB

If you right-click a file name in Windows Explorer and select Properties, Windows dislays a dialog box with a variety of information about the file including its size, creation and modification dates, and attributes. This tip shows you how to display the file properties dialog box from within a VB program.

First you need to declare a UDT, some constants, and an API function in a module in your program:

Public Const SW_SHOW = 5
Public Const SEE_MASK_INVOKEIDLIST = &HC
Public Type SHELLEXECUTEINFO
    cbSize As Long
    fMask As Long
    hwnd As Long
    lpVerb As String
    lpFile As String
    lpParameters As String
    lpDirectory As String
    nShow As Long
    hInstApp As Long
    ' optional fields
    lpIDList As Long
    lpClass As String
    hkeyClass As Long
    dwHotKey As Long
    hIcon As Long
    hProcess As Long
End Type

Public Declare Function ShellExecuteEx Lib "shell32.dll" (ByRef s As SHELLEXECUTEINFO) As Long

Then all you need to do is declare an instance of the UDT, load it with the required information, and call the function:

Dim shInfo As SHELLEXECUTEINFO

With shInfo
    .cbSize = LenB(shInfo)
    .lpFile = FullPathAndNameOfFile
    .nShow = SW_SHOW
    .fMask = SEE_MASK_INVOKEIDLIST
    .lpVerb = "properties"
End With

ShellExecuteEx shInfo

If you try this for a file that does not exist, Windows pops up a message informaing you of the fact. There is no runtime error, however. Finding the properties of a file from within a VB program can be a useful technique.

Visual Basic Tips and Techniques

 

Determine if a Credit Card Number is Valid

Credit card numbers are not assigned at random. Each number, usually 16 digits long, must adhere to certain mathematical conditions to be valid. This is called the Luhn Check and it is used by almost all major cards. The first step in any processs that accepts credit card numbers should be verifying the number. This tip shows you how.

The function CCNumberValid is passed a card number as a string. The string must contain only digits - no embedded spaces or dashes. The function returns true if the number is valid and false if not.

Public Function CCNumberValid(ByVal CCNumber As String) As Boolean

Dim Result As Long
Dim Total As Long
Dim idx As Integer
Dim i As Integer
Dim j As Integer

j = 1
For i = Len(CCNumber) To 1 Step -1
    Result = (CInt(Mid$(CCNumber, i, 1)) * j)

    If Result >= 10 Then
        Total = Total + (CInt(Mid$(CStr(Result), 1, 1)) + CInt(Mid$(CStr(Result), 2, 1)))
    Else
     Total = Total + Result
    End If

    If j = 2 Then
        j = 1
    Else
        j = 2
    Next

If Total Mod 10 = 0 Then
    CCNumberValid = True
Else
    CCNumberValid = False
End If

End Function

Of course the fact that a credit card number is valid does not mean that is it actually assigned to an account or that the account is in good standing. But checking the validity of a number is the best and fastest way to weed out errors in number entry.

Visual Basic Tips and Techniques

 

Normalizing spaces in strings

Some text processing routines require that spaces be normalized. This means that any trailing or leading strings be removed, which is easily done with the Trim() function. It also means that any sequence of 2 or more spaces that is present within the string be replaced with a single space. This takes a little coding, but it is quite easy with the Instr() and Replace() functions. The following function shows how.

Private Function NormalizeSpaces(s As String) As String

  ' Removes all leading and trailing spaces and
  ' replaces any occurrence of 2 or more spaces
  ' with a single space.

   s = Trim(s)

   Do While InStr(s, String(2, " ")) > 0
      s = Replace(s, String(2, " "), " ")
   Loop

   NormalizeSpaces = s

End Function

Visual Basic Tips and Techniques

 

Display a Select Folder Dialog

VBs Common Dialog control makes it easy to let the user select a file, but what about selecting a folder? This too is easy with the Microsoft Shell and Automation component. First add a reference to this component, which is located in Shell32.dll, to your project. You'll need these delarations:

Private shlShell As Shell32.Shell
Private shlFolder As Shell32.Folder
Private Const BIF_RETURNONLYFSDIRS = &H1

Then create an instance of the Shell class:

Set shlShell = New Shell32.Shell

Finally display the dialog box and return the result:

Set shlFolder = shlShell.BrowseForFolder(Me.hWnd, "Select a Folder", BIF_RETURNONLYFSDIRS)

Now the selected folder is available in the property shlFolder.Title.

Visual Basic Tips and Techniques
Fire a Command Button Repeatedly

Normally a Command Button fires one event each time it is clicked. Here's how to create a button that fires an "event" repeatedly as long as it is down (as long as the user has not released the mouse button):

1) Add a Timer Control to the form and set its Interval property to the desired interval between repeated events.

2) Put the code to be executed repeatedly in the Timer's Timer event procedure.

3) Set the Timer's Enabled property to True in the Command Button's MouseDown event procedure.

4) Set the Timer's Enabled property to False in the Command Button's MouseUp event procedure.

This kind of repeating Command Button can be useful in a variety of programming situations.