Home Page                                                                  x

Basically Visual Columns
Visual Basic Tips and Techniques
PGA Consulting - Professional Development Services


For .Net articles, tips, and forums

 

Are you a technical writer preparing long, complex documents in information sciences, hardware design, or software documentation? This is the first book ever written that specifically deals with the many problems and errors that can crop up when you are writing the long, complex documents typical of these technical fields. The book starts by showing you how to set Word's options to avoid unwanted formatting changes and time-wasting document corruption. Then it takes you through those areas of Word that are most diffucult to use and most likely to cause problems, including tables, automatic numbering, headers/footers, document sections, tables of contents, positioning graphics, styles, and templates. It ends with a chapter of dos and don'ts and advice on best practices for document backup and security. Click here for more details and online ordering.


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 a